Skip to main content

Examples

Various code snippets that serve as an example! Feel free to copy and paste in your own projects.

👋Contact

Looking for a specific example or simply to share feedback? Contact me at toxicity@thebeginning.uk

Iterating Over Objects​

Game objects are cached to different lists for faster access. Multiverse provides multiple helper functions for iterating over game objects; these helpers are split into three different functions for optimization purposes.

caution

Objects cannot be cached to multiple lists; therefore objects part of unique lists will not be found in a more "general" list. There are some exceptions for this rule, but for instance, the model M_SHOT_SUPER_WARRIOR is only to be found by the ProcessThingsListSpecial function. One might expect to find the same object through ProcessThingsList with a T_SHOT list.

Parameters Explained​

UBYTE list
Refers to an object type list. Check out the enum types for a full list of possible lists, but do note that some of them might not be available.

UBYTE model
Refers to the object model we're looking for. This parameter is expected by ProcessThingsListModel. For example, we can pass a T_SCENERY list with a M_SCENERY_HEAD model. This would skip all the trees, rocks, plants, etc. which makes it quicker to find our Stonehead. Check out ProcessThingsListModel for a full list of supported List/Model combinations.

UBYTE pn
Refers to the player number. Sometimes lists are subdivided further into owner lists. Do we really want to iterate over all persons, or just Blue owned persons? TRIBE_BLUE is player number 0, followed by TRIBE_RED (1), TRIBE_YELLOW (2) and finally TRIBE_GREEN (3).

std::function<bool(Thing*)> func
We must provide a function with a Thing* parameter. For every Thing* object found in the list, our function will be called and executed. We can return false to stop the iteration, otherwise return true to continue.

ProcessThingsList​

Iterating over all (remaining) objects.
We're not looking for something in particular, so we can pass the function some meaningless arguments which will default to a general search for any valid object that is NOT part of a list already.

--                list      pn  func
ProcessThingsList(-1, -1, function(t)
Log(string.format("Object of Type: %i and Model: %i", t.Type, t.Model))
return true -- continue to next object
end)

Iterating over all Persons.
We're starting our loop at -1 because Wildmen are Neutral.

for pn=-1,3,1 do
ProcessThingsList(T_PERSON, pn, function(t)
t.u.Pers.Life = 0 -- kill off thing
return true -- continue to next object
end)
end

Iterating over all Buildings.
We are not going to find M_BUILDING_LIBRARY, as VoKs are part of a special list (ProcessThingsListSpecial). Neither will we find Prisons, as they're part of a separate list as well.

for pn=TRIBE_BLUE,TRIBE_GREEN,1 do
ProcessThingsList(T_BUILDING, pn, function(t)
t.u.Bldg.Damaged = 99999
return true -- continue to next object
end)
end

ProcessThingsListModel​

Iterating over all Trigger objects. We're making use of ProcessThingsListModel for the first time. The pn parameter does not matter here since triggers are not subdivided in owner lists.

ProcessThingsListModel(T_GENERAL, M_GENERAL_TRIGGER, pn, function(t)
Log("TNum: " .. t.ThingNum)
return true -- continue to next object
end)

At the time of writing this example, these are the supported list & model combinations:

ListModelPlayer NumDescription
T_GENERALM_GENERAL_TRIGGER-Loop over trigger objects.
T_GENERAL-1-Loop over all objects of type T_GENERAL, except for objects already cached to different lists (e.g. Triggers).
T_SCENERYM_SCENERY_HEAD-Loop over all worshippable objects (Stoneheads, Obelisk, Totems).
T_SCENERYM_SCENERY_TREE_1-Loop over all trees.
T_SCENERYM_SCENERY_TREE_2-Loop over all trees.
T_SCENERYM_SCENERY_TREE_3-Loop over all trees.
T_SCENERYM_SCENERY_TREE_4-Loop over all trees.
T_SCENERYM_SCENERY_TREE_5-Loop over all trees.
T_SCENERYM_SCENERY_TREE_6-Loop over all trees.
T_VEHICLEM_VEHICLE_BOAT_1-Loop over all boats.
T_VEHICLEM_VEHICLE_BOAT_2-Loop over all boats.
T_VEHICLEM_VEHICLE_AIRSHIP_1-Loop over all balloons.
T_VEHICLEM_VEHICLE_AIRSHIP_2-Loop over all balloons.
T_EFFECT-1-Loop over all effect objects, except for objects already cached to different lists (e.g. Swamps)
T_EFFECTM_EFFECT_BUILDING_SENTRY-These effects are cached in an unique list.
T_EFFECTM_EFFECT_BUILDING_SHOCK_SENTRY-These effects are cached in an unique list.
T_EFFECTM_EFFECT_BUILDING_PROTECTION_SENTRY-These effects are cached in an unique list.
T_EFFECTM_EFFECT_BUILDING_AIR_SENTRY-These effects are cached in an unique list.
T_EFFECTM_EFFECT_RAIN_SPAWNER-These effects are cached in an unique list.
T_EFFECTM_EFFECT_SWAMP-These effects are cached in an unique list.
T_EFFECTM_EFFECT_PERMANENT_OBJECT-These effects are cached in an unique list.
T_EFFECTM_EFFECT_PICKUP_OBJECT-These effects are cached in an unique list.
T_INTERNALM_INTERNAL_FORMATION0-3
T_INTERNALM_INTERNAL_FIGHT0-3
T_INTERNALM_INTERNAL_PRE_FIGHT0-3

ProcessThingsListSpecial​

This will iterate over a special list. These models are exclusive to this list: M_BUILDING_LIBRARY, M_EFFECT_ATLANTIS_INVOKE, M_INTERNAL_GUARD_CONTROL and M_SHOT_SUPER_WARRIOR.

ProcessThingsListSpecial(function(t)
if (t.Type == T_SHOT and t.Model == M_SHOT_SUPER_WARRIOR) then
Log("Found M_SHOT_SUPER_WARRIOR");
end
return true -- continue to next object
end)

Processing the world cells at a specific position is trivial and something any scripter will want to do at some point. By far the most useful functions are ProcessMap and ProcessMapWho.

ProcessMap​

Given an initial position, the search function will gradually expand depending on the radius given and in the process execute the function provided by the user. We can return false to stop the next cell search/advance or return true to continue.

function smoke_aura(pn, rad)
-- Get Shaman.
local shaman = getShaman(pn)
-- Ensure the Shaman is a valid object.
if (shaman ~= nil) then
-- Convert her Coord3D position to a Map Idx position.
local pos = world_coord3d_to_map_idx(shaman.Pos.D3)
-- Process the map in a circular motion.
ProcessMap(CIRCULAR, 0, 0, rad, pos, function(me)
-- Create an empty Coord2D variable.
local c2d = Coord2D.new()
-- Convert the MapElement we receive from ProcessMap to Coord2D.
map_ptr_to_world_coord2d(me, c2d)
-- Create an object at the location.
create_thing(T_EFFECT, M_EFFECT_SMOKE, pn, c2d)
-- Continue to next cell..
return true
end)
end
end

ProcessMapWho​

ProcessMapWho's last parameter is a function requiring a MapElement* parameter. Ergo, it's a good idea to make use of the passed argument to combine the functionality of ProcessMap with ProcessMapWho — the latter being used to process objects on the cell.

function process_wildmen(pn, rad)
-- Get Shaman.
local shaman = getShaman(pn)
-- Ensure Shaman object is valid.
if (shaman ~= nil) then
-- Convert her Coord3D position to a Map Idx position.
local pos = world_coord3d_to_map_idx(shaman.Pos.D3)
-- Process the map in a circular motion.
ProcessMap(CIRCULAR, 0, 0, rad, pos, function(me)
-- ProcessMapWho will iterate over the objects at the location,
-- so we're required to pass a MapElement alongside a function argument,
-- which allows ProcessMapWho to subsequently pass us the Thing* object it has found.
ProcessMapWho(me, function(t)
-- Check the object's Type and Model.
if (t.Type == T_PERSON and t.Model == M_PERSON_WILD) then
-- Found a Wildman person!
Log("Found Wildman " .. t.ThingNum)
end
-- Continue to next object..
return true
end)
-- Continue to next cell..
return true
end)
end
end

In this example, the search will stop as soon as the first Wildman is found.

function process_wildmen(pos, rad)
local found = false
ProcessMap(SQUARE, 0, 0, rad, pos, function(me)
ProcessMapWho(me, function(t)
if (t.Type == T_PERSON and t.Model == M_PERSON_WILD) then
Log("Found wildman " .. t.ThingNum)
found = true
return false
end
return true
end)
return not found
end)
end

Bitwise Operators​

More than seven years ago, Lua 5.3 introduced native bitwise operators. If you've worked with Lua before you might take them as granted, but Multiverse uses LuaJIT! Thus, we have to make use of the BitOp module — accessible through the bit keyword. There are many instances where you have to work with flags, so let's take a look at some helper functions we can declare.

Set Bit​

function EnableFlag(_f1, _f2)
if ((bit.band(_f1, _f2)) == 0) then
_f1 = bit.bor(_f1, _f2)
end
return _f1
end

convert = get_spells_type_info(M_SPELL_CONVERT_WILD)
convert.Flags = EnableFlag(convert.Flags, SPF_CYCLE_SPELL)

Clear Bit​

function DisableFlag(_f1, _f2)
if ((bit.band(_f1, _f2)) == _f2) then
_f1 = bit.band(_f1, bit.bnot(_f2))
end
return _f1
end

convert = get_spells_type_info(M_SPELL_CONVERT_WILD)
convert.Flags = DisableFlag(convert.Flags, SPF_CYCLE_SPELL)

Check Bit​

function HasFlag(_f1, _f2)
if ((bit.band(_f1, _f2)) == _f2) then
return true
end
return false
end

convert = get_spells_type_info(M_SPELL_CONVERT_WILD);
if (HasFlag(convert.Flags, SPF_CYCLE_SPELL)) then
Log("Flag SPF_CYCLE_SPELL is set!")
end