wesnoth/data/lua/wml/random_placement.lua

98 lines
3.7 KiB
Lua

local helper = wesnoth.require "helper"
local utils = wesnoth.require "wml-utils"
wesnoth.wml_actions.random_placement = function(cfg)
local parsed = wml.shallow_parsed(cfg)
-- TODO: In most cases this tag is used to place units, so maybe make include_borders=no the default for [filter_location]?
local filter = wml.get_child(parsed, "filter_location") or {}
local command = wml.get_child(parsed, "command") or helper.wml_error("[random_placement] missing required [command] subtag")
local distance = cfg.min_distance or 0
local num_items = cfg.num_items or helper.wml_error("[random_placement] missing required 'num_items' attribute")
local variable = cfg.variable or helper.wml_error("[random_placement] missing required 'variable' attribute")
local allow_less = cfg.allow_less == true
local variable_previous = utils.start_var_scope(variable)
local math_abs = math.abs
local locs = wesnoth.get_locations(filter)
if type(num_items) == "string" then
if num_items:match('^%s%(.*%)%s$') then
local params = {size = #locs}
local result = wesnoth.eval_formula(num_items, params)
num_items = math.floor(tonumber(result))
elseif num_items:match('^%d+%%$') then
num_items = tonumber(num_items:sub(1,-2))
else
wesnoth.deprecated_message('num_items=lua', 2, '1.17.0', 'Use of Lua for [random_placement]num_items is deprecated. Use WFL instead and enclose the whole thing in parentheses.')
num_items = math.floor(load("local size = " .. #locs .. "; return " .. num_items)())
print("num_items=" .. num_items .. ", #locs=" .. #locs)
end
end
local size = #locs
for i = 1, num_items do
if size == 0 then
if allow_less then
print("placed only " .. i .. " items")
return
else
helper.wml_error("[random_placement] failed to place items. only " .. i .. " items were placed")
end
end
local index = wesnoth.random(size)
local point = locs[index]
wml.variables[variable .. ".x"] = point[1]
wml.variables[variable .. ".y"] = point[2]
wml.variables[variable .. ".n"] = i
wml.variables[variable .. ".terrain"] = wesnoth.get_terrain(point[1], point[2])
if distance < 0 then
-- optimisation: nothing to do for distance < 0
elseif distance == 0 then
-- optimisation: for distance = 0 we just need to remove the element at index
-- optimisation: swapping elements and storing size in an extra variable is faster than table.remove(locs, j)
locs[index] = locs[size]
size = size - 1
else
-- the default case and the main reason why this was implemented.
for j = size, 1, -1 do
local x1 = locs[j][1]
local y1 = locs[j][2]
local x2 = point[1]
local y2 = point[2]
-- optimisation: same effect as "if wesnoth.map.distance_between(x1,y1,x2,y2) <= distance then goto continue; end" but faster.
local d_x = math_abs(x1-x2)
if d_x > distance then
goto continue
end
if d_x % 2 ~= 0 then
if x1 % 2 == 0 then
y2 = y2 - 0.5
else
y2 = y2 + 0.5
end
end
local d_y = math_abs(y1-y2)
if d_x + 2*d_y > 2*distance then
goto continue
end
-- optimisation: swapping elements and storing size in an extra variable is faster than table.remove(locs, j)
locs[j] = locs[size]
size = size - 1
::continue::
end
end
-- TODO: This should really be "do" but is kept as "command" for compatibility
for do_child in wml.child_range(cfg, "command") do
local action = utils.handle_event_commands(do_child, "loop")
if action == "break" then
utils.set_exiting("none")
return
elseif action == "continue" then
utils.set_exiting("none")
break
elseif action ~= "none" then
utils.end_var_scope(variable, variable_previous)
return
end
end
end
utils.end_var_scope(variable, variable_previous)
end