Backport WC2 fixes

This fixes:
- "Destruction mechanic line 73 bug"
- [traits] in artifacts not working
- custom effects being lost in next scenario
- artifacts reduce HP
- Promoted commander have the wrong id

The fixed are not exactly pretty, in particular the one that uses a
preload event to workaround and engine bug.

Since we are already replacing artifacts.give_item We could in theory
also explicitly fix certain Items here, should we really need to.
This commit is contained in:
gfgtdf 2023-03-17 04:56:43 +01:00
parent dd060f8540
commit df9bbd5b25
6 changed files with 679 additions and 0 deletions

View file

@ -13,3 +13,24 @@
#enddef
#endif
#endif
#define WC_REPLACE_FILE FILE ORIG_FILE VARIABLE
[lua]
code = <<
local args = ...
args.orig_file_name = filesystem.canonical_path(args.orig_file_name)
if wesnoth.package[args.orig_file_name] == nil then
local result = load(args.file_content)()
wesnoth.package[args.orig_file_name] = result
end
if args.variable and args.variable ~= "" then
_G[args.variable] = wesnoth.package[args.orig_file_name]
end
>>
[args]
file_content = {{FILE}}
orig_file_name = {ORIG_FILE}
variable = {VARIABLE}
[/args]
[/lua]
#enddef

View file

@ -36,6 +36,7 @@
## this is needed in both the era and the campaign, in particular in the campaign this data is used to generate the deserters and the ai sides.
[resource]
id = "wc2_era_res"
{WC_REPLACE_FILE campaigns/World_Conquest/lua/shared_utils/wml_converter_updated.lua campaigns/World_Conquest/lua/shared_utils/wml_converter.lua wc2_convert}
[lua]
code = " wesnoth.dofile('campaigns/World_Conquest//lua/era_main.lua') "
[/lua]

View file

@ -0,0 +1,262 @@
--<<
print("artifacts_updated loaded")
local on_event = wesnoth.require("on_event")
local helper = wesnoth.require("helper")
local _ = wesnoth.textdomain 'wesnoth-wc'
local artifacts = {}
artifacts.list = {}
function artifacts.add_artifact_data(a)
table.insert(artifacts.list, a)
end
function artifacts.read_wml_data(cfg)
for i, artifact in ipairs(wc2_convert.wml_to_lon(wml.literal(cfg), "wct_artifact_list").artifact or {}) do
artifacts.add_artifact_data(artifact)
end
end
function artifacts.init_data()
local cfg = wc2_utils.get_wc2_data("artifact")
for i, a in ipairs(wc2_convert.wml_to_lon(cfg, "wct_artifact_list").artifact or {}) do
artifacts.add_artifact_data(a)
end
end
function artifacts.get_artifact(id)
return artifacts.list[id]
end
function artifacts.get_artifact_list()
return artifacts.list
end
function artifacts.drop_message(index)
local aftifact_data = artifacts.get_artifact(index)
wesnoth.wml_actions.message {
speaker = "narrator",
caption = aftifact_data.name,
message = aftifact_data.info .. "\n" .. wc2_color.bonus_text(aftifact_data.description),
image = aftifact_data.icon,
}
end
function wc2_artifact_needs_compensation(side)
return not wc2_scenario.is_human_side(side.side)
end
-- place an artifact with id @a index on the map at position @a x, y.
-- can be used from the bug console as `lua wc2_artifacts.place_item(30,20,1)`
function artifacts.place_item(x, y, index)
wesnoth.wml_actions.item {
x = x,
y = y,
image = artifacts.get_artifact(index).icon,
z_order = 20,
wml.tag.variables { wc2_atrifact_id = index },
}
end
-- give te item with id @a index to unit @a unit, set @a visualize=true, to show the item pickup animation.
function artifacts.give_item(unit, index, visualize)
local aftifact_data = artifacts.get_artifact(index)
if visualize then
-- play visual/sound effects if item have any
wesnoth.wml_actions.sound {
name = aftifact_data.sound or ""
}
if unit.gender == "male" then
wesnoth.wml_actions.sound {
name = aftifact_data.sound_male or ""
}
else
wesnoth.wml_actions.sound {
name = aftifact_data.sound_female or ""
}
end
for i, animate_unit in ipairs(aftifact_data.animate_unit) do
wesnoth.wml_actions.animate_unit(animate_unit)
end
end
local make_holder_loyal = wml.variables["wc2_config_items_make_loyal"] ~= false
-- is_commander or is_hero imples unit.upkeep == "loyal"
-- note that the following `unit.upkeep` does not match normal
-- level 0 (which have still 'full' upkeep) only units with upkeep=0 explicitly set
if make_holder_loyal and (not unit.canrecruit) and (unit.upkeep ~= 0) and (unit.upkeep ~= "loyal") then
unit:add_modification("object", { wml.tag.effect { apply_to = "wc2_overlay", add = "misc/loyal-icon.png" }})
end
local object = {
wc2_atrifact_id = index,
-- cannot filter on wc2_artifact_id being empty so we also need wc2_is_artifact
wc2_is_artifact = true,
}
if make_holder_loyal then
table.insert(object, wml.tag.effect { apply_to= "loyal" })
end
-- IDEA: i _could_ replace the follwing with a 'apply_to=wc2_artifact' effect that
-- basicially applies all effects in the [artifact]s definition. The obvious
-- advantage would be a smaller savefile size. Also this would change how savefiles
-- would behave if an artifacts effect has changed, i am currently not sure
-- whether that'd be good or bad
--
-- One of the reasons why i currently won't do this is to make the artifacts list
-- more flexible: the suggested approach requires that artifacts are loaded before
-- units are created which means artifacts must be loaded at toplevel [lua] tags
for i, effect in ipairs(aftifact_data.effect) do
table.insert(object, wml.tag.effect (effect) )
end
for i, trait in ipairs(aftifact_data.trait) do
if not unit:matches { wml.tag.filter_wml { wml.tag.modifications { wml.tag.trait { id = trait.id } } } } then
unit:add_modification("trait", trait)
end
end
unit:add_modification("object", object)
local unit_initial_hp = unit.hitpoints
--rebuild unit, to reduce savefile size.
unit:transform(unit.type)
-- restore unit hitpoints to before they picked up the artifact
unit.hitpoints = unit_initial_hp
-- the artifact might reduce the max xp.
unit:advance(true, true)
end
-- unit picking up artifacts
on_event("wc2_drop_pickup", function(ec)
local item = wc2_dropping.current_item
local unit = wesnoth.units.get(ec.x1, ec.y1)
if not item.variables.wc2_atrifact_id then
return
end
if not unit then
return
end
local side_num = unit.side
local is_human = wc2_scenario.is_human_side(side_num)
if not wml.variables["wc2_config_experimental_pickup"] and not is_human then
return
end
local index = item.variables.wc2_atrifact_id
local filter = artifacts.get_artifact(index).filter
if filter and not unit:matches(filter) then
if is_human then
wesnoth.wml_actions.message {
id = unit.id,
message = _"I cannot pick up that item.",
}
end
return
end
if is_human and not wml.variables["wc2_config_disable_pickup_confirm"] then
if not wc2_pickup_confirmation_dialog.promt_synced(unit, artifacts.get_artifact(index).icon) then
return
end
end
wc2_dropping.item_taken = true
artifacts.give_item(unit, index, true)
wesnoth.allow_undo(false)
end)
-- returns a list of artifact ids, suitable for the give type ('enemy' for example).
function artifacts.fresh_artifacts_list(for_type)
local res = {}
for i,v in ipairs(artifacts.get_artifact_list()) do
if not for_type or not stringx.map_split(v.not_available or "")[for_type] then
table.insert(res, i)
end
end
return res
end
-- drop all items a dying unit carries.
on_event("die", function(event_context)
local unit = wesnoth.units.get(event_context.x1, event_context.y1)
if not unit then
return
end
if not wml.variables["wc2_config_experimental_pickup"] and wc2_scenario.is_human_side(unit.side) then
return
end
for object in wml.child_range(wml.get_child(unit.__cfg, "modifications") or {}, "object") do
if object.wc2_atrifact_id then
artifacts.place_item(unit.x, unit.y, object.wc2_atrifact_id)
artifacts.drop_message(object.wc2_atrifact_id)
wesnoth.allow_undo(false)
end
end
-- remove the item from the unit, just in case the unit is somehow brought back to life by another addons code. (for example 'besieged druid' can do such a thing)
unit:remove_modifications { wc2_is_artifact = true }
end)
-- returns true if there is an item in the map at the given position,
-- used to determine whether to show the artifact info menu at that position.
function artifacts.is_item_at(x,y)
for i,item in ipairs(wesnoth.interface.get_items(x,y)) do
if item.variables.wc2_atrifact_id then
return true
end
end
return false
end
-- shows an information [message] about the item laying at position
-- @a cfg.x, cfg.y
function wesnoth.wml_actions.wc2_show_item_info(cfg)
local x = cfg.x
local y = cfg.y
for i,item in ipairs(wesnoth.interface.get_items(x,y)) do
if item.variables.wc2_atrifact_id then
local artifact_info = artifacts.get_artifact(item.variables.wc2_atrifact_id)
wesnoth.wml_actions.message {
scroll = false,
image = artifact_info.icon,
caption = artifact_info.name,
message= artifact_info.info .. "\n" .. wc2_color.help_text(artifact_info.description),
}
end
end
end
wc2_utils.menu_item {
id="4_WCT_Item_Info_Option",
description = _ "Remind me what this item does",
image = "icons/terrain/terrain_type_info.png",
synced = false,
filter = artifacts.is_item_at,
handler = function(cx)
wesnoth.wml_actions.wc2_show_item_info {
x = cx.x1,
y = cx.y1,
}
end
}
function wesnoth.wml_actions.wc2_place_item(cfg)
artifacts.place_item(cfg.x, cfg.y, cfg.item_index)
if cfg.message then
artifacts.drop_message(cfg.item_index)
end
end
artifacts.init_data()
return artifacts
-->>

View file

@ -0,0 +1,311 @@
-- <<
-- scema based wml <-> lua table (lon) converter
print("wml_converter_updated loaded")
local schema = {}
local converter = {}
local function split_to_array(s, res)
res = res or {}
for part in tostring(s or ""):gmatch("[^%s,][^,]*") do
table.insert(res, part)
end
return res
end
schema.lua = {
tags = {
args = {
type = "table" ,
type2 = "preserve_order",
}
}
}
schema.time_area = {
tags = {
time = {
type = "list",
}
}
}
schema.scenario = {
tags = {
music = {
type = "list",
},
label = {
type = "list",
},
load_resource = {
type = "list",
},
event = {
type = "list",
id = "event",
},
lua = {
type = "list",
id = "lua",
},
side = {
type = "list",
id = "side",
},
time = {
type = "list",
id = "time",
},
variables = {
type = "single",
},
options = {
type = "single",
},
}
}
schema.side = {
tags = {
village = {
type = "list",
},
unit = {
type = "list",
id = "unit",
},
leader = {
type = "list",
id = "unit",
},
variables = {
type = "single",
},
}
}
schema.time = {
tags = {
}
}
schema.mg_main = {
tags = {
height = {
type = "list",
},
convert = {
type = "list",
},
road_cost = {
type = "list",
},
village = {
type = "list",
},
castle = {
type = "single",
},
}
}
schema.wct_enemy_group_recall = {
attributes = {
level2 = "comma_list",
level3 = "comma_list",
}
}
schema.wct_enemy_group_commander = {
attributes = {
level1 = "comma_list",
level2 = "comma_list",
level3 = "comma_list",
}
}
schema.wct_enemy_group_leader = {
attributes = {
recruit = "comma_list",
}
}
schema.wct_enemy_group = {
tags = {
recall = {
type = "single",
id = "wct_enemy_group_recall",
},
commander = {
type = "single",
id = "wct_enemy_group_commander",
},
leader = {
type = "list",
id = "wct_enemy_group_leader",
},
},
attributes = {
recruit = "comma_list",
allies_available = "comma_list",
}
}
schema.wct_enemy = {
tags = {
group = {
type = "list",
id = "wct_enemy_group",
},
},
attributes = {
factions_available = "comma_list",
}
}
schema.wct_artifact = {
tags = {
animate_unit = {
type = "list",
},
effect = {
type = "list",
--id = "effect",
},
trait = {
type = "list",
},
filter = {
type = "single",
--id = "standard_unit_filter",
},
},
attributes = {
}
}
schema.wct_artifact_list = {
tags = {
artifact = {
type = "list",
id = "wct_artifact"
},
},
attributes = {
}
}
-- i cannot do this because the code in training.lua does
-- variable subsutution on [chance] which only works on configs.
-- schema.wct_trainer_chance = {
-- }
schema.wct_trainer_grade = {
tags = {
chance = {
type = "list",
id = "wct_trainer_chance"
},
},
}
schema.wct_trainer = {
tags = {
grade = {
type = "list",
id = "wct_trainer_grade"
},
},
attributes = {
}
}
schema.wct_trainer_list = {
tags = {
trainer = {
type = "list",
id = "wct_trainer"
},
},
attributes = {
}
}
schema.__attributes = {}
schema.__attributes.comma_list =
{
to_lon = function(attr)
return split_to_array(attr)
end,
to_wml = function(val)
return table.concat(val, ",")
end,
}
function converter.wml_to_lon(cfg, name)
local tag_info = schema[name or "aasdfasdf"]
if tag_info == nil or tag_info.type == "preserve_order" then
return cfg
end
local attrs = tag_info.attributes or {}
local tags = tag_info.tags or {}
local res = {}
for name2, info2 in pairs(tags) do
if info2.type == "single" then
res[name2] = converter.wml_to_lon(wml.get_child(cfg, name2), info2.id)
elseif info2.type == "list" then
local list = {}
res[name2] = list
for tag in wml.child_range(cfg, name2) do
list[#list + 1] = converter.wml_to_lon(tag, info2.id)
end
end
end
for k,v in pairs(cfg) do
if type(k) == "number" then
else --string
local conv = attrs[k] and schema.__attributes[attrs[k]]
if conv then
res[k] = conv.to_lon(v)
else
res[k] = v
end
end
end
return res
end
function converter.lon_to_wml(t, name)
local tag_info = schema[name]
if tag_info == nil then
return t
end
local attrs = tag_info.attributes or {}
local tags = tag_info.tags or {}
local res = {}
for name2, info2 in pairs(tags) do
if info2.type == "single" then
local st = t[name2]
if st ~= nil then
res[#res + 1] = { name2, converter.lon_to_wml(st, info2.id) }
end
elseif info2.type == "list" then
for i, v in ipairs(t[name2] or {}) do
res[#res + 1] = { name2, converter.lon_to_wml(v, info2.id) }
end
end
end
for k,v in pairs(t) do
if not tags[k] then
local conv = attrs[k] and schema.__attributes[attrs[k]]
if conv then
res[k] = conv.to_wml(v)
else
res[k] = v
end
end
end
return res
end
return converter
-->>

View file

@ -6,9 +6,67 @@
#define WORLD_CONQUEST_II_CAMPAIGN_RESOURCE
[resource]
id = "wc2_scenario_res"
[lua]
code = " wc2_utils = wesnoth.dofile('campaigns/World_Conquest/lua/game_mechanics/utils.lua') "
[/lua]
{WC_REPLACE_FILE campaigns/World_Conquest/lua/game_mechanics/artifacts_updated.lua campaigns/World_Conquest/lua/game_mechanics/artifacts.lua wc2_artifacts}
{WC_REPLACE_FILE campaigns/World_Conquest/lua/shared_utils/wml_converter_updated.lua campaigns/World_Conquest/lua/shared_utils/wml_converter.lua wc2_convert}
[lua]
code = " wesnoth.dofile('campaigns/World_Conquest//lua/campaign_main.lua') "
[/lua]
[lua]
code = <<
-- for k,v in pairs(wesnoth.package) do
-- print ("Package '" .. k .. "' loaded.")
-- end
>>
[/lua]
[lua]
code = <<
local on_event = wesnoth.require("on_event")
-- workaround for custom effects not working for units on the recall list at scenario start.
-- automatic recall happens at "start" event so it should work for those too.
on_event("prestart", 1, function(cx)
if wml.variables.wc_disable_1_16_8_workaround then
return
end
for i,u in ipairs(wesnoth.units.find_on_recall { }) do
-- u:transform() might change hp, todo: it would be nice to hav a function which rebuilds the unit without changing hp.
hp = u.hitpoints
u:transform(u.type)
u.hitpoints = hp
end
end)
on_event("prerecall", 1, function(cx)
if wml.variables.wc_disable_1_16_8_workaround then
return
end
local u = wesnoth.units.get(cx.x1, cx.y1)
if u then
-- u:transform() might change hp, todo: it would be nice to hav a function which rebuilds the unit without changing hp.
hp = u.hitpoints
u:transform(u.type)
u.hitpoints = hp
end
end)
-- set promited commanders id to match the save_id of the next scenario.
on_event("victory", function(cx)
for side_num = 1, wml.variables.wc2_player_count do
leaders = wesnoth.units.find_on_map{ side = side_num, canrecruit = true }
if #leaders == 1 then
leaders[1].id = "wc2_leader" .. side_num
end
end
end)
>>
[/lua]
[load_resource]
id = "wc2_trainer_data"
[/load_resource]
@ -50,6 +108,24 @@
id = "wc2_scenario_res_extra"
## additional mods that work independent on the wc2 core.
## but note that they use our utils.lua and assume it to be present in global namespace.
[lua]
code = <<
local on_event = wesnoth.require("on_event")
-- increased priority '1' so that it is executed before the event from destruction.lua
-- to prevent a codepath in there from being reached.
on_event("die", 1, function(cx)
local map = wesnoth.current.map
local loc = wesnoth.map.get(cx.x1, cx.y1)
if wml.variables.wc2_config_enable_terrain_destruction == false then
return
end
if loc:matches{terrain = "Cv^Fds"} then
map[loc] = "Cv^Fdw"
end
end)
>>
[/lua]
[lua]
code = " wesnoth.dofile('campaigns/World_Conquest//lua/optional_mechanics/destruction.lua') "
[/lua]

View file

@ -169,6 +169,14 @@ _ "World Conquest 3p" #enddef
comment= "intermittent maintainer"
[/entry]
[/about]
# [options]
# [checkbox]
# id=wc_disable_1_16_8_workaround
# default=no
# name= _ "Disable Wesnoth 1.16.8 Workaround"
# description= _"This disables a workaround for a wesnoth 1.16.8 bug, Enable this only if _all_ participants of the game use wesnoth 1.16.9 or newer"
# [/checkbox]
# [/options]
[/campaign]
#enddef