mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-15 00:01:30 +00:00
204 lines
7.3 KiB
Lua
204 lines
7.3 KiB
Lua
local utils = wesnoth.require "wml-utils"
|
|
local wml_actions = wesnoth.wml_actions
|
|
local T = wml.tag
|
|
|
|
function wml_actions.harm_unit(cfg)
|
|
local filter = wml.get_child(cfg, "filter") or 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 wml.shallow_literal(cfg).amount then 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 <close> = utils.scoped_var("this_unit")
|
|
|
|
for index, unit_to_harm in ipairs(wesnoth.units.find_on_map(filter)) do
|
|
if unit_to_harm.valid then
|
|
-- block to support $this_unit
|
|
wml.variables["this_unit"] = nil -- clearing this_unit
|
|
wml.variables["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 = wml.get_child(cfg, "primary_attack")
|
|
local secondary_attack = wml.get_child(cfg, "secondary_attack")
|
|
local harmer_filter = wml.get_child(cfg, "filter_second")
|
|
local experience = cfg.experience
|
|
local resistance_multiplier = tonumber(cfg.resistance_multiplier) or 1
|
|
if harmer_filter then harmer = wesnoth.units.find_on_map(harmer_filter)[1] end
|
|
-- end of block to support $this_unit
|
|
|
|
if animate then
|
|
if animate ~= "defender" and harmer and harmer.valid then
|
|
wesnoth.interface.scroll_to_hex(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.interface.scroll_to_hex(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.max(0, wesnoth.current.schedule.liminal_bonus - math.abs(tod_bonus))
|
|
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.schedule.get_illumination(unit_to_harm).lawful_bonus,
|
|
100 - unit_to_harm:resistance_against( 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.audio.play(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
|
|
unit_to_harm:extract()
|
|
unit_to_harm:to_map()
|
|
|
|
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 ( secondary_attack ),
|
|
T.secondary_attack ( primary_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 ( secondary_attack ),
|
|
T.secondary_attack ( primary_attack ),
|
|
}
|
|
end
|
|
end
|
|
|
|
wesnoth.interface.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 math.ceil(wesnoth.game_config.kill_experience / 2)
|
|
else return level * wesnoth.game_config.kill_experience end
|
|
end
|
|
|
|
if experience ~= false and harmer and harmer.valid
|
|
and wesnoth.sides.is_enemy( unit_to_harm.side, harmer.side )
|
|
then
|
|
if kill ~= false and unit_to_harm.hitpoints <= 0 then
|
|
harmer.experience = harmer.experience + calc_xp( unit_to_harm.level )
|
|
else
|
|
unit_to_harm.experience = unit_to_harm.experience + harmer.level
|
|
harmer.experience = harmer.experience + wesnoth.game_config.combat_experience * unit_to_harm.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, harmer and T.secondary_unit { id = harmer.id } }
|
|
end
|
|
|
|
if animate then
|
|
wesnoth.interface.delay(delay)
|
|
end
|
|
|
|
if variable then
|
|
wml.variables[string.format("%s[%d]", variable, index - 1)] = { id = unit_to_harm.id, 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
|
|
end
|