diff --git a/data/lua/wml-flow.lua b/data/lua/wml-flow.lua
new file mode 100644
index 000000000000..84e8e45d2ce9
--- /dev/null
+++ b/data/lua/wml-flow.lua
@@ -0,0 +1,257 @@
+
+
+function wml_actions.command(cfg)
+ utils.handle_event_commands(cfg, "plain")
+end
+
+-- we can't create functions with names that are Lua keywords (eg if, while)
+-- instead, we store the following anonymous functions directly into
+-- the table, using the [] operator, rather than by using the point syntax
+
+wml_actions["if"] = function(cfg)
+ if not (helper.get_child(cfg, 'then') or helper.get_child(cfg, 'elseif') or helper.get_child(cfg, 'else')) then
+ helper.wml_error("[if] didn't find any [then], [elseif], or [else] children.")
+ end
+
+ if wesnoth.eval_conditional(cfg) then -- evaluate [if] tag
+ for then_child in helper.child_range(cfg, "then") do
+ local action = utils.handle_event_commands(then_child, "conditional")
+ if action ~= "none" then break end
+ end
+ return -- stop after executing [then] tags
+ end
+
+ for elseif_child in helper.child_range(cfg, "elseif") do
+ if wesnoth.eval_conditional(elseif_child) then -- we'll evaluate the [elseif] tags one by one
+ for then_tag in helper.child_range(elseif_child, "then") do
+ local action = utils.handle_event_commands(then_tag, "conditional")
+ if action ~= "none" then break end
+ end
+ return -- stop on first matched condition
+ end
+ end
+
+ -- no matched condition, try the [else] tags
+ for else_child in helper.child_range(cfg, "else") do
+ local action = utils.handle_event_commands(else_child, "conditional")
+ if action ~= "none" then break end
+ end
+end
+
+wml_actions["while"] = function( cfg )
+ if helper.child_count(cfg, "do") == 0 then
+ helper.wml_error "[while] does not contain any [do] tags"
+ end
+
+ -- execute [do] up to 65536 times
+ for i = 1, 65536 do
+ if wesnoth.eval_conditional( cfg ) then
+ for do_child in helper.child_range( cfg, "do" ) 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
+ return
+ end
+ end
+ else return end
+ end
+end
+
+wml_actions["break"] = function(cfg)
+ utils.set_exiting("break")
+end
+
+wml_actions["return"] = function(cfg)
+ utils.set_exiting("return")
+end
+
+function wml_actions.continue(cfg)
+ utils.set_exiting("continue")
+end
+
+wesnoth.wml_actions["for"] = function(cfg)
+ if helper.child_count(cfg, "do") == 0 then
+ helper.wml_error "[for] does not contain any [do] tags"
+ end
+
+ local loop_lim = {}
+ local first
+ if cfg.array ~= nil then
+ if cfg.reverse then
+ first = wesnoth.get_variable(cfg.array .. ".length") - 1
+ loop_lim.last = 0
+ loop_lim.step = -1
+ else
+ first = 0
+ loop_lim.last = '$($' .. cfg.array .. ".length - 1)"
+ loop_lim.step = 1
+ end
+ else
+ -- Get a literal config to fetch end and step;
+ -- this done is to delay expansion of variables
+ local cfg_lit = helper.literal(cfg)
+ first = cfg.start or 0
+ loop_lim.last = cfg_lit["end"] or first
+ if cfg.step then loop_lim.step = cfg_lit.step end
+ end
+ loop_lim = wesnoth.tovconfig(loop_lim)
+ if loop_lim.step == 0 then -- Sanity check
+ helper.wml_error("[for] has a step of 0!")
+ end
+ if (first < loop_lim.last and loop_lim.step <= 0) or (first > loop_lim.last and loop_lim.step >= 0) then
+ -- Sanity check: If they specify something like start,end,step=1,4,-1
+ -- then we do nothing
+ return
+ end
+ local i_var = cfg.variable or "i"
+ local save_i = utils.start_var_scope(i_var)
+ wesnoth.set_variable(i_var, first)
+ local function loop_condition()
+ local sentinel = loop_lim.last
+ if loop_lim.step then
+ sentinel = sentinel + loop_lim.step
+ elseif loop_lim.last < first then
+ sentinel = sentinel - 1
+ else
+ sentinel = sentinel + 1
+ end
+ if loop_lim.step > 0 then
+ return wesnoth.get_variable(i_var) < sentinel
+ else
+ return wesnoth.get_variable(i_var) > sentinel
+ end
+ end
+ while loop_condition() do
+ for do_child in helper.child_range( cfg, "do" ) do
+ local action = utils.handle_event_commands(do_child, "loop")
+ if action == "break" then
+ utils.set_exiting("none")
+ goto exit
+ elseif action == "continue" then
+ utils.set_exiting("none")
+ break
+ elseif action ~= "none" then
+ goto exit
+ end
+ end
+ wesnoth.set_variable(i_var, wesnoth.get_variable(i_var) + loop_lim.step)
+ end
+ ::exit::
+ utils.end_var_scope(i_var, save_i)
+end
+
+wml_actions["repeat"] = function(cfg)
+ if helper.child_count(cfg, "do") == 0 then
+ helper.wml_error "[repeat] does not contain any [do] tags"
+ end
+
+ local times = cfg.times or 1
+ for i = 1, times do
+ for do_child in helper.child_range( cfg, "do" ) 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
+ return
+ end
+ end
+ end
+end
+
+function wml_actions.foreach(cfg)
+ if helper.child_count(cfg, "do") == 0 then
+ helper.wml_error "[foreach] does not contain any [do] tags"
+ end
+
+ local array_name = cfg.variable or helper.wml_error "[foreach] missing required variable= attribute"
+ local array = helper.get_variable_array(array_name)
+ if #array == 0 then return end -- empty and scalars unwanted
+ local item_name = cfg.item_var or "this_item"
+ local this_item = utils.start_var_scope(item_name) -- if this_item is already set
+ local i_name = cfg.index_var or "i"
+ local i = utils.start_var_scope(i_name) -- if i is already set
+ local array_length = wesnoth.get_variable(array_name .. ".length")
+
+ for index, value in ipairs(array) do
+ -- Some protection against external modification
+ -- It's not perfect, though - it'd be nice if *any* change could be detected
+ if array_length ~= wesnoth.get_variable(array_name .. ".length") then
+ helper.wml_error("WML array length changed during [foreach] iteration")
+ end
+ wesnoth.set_variable(item_name, value)
+ -- set index variable
+ wesnoth.set_variable(i_name, index-1) -- here -1, because of WML array
+ -- perform actions
+ for do_child in helper.child_range(cfg, "do") do
+ local action = utils.handle_event_commands(do_child, "loop")
+ if action == "break" then
+ utils.set_exiting("none")
+ goto exit
+ elseif action == "continue" then
+ utils.set_exiting("none")
+ break
+ elseif action ~= "none" then
+ goto exit
+ end
+ end
+ -- set back the content, in case the author made some modifications
+ if not cfg.readonly then
+ array[index] = wesnoth.get_variable(item_name)
+ end
+ end
+ ::exit::
+
+ -- house cleaning
+ utils.end_var_scope(item_name, this_item)
+ utils.end_var_scope(i_name, i)
+
+ --[[
+ This forces the readonly key to be taken literally.
+
+ If readonly=yes, then this line guarantees that the array
+ is unchanged after the [foreach] loop ends.
+
+ If readonly=no, then this line updates the array with any
+ changes the user has applied through the $this_item
+ variable (or whatever variable was given in item_var).
+
+ Note that altering the array via indexing (with the index_var)
+ is not supported; any such changes will be reverted by this line.
+ ]]
+ helper.set_variable_array(array_name, array)
+end
+
+function wml_actions.switch(cfg)
+ local var_value = wesnoth.get_variable(cfg.variable)
+ local found = false
+
+ -- Execute all the [case]s where the value matches.
+ for v in helper.child_range(cfg, "case") do
+ for w in utils.split(v.value) do
+ if w == tostring(var_value) then
+ local action = utils.handle_event_commands(v, "switch")
+ found = true
+ if action ~= "none" then goto exit end
+ break
+ end
+ end
+ end
+ ::exit::
+
+ -- Otherwise execute [else] statements.
+ if not found then
+ for v in helper.child_range(cfg, "else") do
+ local action = utils.handle_event_commands(v, "switch")
+ if action ~= "none" then break end
+ end
+ end
+end
\ No newline at end of file
diff --git a/data/lua/wml-tags.lua b/data/lua/wml-tags.lua
index 21c1b1a78001..da547ad42dfb 100644
--- a/data/lua/wml-tags.lua
+++ b/data/lua/wml-tags.lua
@@ -11,10 +11,16 @@ function wesnoth.game_events.on_save()
return {}
end
+wesnoth.require "lua/wml-flow.lua"
wesnoth.require "lua/wml/objectives.lua"
wesnoth.require "lua/wml/items.lua"
wesnoth.require "lua/wml/message.lua"
wesnoth.require "lua/wml/object.lua"
+wesnoth.require "lua/wml/modify_unit.lua"
+wesnoth.require "lua/wml/harm_unit.lua"
+wesnoth.require "lua/wml/find_path.lua"
+wesnoth.require "lua/wml/endlevel.lua"
+wesnoth.require "lua/wml/random_placement.lua"
local helper = wesnoth.require "lua/helper.lua"
local location_set = wesnoth.require "lua/location_set.lua"
@@ -260,262 +266,6 @@ function wml_actions.music(cfg)
wesnoth.set_music(cfg)
end
-function wml_actions.command(cfg)
- utils.handle_event_commands(cfg, "plain")
-end
-
--- we can't create functions with names that are Lua keywords (eg if, while)
--- instead, we store the following anonymous functions directly into
--- the table, using the [] operator, rather than by using the point syntax
-
-wml_actions["if"] = function(cfg)
- if not (helper.get_child(cfg, 'then') or helper.get_child(cfg, 'elseif') or helper.get_child(cfg, 'else')) then
- helper.wml_error("[if] didn't find any [then], [elseif], or [else] children.")
- end
-
- if wesnoth.eval_conditional(cfg) then -- evaluate [if] tag
- for then_child in helper.child_range(cfg, "then") do
- local action = utils.handle_event_commands(then_child, "conditional")
- if action ~= "none" then break end
- end
- return -- stop after executing [then] tags
- end
-
- for elseif_child in helper.child_range(cfg, "elseif") do
- if wesnoth.eval_conditional(elseif_child) then -- we'll evaluate the [elseif] tags one by one
- for then_tag in helper.child_range(elseif_child, "then") do
- local action = utils.handle_event_commands(then_tag, "conditional")
- if action ~= "none" then break end
- end
- return -- stop on first matched condition
- end
- end
-
- -- no matched condition, try the [else] tags
- for else_child in helper.child_range(cfg, "else") do
- local action = utils.handle_event_commands(else_child, "conditional")
- if action ~= "none" then break end
- end
-end
-
-wml_actions["while"] = function( cfg )
- if helper.child_count(cfg, "do") == 0 then
- helper.wml_error "[while] does not contain any [do] tags"
- end
-
- -- execute [do] up to 65536 times
- for i = 1, 65536 do
- if wesnoth.eval_conditional( cfg ) then
- for do_child in helper.child_range( cfg, "do" ) 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
- return
- end
- end
- else return end
- end
-end
-
-wml_actions["break"] = function(cfg)
- utils.set_exiting("break")
-end
-
-wml_actions["return"] = function(cfg)
- utils.set_exiting("return")
-end
-
-function wml_actions.continue(cfg)
- utils.set_exiting("continue")
-end
-
-wesnoth.wml_actions["for"] = function(cfg)
- if helper.child_count(cfg, "do") == 0 then
- helper.wml_error "[for] does not contain any [do] tags"
- end
-
- local loop_lim = {}
- local first
- if cfg.array ~= nil then
- if cfg.reverse then
- first = wesnoth.get_variable(cfg.array .. ".length") - 1
- loop_lim.last = 0
- loop_lim.step = -1
- else
- first = 0
- loop_lim.last = '$($' .. cfg.array .. ".length - 1)"
- loop_lim.step = 1
- end
- else
- -- Get a literal config to fetch end and step;
- -- this done is to delay expansion of variables
- local cfg_lit = helper.literal(cfg)
- first = cfg.start or 0
- loop_lim.last = cfg_lit["end"] or first
- if cfg.step then loop_lim.step = cfg_lit.step end
- end
- loop_lim = wesnoth.tovconfig(loop_lim)
- if loop_lim.step == 0 then -- Sanity check
- helper.wml_error("[for] has a step of 0!")
- end
- if (first < loop_lim.last and loop_lim.step <= 0) or (first > loop_lim.last and loop_lim.step >= 0) then
- -- Sanity check: If they specify something like start,end,step=1,4,-1
- -- then we do nothing
- return
- end
- local i_var = cfg.variable or "i"
- local save_i = utils.start_var_scope(i_var)
- wesnoth.set_variable(i_var, first)
- local function loop_condition()
- local sentinel = loop_lim.last
- if loop_lim.step then
- sentinel = sentinel + loop_lim.step
- elseif loop_lim.last < first then
- sentinel = sentinel - 1
- else
- sentinel = sentinel + 1
- end
- if loop_lim.step > 0 then
- return wesnoth.get_variable(i_var) < sentinel
- else
- return wesnoth.get_variable(i_var) > sentinel
- end
- end
- while loop_condition() do
- for do_child in helper.child_range( cfg, "do" ) do
- local action = utils.handle_event_commands(do_child, "loop")
- if action == "break" then
- utils.set_exiting("none")
- goto exit
- elseif action == "continue" then
- utils.set_exiting("none")
- break
- elseif action ~= "none" then
- goto exit
- end
- end
- wesnoth.set_variable(i_var, wesnoth.get_variable(i_var) + loop_lim.step)
- end
- ::exit::
- utils.end_var_scope(i_var, save_i)
-end
-
-wml_actions["repeat"] = function(cfg)
- if helper.child_count(cfg, "do") == 0 then
- helper.wml_error "[repeat] does not contain any [do] tags"
- end
-
- local times = cfg.times or 1
- for i = 1, times do
- for do_child in helper.child_range( cfg, "do" ) 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
- return
- end
- end
- end
-end
-
-function wml_actions.foreach(cfg)
- if helper.child_count(cfg, "do") == 0 then
- helper.wml_error "[foreach] does not contain any [do] tags"
- end
-
- local array_name = cfg.variable or helper.wml_error "[foreach] missing required variable= attribute"
- local array = helper.get_variable_array(array_name)
- if #array == 0 then return end -- empty and scalars unwanted
- local item_name = cfg.item_var or "this_item"
- local this_item = utils.start_var_scope(item_name) -- if this_item is already set
- local i_name = cfg.index_var or "i"
- local i = utils.start_var_scope(i_name) -- if i is already set
- local array_length = wesnoth.get_variable(array_name .. ".length")
-
- for index, value in ipairs(array) do
- -- Some protection against external modification
- -- It's not perfect, though - it'd be nice if *any* change could be detected
- if array_length ~= wesnoth.get_variable(array_name .. ".length") then
- helper.wml_error("WML array length changed during [foreach] iteration")
- end
- wesnoth.set_variable(item_name, value)
- -- set index variable
- wesnoth.set_variable(i_name, index-1) -- here -1, because of WML array
- -- perform actions
- for do_child in helper.child_range(cfg, "do") do
- local action = utils.handle_event_commands(do_child, "loop")
- if action == "break" then
- utils.set_exiting("none")
- goto exit
- elseif action == "continue" then
- utils.set_exiting("none")
- break
- elseif action ~= "none" then
- goto exit
- end
- end
- -- set back the content, in case the author made some modifications
- if not cfg.readonly then
- array[index] = wesnoth.get_variable(item_name)
- end
- end
- ::exit::
-
- -- house cleaning
- utils.end_var_scope(item_name, this_item)
- utils.end_var_scope(i_name, i)
-
- --[[
- This forces the readonly key to be taken literally.
-
- If readonly=yes, then this line guarantees that the array
- is unchanged after the [foreach] loop ends.
-
- If readonly=no, then this line updates the array with any
- changes the user has applied through the $this_item
- variable (or whatever variable was given in item_var).
-
- Note that altering the array via indexing (with the index_var)
- is not supported; any such changes will be reverted by this line.
- ]]
- helper.set_variable_array(array_name, array)
-end
-
-function wml_actions.switch(cfg)
- local var_value = wesnoth.get_variable(cfg.variable)
- local found = false
-
- -- Execute all the [case]s where the value matches.
- for v in helper.child_range(cfg, "case") do
- for w in utils.split(v.value) do
- if w == tostring(var_value) then
- local action = utils.handle_event_commands(v, "switch")
- found = true
- if action ~= "none" then goto exit end
- break
- end
- end
- end
- ::exit::
-
- -- Otherwise execute [else] statements.
- if not found then
- for v in helper.child_range(cfg, "else") do
- local action = utils.handle_event_commands(v, "switch")
- if action ~= "none" then break end
- end
- end
-end
-
-- This is mainly for use in unit test macros, but maybe it can be useful elsewhere too
function wml_actions.test_condition(cfg)
local logger = cfg.logger or "warning"
@@ -748,77 +498,6 @@ function wml_actions.unhide_unit(cfg)
wml_actions.redraw {}
end
-function wml_actions.modify_unit(cfg)
- local unit_variable = "LUA_modify_unit"
-
- local function handle_attributes(cfg, unit_path, toplevel)
- for current_key, current_value in pairs(helper.shallow_parsed(cfg)) do
- if type(current_value) ~= "table" and (not toplevel or current_key ~= "type") then
- wesnoth.set_variable(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 = helper.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 = helper.get_child(cfg, "filter") or helper.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 = wesnoth.get_variable(unit_path)
- wesnoth.set_variable("this_unit", this_unit)
- handle_attributes(cfg, unit_path, true)
-
- for current_index, current_table in ipairs(helper.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 unit = wesnoth.get_variable(unit_path)
- unit = wesnoth.create_unit(unit)
- wesnoth.add_modification(unit, current_tag, current_table[2])
- unit = unit.__cfg;
- wesnoth.set_variable(unit_path, unit)
- else
- 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 wesnoth.set_variable(unit_path .. ".advances_to", cfg.type) end
- wesnoth.set_variable(unit_path .. ".experience", wesnoth.get_variable(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 = wesnoth.get_variable(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)
-
- wesnoth.set_variable(unit_variable)
-end
-
function wml_actions.move_unit(cfg)
local coordinate_error = "invalid coordinate in [move_unit]"
local to_x = tostring(cfg.to_x or helper.wml_error(coordinate_error))
@@ -949,207 +628,6 @@ function wml_actions.unpetrify(cfg)
end
end
-function wml_actions.harm_unit(cfg)
- local filter = helper.get_child(cfg, "filter") or helper.wml_error("[harm_unit] missing required [filter] tag")
- -- we need to use shallow_literal field, to avoid raising an error if $this_unit (not yet assigned) is used
- if not helper.shallow_literal(cfg).amount then helper.wml_error("[harm_unit] has missing required amount= attribute") end
- local variable = cfg.variable -- kept out of the way to avoid problems
- local _ = wesnoth.textdomain "wesnoth"
- -- #textdomain wesnoth
- local harmer
-
- local function toboolean( value ) -- helper for animate fields
- -- units will be animated upon leveling or killing, even
- -- with special attacker and defender values
- if value then return true
- else return false end
- end
-
- local this_unit = utils.start_var_scope("this_unit")
-
- for index, unit_to_harm in ipairs(wesnoth.get_units(filter)) do
- if unit_to_harm.valid then
- -- block to support $this_unit
- wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
- wesnoth.set_variable("this_unit", unit_to_harm.__cfg) -- cfg field needed
- local amount = tonumber(cfg.amount)
- local animate = cfg.animate -- attacker and defender are special values
- local delay = cfg.delay or 500
- local kill = cfg.kill
- local fire_event = cfg.fire_event
- local primary_attack = helper.get_child(cfg, "primary_attack")
- local secondary_attack = helper.get_child(cfg, "secondary_attack")
- local harmer_filter = helper.get_child(cfg, "filter_second")
- local experience = cfg.experience
- local resistance_multiplier = tonumber(cfg.resistance_multiplier) or 1
- if harmer_filter then harmer = wesnoth.get_units(harmer_filter)[1] end
- -- end of block to support $this_unit
-
- if animate then
- if animate ~= "defender" and harmer and harmer.valid then
- wesnoth.scroll_to_tile(harmer.x, harmer.y, true)
- wml_actions.animate_unit {
- flag = "attack",
- hits = true,
- with_bars = true,
- T.filter { id = harmer.id },
- T.primary_attack ( primary_attack ),
- T.secondary_attack ( secondary_attack ),
- T.facing { x = unit_to_harm.x, y = unit_to_harm.y },
- }
- end
- wesnoth.scroll_to_tile(unit_to_harm.x, unit_to_harm.y, true)
- end
-
- -- the two functions below are taken straight from the C++ engine, utils.cpp and actions.cpp, with a few unuseful parts removed
- -- may be moved in helper.lua in 1.11
- local function round_damage( base_damage, bonus, divisor )
- local rounding
- if base_damage == 0 then return 0
- else
- if bonus < divisor or divisor == 1 then
- rounding = divisor / 2 - 0
- else
- rounding = divisor / 2 - 1
- end
- return math.max( 1, math.floor( ( base_damage * bonus + rounding ) / divisor ) )
- end
- end
-
- local function calculate_damage( base_damage, alignment, tod_bonus, resistance, modifier )
- local damage_multiplier = 100
- if alignment == "lawful" then
- damage_multiplier = damage_multiplier + tod_bonus
- elseif alignment == "chaotic" then
- damage_multiplier = damage_multiplier - tod_bonus
- elseif alignment == "liminal" then
- damage_multiplier = damage_multiplier - math.abs( tod_bonus )
- else -- neutral, do nothing
- end
- local resistance_modified = resistance * modifier
- damage_multiplier = damage_multiplier * resistance_modified
- local damage = round_damage( base_damage, damage_multiplier, 10000 ) -- if harmer.status.slowed, this may be 20000 ?
- return damage
- end
-
- local damage = calculate_damage(
- amount,
- cfg.alignment or "neutral",
- wesnoth.get_time_of_day( { unit_to_harm.x, unit_to_harm.y, true } ).lawful_bonus,
- wesnoth.unit_resistance( unit_to_harm, cfg.damage_type or "dummy" ),
- resistance_multiplier
- )
-
- if unit_to_harm.hitpoints <= damage then
- if kill == false then damage = unit_to_harm.hitpoints - 1
- else damage = unit_to_harm.hitpoints
- end
- end
-
- unit_to_harm.hitpoints = unit_to_harm.hitpoints - damage
- local text = string.format("%d%s", damage, "\n")
- local add_tab = false
- local gender = unit_to_harm.__cfg.gender
-
- local function set_status(name, male_string, female_string, sound)
- if not cfg[name] or unit_to_harm.status[name] then return end
- if gender == "female" then
- text = string.format("%s%s%s", text, tostring(female_string), "\n")
- else
- text = string.format("%s%s%s", text, tostring(male_string), "\n")
- end
-
- unit_to_harm.status[name] = true
- add_tab = true
-
- if animate and sound then -- for unhealable, that has no sound
- wesnoth.play_sound (sound)
- end
- end
-
- if not unit_to_harm.status.unpoisonable then
- set_status("poisoned", _"poisoned", _"female^poisoned", "poison.ogg")
- end
- set_status("slowed", _"slowed", _"female^slowed", "slowed.wav")
- set_status("petrified", _"petrified", _"female^petrified", "petrified.ogg")
- set_status("unhealable", _"unhealable", _"female^unhealable")
-
- -- Extract unit and put it back to update animation if status was changed
- wesnoth.extract_unit(unit_to_harm)
- wesnoth.put_unit(unit_to_harm)
-
- if add_tab then
- text = string.format("%s%s", "\t", text)
- end
-
- if animate and animate ~= "attacker" then
- if harmer and harmer.valid then
- wml_actions.animate_unit {
- flag = "defend",
- hits = true,
- with_bars = true,
- T.filter { id = unit_to_harm.id },
- T.primary_attack ( primary_attack ),
- T.secondary_attack ( secondary_attack ),
- T.facing { x = harmer.x, y = harmer.y },
- }
- else
- wml_actions.animate_unit {
- flag = "defend",
- hits = true,
- with_bars = true,
- T.filter { id = unit_to_harm.id },
- T.primary_attack ( primary_attack ),
- T.secondary_attack ( secondary_attack ),
- }
- end
- end
-
- wesnoth.float_label( unit_to_harm.x, unit_to_harm.y, string.format( "%s", text ) )
-
- local function calc_xp( level ) -- to calculate the experience in case of kill
- if level == 0 then return 4
- else return level * 8 end
- end
-
- if experience ~= false and harmer and harmer.valid and wesnoth.is_enemy( unit_to_harm.side, harmer.side ) then -- no XP earned for harming friendly units
- if kill ~= false and unit_to_harm.hitpoints <= 0 then
- harmer.experience = harmer.experience + calc_xp( unit_to_harm.__cfg.level )
- else
- unit_to_harm.experience = unit_to_harm.experience + harmer.__cfg.level
- harmer.experience = harmer.experience + unit_to_harm.__cfg.level
- end
- end
-
- if kill ~= false and unit_to_harm.hitpoints <= 0 then
- wml_actions.kill { id = unit_to_harm.id, animate = toboolean( animate ), fire_event = fire_event }
- end
-
- if animate then
- wesnoth.delay(delay)
- end
-
- if variable then
- wesnoth.set_variable(string.format("%s[%d]", variable, index - 1), { harm_amount = damage })
- end
-
- -- both units may no longer be alive at this point, so double check
- if experience ~= false and unit_to_harm and unit_to_harm.valid then
- unit_to_harm:advance(toboolean(animate), fire_event ~= false)
- end
-
- if experience ~= false and harmer and harmer.valid then
- harmer:advance(toboolean(animate), fire_event ~= false)
- end
- end
-
- wml_actions.redraw {}
- end
-
- wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
- utils.end_var_scope("this_unit", this_unit)
-end
-
function wml_actions.heal_unit(cfg)
wesnoth.heal_unit(cfg)
end
@@ -1255,103 +733,6 @@ function wml_actions.add_ai_behavior(cfg)
}
end
-function wml_actions.find_path(cfg)
- local filter_unit = helper.get_child(cfg, "traveler") or helper.wml_error("[find_path] missing required [traveler] tag")
- -- only the first unit matching
- local unit = wesnoth.get_units(filter_unit)[1] or helper.wml_error("[find_path]'s filter didn't match any unit")
- local filter_location = helper.get_child(cfg, "destination") or helper.wml_error( "[find_path] missing required [destination] tag" )
- -- support for $this_unit
- local this_unit = utils.start_var_scope("this_unit")
-
- wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
- wesnoth.set_variable("this_unit", unit.__cfg) -- cfg field needed
-
- local variable = cfg.variable or "path"
- local ignore_units = false
- local ignore_teleport = false
-
- if cfg.check_zoc == false then --if we do not want to check the ZoCs, we must ignore units
- ignore_units = true
- end
- if cfg.check_teleport == false then --if we do not want to check teleport, we must ignore it
- ignore_teleport = true
- end
-
- local allow_multiple_turns = cfg.allow_multiple_turns
- local viewing_side
-
- if not cfg.check_visibility then viewing_side = 0 end -- if check_visiblity then shroud is taken in account
-
- local locations = wesnoth.get_locations(filter_location) -- only the location with the lowest distance and lowest movement cost will match. If there will still be more than 1, only the 1st maching one.
- local max_cost = nil
- if not allow_multiple_turns then max_cost = unit.moves end --to avoid wrong calculation on already moved units
- local current_distance, current_cost = math.huge, math.huge
- local current_location = {}
-
- local width,heigth,border = wesnoth.get_map_size() -- data for test below
-
- for index, location in ipairs(locations) do
- -- we test if location passed to pathfinder is invalid (border); if is, do nothing, do not return and continue the cycle
- if location[1] == 0 or location[1] == ( width + 1 ) or location[2] == 0 or location[2] == ( heigth + 1 ) then
- else
- local distance = helper.distance_between ( unit.x, unit.y, location[1], location[2] )
- -- if we pass an unreachable locations an high value will be returned
- local path, cost = wesnoth.find_path( unit, location[1], location[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
-
- if ( distance < current_distance and cost <= current_cost ) or ( cost < current_cost and distance <= current_distance ) then -- to avoid changing the hex with one with less distance and more cost, or vice versa
- current_distance = distance
- current_cost = cost
- current_location = location
- end
- end
- end
-
- if #current_location == 0 then wesnoth.message("WML warning","[find_path]'s filter didn't match any location")
- else
- local path, cost = wesnoth.find_path( unit, current_location[1], current_location[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
- local turns
-
- if cost == 0 then -- if location is the same, of course it doesn't cost any MP
- turns = 0
- else
- turns = math.ceil( ( ( cost - unit.moves ) / unit.max_moves ) + 1 )
- end
-
- if cost >= 42424242 then -- it's the high value returned for unwalkable or busy terrains
- wesnoth.set_variable ( string.format("%s", variable), { hexes = 0 } ) -- set only length, nil all other values
- -- support for $this_unit
- wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
- utils.end_var_scope("this_unit", this_unit)
- return end
-
- if not allow_multiple_turns and turns > 1 then -- location cannot be reached in one turn
- wesnoth.set_variable ( string.format("%s", variable), { hexes = 0 } )
- -- support for $this_unit
- wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
- utils.end_var_scope("this_unit", this_unit)
- return end -- skip the cycles below
-
- wesnoth.set_variable ( string.format( "%s", variable ), { hexes = current_distance, from_x = unit.x, from_y = unit.y, to_x = current_location[1], to_y = current_location[2], movement_cost = cost, required_turns = turns } )
-
- for index, path_loc in ipairs(path) do
- local sub_path, sub_cost = wesnoth.find_path( unit, path_loc[1], path_loc[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
- local sub_turns
-
- if sub_cost == 0 then
- sub_turns = 0
- else
- sub_turns = math.ceil( ( ( sub_cost - unit.moves ) / unit.max_moves ) + 1 )
- end
-
- wesnoth.set_variable ( string.format( "%s.step[%d]", variable, index - 1 ), { x = path_loc[1], y = path_loc[2], terrain = wesnoth.get_terrain( path_loc[1], path_loc[2] ), movement_cost = sub_cost, required_turns = sub_turns } ) -- this structure takes less space in the inspection window
- end
- end
-
- -- support for $this_unit
- wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
- utils.end_var_scope("this_unit", this_unit)
-end
-
function wml_actions.store_starting_location(cfg)
local writer = utils.vwriter.init(cfg, "location")
for possibly_wrong_index, side in ipairs(wesnoth.get_sides(cfg)) do
@@ -1459,98 +840,6 @@ function wml_actions.end_turn(cfg)
wesnoth.end_turn()
end
-function wml_actions.endlevel(cfg)
- local parsed = helper.parsed(cfg)
- if wesnoth.check_end_level_disabled() then
- wesnoth.message("Repeated [endlevel] execution, ignoring")
- return
- end
-
- local next_scenario = cfg.next_scenario
- if next_scenario then
- wesnoth.set_next_scenario(next_scenario)
- end
-
- local end_text = cfg.end_text
- local end_text_duration = cfg.end_text_duration
- if end_text or end_text_duration then
- wesnoth.set_end_campaign_text(end_text or "", end_text_duration)
- end
-
- local end_credits = cfg.end_credits
- if end_credits ~= nil then
- wesnoth.set_end_campaign_credits(end_credits)
- end
-
- local side_results = {}
- for result in helper.child_range(parsed, "result") do
- local side = result.side or helper.wml_error("[result] in [endlevel] missing required side= key")
- side_results[side] = result
- end
- local there_is_a_human_victory = false
- local there_is_a_human_defeat = false
- local there_is_a_local_human_victory = false
- local there_is_a_local_human_defeat = false
- local bool_int = function(b)
- if b == true then
- return 1
- elseif b == false then
- return 0
- else
- return b
- end
- end
- for k,v in ipairs(wesnoth.sides) do
- local side_result = side_results[v.side] or {}
- local victory_or_defeat = side_result.result or cfg.result or "victory"
- local victory = victory_or_defeat == "victory"
- if victory_or_defeat ~= "victory" and victory_or_defeat ~= "defeat" then
- return helper.wml_error("invalid result= key in [endlevel] '" .. victory_or_defeat .."'")
- end
- if v.controller == "human" or v.controller == "network" then
- if victory then
- there_is_a_human_victory = true
- else
- there_is_a_human_defeat = true
- end
- end
- if v.controller == "human" then
- if victory then
- there_is_a_local_human_victory = true
- else
- there_is_a_local_human_defeat = true
- end
- end
- if side_result.bonus ~= nil then
- v.carryover_bonus = bool_int(side_result.bonus)
- elseif cfg.bonus ~= nil then
- v.carryover_bonus = bool_int(cfg.bonus)
- end
- if side_result.carryover_add ~= nil then
- v.carryover_add = side_result.carryover_add
- elseif cfg.carryover_add ~= nil then
- v.carryover_add = cfg.carryover_add
- end
- if side_result.carryover_percentage ~= nil then
- v.carryover_percentage = side_result.carryover_percentage
- elseif cfg.carryover_percentage ~= nil then
- v.carryover_percentage = cfg.carryover_percentage
- end
- end
- local proceed_to_next_level = there_is_a_human_victory or (not there_is_a_human_defeat and cfg.result ~= "defeat")
- local victory = there_is_a_local_human_victory or (not there_is_a_local_human_defeat and proceed_to_next_level)
- wesnoth.end_level {
- music = cfg.music,
- carryover_report = cfg.carryover_report,
- save = cfg.save,
- replay_save = cfg.replay_save,
- linger_mode = cfg.linger_mode,
- reveal_map = cfg.reveal_map,
- proceed_to_next_level = proceed_to_next_level,
- result = victory and "victory" or "defeat",
- }
-end
-
function wml_actions.event(cfg)
if cfg.remove then
wml_actions.remove_event(cfg)
@@ -1661,79 +950,6 @@ function wml_actions.unsynced(cfg)
end)
end
-wesnoth.wml_actions.random_placement = function(cfg)
- local dist_le = nil
-
- local parsed = helper.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 = helper.get_child(parsed, "filter_location") or {}
- local command = helper.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)
-
- if distance < 0 then
- -- optimisation for distance = -1
- dist_le = function() return false end
- elseif distance == 0 then
- -- optimisation for distance = 0
- dist_le = function(x1,y1,x2,y2) return x1 == x2 and y1 == y2 end
- else
- -- optimisation: cloasure is faster than string lookups.
- local math_abs = math.abs
- -- same effect as helper.distance_between(x1,y1,x2,y2) <= distance but faster.
- dist_le = function(x1,y1,x2,y2)
- local d_x = math_abs(x1-x2)
- if d_x > distance then
- return false
- 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)
- return d_x + 2*d_y <= 2*distance
- end
- end
-
- local locs = wesnoth.get_locations(filter)
- if type(num_items) == "string" then
- num_items = math.floor(loadstring("local size = " .. #locs .. "; return " .. num_items)())
- print("num_items=" .. num_items .. ", #locs=" .. #locs)
- 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]
- wesnoth.set_variable(variable .. ".x", point[1])
- wesnoth.set_variable(variable .. ".y", point[2])
- wesnoth.set_variable(variable .. ".n", i)
- for j = size, 1, -1 do
- if dist_le(locs[j][1], locs[j][2], point[1], point[2]) then
- -- optimisation: swapping elements and storing size in an extra variable is faster than table.remove(locs, j)
- locs[j] = locs[size]
- size = size - 1
- end
- end
- wesnoth.wml_actions.command (command)
- end
- utils.end_var_scope(variable, variable_previous)
-
-end
-
local function on_board(x, y)
if type(x) ~= "number" or type(y) ~= "number" then
return false
diff --git a/data/lua/wml/endlevel.lua b/data/lua/wml/endlevel.lua
new file mode 100644
index 000000000000..5590ccba0932
--- /dev/null
+++ b/data/lua/wml/endlevel.lua
@@ -0,0 +1,93 @@
+
+
+function wml_actions.endlevel(cfg)
+ local parsed = helper.parsed(cfg)
+ if wesnoth.check_end_level_disabled() then
+ wesnoth.message("Repeated [endlevel] execution, ignoring")
+ return
+ end
+
+ local next_scenario = cfg.next_scenario
+ if next_scenario then
+ wesnoth.set_next_scenario(next_scenario)
+ end
+
+ local end_text = cfg.end_text
+ local end_text_duration = cfg.end_text_duration
+ if end_text or end_text_duration then
+ wesnoth.set_end_campaign_text(end_text or "", end_text_duration)
+ end
+
+ local end_credits = cfg.end_credits
+ if end_credits ~= nil then
+ wesnoth.set_end_campaign_credits(end_credits)
+ end
+
+ local side_results = {}
+ for result in helper.child_range(parsed, "result") do
+ local side = result.side or helper.wml_error("[result] in [endlevel] missing required side= key")
+ side_results[side] = result
+ end
+ local there_is_a_human_victory = false
+ local there_is_a_human_defeat = false
+ local there_is_a_local_human_victory = false
+ local there_is_a_local_human_defeat = false
+ local bool_int = function(b)
+ if b == true then
+ return 1
+ elseif b == false then
+ return 0
+ else
+ return b
+ end
+ end
+ for k,v in ipairs(wesnoth.sides) do
+ local side_result = side_results[v.side] or {}
+ local victory_or_defeat = side_result.result or cfg.result or "victory"
+ local victory = victory_or_defeat == "victory"
+ if victory_or_defeat ~= "victory" and victory_or_defeat ~= "defeat" then
+ return helper.wml_error("invalid result= key in [endlevel] '" .. victory_or_defeat .."'")
+ end
+ if v.controller == "human" or v.controller == "network" then
+ if victory then
+ there_is_a_human_victory = true
+ else
+ there_is_a_human_defeat = true
+ end
+ end
+ if v.controller == "human" then
+ if victory then
+ there_is_a_local_human_victory = true
+ else
+ there_is_a_local_human_defeat = true
+ end
+ end
+ if side_result.bonus ~= nil then
+ v.carryover_bonus = bool_int(side_result.bonus)
+ elseif cfg.bonus ~= nil then
+ v.carryover_bonus = bool_int(cfg.bonus)
+ end
+ if side_result.carryover_add ~= nil then
+ v.carryover_add = side_result.carryover_add
+ elseif cfg.carryover_add ~= nil then
+ v.carryover_add = cfg.carryover_add
+ end
+ if side_result.carryover_percentage ~= nil then
+ v.carryover_percentage = side_result.carryover_percentage
+ elseif cfg.carryover_percentage ~= nil then
+ v.carryover_percentage = cfg.carryover_percentage
+ end
+ end
+ local proceed_to_next_level = there_is_a_human_victory or (not there_is_a_human_defeat and cfg.result ~= "defeat")
+ local victory = there_is_a_local_human_victory or (not there_is_a_local_human_defeat and proceed_to_next_level)
+ wesnoth.end_level {
+ music = cfg.music,
+ carryover_report = cfg.carryover_report,
+ save = cfg.save,
+ replay_save = cfg.replay_save,
+ linger_mode = cfg.linger_mode,
+ reveal_map = cfg.reveal_map,
+ proceed_to_next_level = proceed_to_next_level,
+ result = victory and "victory" or "defeat",
+ }
+end
\ No newline at end of file
diff --git a/data/lua/wml/find_path.lua b/data/lua/wml/find_path.lua
new file mode 100644
index 000000000000..23be65cd0c5e
--- /dev/null
+++ b/data/lua/wml/find_path.lua
@@ -0,0 +1,98 @@
+
+
+function wml_actions.find_path(cfg)
+ local filter_unit = helper.get_child(cfg, "traveler") or helper.wml_error("[find_path] missing required [traveler] tag")
+ -- only the first unit matching
+ local unit = wesnoth.get_units(filter_unit)[1] or helper.wml_error("[find_path]'s filter didn't match any unit")
+ local filter_location = helper.get_child(cfg, "destination") or helper.wml_error( "[find_path] missing required [destination] tag" )
+ -- support for $this_unit
+ local this_unit = utils.start_var_scope("this_unit")
+
+ wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
+ wesnoth.set_variable("this_unit", unit.__cfg) -- cfg field needed
+
+ local variable = cfg.variable or "path"
+ local ignore_units = false
+ local ignore_teleport = false
+
+ if cfg.check_zoc == false then --if we do not want to check the ZoCs, we must ignore units
+ ignore_units = true
+ end
+ if cfg.check_teleport == false then --if we do not want to check teleport, we must ignore it
+ ignore_teleport = true
+ end
+
+ local allow_multiple_turns = cfg.allow_multiple_turns
+ local viewing_side
+
+ if not cfg.check_visibility then viewing_side = 0 end -- if check_visiblity then shroud is taken in account
+
+ local locations = wesnoth.get_locations(filter_location) -- only the location with the lowest distance and lowest movement cost will match. If there will still be more than 1, only the 1st maching one.
+ local max_cost = nil
+ if not allow_multiple_turns then max_cost = unit.moves end --to avoid wrong calculation on already moved units
+ local current_distance, current_cost = math.huge, math.huge
+ local current_location = {}
+
+ local width,heigth,border = wesnoth.get_map_size() -- data for test below
+
+ for index, location in ipairs(locations) do
+ -- we test if location passed to pathfinder is invalid (border); if is, do nothing, do not return and continue the cycle
+ if location[1] == 0 or location[1] == ( width + 1 ) or location[2] == 0 or location[2] == ( heigth + 1 ) then
+ else
+ local distance = helper.distance_between ( unit.x, unit.y, location[1], location[2] )
+ -- if we pass an unreachable locations an high value will be returned
+ local path, cost = wesnoth.find_path( unit, location[1], location[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
+
+ if ( distance < current_distance and cost <= current_cost ) or ( cost < current_cost and distance <= current_distance ) then -- to avoid changing the hex with one with less distance and more cost, or vice versa
+ current_distance = distance
+ current_cost = cost
+ current_location = location
+ end
+ end
+ end
+
+ if #current_location == 0 then wesnoth.message("WML warning","[find_path]'s filter didn't match any location")
+ else
+ local path, cost = wesnoth.find_path( unit, current_location[1], current_location[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
+ local turns
+
+ if cost == 0 then -- if location is the same, of course it doesn't cost any MP
+ turns = 0
+ else
+ turns = math.ceil( ( ( cost - unit.moves ) / unit.max_moves ) + 1 )
+ end
+
+ if cost >= 42424242 then -- it's the high value returned for unwalkable or busy terrains
+ wesnoth.set_variable ( string.format("%s", variable), { hexes = 0 } ) -- set only length, nil all other values
+ -- support for $this_unit
+ wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
+ utils.end_var_scope("this_unit", this_unit)
+ return end
+
+ if not allow_multiple_turns and turns > 1 then -- location cannot be reached in one turn
+ wesnoth.set_variable ( string.format("%s", variable), { hexes = 0 } )
+ -- support for $this_unit
+ wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
+ utils.end_var_scope("this_unit", this_unit)
+ return end -- skip the cycles below
+
+ wesnoth.set_variable ( string.format( "%s", variable ), { hexes = current_distance, from_x = unit.x, from_y = unit.y, to_x = current_location[1], to_y = current_location[2], movement_cost = cost, required_turns = turns } )
+
+ for index, path_loc in ipairs(path) do
+ local sub_path, sub_cost = wesnoth.find_path( unit, path_loc[1], path_loc[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
+ local sub_turns
+
+ if sub_cost == 0 then
+ sub_turns = 0
+ else
+ sub_turns = math.ceil( ( ( sub_cost - unit.moves ) / unit.max_moves ) + 1 )
+ end
+
+ wesnoth.set_variable ( string.format( "%s.step[%d]", variable, index - 1 ), { x = path_loc[1], y = path_loc[2], terrain = wesnoth.get_terrain( path_loc[1], path_loc[2] ), movement_cost = sub_cost, required_turns = sub_turns } ) -- this structure takes less space in the inspection window
+ end
+ end
+
+ -- support for $this_unit
+ wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
+ utils.end_var_scope("this_unit", this_unit)
+end
\ No newline at end of file
diff --git a/data/lua/wml/harm_unit.lua b/data/lua/wml/harm_unit.lua
new file mode 100644
index 000000000000..e57bfdf2109e
--- /dev/null
+++ b/data/lua/wml/harm_unit.lua
@@ -0,0 +1,202 @@
+
+
+function wml_actions.harm_unit(cfg)
+ local filter = helper.get_child(cfg, "filter") or helper.wml_error("[harm_unit] missing required [filter] tag")
+ -- we need to use shallow_literal field, to avoid raising an error if $this_unit (not yet assigned) is used
+ if not helper.shallow_literal(cfg).amount then helper.wml_error("[harm_unit] has missing required amount= attribute") end
+ local variable = cfg.variable -- kept out of the way to avoid problems
+ local _ = wesnoth.textdomain "wesnoth"
+ -- #textdomain wesnoth
+ local harmer
+
+ local function toboolean( value ) -- helper for animate fields
+ -- units will be animated upon leveling or killing, even
+ -- with special attacker and defender values
+ if value then return true
+ else return false end
+ end
+
+ local this_unit = utils.start_var_scope("this_unit")
+
+ for index, unit_to_harm in ipairs(wesnoth.get_units(filter)) do
+ if unit_to_harm.valid then
+ -- block to support $this_unit
+ wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
+ wesnoth.set_variable("this_unit", unit_to_harm.__cfg) -- cfg field needed
+ local amount = tonumber(cfg.amount)
+ local animate = cfg.animate -- attacker and defender are special values
+ local delay = cfg.delay or 500
+ local kill = cfg.kill
+ local fire_event = cfg.fire_event
+ local primary_attack = helper.get_child(cfg, "primary_attack")
+ local secondary_attack = helper.get_child(cfg, "secondary_attack")
+ local harmer_filter = helper.get_child(cfg, "filter_second")
+ local experience = cfg.experience
+ local resistance_multiplier = tonumber(cfg.resistance_multiplier) or 1
+ if harmer_filter then harmer = wesnoth.get_units(harmer_filter)[1] end
+ -- end of block to support $this_unit
+
+ if animate then
+ if animate ~= "defender" and harmer and harmer.valid then
+ wesnoth.scroll_to_tile(harmer.x, harmer.y, true)
+ wml_actions.animate_unit {
+ flag = "attack",
+ hits = true,
+ with_bars = true,
+ T.filter { id = harmer.id },
+ T.primary_attack ( primary_attack ),
+ T.secondary_attack ( secondary_attack ),
+ T.facing { x = unit_to_harm.x, y = unit_to_harm.y },
+ }
+ end
+ wesnoth.scroll_to_tile(unit_to_harm.x, unit_to_harm.y, true)
+ end
+
+ -- the two functions below are taken straight from the C++ engine, utils.cpp and actions.cpp, with a few unuseful parts removed
+ -- may be moved in helper.lua in 1.11
+ local function round_damage( base_damage, bonus, divisor )
+ local rounding
+ if base_damage == 0 then return 0
+ else
+ if bonus < divisor or divisor == 1 then
+ rounding = divisor / 2 - 0
+ else
+ rounding = divisor / 2 - 1
+ end
+ return math.max( 1, math.floor( ( base_damage * bonus + rounding ) / divisor ) )
+ end
+ end
+
+ local function calculate_damage( base_damage, alignment, tod_bonus, resistance, modifier )
+ local damage_multiplier = 100
+ if alignment == "lawful" then
+ damage_multiplier = damage_multiplier + tod_bonus
+ elseif alignment == "chaotic" then
+ damage_multiplier = damage_multiplier - tod_bonus
+ elseif alignment == "liminal" then
+ damage_multiplier = damage_multiplier - math.abs( tod_bonus )
+ else -- neutral, do nothing
+ end
+ local resistance_modified = resistance * modifier
+ damage_multiplier = damage_multiplier * resistance_modified
+ local damage = round_damage( base_damage, damage_multiplier, 10000 ) -- if harmer.status.slowed, this may be 20000 ?
+ return damage
+ end
+
+ local damage = calculate_damage(
+ amount,
+ cfg.alignment or "neutral",
+ wesnoth.get_time_of_day( { unit_to_harm.x, unit_to_harm.y, true } ).lawful_bonus,
+ wesnoth.unit_resistance( unit_to_harm, cfg.damage_type or "dummy" ),
+ resistance_multiplier
+ )
+
+ if unit_to_harm.hitpoints <= damage then
+ if kill == false then damage = unit_to_harm.hitpoints - 1
+ else damage = unit_to_harm.hitpoints
+ end
+ end
+
+ unit_to_harm.hitpoints = unit_to_harm.hitpoints - damage
+ local text = string.format("%d%s", damage, "\n")
+ local add_tab = false
+ local gender = unit_to_harm.__cfg.gender
+
+ local function set_status(name, male_string, female_string, sound)
+ if not cfg[name] or unit_to_harm.status[name] then return end
+ if gender == "female" then
+ text = string.format("%s%s%s", text, tostring(female_string), "\n")
+ else
+ text = string.format("%s%s%s", text, tostring(male_string), "\n")
+ end
+
+ unit_to_harm.status[name] = true
+ add_tab = true
+
+ if animate and sound then -- for unhealable, that has no sound
+ wesnoth.play_sound (sound)
+ end
+ end
+
+ if not unit_to_harm.status.unpoisonable then
+ set_status("poisoned", _"poisoned", _"female^poisoned", "poison.ogg")
+ end
+ set_status("slowed", _"slowed", _"female^slowed", "slowed.wav")
+ set_status("petrified", _"petrified", _"female^petrified", "petrified.ogg")
+ set_status("unhealable", _"unhealable", _"female^unhealable")
+
+ -- Extract unit and put it back to update animation if status was changed
+ wesnoth.extract_unit(unit_to_harm)
+ wesnoth.put_unit(unit_to_harm)
+
+ if add_tab then
+ text = string.format("%s%s", "\t", text)
+ end
+
+ if animate and animate ~= "attacker" then
+ if harmer and harmer.valid then
+ wml_actions.animate_unit {
+ flag = "defend",
+ hits = true,
+ with_bars = true,
+ T.filter { id = unit_to_harm.id },
+ T.primary_attack ( primary_attack ),
+ T.secondary_attack ( secondary_attack ),
+ T.facing { x = harmer.x, y = harmer.y },
+ }
+ else
+ wml_actions.animate_unit {
+ flag = "defend",
+ hits = true,
+ with_bars = true,
+ T.filter { id = unit_to_harm.id },
+ T.primary_attack ( primary_attack ),
+ T.secondary_attack ( secondary_attack ),
+ }
+ end
+ end
+
+ wesnoth.float_label( unit_to_harm.x, unit_to_harm.y, string.format( "%s", text ) )
+
+ local function calc_xp( level ) -- to calculate the experience in case of kill
+ if level == 0 then return 4
+ else return level * 8 end
+ end
+
+ if experience ~= false and harmer and harmer.valid and wesnoth.is_enemy( unit_to_harm.side, harmer.side ) then -- no XP earned for harming friendly units
+ if kill ~= false and unit_to_harm.hitpoints <= 0 then
+ harmer.experience = harmer.experience + calc_xp( unit_to_harm.__cfg.level )
+ else
+ unit_to_harm.experience = unit_to_harm.experience + harmer.__cfg.level
+ harmer.experience = harmer.experience + unit_to_harm.__cfg.level
+ end
+ end
+
+ if kill ~= false and unit_to_harm.hitpoints <= 0 then
+ wml_actions.kill { id = unit_to_harm.id, animate = toboolean( animate ), fire_event = fire_event }
+ end
+
+ if animate then
+ wesnoth.delay(delay)
+ end
+
+ if variable then
+ wesnoth.set_variable(string.format("%s[%d]", variable, index - 1), { harm_amount = damage })
+ end
+
+ -- both units may no longer be alive at this point, so double check
+ if experience ~= false and unit_to_harm and unit_to_harm.valid then
+ unit_to_harm:advance(toboolean(animate), fire_event ~= false)
+ end
+
+ if experience ~= false and harmer and harmer.valid then
+ harmer:advance(toboolean(animate), fire_event ~= false)
+ end
+ end
+
+ wml_actions.redraw {}
+ end
+
+ wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
+ utils.end_var_scope("this_unit", this_unit)
+end
\ No newline at end of file
diff --git a/data/lua/wml/modify_unit.lua b/data/lua/wml/modify_unit.lua
new file mode 100644
index 000000000000..3ce4f0363a61
--- /dev/null
+++ b/data/lua/wml/modify_unit.lua
@@ -0,0 +1,72 @@
+
+
+function wml_actions.modify_unit(cfg)
+ local unit_variable = "LUA_modify_unit"
+
+ local function handle_attributes(cfg, unit_path, toplevel)
+ for current_key, current_value in pairs(helper.shallow_parsed(cfg)) do
+ if type(current_value) ~= "table" and (not toplevel or current_key ~= "type") then
+ wesnoth.set_variable(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 = helper.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 = helper.get_child(cfg, "filter") or helper.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 = wesnoth.get_variable(unit_path)
+ wesnoth.set_variable("this_unit", this_unit)
+ handle_attributes(cfg, unit_path, true)
+
+ for current_index, current_table in ipairs(helper.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 unit = wesnoth.get_variable(unit_path)
+ unit = wesnoth.create_unit(unit)
+ wesnoth.add_modification(unit, current_tag, current_table[2])
+ unit = unit.__cfg;
+ wesnoth.set_variable(unit_path, unit)
+ else
+ 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 wesnoth.set_variable(unit_path .. ".advances_to", cfg.type) end
+ wesnoth.set_variable(unit_path .. ".experience", wesnoth.get_variable(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 = wesnoth.get_variable(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)
+
+ wesnoth.set_variable(unit_variable)
+end
\ No newline at end of file
diff --git a/data/lua/wml/random_placement.lua b/data/lua/wml/random_placement.lua
new file mode 100644
index 000000000000..20f8293f424d
--- /dev/null
+++ b/data/lua/wml/random_placement.lua
@@ -0,0 +1,74 @@
+
+
+wesnoth.wml_actions.random_placement = function(cfg)
+ local dist_le = nil
+
+ local parsed = helper.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 = helper.get_child(parsed, "filter_location") or {}
+ local command = helper.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)
+
+ if distance < 0 then
+ -- optimisation for distance = -1
+ dist_le = function() return false end
+ elseif distance == 0 then
+ -- optimisation for distance = 0
+ dist_le = function(x1,y1,x2,y2) return x1 == x2 and y1 == y2 end
+ else
+ -- optimisation: cloasure is faster than string lookups.
+ local math_abs = math.abs
+ -- same effect as helper.distance_between(x1,y1,x2,y2) <= distance but faster.
+ dist_le = function(x1,y1,x2,y2)
+ local d_x = math_abs(x1-x2)
+ if d_x > distance then
+ return false
+ 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)
+ return d_x + 2*d_y <= 2*distance
+ end
+ end
+
+ local locs = wesnoth.get_locations(filter)
+ if type(num_items) == "string" then
+ num_items = math.floor(loadstring("local size = " .. #locs .. "; return " .. num_items)())
+ print("num_items=" .. num_items .. ", #locs=" .. #locs)
+ 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]
+ wesnoth.set_variable(variable .. ".x", point[1])
+ wesnoth.set_variable(variable .. ".y", point[2])
+ wesnoth.set_variable(variable .. ".n", i)
+ for j = size, 1, -1 do
+ if dist_le(locs[j][1], locs[j][2], point[1], point[2]) then
+ -- optimisation: swapping elements and storing size in an extra variable is faster than table.remove(locs, j)
+ locs[j] = locs[size]
+ size = size - 1
+ end
+ end
+ wesnoth.wml_actions.command (command)
+ end
+ utils.end_var_scope(variable, variable_previous)
+
+end
\ No newline at end of file