This week's tutorials focuses on Text Displays. Aeron's tutorial last week brushed upon this last week. This week I intend to make it clear about the system of text displays, and show their flexibility.
So what exactly is the script? It's a portion (only a portion, we may show the rest of the script at a later stage) of my Unreal Tournament script. It basically copies all our favourite messages that would appear on screen when events such as killing enemies happened.
Forum support thread: forum.mtavc.com Please ask your questions in this forum thread.
Video footage: Video footage: scriptvideo5.wmv - WMV9 codec (640x448, 30fps)
UT Script: script5a.lua
Kill messages: script5b.lua
Preparation
addEventHandler ( "onPlayerSpawn", getRootElement(), "utSpawnClearSpree" ) function utSpawnClearSpree ( sp, team ) setElementData ( source, "currentSpeedKill", 0 ) setElementData ( source, "killingSpree", 0 ) end |
When a player spawns, we set some values that are stored to a player to 0. These are * currentSpeedKill - The amount of kills the player has done quickly. This allows for "Double kill" and "Multi Kill" etc messages * killingSpree - The number of kills a player has done without dying.
As these both will always be 0 when a player dies/respawns, they are set to 0 here.
Next, we repeat the process for when the resource is loaded
addEventHandler ( "onResourceStart", getRootElement(), "utLoadClearSpree" ) function utLoadClearSpree ( sp, team ) for k,v in getElementsByType ( "player" ) do setElementData ( source, "currentSpeedKill", 0 ) setElementData ( source, "killingSpree", 0 ) end end |
Setting up static displays Static displays? What the hell are they? Well, this is text that does not need to change on the fly. For example, when a player achieves a double kill, it will display "Double kill!" on his screen. This text never needs to change, it will always display this same text whenever needed.
addEventHandler ( "onResourceStart", getRootElement(), "utLoad" ) function utLoad ( name ) |
Back on topic, I'll need to introduce MTA's text display system and how it works.
The MTA text display system has two main parts - text displays and text items.
A text item is a piece of text - where you enter your text, colour, size, etc.
A text display can be thought of as a group of text items. You can't physically see a text display, only the text items attached to it.
This system is very useful as it allows lots of text to be displayed simultaneously within a group (i.e. the text display). You can create as many text displays as you like and hide/show them upon will. It should be made clear that a text item is useless if it is not attached to a text display - there is no way to display a text item on its own, it must be part of a text display.
So first, let's create our text displays:
---create our text displays staticDoubleKill = textCreateDisplay () staticMultiKill = textCreateDisplay () staticUltraKill = textCreateDisplay () staticMonsterKill = textCreateDisplay () staticHeadshot = textCreateDisplay () |
Next, I create my first text item:
local txt_staticDoubleKill = textCreateTextItem ( "Double Kill!", 0.5, 0.3, "medium", 255, 0, 0, 255, 1.7 ) --create the text |
This means they can be placed anywhere on the screen, with any colour and any size. The "alpha" argument allows for transparancy. The "priority" argument is the rate at which the text item needs to be updated. In most cases it is safe to set this to "medium".
Next we need to attach this text item to the appropriate text display:
textDisplayAddText ( staticDoubleKill, txt_staticDoubleKill ) --add the text to our staticDoubleKill display |
We then repeat the process for all other text items:
local txt_staticMultiKill = textCreateTextItem ( "Multi Kill!", 0.5, 0.3, "medium", 255, 0, 0, 255, 1.9 ) --create the text textDisplayAddText ( staticMultiKill, txt_staticMultiKill ) --same process, add the text to our staticMultiKill display --Ultra kill local txt_staticUltraKill = textCreateTextItem ( "ULTRA KILL!!", 0.5, 0.3, "medium", 255, 0, 0, 255, 1.9 ) textDisplayAddText ( staticUltraKill, txt_staticUltraKill ) --Monster kill local txt_staticMonsterKill = textCreateTextItem ( "M O N S T E R K I L L !!!", 0.5, 0.3, "medium", 255, 0, 0, 255, 1.9 ) textDisplayAddText ( staticMonsterKill, txt_staticMonsterKill ) --Headshot local txt_staticHeadshot = textCreateTextItem ( "Head Shot!!", 0.5, 0.204, "medium", 255, 0, 0, 255, 1.5 ) textDisplayAddText ( staticHeadshot, txt_staticHeadshot ) end |
Now we've set up all our static text displays - we just need to trigger them
Triggering static text displays
addEventHandler ( "onPlayerWasted", getRootElement(), "utPlayerWastedShowStaticText" ) function utPlayerWastedShowStaticText ( totalammo, killer, killerweapon, bodypart ) if getElementData ( killer, "currentSpeedKillTimer" ) ~= nil then --check if there's a timer stored killTimer ( getElementData ( killer, "currentSpeedKillTimer" ) ) --destroy it if there is end |
This part checks if there's a timer stored in a player's element data, and kills it if so. This will be explained in more detail a little later.
if bodypart == 6 or bodypart == 5 then --if the bodypart is the id of the head textDisplayAddObserver ( staticHeadshot, killer ) --display the static headshot setTimer ( "textDisplayRemoveObserver", 3000, 1, staticHeadshot, killer ) --and remove it 3 seconds later end |
If it was the head, then we use the textDisplayAddObserver function. This basically allows showing of a text display to a certain person. In this case, we display the staticHeadshot text display to the killer.
We then set a timer for 3 seconds to remove the text display from the killer's screen
Next we set up our speed kills code:
if ( killer ) and ( killer ~= source ) then --if there is a killer, and the killer is not the same person who died (i.e. he killed himself) local currentSpeedKills = getElementData ( killer, "currentSpeedKill" ) --get the number of speed kills he's done setElementData ( killer, "currentSpeedKill", currentSpeedKills + 1 ) --and increase it by one |
We first use getElementData to get his current amount of speed kills, (remember this is set to 0 by default), then we set the new one to currentSpeedKills + 1, as the killer has just made a new kill
local returnTimer = setTimer ( "setElementData", 3000, 1, killer, "currentSpeedKill", 0 ) --restore it to 0 in three seconds setElementData ( killer, "currentSpeedKillTimer", returnTimer ) --store this timer, so it can be killed later |
This part relates to killing the timer earlier. Basically, since it is a speed kill, the number of consecutive kills must return to 0 after 3 seconds. Therefore a timer is set to 0 after 3 seconds. This timer is then stored in element data of the player. This means next time onPlayerWasted is called, the timer to return it to 0 can be killed to prevent it interfering.
showSpeedKill ( killer ) --initiate the showSpeedKill function end end |
function showSpeedKill ( player ) local currentSpeedKills = getElementData ( player, "currentSpeedKill" ) local display if currentSpeedKills >= 2 then --if the amount of speed kills is greater than 2 --then we'll need to create an announcement for multiple kills --therefore remove any previous text displays of these announements to prevent overlap textDisplayRemoveObserver ( staticDoubleKill, player ) textDisplayRemoveObserver ( staticMultiKill, player ) textDisplayRemoveObserver ( staticUltraKill, player ) textDisplayRemoveObserver ( staticMonsterKill, player ) |
We then kill any timers that may be around to destroy the text by checking element data (see a little further below)
--kill the timer that was supposed to remove one of those text displays killTimer ( getElementData ( player, "killDisplayTimer" ) ) |
Next we follow a similar process with displaying "Headshot!!" earlier:
--Setting the display depending on the number of kills if currentSpeedKills == 2 then display = staticDoubleKill elseif currentSpeedKills == 3 then display = staticMultiKill elseif currentSpeedKills == 4 then display = staticUltraKill elseif currentSpeedKills >= 5 then display = staticMonsterKill end</p> <p>--Display it textDisplayAddObserver ( display, player ) --Remove the text display after 3 seconds local killDisplayTimer = setTimer ( "textDisplayRemoveObserver", 3000, 1, display, player ) |
Note that Monster kill is >=5 - this means it repeats for any value greater than or equal to 5
Finally, like with the headshot message, we set a timer to remove the message after 3 seconds.
Setting up dynamic displays As the name suggests - these are the opposite of static text displays. They will need to change according to the situation.
So let's first off set everything up onResourceLoad:
addEventHandler ( "onResourceStart", getRootElement(), "utStartCreateDynamic" ) function utStartCreateDynamic ( name ) --killing spree text (e.g. "x is on a killing spree!") is global. Therefore only one text display is required but everyone must be an observer. local dynamicKillingSpree = textCreateDisplay () killingSpreeText = textCreateTextItem ( "", 0.5, 0.271, "medium", 0, 128, 255, 255, 1.5 ) killingSpreeTimer = false textDisplayAddText ( dynamicKillingSpree, killingSpreeText ) |
Next we do a loop of every player in the server:
for k,v in getElementsByType ( "player" ) do |
So why are we looping through players? We need to create a text display unique to every player, as that player will only see his or her designated messages. So we loop through every player in the server and create a text display for them each. The reason we did not do this for 'dynamicKillingSpree' is because this refers to the "Talidan is on a killing spree!" message that appears for every player, so only one text display is required.
--All these displays require one per player, not global. Therefore they're created on a per-player basis. local dynamicTextDisplay = textCreateDisplay () --create a single display to handle all dynamic text textDisplayAddObserver ( dynamicTextDisplay, v ) --next we create a set of blank text items for this single display local youWereKilledText = textCreateTextItem ( "", 0.5, 0.335, "medium", 255, 0, 0, 255, 1.5 ) --for "You were killed by x" local youKilledText = textCreateTextItem ( "", 0.5, 0.237, "medium", 0, 128, 255, 255, 1.5 ) --for "You killed x" local weaponPickup = textCreateTextItem ( "", 0.5, 0.9, "medium", 255, 255, 255, 255, 1.5 ) --for "You picked up x ammo" --add all these text items to the display textDisplayAddText ( dynamicTextDisplay, youWereKilledText ) textDisplayAddText ( dynamicTextDisplay, youKilledText ) textDisplayAddText ( dynamicTextDisplay, weaponPickup ) --store them as element data to the player, so they can be retrieved later |
So we create 3 text items, i'll quickly explain which each of these refer to: * youWereKilledText - This refers to the text that that displays "You were killed by Talidan" which appears on your screen when you are killed * youKilledText - This refers to the text when YOU kill another player - "You killed Talidan" * weaponPickup - This refers to the text when you pick up a pickup - e.g. "You picked up an M4"
Next we have to attach these individual displays to the player so they can be retrieved later. As usual this is done via element data:
setElementData ( v, "textItem_youWereKilledText", youWereKilledText ) setElementData ( v, "textItem_youKilledText", youKilledText ) setElementData ( v, "textItem_weaponPickup", weaponPickup ) |
Then:
textDisplayAddObserver ( dynamicKillingSpree, v )--make everyone an observer of the killing spree text(see above) end end |
The next part is a duplicate of the one above, only designed for players who are joining the server and are not around when the resource is started:
--here we repeat the process for a player who joins addEventHandler ( "onPlayerJoin", getRootElement(), "utJoinCreateDynamic" ) function utJoinCreateDynamic () local dynamicTextDisplay = textCreateDisplay () textDisplayAddObserver ( dynamicTextDisplay, source ) local youWereKilledText = textCreateTextItem ( "", 0.5, 0.335, "medium", 255, 0, 0, 255, 1.5 ) local youKilledText = textCreateTextItem ( "", 0.5, 0.237, "medium", 0, 128, 255, 255, 1.5 ) local weaponPickup = textCreateTextItem ( "", 0.5, 0.9, "medium", 255, 255, 255, 255, 1.5 ) textDisplayAddText ( dynamicTextDisplay, youWereKilledText ) textDisplayAddText ( dynamicTextDisplay, youKilledText ) textDisplayAddText ( dynamicTextDisplay, weaponPickup ) setElementData ( source, "textItem_youWereKilledText", youWereKilledText ) setElementData ( source, "textItem_youKilledText", youKilledText ) setElementData ( source, "textItem_weaponPickup", weaponPickup ) --make sure he's an observer of the killing spree text display textDisplayAddObserver ( dynamicKillingSpree, source ) end |
Triggering dynamic text displays So we've created or dynamic text displays, each unique for every player besides the killing spree text. Now all we need to do is initiate them.
The youWereKilledText and youKilledText are triggered when a player dies, therefore we need to use the onPlayerWasted event.
addEventHandler ( "onPlayerWasted", getRootElement(), "utWastedDynamic" ) function utWastedDynamic ( totalammo, killer, killerweapon, bodypart ) if ( killer ) and ( killer ~= source ) then |
Next we setup our youKilledText:
--Display "You killed x" for the killer local youKilledText = getElementData ( killer, "textItem_youKilledText" ) textItemSetText ( youKilledText, "You killed "..getClientName(source) ) |
Next we use a different function to previous ones - textItemSetText. The name says it all - it allows setting, or changing the text of a text item. So here we change the blank youKilledText into "You killed "...getClientName(source), where the getClientName retrieves the nickname of the dead player.
local killerTimer = setTimer ( "textItemSetText", 5000, 1, youKilledText, "" ) |
Then we follow a similar process for youKilledText
--Display "You were killed by x!" for the person who died local youWereKilledText = getElementData ( source, "textItem_youWereKilledText" ) textItemSetText ( youWereKilledText, "You were killed by "..getClientName(killer).."!" ) local deadPersonTimer = setTimer ( "textItemSetText", 5000, 1, youWereKilledText, "" ) |
The last thing that is initiated when a player dies, is the killing spree text.
local currentSpree = getElementData ( killer, "killingSpree" ) setElementData ( killer, "killingSpree", currentSpree + 1 ) |
showKillingSpree ( killer ) end |
function showKillingSpree ( killer ) local killingSpree = getElementData ( killer, "killingSpree" ) local killername = getClientName(killer) if killingSpreeTimer == true then killTimer ( killingSpreeTimer ) end |
We start off by retrieving the player's killing spree and defining it. We also define the killer's name. Then we check the global killingSpreeTimer we created earlier. If there is a timer stored there, we can kill it.
if math.mod ( 25, killingSpree ) == 0 then |
if killingSpree == 5 then textItemSetText ( killingSpreeText, killername.." is on a killing spree!" ) |
We then repeat the process for other factors of 25, with varied messages:
elseif killingSpree == 10 then textItemSetText ( killingSpreeText, killername.." is on a rampage!" ) elseif killingSpree == 15 then textItemSetText ( killingSpreeText, killername.." is dominating!" ) elseif killingSpree == 20 then textItemSetText ( killingSpreeText, killername.." is unstoppable!" ) elseif killingSpree == 25 then textItemSetText ( killingSpreeText, killername.." is Godlike!" ) end |
We then set a timer to blank out the text after 3 seconds, and define it as killingSpreeTimer again, so it can be killed:
killingSpreeTimer = setTimer ( "textItemSetText", 5000, 1, killingSpreeText, "" ) end end |
So, we've done our youKilledText, youWereKilledText, and killing spree text. What's left? Our pickup text.
This text shows "You picked up x". It needs to be triggered when a player uses a pickup. Therefore, we use MTA's onPickupUse event.
addEventHandler ( "onPickupUse", getRootElement(), "utPickupUseShowInfo" ) function utPickupUseShowInfo ( player ) |
We start off, as usual, by killing off any timers to destroy the text:
local oldTimer = getElementData ( player, "textItem_weaponPickupTimer" ) if oldTimer ~= nil or oldTimer ~= false then killTimer ( oldTimer ) end |
We then retrieve the weaponPickup text with element data:
local textPickup = getElementData ( player, "textItem_weaponPickup" ) |
Now i'll breifly introduce you to MTA's/GTA's pickup system. There are 3 main types of pickups: Armour, health, and weapon pickups.
Health and armour pickups are very alike - they both give a certain amount of health of armour and therefore share a function - getPickupAmount( pickup ). This retrieves the hit points that the pickup represents, and works for both armour and health pickups
Weapon pickups use slightly different functions. There is a getPickupAmmo( pickup ) function, which retrives the amount of ammo a pickup has. There is also a getPickupWeapon( pickup ) - this retrives the weapon ID of the pickup.
So lets move on,
if getPickupType ( source ) == 0 then textItemSetText ( textPickup, "You picked up "..getPickupAmount(source).." health." ) |
We do the same thing for armour, only change the text a little:
elseif getPickupType ( source ) == 1 then textItemSetText ( textPickup, "You picked up "..getPickupAmount(source).." armour." ) |
Weapon pickups are slightly more complex.
First we check that the type of pickup is a weapon:
elseif getPickupType ( source ) == 2 then |
Next i'll introduce some of the weapon functions. The getSlotFromWeapon retrives the slot of a weapon. In GTA, each weapon has a slot - e.g. grenades, smgs, pistols all have different slots. You can see all the weapon slots at http://vces.net/info/WeaponIDs.html (Thanks to Brophy and Ratt for compiling these lists).
So why do we need to know weapon slots? If we want to display messages, they need to have correct grammar! This means we can't have "You picked up 200 ammo for M4" text displaying for grenades..as we'd get "You picked up 5 ammo for Grenades" - but in reality you are picking up 5 grenades
Moving on:
local slot = getSlotFromWeapon ( getPickupWeapon ( source ) ) if slot == 0 or slot == 1 or slot == 10 or slot == 12 then textItemSetText ( textPickup, "You picked up a "..getWeaponNameFromID(getPickupWeapon(source))..".") |
elseif slot == 8 then textItemSetText ( textPickup, "You picked up "..getPickupAmmo(source).." "..getWeaponNameFromID(getPickupWeapon(source)).." explosives.") else textItemSetText ( textPickup, "You picked up "..getPickupAmmo(source).." ammo for the "..getWeaponNameFromID(getPickupWeapon(source))..".") end |
Next we repeat the process. All weapons of slot 8 are explosives, so we print "You picked up 5 grenade explosives instead.
Else, all other slots are weapons that have standard ammo/weapon style. So we can have, "You picked up 500 ammo for the M4".
Finally,
local textTimer = setTimer ( "textItemSetText", 3000, 1, textPickup, "" ) setElementData ( player, "textItem_weaponPickupTimer", textTimer ) end |
We're done with our UT script. Hurrah.
Though, we're not finished yet. If you look at the video you'll notice new death messages to the right of the screen - also done with text displays. Hows about I explain that too, as compensation for this late tutorial?
Creating our text display and adding observers. First, we create our text display when the resource starts:
addEventHandler ( "onResourceStart", getRootElement(), "createTextOnLoad" ) function createTextOnLoad ( name ) local outputMessageTextDisplay = textCreateDisplay () |
Next we define some variables for the main settings of the display:
local distance = 0.027 local start = 0.25 local size = 1.2 |
We then create our text displays for our death messages:
outputMessageTextItem1 = textCreateTextItem ( "", 0.8, start + distance, 1, 255, 255, 255, 255, size ) outputMessageTextItem2 = textCreateTextItem ( "", 0.8, start + (distance * 2 ), 1, 255, 255, 255, 255, size ) outputMessageTextItem3 = textCreateTextItem ( "", 0.8, start + (distance * 3 ), 1, 255, 255, 255, 255, size ) outputMessageTextItem4 = textCreateTextItem ( "", 0.8, start + (distance * 4 ), 1, 255, 255, 255, 255, size ) outputMessageTextItem5 = textCreateTextItem ( "", 0.8, start + (distance * 5 ), 1, 255, 255, 255, 255, size ) outputMessageTextItem6 = textCreateTextItem ( "", 0.8, start + (distance * 6 ), 1, 255, 255, 255, 255, size ) |
textDisplayAddText ( outputMessageTextDisplay, outputMessageTextItem1 ) textDisplayAddText ( outputMessageTextDisplay, outputMessageTextItem2 ) textDisplayAddText ( outputMessageTextDisplay, outputMessageTextItem3 ) textDisplayAddText ( outputMessageTextDisplay, outputMessageTextItem4 ) textDisplayAddText ( outputMessageTextDisplay, outputMessageTextItem5 ) textDisplayAddText ( outputMessageTextDisplay, outputMessageTextItem6 ) |
Finally, we make everyone in the server viewers of this text display:
local players = getElementsByType ( "player" ) for k,v in players do textDisplayAddObserver ( outputMessageTextDisplay, v ) end end |
We must repeat this for players that join:
addEventHandler ( "onPlayerJoin", getRootElement(), "showTextPlayerJoin" ) function showTextPlayerJoin () textDisplayAddObserver ( outputMessageTextDisplay, source ) end |
Setting up our output function
Next, I introduce the custom outputMessage function. This is probably the main part of the script, and handles all incoming text and updates the text display.
It has 4 parameters:outputMessage ( text, r, g, b, a ) Users can specify the text, the colour, and the alpha.
The whole thing is actually very simple. It first retrives all the current text's colours and text, and sets them to the one above, and sets the newest one to the bottom one.
So first it gets all the current text's colour, and text:
local text2 = textItemGetText ( outputMessageTextItem2 ) local r2,g2,b2,a2 = textItemGetColor ( outputMessageTextItem2 ) local text3 = textItemGetText ( outputMessageTextItem3 ) local r3,g3,b3,a3 = textItemGetColor ( outputMessageTextItem3 ) local text4 = textItemGetText ( outputMessageTextItem4 ) local r4,g4,b4,a4 = textItemGetColor ( outputMessageTextItem4 ) local text5 = textItemGetText ( outputMessageTextItem5 ) local r5,g5,b5,a5 = textItemGetColor ( outputMessageTextItem5 ) local text6 = textItemGetText ( outputMessageTextItem6 ) local r6,g6,b6,a6 = textItemGetColor ( outputMessageTextItem6 ) |
Next, we move all the text up one:
textItemSetText ( outputMessageTextItem5, text6 ) textItemSetColor ( outputMessageTextItem5, r6, g6, b6, a6 ) |
We do the same for the rest of them:
textItemSetText ( outputMessageTextItem4, text5 ) textItemSetColor ( outputMessageTextItem4, r5, g5, b5, a5 ) textItemSetText ( outputMessageTextItem3, text4 ) textItemSetColor ( outputMessageTextItem3, r4, g4, b4, a4 ) textItemSetText ( outputMessageTextItem2, text3 ) textItemSetColor ( outputMessageTextItem2, r3, g3, b3, a3 ) textItemSetText ( outputMessageTextItem1, text2 ) textItemSetColor ( outputMessageTextItem1, r2, g2, b2, a2 ) |
So we've moved all our text up one. We need to add the new text passed into the function as the bottom text:
textItemSetText ( outputMessageTextItem6, text ) textItemSetColor ( outputMessageTextItem6, r, g, b, a ) |
Outputting our messages Lastly, we have to actually have the event to trigger the outputMessage function.
addEventHandler ( "onPlayerWasted", getRootElement(), "KillMessages_onPlayerWasted" ) function KillMessages_onPlayerWasted ( totalammo, killer, killerweapon, bodypart ) -- Grab the nick of the killed player local killed_nick = getClientName ( source ) local r,g,b = getPlayerNametagColor ( source ) |
-- Got a killer? Print the normal "* X died" if not if ( killer ) then -- Suicide? if (source == killer) then killed_nick = "himself" end |
-- Populate a "' X killed Y" killmessage local killmessage = getClientName(killer).." killed "..killed_nick -- Grab the name of the killer weapon. Got any? Print the message with or without the weapon name |
local killerweapon_name = getWeaponNameFromID ( killerweapon ) if ( killerweapon_name ) then outputMessage( killmessage.." ("..killerweapon_name..")", r, g, b, 255 ) |
However, if there was not a weapon involved:
else outputMessage( killmessage, r, g, b, 255 ) end |
else outputMessage( killed_nick.." died", r, g, b, 255 ) end end |
And that's all there is to it!
Before we finish, i'd just like to say that the kill messages script is designed to output anything into its display using the outputMessage ( text, r, g, b, a ) function. In this example i used it for death messages. But you, as a scripter, could display anything you wanted to announce there.
Thanks for reading, and sorry for the delay. Have a very merry christmas.




