Split the more complicated WML tags into separate Lua files

This commit is contained in:
Celtic Minstrel 2016-04-04 17:03:38 -04:00
parent 2d72d1512b
commit a791909544
7 changed files with 802 additions and 790 deletions

257
data/lua/wml-flow.lua Normal file
View file

@ -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

View file

@ -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( "<span foreground='red'>%s</span>", 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

93
data/lua/wml/endlevel.lua Normal file
View file

@ -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

View file

@ -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

202
data/lua/wml/harm_unit.lua Normal file
View file

@ -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( "<span foreground='red'>%s</span>", 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

View file

@ -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

View file

@ -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