Supply Drop (LUA)

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:

1
2
3
4
5
6
7
8
9
function GetRandomOrg()
    print("DEBUG: GetRandomOrg - Executing.")
    local nodecount = HL2.GetNodeCounts()
    local node = HL2.GetNodeData(HL2.RandomInt(0,nodecount-1))
    if node ~= nil then
        local org = HL2.Vector(node.x, node.y, node.z)
        return org
    end
end

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function SupplyInit()
    print("DEBUG: SupplyInit - Executing.")
    while SUPPLY_COUNT > 0 do
        SUPPLY_COUNT = SUPPLY_COUNT - 1
        local org = GetRandomOrg()
        local ang = HL2.Vector(0,HL2.RandomInt(0,180),0)
        if org ~= nil then
            SupplySpawn(org, ang, GetSupplyClass())
        else
            print("\n\nERROR: SupplyInit - 'org' invalid.\n\n")
        end
    end
    print("DEBUG: SupplyInit - SUPPLY_COUNT pool exhausted.")
end

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function GetSupplyClass()
    print("DEBUG: GetSupplyClass - Executing.")
    if HL2.RandomInt(1,100) <= SUPPLY_CHANCE[4] then
        print("DEBUG: GetSupplyClass - Content set to npc_rollermine.")
        return "npc_rollermine"
    else
        local class = HL2.RandomInt(1,100)
        if class <= SUPPLY_CHANCE[1] then
            print("DEBUG: GetSupplyRandom - CRATE set to SUPPLY_AMMO.")
            return SUPPLY_AMMO[HL2.RandomInt(1,table.getn(SUPPLY_AMMO))]
        elseif class <= (SUPPLY_CHANCE[1] + SUPPLY_CHANCE[2]) then
            print("DEBUG: GetSupplyRandom - CRATE set to SUPPLY_AMMO_UNCOMMON.")
            return SUPPLY_AMMO_UNCOMMON[HL2.RandomInt(1,table.getn(SUPPLY_AMMO_UNCOMMON))]
        elseif class <= (100) then
            print("DEBUG: GetSupplyRandom - CRATE set to SUPPLY_OTHER.")
            return SUPPLY_OTHER[HL2.RandomInt(1,table.getn(SUPPLY_OTHER))]
        end
    end
end

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SupplySpawn(org, ang, class)
    print("DEBUG: SupplySpawn - Executing.  (class: "..class..")")
    local count = 0
    local ent = HL2.CreateEntity("item_item_crate", org, ang)
    if ent ~= nil then
        if class == "npc_rollermine" then
            count = 1
        else
            count = HL2.RandomInt(2,4)
        end
        HL2.KeyValue(ent, "ItemClass", class)
        HL2.KeyValue(ent, "ItemCount", count)
        HL2.SpawnEntity(ent)
        print("DEBUG: SupplySpawn - Successfully spawned an item crate.  (class: "..class..", count: "..count..")")
    else
        print("\n\nERROR: SupplySpawn - 'ent' found invalid.\n\n")
        HL2.RemoveEntity(ent)
    end
end

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!

crate01_t.jpg crate02_t.jpg crate03a_t.jpg crate03b_t.jpg

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.

mapaddex_dl.png The example scripts this article is based on can be downloaded here for reference. Simply rename the scripts according to the map's name (e.g. gm_construct.txt, gm_construct.lua) after they're extracted to your mapadd folder.


Customization

Mapadd Command Reference (Non-LUA)Command Reference (LUA)Getting StartedPorts 'n' Doors
Alarm, Alarm!Color Correction in SMODDoor BreachingMobile APCsWorking With Dropships
Supply Drop (LUA)Countdown (LUA)
kh0rn3's Mapadd Generator
Scripts addcontentsoverride_classsmod_custom_explosivesmodaclistSMOD Soundscripts

npc_gib_modelnpc_replace_modelnpc_shieldsetnpc_weapon_randomizenpc_weaponweight

excludeweaponsweapon_categoryweapon_customConsole Command List
Other Crosshair CustomizationGenerating AI NodesUsing the NodemakerSubViewCam
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License