Overview
In this example, a Lua mapadd script will spawn a random number of item crates with varying item types (common ammo, uncommon ammo, other, trap). Their contents will be selected at random from a preset list depending on their type; ammo crates will spawn 2-4 ammo items of a certain type while a booby-trapped crate would spawn a single rollermine. You can see a similar system used in the CSS SCI FI mod.
Due to the complexity involved and need to keep its length reasonable, this article is more a dissection of an example script than a "how-to" page like most of the TXT mapadd articles on the wiki. Try to carefully read through and think about how the methods described could be applied to your own mapadd ideas.
While the reader doesn't need too much familiarity with mapadd fundamentals (syntax, grabbing coords, etc.), a decent grasp on programming basics is a must. If a term seems unfamiliar, try the site glossary.
How-To
Node Check!
Begin by picking a map you'd like to work with and ensure it has a matching SNL file in your mapadd\nodes folder. The method used to grab random locations depends on having an SNL file and crashes are possible without one. AINs will not suffice so if you don't have an SNL, try generating one.
Prepping the TXT
Before getting into the Lua proper, a few small things could use adding to the mapadd's TXT. First, a suit to allow better movement. Under the main Entities section, a simple relay and point_clientcommand will do the trick:
"point_clientcommand"
{
"keyvalues"
{ "targetname" "cmd" }
}
"logic_relay"
{
"keyvalues"
{
"spawnflags" "1" // Terminate this relay once done.
"OnSpawn" "cmd,Command,give item_suit,0,-1" // Will work even with sv_cheats set to 0.
}
}
Moving on, to use a Lua script in a mapadd, it must first be called from the TXT side with the Lua command. An example snippet that calls the Init() function:
"lua"
{
"callfunc" "Init"
}
Now that the TXT call is set up, we can begin getting to the meat of this article.
Vars and Init
After creating/opening up a map's Lua script, writing some variables at the top can help you get a better idea of what you want from the script and how it should be achieved. Try to make variable names simple to remember. Below is an example snippet where the main variables are specified in caps to better differ between them and temporary ones the different functions will use.
local SUPPLY_COUNT = HL2.RandomInt(4,7) -- # of crates to be spawned; random number between 4 and 7.
local SUPPLY_AMMO = {
"item_ammo_ar2",
"item_ammo_smg1",
"item_ammo_pistol",
"item_box_buckshot"
}
-- If you're unfamiliar with them, these are arrays, which can store several values in a single variable.
-- If the first value held in SUPPLY_AMMO ("item_ammo_ar2") was needed in a logic piece, it could be specified as SUPPLY_AMMO[1].
local SUPPLY_AMMO_UNCOMMON = {
"item_ammo_ar2_altfire",
"weapon_frag"
}
local SUPPLY_OTHER = {
"item_healthkit",
"item_healthvial",
"item_battery"
}
local SUPPLY_CHANCE = { 60, 15, 25, 2 } -- % chance for each type; AMMO, AMMO_UNCOMMON, OTHER, TRAP (independent)
After that, a small Init() function that calls SupplyInit() will give the TXT side something to run. It's nice to have an Init function like this for starting all your other scripts, as it helps keep things tidy and easier to read.
function Init()
SupplyInit()
end
Before creating SupplyInit however…
GetRandomOrg
The script will need a way of grabbing random locations for each crate. GetRandomOrg's task will be to root through the map's node list, select a random node, and return the node's origin, via the functions HL2.GetNodeCounts and HL2.GetNodeData. Below is GetRandomOrg as it appears in the example script:
|
|
Line 2: Prints a debug message to the console. Debug messages can be vital for pinning down the cause of (crash-causing) bugs later, by showing when the problem starts. The all-caps "DEBUG" prefix makes it easier to find in the console log.
Line 3: Declares a new variable, "nodecount", which will hold the number of nodes found in the map's SNL.
Line 4: Declares another variable, "node". HL2.GetNodeData sets its value to a random node from the list. Because nodes begin counting at zero ("0000", "0001"), the upper limit used is "nodecount-1" rather than just "nodecount".
Line 5: Checks if the "node" variable has a value (meaning the SNL is valid and HL2.GetNodeCounts succeeded). If not, lines 6 and 7 are skipped and the function won't return a value.
Line 6: declares "org", which will be the data returned to SupplyInit. Its value is specified as a vector containing the random node's X, Y, and Z origin coordinates.
Line 7: Return the origin coordinates of the random node.
Line 8: Closes the if-then statement started on line 5.
SupplyInit
This function's job will be to pass along crates' origin, angle, and item type to the future SupplySpawn function, and limit the number of crates spawned by the script:
|
|
Line 3: Start a while loop, which will execute lines 4-11 so long as SUPPLY_COUNT's value is greater than 0. Once SUPPLY_COUNT no longer fits this, stop the loop and skip to line 12.
Line 4: Decrement SUPPLY_COUNT by 1 so the loop doesn't continue running for infinity. Lines 3 and 4 together are analogous to a for loop, but I usually write loops like this 'cause they're slightly simpler to read.
Line 5: Declare "org", a vector set by GetRandomOrg.
Line 6: Declare "ang", a vector with a random yaw (0-180) but no pitch or roll.
Line 7: Check if "org" is valid (meaning GetRandomOrg succeeded). If so, run line 8. Otherwise, run line 9.
Line 8: Pass the origin, angle, and class of the crate to SupplySpawn. The class is set by GetSupplyClass.
Line 9: Run line 10, since Line 7's check came back negative.
Line 10: Post an error message to the console saying the value got from GetRandomOrg was useless. Each \n starts a new line in the console, making it easier to pick this error out from other debug text.
Line 11: Close the if-then-else statement started on line 7.
Line 13: Debug message printed once the loop at 3-12 is finished.
GetSupplyClass
This function is tasked with selecting a random item type for crates, using the main variables as guidelines.
|
|
Line 3: Select a random number between 1 and 100. If the result is less than or equal to SUPPLY_CHANCE[4] (2%), run lines 4-5. If the crate is not a trap, run lines 7-16.
Line 7: Select another random 1-100 number and store it in the "class" variable.
Line 8: If "class" is less than or equal to SUPPLY_CHANCE[1] (60), then run lines 9-10; the item is a common ammo type. If "class" is greater than SUPPLY_CHANCE[1], run line 11.
Line 10: Select a random item from SUPPLY_AMMO's list and return it to SupplyInit. table.getn(SUPPLY_AMMO) counts how many values are in SUPPLY_AMMO, so the list can be changed without having to also change this line's maximum number by hand.
Lines 13 and 16: Like line 10, select random items from their respective lists (AMMO_UNCOMMON, OTHER).
Line 11: If "class" is less than or equal to SUPPLY_CHANCE's first and second values taken together (60+15=75), then run lines 12-13; the item is an uncommon ammo type. If "class" is greater than this sum, run line 14.
Line 14: If "class" is less than or equal to 100, the "class" var's highest possible value, then run lines 15-16; the item should be picked from the miscellaneous list.
SupplySpawn
The last part of the script that needs building. Per its name, SupplySpawn's job is to spawn item crates, given the origin, angle, and class values passed from SupplyInit.
|
|
Line 2: The double-quote and two periods found before and after "class" are used to display variables within set strings (known as concatenating). In this case, it's used so that if "class" was set to item_ammo_ar2, the string would display as "(class: item_ammo_ar2)". When concatenating numbers, it's important to put an extra space around the value (".. 55 ..") so it's not misinterpreted by Lua as a decimal.
Line 3: Declare "count", which will be how many items are held in the crate.
Line 4: Declare "ent", a created entity specified as an item crate using SupplyInit's origin and angle. Keep in mind that created entities are not the same as spawned entities.
Line 5: If "ent" is valid (the entity's classname, origin, and angle work out), then run lines 6-14. Otherwise, run line 15 (in turn running 16-17).
Line 6: If the crate is to hold a rollermine trap, then run line 7. Otherwise, run line 8 (in turn running 9).
Line 7: The trap is a rollermine, so set the number of crate items to 1.
Line 9: The crate isn't a trap, so set the number of crate items between 2 and 4.
Line 11: Set the crate's "ItemClass" keyvalue to reflect the "class" variable's value. Line 12 does the same for "ItemCount" and "count".
Line 13: Spawn the "ent" entity.
Line 17: HL2.CreateEntity didn't work properly, so remove "ent".
Result and Wrap-Up
With all the above or a similar set-up built, you should now be able to select your chosen map and find a few item crates scattered throughout, with different contents for each. Go ahead, fetch yourself a cookie if you managed to read through this far!
The purpose of this page, along with the other mapadd pages, is to serve as a starting place for your own mapadd ideas. Bear in mind that just about anything can be done, it's just a matter of figuring how to make it happen.
Note that this script was crafted (or rather, cut and simplified from an old CTF script) mostly for demonstration. In practice, this script's effect is better achieved by using the TXT's Entities section to create named info_target ents around the map to used as potential spawnpoints, to keep crates away from the player's spawn and keep NPC paths clear of blockage. Using HL2.FindEntityByName, HL2.RandomInt, and HL2.GetEntityAbsOrigin, you could then grab each target and give it a certain chance to spawn a crate.
Customization |