local utils = wesnoth.require "wml-utils" local wml_actions = wesnoth.wml_actions -- The [modify_unit] implementation -- modify_unit has basicially two implementations, the optimized implementation -- in the first part of this file and the fallback (old) implementation in the -- second part of this file local function make_set(t) local res = {} for i, v in ipairs(t) do res[v] = true end return res end local known_attributes = make_set { "mode", "x", "y", "ai_special", "goto_x", "goto_y", "extra_recruit", "side", "name", "role", "facing", "attacks_left", "hitpoints", "max_hitpoints", "moves", "max_moves", "experience", "max_experience", "resting", "canrecruit", "type", "variation", "ellipse", "halo", "recall_cost", "description", "hidden", "unrenamable", "profile", "zoc", "usage", "upkeep", } local known_tags = make_set { "object", "advancement", "trait", "effect", "filter", "status", "set_variable", -- todo: "set_variables", "clear_variable", "filter_recall", "variables", } local function is_simple(cfg) for k, v in pairs(wml.shallow_literal(cfg)) do if type(k) == "string" then if not known_attributes[k] then return false end else if not known_tags[v[1]] then return false end end end return true end local function simple_modify_unit(cfg) local filter = wml.get_child(cfg, "filter") or wml.error "[modify_unit] missing required [filter] tag" -- todo: investigate the following attrtibutes: -- id, alpha, flying, overlays local simple_attributes = { "ellipse", "halo", "recall_cost", "description", "hidden", "side", "name", "role", "facing", "attacks_left", "hitpoints", "max_hitpoints", "moves", "max_moves", "experience", "max_experience", "resting", "canrecruit", "zoc", "usage", "upkeep", } local function handle_unit(u) ---------- ATTRIBUTES THAT NEED SPECIAL HANDLING ---------- if cfg.x or cfg.y then u.loc = { cfg.x or u.x , cfg.y or u.y } end if cfg.goto_x or cfg.goto_y then u["goto"] = { cfg.goto_x or u["goto"][1] , cfg.goto_y or u["goto"][2] } end if cfg.extra_recruit then u.extra_recruit = cfg.extra_recruit:split() end if cfg.ai_special == "guardian" then u.status.guardian = true end if cfg.profile ~= nil then u.portrait = cfg.profile end if cfg.unrenamable ~= nil then u.renamable = not cfg.unrenamable end ---------- SIMPLE ATTRIBUTES ---------- for i, name in ipairs(simple_attributes) do if cfg[name] ~= nil then u[name] = cfg[name] end end ---------- TAGS ---------- local found_recall, found_variables = false, false for i, t in ipairs(wml.shallow_parsed(cfg)) do local tagname, tagcontent = t[1], t[2] if tagname == "object" or tagname == "trait" or tagname == "advancement" then if tagcontent.delayed_variable_substitution then tagcontent = wml.literal(tagcontent) else tagcontent = wml.parsed(tagcontent) end u:add_modification(tagname, tagcontent); elseif tagname == "effect" then local apply_to = tagcontent.apply_to if wesnoth.effects[apply_to] then wesnoth.effects[apply_to](u, tagcontent) else wml.error("[modify_unit] had invalid [effect]apply_to value") end elseif tagname == "status" then for i, v in pairs(tagcontent) do u.status[i] = v end elseif not found_recall and tagname == "filter_recall" then u.recall_filter = wml.merge(u.recall_filter, tagcontent, cfg.mode or "merge") found_recall = true -- Ignore all but the first elseif not found_variables and tagname == "variables" then u.variables.__cfg = wml.merge(u.variables.__cfg, tagcontent, cfg.mode or "merge") found_variables = true -- Ignore all but the first elseif tagname == "set_variable" then wesnoth.wml_actions.set_variable(tagcontent, u.variables) elseif tagname == "clear_variable" then wesnoth.wml_actions.clear_variable(tagcontent, u.variables) end end -- handle 'type' and 'variation' last. if cfg.type == "" then u.experience = u.max_experience elseif cfg.type or cfg.variation then u.experience = 0 u:transform(cfg.type or u.type, cfg.variation) end -- always do an advancement here (not only when experience/max_experience/type was modified) -- for compatibility with old code. -- Skip for recall list units if u.valid == 'map' then u:advance() end end local this_unit = utils.start_var_scope("this_unit") for i, u in ipairs(wesnoth.units.find(filter)) do wml.variables["this_unit"] = u.__cfg handle_unit(u) end utils.end_var_scope("this_unit", this_unit) end function wml_actions.modify_unit(cfg) if is_simple(cfg) then simple_modify_unit(cfg) return end local unit_variable = "LUA_modify_unit" local replace_mode = cfg.mode == "replace" local function handle_attributes(cfg, unit_path, toplevel) for current_key, current_value in pairs(wml.shallow_parsed(cfg)) do if type(current_value) ~= "table" and (not toplevel or (current_key ~= "type" and current_key ~= "mode")) then wml.variables[string.format("%s.%s", unit_path, current_key)] = current_value end end end local function handle_child(cfg, unit_path) local children_handled = {} local cfg = wml.shallow_parsed(cfg) handle_attributes(cfg, unit_path) for current_index, current_table in ipairs(cfg) do local current_tag = current_table[1] local tag_index = children_handled[current_tag] or 0 handle_child(current_table[2], string.format("%s.%s[%u]", unit_path, current_tag, tag_index)) children_handled[current_tag] = tag_index + 1 end end local filter = wml.get_child(cfg, "filter") or wml.error "[modify_unit] missing required [filter] tag" local function handle_unit(unit_num) local children_handled = {} local unit_path = string.format("%s[%u]", unit_variable, unit_num) local this_unit = wml.variables[unit_path] wml.variables["this_unit"] = this_unit handle_attributes(cfg, unit_path, true) for current_index, current_table in ipairs(wml.shallow_parsed(cfg)) do local current_tag = current_table[1] if current_tag == "filter" then -- nothing elseif current_tag == "object" or current_tag == "trait" or current_tag == "advancement" then local mod = current_table[2] if mod.delayed_variable_substitution then mod = wml.literal(mod) else mod = wml.parsed(mod) end local unit = wml.variables[unit_path] unit = wesnoth.units.create(unit) unit:add_modification(current_tag, mod) unit = unit.__cfg; wml.variables[unit_path] = unit elseif current_tag == "effect" then local mod = current_table[2] local apply_to = mod.apply_to if wesnoth.effects[apply_to] then local unit = wml.variables[unit_path] unit = wesnoth.units.create(unit) wesnoth.effects[apply_to](unit, mod) unit = unit.__cfg; wml.variables[unit_path] = unit else wml.error("[modify_unit] had invalid [effect]apply_to value") end elseif current_tag == "set_variable" then local unit = wesnoth.units.create(wml.variables[unit_path]) wesnoth.wml_actions.set_variable(current_table[2], unit.variables) wml.variables[unit_path] = unit.__cfg elseif current_tag == "clear_variable" then local unit = wesnoth.units.create(wml.variables[unit_path]) wesnoth.wml_actions.clear_variable(current_table[2], unit.variables) wml.variables[unit_path] = unit.__cfg else if replace_mode then wml.variables[string.format("%s.%s", unit_path, current_tag)] = {} end local tag_index = children_handled[current_tag] or 0 handle_child(current_table[2], string.format("%s.%s[%u]", unit_path, current_tag, tag_index)) children_handled[current_tag] = tag_index + 1 end end if cfg.type then if cfg.type ~= "" then wml.variables[unit_path .. ".advances_to"] = cfg.type end wml.variables[unit_path .. ".experience"] = wml.variables[unit_path .. ".max_experience"] end wml_actions.kill({ id = this_unit.id, animate = false }) wml_actions.unstore_unit { variable = unit_path } end wml_actions.store_unit { {"filter", filter}, variable = unit_variable } local max_index = wml.variables[unit_variable .. ".length"] - 1 local this_unit = utils.start_var_scope("this_unit") for current_unit = 0, max_index do handle_unit(current_unit) end utils.end_var_scope("this_unit", this_unit) wml.variables[unit_variable] = nil end