[Lua] Use wml.tag to construct WML tables

This commit is contained in:
Celtic Minstrel 2024-02-04 15:11:47 -05:00 committed by Celtic Minstrel
parent ef13ea4565
commit 0f6fe67477
24 changed files with 86 additions and 82 deletions

View file

@ -882,14 +882,14 @@ function ai_helper.get_closest_location(hex, location_filter, unit, avoid_map)
local loc_filter = {}
if (radius == 0) then
loc_filter = {
{ "and", { x = hex[1], y = hex[2], include_borders = include_borders, radius = radius } },
{ "and", location_filter }
wml.tag["and"] { x = hex[1], y = hex[2], include_borders = include_borders, radius = radius },
wml.tag["and"] ( location_filter )
}
else
loc_filter = {
{ "and", { x = hex[1], y = hex[2], include_borders = include_borders, radius = radius } },
{ "not", { x = hex[1], y = hex[2], radius = radius - 1 } },
{ "and", location_filter }
wml.tag["and"] { x = hex[1], y = hex[2], include_borders = include_borders, radius = radius },
wml.tag["not"] { x = hex[1], y = hex[2], radius = radius - 1 },
wml.tag["and"] ( location_filter )
}
end
@ -1138,7 +1138,7 @@ end
---@return unit[]
function ai_helper.get_live_units(filter)
-- Note: the order of the filters and the [and] tags are important for speed reasons
return wesnoth.units.find_on_map { { "not", { status = "petrified" } }, { "and", filter } }
return wesnoth.units.find_on_map { wml.tag["not"] { status = "petrified" }, wml.tag["and"] ( filter ) }
end
---Find units who can move that match the specified filter.
@ -1153,9 +1153,9 @@ function ai_helper.get_units_with_moves(filter, exclude_guardians)
exclude_status = exclude_status .. ',guardian'
end
return wesnoth.units.find_on_map {
{ "and", { formula = "moves > 0" } },
{ "not", { status = exclude_status } },
{ "and", filter }
wml.tag["and"] { formula = "moves > 0" },
wml.tag["not"] { status = exclude_status },
wml.tag["and"] ( filter )
}
end
@ -1165,9 +1165,9 @@ end
function ai_helper.get_units_with_attacks(filter)
-- Note: the order of the filters and the [and] tags are important for speed reasons
return wesnoth.units.find_on_map {
{ "and", { formula = "attacks_left > 0 and size(attacks) > 0" } },
{ "not", { status = "petrified" } },
{ "and", filter }
wml.tag["and"] { formula = "attacks_left > 0 and size(attacks) > 0" },
wml.tag["not"] { status = "petrified" },
wml.tag["and"] ( filter )
}
end
@ -1202,7 +1202,7 @@ function ai_helper.is_visible_unit(viewing_side, unit)
if (not unit) then return false end
if unit:matches({ { "filter_vision", { side = viewing_side, visible = 'no' } } }) then
if unit:matches({ wml.tag.filter_vision { side = viewing_side, visible = 'no' } }) then
return false
end
@ -1228,12 +1228,13 @@ function ai_helper.get_attackable_enemies(filter, side, cfg)
ai_helper.check_viewing_side(viewing_side)
local ignore_visibility = cfg and cfg.ignore_visibility
---@type WMLTable
local filter_plus_vision = {}
if filter then filter_plus_vision = ai_helper.table_copy(filter) end
if (not ignore_visibility) then
filter_plus_vision = {
{ "and", filter_plus_vision },
{ "filter_vision", { side = viewing_side, visible = 'yes' } }
wml.tag["and"] ( filter_plus_vision ),
wml.tag.filter_vision { side = viewing_side, visible = 'yes' }
}
end
@ -1408,7 +1409,7 @@ end
function ai_helper.get_enemy_dst_src(enemies, cfg)
if (not enemies) then
enemies = wesnoth.units.find_on_map {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side} } } }
wml.tag.filter_side { wml.tag.enemy_of { side = wesnoth.current.side} }
}
end
@ -1567,9 +1568,9 @@ function ai_helper.next_hop(unit, x, y, cfg)
local units
if ignore_visibility then
units = wesnoth.units.find_on_map({ { "not", { id = unit.id } } })
units = wesnoth.units.find_on_map({ wml.tag["not"] { id = unit.id } })
else
units = ai_helper.get_visible_units(viewing_side, { { "not", { id = unit.id } } })
units = ai_helper.get_visible_units(viewing_side, { wml.tag["not"] { id = unit.id } })
end
local unit_map = LS.create()
for _,u in ipairs(units) do unit_map:insert(u.x, u.y, u.id) end

View file

@ -392,8 +392,8 @@ local function init_data(leader)
end
-- Collect all possible enemy recruits and count them as virtual enemies
local enemy_sides = wesnoth.sides.find({
{ "enemy_of", {side = wesnoth.current.side} },
{ "has_unit", { canrecruit = true }} })
wml.tag.enemy_of {side = wesnoth.current.side},
wml.tag.has_unit { canrecruit = true }})
for i, side in ipairs(enemy_sides) do
possible_enemy_recruit_count = possible_enemy_recruit_count + #(wesnoth.sides[side.side].recruit)
for j, unit_type in ipairs(wesnoth.sides[side.side].recruit) do
@ -679,7 +679,7 @@ local function find_best_recruit(attack_type_count, unit_attack_type_count, recr
-- If no enemy is on the map, then we first use closest enemy start hex,
-- and if that does not exist either, a location mirrored w.r.t the center of the map
if not enemy_location then
local enemy_sides = wesnoth.sides.find({ { "enemy_of", {side = wesnoth.current.side} } })
local enemy_sides = wesnoth.sides.find({ wml.tag.enemy_of {side = wesnoth.current.side} })
local min_dist = math.huge
for _, side in ipairs(enemy_sides) do
local enemy_start_hex = wesnoth.current.map.special_locations[side.side]
@ -895,7 +895,7 @@ function ca_recruit_rushers:evaluation(cfg, data, filter_own)
local leader = wesnoth.units.find_on_map {
side = wesnoth.current.side,
canrecruit = 'yes',
{ "and", filter_own }
wml.tag["and"] ( filter_own )
}[1]
if (not leader) or (not wesnoth.terrain_types[wesnoth.current.map[leader]].keep) then
@ -1069,7 +1069,7 @@ function ca_recruit_rushers:execution(cfg, data, filter_own)
local leader = wesnoth.units.find_on_map {
side = wesnoth.current.side,
canrecruit = 'yes',
{ "and", filter_own }
wml.tag["and"] ( filter_own )
}[1]
repeat
recruit_data.recruit.best_hex, recruit_data.recruit.target_hex = find_best_recruit_hex(leader, recruit_data)

View file

@ -66,7 +66,7 @@ function ca_village_hunt:evaluation(cfg, data, filter_own)
for _,unit in ipairs(units) do
local best_cost = AH.no_path
for i,v in ipairs(villages) do
if not wesnoth.map.matches(v, { {"filter_owner", { {"ally_of", { side = wesnoth.current.side }} }} }) then
if not wesnoth.map.matches(v, { wml.tag.filter_owner { wml.tag.ally_of { side = wesnoth.current.side } } }) then
local path, cost = AH.find_path_with_avoid(unit, v[1], v[2], avoid_map2)
if (cost < best_cost) then
local dst = AH.next_hop(unit, nil, nil, { path = path, avoid_map = avoid_map2 })

View file

@ -344,7 +344,7 @@ return {
local leader = wesnoth.units.find_on_map {
side = wesnoth.current.side,
canrecruit = 'yes',
{ "and", params.filter_own }
wml.tag["and"] ( params.filter_own )
}[1]
if (not leader) or (not wesnoth.terrain_types[wesnoth.current.map[leader]].keep) then
@ -416,8 +416,8 @@ return {
end
-- Collect all possible enemy recruits and count them as virtual enemies
local enemy_sides = wesnoth.sides.find({
{ "enemy_of", {side = wesnoth.current.side} },
{ "has_unit", { canrecruit = true }} })
wml.tag.enemy_of {side = wesnoth.current.side},
wml.tag.has_unit { canrecruit = true } })
for i, side in ipairs(enemy_sides) do
possible_enemy_recruit_count = possible_enemy_recruit_count + #(wesnoth.sides[side.side].recruit)
for j, unit_type in ipairs(wesnoth.sides[side.side].recruit) do
@ -577,7 +577,7 @@ return {
local leader = wesnoth.units.find_on_map {
side = wesnoth.current.side,
canrecruit = 'yes',
{ "and", params.filter_own }
wml.tag["and"] ( params.filter_own )
}[1]
repeat
recruit_data.recruit.best_hex, recruit_data.recruit.target_hex = ai_cas:find_best_recruit_hex(leader, recruit_data)
@ -684,7 +684,7 @@ return {
-- If no enemy is on the map, then we first use closest enemy start hex,
-- and if that does not exist either, a location mirrored w.r.t the center of the map
if not enemy_location then
local enemy_sides = wesnoth.sides.find({ { "enemy_of", {side = wesnoth.current.side} } })
local enemy_sides = wesnoth.sides.find({ wml.tag.enemy_of {side = wesnoth.current.side} })
local min_dist = math.huge
for _, side in ipairs(enemy_sides) do
local enemy_start_hex = wesnoth.current.map.special_locations[side.side]

View file

@ -8,8 +8,8 @@ local function get_units_target(cfg)
}
local target = wesnoth.units.find_on_map {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "and", wml.get_child(cfg, "filter_second") }
wml.tag.filter_side { wml.tag.enemy_of { side = wesnoth.current.side } },
wml.tag["and"] ( wml.get_child(cfg, "filter_second") )
}[1]
return units, target

View file

@ -229,9 +229,9 @@ function ca_bottleneck_move:evaluation(cfg, data)
local units = {}
if MAISD.get_mai_self_data(data, cfg.ai_id, "side_leader_activated") then
units = AH.get_units_with_moves { side = wesnoth.current.side, { "and", wml.get_child(cfg, "filter") } }
units = AH.get_units_with_moves { side = wesnoth.current.side, wml.tag["and"] ( wml.get_child(cfg, "filter") ) }
else
units = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no', { "and", wml.get_child(cfg, "filter") } }
units = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no', wml.tag["and"] ( wml.get_child(cfg, "filter") ) }
end
if (not units[1]) then return 0 end
@ -343,8 +343,8 @@ function ca_bottleneck_move:evaluation(cfg, data)
-- reachable, but must be excluded. This could also be done below by
-- using bottleneck_move_out_of_way(), but this is much faster
local allies = AH.get_visible_units(wesnoth.current.side, {
{ "filter_side", { { "allied_with", { side = wesnoth.current.side } } } },
{ "not", { side = wesnoth.current.side } }
wml.tag.filter_side { wml.tag.allied_with { side = wesnoth.current.side } },
wml.tag["not"] ( { side = wesnoth.current.side } )
})
local allies_map = LS.create()
for _,ally in ipairs(allies) do
@ -445,7 +445,7 @@ function ca_bottleneck_move:evaluation(cfg, data)
-- If there's another unit in the best location, moving it out of the way becomes the best move
---@type unit?
local unit_in_way = wesnoth.units.find_on_map { x = best_hex[1], y = best_hex[2],
{ "not", { id = best_unit.id } }
wml.tag["not"] { id = best_unit.id }
}[1]
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
unit_in_way = nil
@ -469,9 +469,9 @@ function ca_bottleneck_move:execution(cfg, data)
if BD_bottleneck_moves_done then
local units = {}
if MAISD.get_mai_self_data(data, cfg.ai_id, "side_leader_activated") then
units = AH.get_units_with_moves { side = wesnoth.current.side, { "and", wml.get_child(cfg, "filter") } }
units = AH.get_units_with_moves { side = wesnoth.current.side, wml.tag["and"] ( wml.get_child(cfg, "filter") ) }
else
units = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no', { "and", wml.get_child(cfg, "filter") } }
units = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no', wml.tag["and"] ( wml.get_child(cfg, "filter") ) }
end
for _,unit in ipairs(units) do

View file

@ -48,7 +48,7 @@ function ca_forest_animals_tusker_attack:execution(cfg)
local adj_tusklets = wesnoth.units.find_on_map {
side = wesnoth.current.side,
type = cfg.tusklet_type,
{ "filter_adjacent", { id = target.id } }
wml.tag.filter_adjacent { id = target.id }
}
local best_hex = AH.find_best_move(attacker, function(x, y)

View file

@ -27,7 +27,7 @@ function ca_hang_out:evaluation(cfg, data)
MAISD.insert_mai_self_data(data, cfg.ai_id, { mobilize_units = true })
-- Need to unmark all units also (all units, with and without moves)
local units = wesnoth.units.find_on_map { side = wesnoth.current.side, { "and", wml.get_child(cfg, "filter") } }
local units = wesnoth.units.find_on_map { side = wesnoth.current.side, wml.tag["and"] ( wml.get_child(cfg, "filter") ) }
for _,unit in ipairs(units) do
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
end

View file

@ -16,9 +16,9 @@ function ca_healer_initialize:execution(cfg, data)
name = "ai_default_rca::aspect_attacks",
id = "no_healers_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", { ability = "healing", { "and", wml.get_child(cfg, "filter") } } }
} }
wml.tag.filter_own {
wml.tag["not"] { ability = "healing", wml.tag["and"] ( wml.get_child(cfg, "filter") ) }
}
}
)

View file

@ -14,7 +14,7 @@ function ca_healer_move:evaluation(cfg, data)
local all_healers = wesnoth.units.find_on_map {
side = wesnoth.current.side,
ability = "healing",
{ "and", wml.get_child(cfg, "filter") }
wml.tag["and"] ( wml.get_child(cfg, "filter") )
}
local healers, healers_noMP = {}, {}
@ -32,7 +32,7 @@ function ca_healer_move:evaluation(cfg, data)
local all_healees = wesnoth.units.find_on_map {
side = wesnoth.current.side,
{ "and", wml.get_child(cfg, "filter_second") }
wml.tag["and"] ( wml.get_child(cfg, "filter_second") )
}
local healees, healees_MP = {}, {}

View file

@ -4,7 +4,7 @@ local M = wesnoth.map
local function get_sheep(cfg)
local sheep = wesnoth.units.find_on_map {
side = wesnoth.current.side,
{ "and", wml.get_child(cfg, "filter_second") }
wml.tag["and"] ( wml.get_child(cfg, "filter_second") )
}
return sheep
end
@ -12,16 +12,19 @@ end
local function get_dogs(cfg)
local dogs = AH.get_units_with_attacks {
side = wesnoth.current.side,
{ "and", wml.get_child(cfg, "filter") }
wml.tag["and"] ( wml.get_child(cfg, "filter") )
}
return dogs
end
local function get_enemies(cfg, radius)
local enemies = AH.get_attackable_enemies {
{ "filter_location",
{ radius = radius,
{ "filter", { side = wesnoth.current.side, { "and", wml.get_child(cfg, "filter_second") } } } }
wml.tag.filter_location {
radius = radius,
wml.tag.filter {
side = wesnoth.current.side,
wml.tag["and"] ( wml.get_child(cfg, "filter_second") )
}
}
}
return enemies

View file

@ -10,7 +10,7 @@ return function(cfg)
x = herd_loc[1],
y = herd_loc[2],
radius = 999,
{ "filter_radius", { { "not", location_filter } } }
wml.tag.filter_radius { wml.tag["not"] ( location_filter ) }
})
-- Then, also exclude hexes next to herding_perimeter; some of the functions work better like that

View file

@ -14,8 +14,8 @@ end
local function get_sheep_to_herd(cfg)
local all_sheep = wesnoth.units.find_on_map {
side = wesnoth.current.side,
{ "and", wml.get_child(cfg, "filter_second") },
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, { "and", wml.get_child(cfg, "filter") } } } } }
wml.tag["and"] ( wml.get_child(cfg, "filter_second") ),
wml.tag["not"] { wml.tag.filter_adjacent { side = wesnoth.current.side, wml.tag["and"] ( wml.get_child(cfg, "filter") ) } }
}
local sheep_to_herd = {}

View file

@ -23,8 +23,8 @@ function ca_herding_sheep_runs_dog:execution(cfg)
local sheep = get_next_sheep(cfg)
-- Get the first dog that the sheep is adjacent to
local dog = wesnoth.units.find_on_map { side = wesnoth.current.side, { "and", wml.get_child(cfg, "filter") },
{ "filter_adjacent", { x = sheep.x, y = sheep.y } }
local dog = wesnoth.units.find_on_map { side = wesnoth.current.side, wml.tag["and"] ( wml.get_child(cfg, "filter") ),
wml.tag.filter_adjacent { x = sheep.x, y = sheep.y }
}[1]
local herd_loc = AH.get_named_loc_xy('herd', cfg)

View file

@ -28,8 +28,8 @@ function ca_lurkers:execution(cfg)
local lurk_area = wml.get_child(cfg, "filter_location")
local reachable_attack_terrain =
LS.of_pairs(wesnoth.map.find {
{ "and", { x = lurker.x, y = lurker.y, radius = lurker.moves } },
{ "and", lurk_area }
wml.tag["and"] { x = lurker.x, y = lurker.y, radius = lurker.moves },
wml.tag["and"] ( lurk_area )
})
reachable_attack_terrain:inter(reach)
@ -37,7 +37,7 @@ function ca_lurkers:execution(cfg)
reachable_attack_terrain = reachable_attack_terrain:filter(function(x, y, v)
local occ_hex = AH.get_visible_units(wesnoth.current.side, {
x = x, y = y,
{ "not", { x = lurker.x, y = lurker.y } }
wml.tag["not"] { x = lurker.x, y = lurker.y }
})[1]
return not occ_hex
end)
@ -64,8 +64,8 @@ function ca_lurkers:execution(cfg)
if (lurker.moves > 0) and (not cfg.stationary) then
local reachable_wander_terrain =
LS.of_pairs( wesnoth.map.find {
{ "and", { x = lurker.x, y = lurker.y, radius = lurker.moves } },
{ "and", wml.get_child(cfg, "filter_location_wander") or lurk_area }
wml.tag["and"] { x = lurker.x, y = lurker.y, radius = lurker.moves },
wml.tag["and"] ( wml.get_child(cfg, "filter_location_wander") or lurk_area )
})
reachable_wander_terrain:inter(reach)
@ -73,7 +73,7 @@ function ca_lurkers:execution(cfg)
reachable_wander_terrain = reachable_wander_terrain:filter(function(x, y, v)
local occ_hex = AH.get_visible_units(wesnoth.current.side, {
x = x, y = y,
{ "not", { x = lurker.x, y = lurker.y } }
wml.tag["not"] { x = lurker.x, y = lurker.y }
})[1]
return not occ_hex
end)

View file

@ -10,7 +10,7 @@ return function(cfg)
-- Returns nil for all arguments if there are no messengers on the map
local filter = wml.get_child(cfg, "filter") or { id = cfg.id }
local messengers = wesnoth.units.find_on_map { side = wesnoth.current.side, { "and", filter } }
local messengers = wesnoth.units.find_on_map { side = wesnoth.current.side, wml.tag["and"] ( filter ) }
if (not messengers[1]) then return end
local waypoints = AH.get_multi_named_locs_xy('waypoint', cfg)

View file

@ -6,7 +6,7 @@ local ca_swarm_move = {}
function ca_swarm_move:evaluation(cfg)
local units = wesnoth.units.find_on_map {
side = wesnoth.current.side,
{ "and" , wml.get_child(cfg, "filter") }
wml.tag["and"] ( wml.get_child(cfg, "filter") )
}
for _,unit in ipairs(units) do
if (unit.moves > 0) then return cfg.ca_score end
@ -22,7 +22,7 @@ function ca_swarm_move:execution(cfg)
-- If no close enemies, swarm will move semi-randomly, staying close together, but away from enemies
local all_units = wesnoth.units.find_on_map {
side = wesnoth.current.side,
{ "and" , wml.get_child(cfg, "filter") }
wml.tag["and"] ( wml.get_child(cfg, "filter") )
}
local units, units_no_moves = {}, {}

View file

@ -20,15 +20,15 @@ return {
-- 1. next to target
-- 2. not occupied by an allied unit (except for unit itself)
W.store_reachable_locations {
{ "filter", { id = attacker.id } },
{ "filter_location", {
{ "filter_adjacent_location", {
{ "filter", { id = target_id } }
} },
{ "not", {
{ "filter", { { "not", { id = attacker.id } } } }
} }
} },
wml.tag.filter { id = attacker.id },
wml.tag.filter_location {
wml.tag.filter_adjacent_location {
wml.tag.filter { id = target_id }
},
wml.tag["not"] {
wml.tag.filter { wml.tag["not"] { id = attacker.id } }
}
},
moves = "current",
variable = "tmp_locs"
}
@ -51,7 +51,7 @@ return {
name = "ai_default_rca::aspect_attacks",
id = "limited_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", { id = target_id } }
wml.tag.filter_enemy { id = target_id }
}
)

View file

@ -19,7 +19,7 @@ function ca_ogres_flee:execution()
}
-- Need the enemy map and enemy attack map if avoid_enemies is set
local enemies = wesnoth.units.find_on_map { { "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } } }
local enemies = wesnoth.units.find_on_map { wml.tag.filter_side { wml.tag.enemy_of {side = wesnoth.current.side} } }
local enemy_attack_map = BC.get_attack_map(enemies)
local max_rating, best_hex, best_unit = - math.huge, nil, nil

View file

@ -48,7 +48,7 @@ function ca_transport:execution()
local landing_site_map = LS.of_pairs(
wesnoth.map.find {
terrain = 'W*',
{ "filter_adjacent_location", { terrain = '!, W*' } }
wml.tag.filter_adjacent_location { terrain = '!, W*' }
}
)
@ -79,7 +79,7 @@ function ca_transport:execution()
elseif wesnoth.map.matches(x, y,
{
terrain = "W*",
{ "filter_adjacent_location", { terrain = "!, W*" } }
wml.tag.filter_adjacent_location { terrain = "!, W*" }
}
)
then
@ -123,7 +123,7 @@ function ca_transport:execution()
local deep_water_map = LS.of_pairs(
wesnoth.map.find {
terrain = 'Wo',
{ "not", { { "filter_adjacent_location", { terrain = '!, Wo' } } } }
wml.tag["not"] { wml.tag.filter_adjacent_location { terrain = '!, Wo' } }
}
)

View file

@ -16,7 +16,7 @@ end
function muff_toras_move:execution()
local muff_toras = wesnoth.units.find_on_map { id = 'Muff Toras' }[1]
local units = wesnoth.units.find_on_map { side = 3, { 'not', { id = 'Muff Toras' } } }
local units = wesnoth.units.find_on_map { side = 3, wml.tag['not'] { id = 'Muff Toras' } }
local enemies = AH.get_attackable_enemies()
local enemy_attack_map = BC.get_attack_map(enemies)

View file

@ -43,7 +43,7 @@ end
-- set the teams color in later scenarios acccordingly.
function wesnoth.wml_actions.wc2_fix_colors(cfg)
local player_sides = wesnoth.sides.find(wml.get_child(cfg, "player_sides"))
local other_sides = wesnoth.sides.find { { "not", wml.get_child(cfg, "player_sides") } }
local other_sides = wesnoth.sides.find { wml.tag["not"] ( wml.get_child(cfg, "player_sides") ) }
local available_colors = { "red", "blue", "green", "purple", "black", "brown", "orange", "white", "teal", "gold" }
local taken_colors = {}

View file

@ -48,7 +48,7 @@ local function status_anim_update(is_undo)
-- find all units on map with ability = diversion but not status.diversion = true
local div_candidates = wesnoth.units.find_on_map({
ability = "diversion",
{"not", { status = "diversion" }}
wml.tag["not"] { status = "diversion" }
})
-- for those that pass the filter now, change status and fire animation
for index, ec_unit in ipairs(div_candidates) do

View file

@ -96,7 +96,7 @@ function wml_actions.modify_unit(cfg)
wml_actions.unstore_unit { variable = unit_path }
end
wml_actions.store_unit { {"filter", filter}, variable = unit_variable }
wml_actions.store_unit { wml.tag.filter(filter), variable = unit_variable }
local max_index = wml.variables[unit_variable .. ".length"] - 1
local this_unit <close> = utils.scoped_var("this_unit")