Merge pull request #492 from CelticMinstrel/lua-message-object

Port [message] and [object] ActionWML to Lua
This commit is contained in:
CelticMinstrel 2015-09-19 12:14:19 -04:00
commit e841252f19
15 changed files with 702 additions and 635 deletions

View file

@ -30,6 +30,11 @@ Version 1.13.1+dev:
* Added support for current.event_context.unit_x/y fields (bug #23507)
* Added wesnoth.set_dialog_focus function
* Added wesnoth.set_dialog_visible function
* Added wesnoth.show_message_dialog function
* Added wesnoth.show_popup_dialog function
* Added wesnoth.deselect_hex function
* Added wesnoth.is_skipping_messages and wesnoth.skip_messages functions
* New parameter write_to_mods in wesnoth.add_modification
* Added wesnoth.random function
* helper.shuffle is now synced
* Music and sound effects:
@ -85,6 +90,7 @@ Version 1.13.1+dev:
number of movement points the attack consumes
* Ability to patch movetypes to account for custom terrains or damage types
* Removed y offset by -1 from [message]'s scroll-to-unit logic.
* Add [found_item] ConditionalWML to check if an [object]id= ActionWML has been taken
* Editor:
* Added Category field and color sliders to the Edit Label panel.
* Miscellaneous and bug fixes:

View file

@ -13,78 +13,21 @@ end
wesnoth.require "lua/wml/objectives.lua"
wesnoth.require "lua/wml/items.lua"
wesnoth.require "lua/wml/message.lua"
wesnoth.require "lua/wml/object.lua"
local helper = wesnoth.require "lua/helper.lua"
local location_set = wesnoth.require "lua/location_set.lua"
local utils = wesnoth.require "lua/wml-utils.lua"
local wml_actions = wesnoth.wml_actions
local function trim(s)
-- use (f(a)) to get first argument
return (tostring(s):gsub("^%s*(.-)%s*$", "%1"))
end
local function split(s)
return tostring(s):gmatch("[^%s,][^,]*")
end
local function vwriter_init(cfg, default_variable)
local variable = cfg.variable or default_variable
local is_explicit_index = string.sub(variable, string.len(variable)) == "]"
local mode = cfg.mode or "always_clear"
local index = 0
if is_explicit_index then
-- explicit indexes behave always like "replace"
elseif mode == "append" then
index = wesnoth.get_variable(variable .. ".length")
elseif mode ~= "replace" then
wesnoth.set_variable(variable)
end
return {
variable = variable,
is_explicit_index = is_explicit_index,
index = index,
}
end
local function vwriter_write(self, container)
if self.is_explicit_index then
wesnoth.set_variable(self.variable, container)
else
wesnoth.set_variable(string.format("%s[%u]", self.variable, self.index), container)
end
self.index = self.index + 1
end
local function optional_side_filter(cfg, key_name, filter_name)
local key_name = key_name or "side"
local sides = cfg[key_name]
local filter_name = filter_name or "filter_side"
local filter_side = helper.get_child(cfg, filter_name)
if filter_side then
sides = wesnoth.get_sides(filter_side)
elseif sides then
local dummy_cfg = {side=sides}
sides = wesnoth.get_sides(dummy_cfg)
else
return true
end
for index,side in ipairs(sides) do
if side.controller == "human" then
return true
end
end
return false
end
local engine_message = wml_actions.message
function wml_actions.sync_variable(cfg)
local names = cfg.name or helper.wml_error "[sync_variable] missing required name= attribute."
local result = wesnoth.synchronize_choice(
function()
local res = {}
for name_raw in split(names) do
local name = trim(name_raw)
for name_raw in utils.split(names) do
local name = utils.trim(name_raw)
local variable_type = string.sub(name, string.len(name)) == "]" and "indexed" or ( wesnoth.get_variable(name .. ".length") > 0 and "array" or "attribute")
local variable_info = { name = name, type = variable_type }
table.insert(res, { "variable", variable_info })
@ -116,13 +59,6 @@ function wml_actions.sync_variable(cfg)
end
end
function wml_actions.message(cfg)
local show_if = helper.get_child(cfg, "show_if")
if not show_if or wesnoth.eval_conditional(show_if) then
engine_message(cfg)
end
end
function wml_actions.chat(cfg)
local side_list = wesnoth.get_sides(cfg)
local speaker = tostring(cfg.speaker or "WML")
@ -157,8 +93,8 @@ end
function wml_actions.clear_variable(cfg)
local names = cfg.name or
helper.wml_error "[clear_variable] missing required name= attribute."
for w in split(names) do
wesnoth.set_variable(trim(w))
for w in utils.split(names) do
wesnoth.set_variable(utils.trim(w))
end
end
@ -175,11 +111,11 @@ end
function wml_actions.store_unit_type(cfg)
local types = cfg.type or
helper.wml_error "[store_unit_type] missing required type= attribute."
local writer = vwriter_init(cfg, "unit_type")
for w in split(types) do
local writer = utils.vwriter.init(cfg, "unit_type")
for w in utils.split(types) do
local unit_type = wesnoth.unit_types[w] or
helper.wml_error(string.format("Attempt to store nonexistent unit type '%s'.", w))
vwriter_write(writer, unit_type.__cfg)
utils.vwriter.write(writer, unit_type.__cfg)
end
end
@ -205,7 +141,7 @@ function wml_actions.allow_recruit(cfg)
local unit_types = cfg.type or helper.wml_error("[allow_recruit] missing required type= attribute")
for index, team in ipairs(wesnoth.get_sides(cfg)) do
local v = team.recruit
for type in split(unit_types) do
for type in utils.split(unit_types) do
table.insert(v, type)
wesnoth.add_known_unit(type)
end
@ -217,7 +153,7 @@ function wml_actions.allow_extra_recruit(cfg)
local recruits = cfg.extra_recruit or helper.wml_error("[allow_extra_recruit] missing required extra_recruit= attribute")
for index, unit in ipairs(wesnoth.get_units(cfg)) do
local v = unit.extra_recruit
for recruit in split(recruits) do
for recruit in utils.split(recruits) do
table.insert(v, recruit)
wesnoth.add_known_unit(recruit)
end
@ -230,7 +166,7 @@ function wml_actions.disallow_recruit(cfg)
for index, team in ipairs(wesnoth.get_sides(cfg)) do
if unit_types then
local v = team.recruit
for w in split(unit_types) do
for w in utils.split(unit_types) do
for i, r in ipairs(v) do
if r == w then
table.remove(v, i)
@ -249,7 +185,7 @@ function wml_actions.disallow_extra_recruit(cfg)
local recruits = cfg.extra_recruit or helper.wml_error("[disallow_extra_recruit] missing required extra_recruit= attribute")
for index, unit in ipairs(wesnoth.get_units(cfg)) do
local v = unit.extra_recruit
for w in split(recruits) do
for w in utils.split(recruits) do
for i, r in ipairs(v) do
if r == w then
table.remove(v, i)
@ -265,7 +201,7 @@ function wml_actions.set_recruit(cfg)
local recruit = cfg.recruit or helper.wml_error("[set_recruit] missing required recruit= attribute")
for index, team in ipairs(wesnoth.get_sides(cfg)) do
local v = {}
for w in split(recruit) do
for w in utils.split(recruit) do
table.insert(v, w)
end
team.recruit = v
@ -276,7 +212,7 @@ function wml_actions.set_extra_recruit(cfg)
local recruits = cfg.extra_recruit or helper.wml_error("[set_extra_recruit] missing required extra_recruit= attribute")
local v = {}
for w in split(recruits) do
for w in utils.split(recruits) do
table.insert(v, w)
end
@ -300,7 +236,7 @@ function wml_actions.unit_worth(cfg)
local hp = u.hitpoints / u.max_hitpoints
local xp = u.experience / u.max_experience
local best_adv = ut.cost
for w in split(ut.__cfg.advances_to) do
for w in utils.split(ut.__cfg.advances_to) do
local uta = wesnoth.unit_types[w]
if uta and uta.cost > best_adv then best_adv = uta.cost end
end
@ -339,53 +275,7 @@ function wml_actions.music(cfg)
wesnoth.set_music(cfg)
end
local function handle_event_commands(cfg)
-- The WML might be modifying the currently executed WML by mixing
-- [insert_tag] with [set_variables] and [clear_variable], so we
-- have to be careful not to get confused by tags vanishing during
-- the execution, hence the manual handling of [insert_tag].
local cmds = helper.shallow_literal(cfg)
for i = 1,#cmds do
local v = cmds[i]
local cmd = v[1]
local arg = v[2]
local insert_from
if cmd == "insert_tag" then
cmd = arg.name
local from = arg.variable or
helper.wml_error("[insert_tag] found with no variable= field")
arg = wesnoth.get_variable(from)
if type(arg) ~= "table" then
-- Corner case: A missing variable is replaced
-- by an empty container rather than being ignored.
arg = {}
elseif string.sub(from, -1) ~= ']' then
insert_from = from
end
arg = wesnoth.tovconfig(arg)
end
if not string.find(cmd, "^filter") then
cmd = wml_actions[cmd] or
helper.wml_error(string.format("[%s] not supported", cmd))
if insert_from then
local j = 0
repeat
cmd(arg)
j = j + 1
if j >= wesnoth.get_variable(insert_from .. ".length") then break end
arg = wesnoth.tovconfig(wesnoth.get_variable(string.format("%s[%d]", insert_from, j)))
until false
else
cmd(arg)
end
end
end
-- Apply music alterations once all the commands have been processed.
wesnoth.set_music()
end
wml_actions.command = handle_event_commands
wml_actions.command = utils.handle_event_commands
-- since if and while are Lua keywords, we can't create functions with such names
-- instead, we store the following anonymous functions directly into
@ -398,7 +288,7 @@ wml_actions["if"] = function(cfg)
if wesnoth.eval_conditional(cfg) then -- evaluate [if] tag
for then_child in helper.child_range(cfg, "then") do
handle_event_commands(then_child)
utils.handle_event_commands(then_child)
end
return -- stop after executing [then] tags
end
@ -406,7 +296,7 @@ wml_actions["if"] = function(cfg)
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
handle_event_commands(then_tag)
utils.handle_event_commands(then_tag)
end
return -- stop on first matched condition
end
@ -414,7 +304,7 @@ wml_actions["if"] = function(cfg)
-- no matched condition, try the [else] tags
for else_child in helper.child_range(cfg, "else") do
handle_event_commands(else_child)
utils.handle_event_commands(else_child)
end
end
@ -423,7 +313,7 @@ wml_actions["while"] = function( cfg )
for i = 1, 65536 do
if wesnoth.eval_conditional( cfg ) then
for do_child in helper.child_range( cfg, "do" ) do
handle_event_commands( do_child )
utils.handle_event_commands( do_child )
end
else return end
end
@ -435,9 +325,9 @@ function wml_actions.switch(cfg)
-- Execute all the [case]s where the value matches.
for v in helper.child_range(cfg, "case") do
for w in split(v.value) do
for w in utils.split(v.value) do
if w == tostring(var_value) then
handle_event_commands(v)
utils.handle_event_commands(v)
found = true
break
end
@ -447,7 +337,7 @@ function wml_actions.switch(cfg)
-- Otherwise execute [else] statements.
if not found then
for v in helper.child_range(cfg, "else") do
handle_event_commands(v)
utils.handle_event_commands(v)
end
end
end
@ -455,14 +345,14 @@ end
function wml_actions.scroll_to(cfg)
local loc = wesnoth.get_locations( cfg )[1]
if not loc then return end
if not optional_side_filter(cfg) then return end
if not utils.optional_side_filter(cfg) then return end
wesnoth.scroll_to_tile(loc[1], loc[2], cfg.check_fogged, cfg.immediate)
end
function wml_actions.scroll_to_unit(cfg)
local u = wesnoth.get_units(cfg)[1]
if not u then return end
if not optional_side_filter(cfg, "for_side", "for_side") then return end
if not utils.optional_side_filter(cfg, "for_side", "for_side") then return end
wesnoth.scroll_to_tile(u.x, u.y, cfg.check_fogged, cfg.immediate)
end
@ -484,7 +374,7 @@ function wml_actions.unit_overlay(cfg)
local img = cfg.image or helper.wml_error( "[unit_overlay] missing required image= attribute" )
for i,u in ipairs(wesnoth.get_units(cfg)) do
local ucfg = u.__cfg
for w in split(ucfg.overlays) do
for w in utils.split(ucfg.overlays) do
if w == img then ucfg = nil end
end
if ucfg then
@ -497,43 +387,10 @@ end
function wml_actions.remove_unit_overlay(cfg)
local img = cfg.image or helper.wml_error( "[remove_unit_overlay] missing required image= attribute" )
-- Splits the string argument on commas, excepting those commas that occur
-- within paired parentheses. The result is returned as a (non-empty) table.
-- (The table might have a single entry that is an empty string, though.)
-- Spaces around splitting commas are stripped (as in the C++ version).
-- Empty strings are not removed (unlike the C++ version).
local function parenthetical_split(str)
local t = {""}
-- To simplify some logic, end the string with paired parentheses.
local formatted = (str or "") .. ",()"
-- Isolate paired parentheses.
for prefix,paren in string.gmatch(formatted, "(.-)(%b())") do
-- Separate on commas
for comma,text in string.gmatch(prefix, "(,?)([^,]*)") do
if comma == "" then
-- We are continuing the last string found.
t[#t] = t[#t] .. text
else
-- We are starting the next string.
-- (Now that we know the last string is complete,
-- strip leading and trailing spaces from it.)
t[#t] = string.match(t[#t], "^%s*(.-)%s*$")
table.insert(t, text)
end
end
-- Add the parenthetical part to the last string found.
t[#t] = t[#t] .. paren
end
-- Remove the empty parentheses we had added to the end.
table.remove(t)
return t
end
-- Loop through all matching units.
for i,u in ipairs(wesnoth.get_units(cfg)) do
local ucfg = u.__cfg
local t = parenthetical_split(ucfg.overlays)
local t = utils.parenthetical_split(ucfg.overlays)
-- Remove the specified image from the overlays.
for i = #t,1,-1 do
if t[i] == img then table.remove(t, i) end
@ -558,10 +415,10 @@ function wml_actions.store_unit(cfg)
local units = wesnoth.get_units(filter)
local recall_units = wesnoth.get_recall_units(filter)
local writer = vwriter_init(cfg, "unit")
local writer = utils.vwriter.init(cfg, "unit")
for i,u in ipairs(units) do
vwriter_write(writer, u.__cfg)
utils.vwriter.write(writer, u.__cfg)
if kill_units then wesnoth.put_unit(u.x, u.y) end
end
@ -570,7 +427,7 @@ function wml_actions.store_unit(cfg)
local ucfg = u.__cfg
ucfg.x = "recall"
ucfg.y = "recall"
vwriter_write(writer, ucfg)
utils.vwriter.write(writer, ucfg)
if kill_units then wesnoth.extract_unit(u) end
end
end
@ -585,7 +442,7 @@ function wml_actions.store_locations(cfg)
-- the variable can be mentioned in a [find_in] subtag, so it
-- cannot be cleared before the locations are recovered
local locs = wesnoth.get_locations(cfg)
local writer = vwriter_init(cfg, "location")
local writer = utils.vwriter.init(cfg, "location")
for i, loc in ipairs(locs) do
local x, y = loc[1], loc[2]
local t = wesnoth.get_terrain(x, y)
@ -593,7 +450,7 @@ function wml_actions.store_locations(cfg)
if wesnoth.get_terrain_info(t).village then
res.owner_side = wesnoth.get_village_owner(x, y) or 0
end
vwriter_write(writer, res)
utils.vwriter.write(writer, res)
end
end
@ -657,23 +514,6 @@ function wml_actions.unhide_unit(cfg)
wml_actions.redraw {}
end
--note: when using these, make sure that nothing can throw over the call to end_var_scope
local function start_var_scope(name)
local var = helper.get_variable_array(name) --containers and arrays
if #var == 0 then var = wesnoth.get_variable(name) end --scalars (and nil/empty)
wesnoth.set_variable(name)
return var
end
local function end_var_scope(name, var)
wesnoth.set_variable(name)
if type(var) == "table" then
helper.set_variable_array(name, var)
else
wesnoth.set_variable(name, var)
end
end
function wml_actions.modify_unit(cfg)
local unit_variable = "LUA_modify_unit"
@ -736,11 +576,11 @@ function wml_actions.modify_unit(cfg)
wml_actions.store_unit { {"filter", filter}, variable = unit_variable }
local max_index = wesnoth.get_variable(unit_variable .. ".length") - 1
local this_unit = start_var_scope("this_unit")
local this_unit = utils.start_var_scope("this_unit")
for current_unit = 0, max_index do
handle_unit(current_unit)
end
end_var_scope("this_unit", this_unit)
utils.end_var_scope("this_unit", this_unit)
wesnoth.set_variable(unit_variable)
end
@ -891,7 +731,7 @@ function wml_actions.harm_unit(cfg)
else return false end
end
local this_unit = start_var_scope("this_unit")
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
@ -922,7 +762,7 @@ function wml_actions.harm_unit(cfg)
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, util.cpp and actions.cpp, with a few unuseful parts removed
-- 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
@ -1071,7 +911,7 @@ function wml_actions.harm_unit(cfg)
end
wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
end_var_scope("this_unit", this_unit)
utils.end_var_scope("this_unit", this_unit)
end
function wml_actions.heal_unit(cfg)
@ -1107,7 +947,7 @@ function wml_actions.transform_unit(cfg)
end
function wml_actions.store_side(cfg)
local writer = vwriter_init(cfg, "side")
local writer = utils.vwriter.init(cfg, "side")
for t, side_number in helper.get_sides(cfg) do
local container = {
controller = t.controller,
@ -1128,7 +968,7 @@ function wml_actions.store_side(cfg)
flag_icon = t.flag_icon,
side = side_number
}
vwriter_write(writer, container)
utils.vwriter.write(writer, container)
end
end
@ -1189,7 +1029,7 @@ function wml_actions.find_path(cfg)
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 = start_var_scope("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
@ -1249,14 +1089,14 @@ function wml_actions.find_path(cfg)
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
end_var_scope("this_unit", 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
end_var_scope("this_unit", 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 } )
@ -1277,11 +1117,11 @@ function wml_actions.find_path(cfg)
-- support for $this_unit
wesnoth.set_variable ( "this_unit" ) -- clearing this_unit
end_var_scope("this_unit", this_unit)
utils.end_var_scope("this_unit", this_unit)
end
function wml_actions.store_starting_location(cfg)
local writer = vwriter_init(cfg, "location")
local writer = utils.vwriter.init(cfg, "location")
for possibly_wrong_index, side in ipairs(wesnoth.get_sides(cfg)) do
local loc = wesnoth.get_starting_location(side.side)
if loc then
@ -1290,16 +1130,16 @@ function wml_actions.store_starting_location(cfg)
if wesnoth.get_terrain_info(terrain).village then
result.owner_side = wesnoth.get_village_owner(loc[1], loc[2]) or 0
end
vwriter_write(writer, result)
utils.vwriter.write(writer, result)
end
end
end
function wml_actions.store_villages( cfg )
local villages = wesnoth.get_villages( cfg )
local writer = vwriter_init(cfg, "location")
local writer = utils.vwriter.init(cfg, "location")
for index, village in ipairs( villages ) do
vwriter_write(writer, {
utils.vwriter.write(writer, {
x = village[1],
y = village[2],
terrain = wesnoth.get_terrain( village[1], village[2] ),
@ -1417,7 +1257,7 @@ end
function wml_actions.remove_event(cfg)
local id = cfg.id or helper.wml_error("[remove_event] missing required id= key")
for w in split(id) do
for w in utils.split(id) do
wesnoth.remove_event_handler(w)
end
end
@ -1468,8 +1308,8 @@ function wml_actions.role(cfg)
local filter = helper.shallow_literal(cfg)
local types = {}
for value in split(cfg.type) do
table.insert(types, trim(value))
for value in utils.split(cfg.type) do
table.insert(types, utils.trim(value))
end
filter.role, filter.type = nil, nil

170
data/lua/wml-utils.lua Normal file
View file

@ -0,0 +1,170 @@
local helper = wesnoth.require "lua/helper.lua"
local utils = {vwriter = {}}
function utils.trim(s)
-- use (f(a)) to get first argument
return (tostring(s):gsub("^%s*(.-)%s*$", "%1"))
end
function utils.split(s)
return tostring(s):gmatch("[^%s,][^,]*")
end
function utils.check_key(val, key, tag, convert_spaces)
if not val then return nil end
if convert_spaces then
val = tostring(val):gsub(' ', '_')
end
if not val:match('^[a-zA-Z0-9_]+$') then
helper.wml_error("Invalid " .. key .. "= in [" .. tag .. "]")
end
return val
end
function utils.vwriter.init(cfg, default_variable)
local variable = cfg.variable or default_variable
local is_explicit_index = string.sub(variable, string.len(variable)) == "]"
local mode = cfg.mode or "always_clear"
local index = 0
if is_explicit_index then
-- explicit indexes behave always like "replace"
elseif mode == "append" then
index = wesnoth.get_variable(variable .. ".length")
elseif mode ~= "replace" then
wesnoth.set_variable(variable)
end
return {
variable = variable,
is_explicit_index = is_explicit_index,
index = index,
}
end
function utils.vwriter.write(self, container)
if self.is_explicit_index then
wesnoth.set_variable(self.variable, container)
else
wesnoth.set_variable(string.format("%s[%u]", self.variable, self.index), container)
end
self.index = self.index + 1
end
function utils.optional_side_filter(cfg, key_name, filter_name)
local key_name = key_name or "side"
local sides = cfg[key_name]
local filter_name = filter_name or "filter_side"
local filter_side = helper.get_child(cfg, filter_name)
if filter_side then
sides = wesnoth.get_sides(filter_side)
elseif sides then
local dummy_cfg = {side=sides}
sides = wesnoth.get_sides(dummy_cfg)
else
return true
end
for index,side in ipairs(sides) do
if side.controller == "human" then
return true
end
end
return false
end
function utils.handle_event_commands(cfg)
-- The WML might be modifying the currently executed WML by mixing
-- [insert_tag] with [set_variables] and [clear_variable], so we
-- have to be careful not to get confused by tags vanishing during
-- the execution, hence the manual handling of [insert_tag].
local cmds = helper.shallow_literal(cfg)
for i = 1,#cmds do
local v = cmds[i]
local cmd = v[1]
local arg = v[2]
local insert_from
if cmd == "insert_tag" then
cmd = arg.name
local from = arg.variable or
helper.wml_error("[insert_tag] found with no variable= field")
arg = wesnoth.get_variable(from)
if type(arg) ~= "table" then
-- Corner case: A missing variable is replaced
-- by an empty container rather than being ignored.
arg = {}
elseif string.sub(from, -1) ~= ']' then
insert_from = from
end
arg = wesnoth.tovconfig(arg)
end
if not string.find(cmd, "^filter") then
cmd = wesnoth.wml_actions[cmd] or
helper.wml_error(string.format("[%s] not supported", cmd))
if insert_from then
local j = 0
repeat
cmd(arg)
j = j + 1
if j >= wesnoth.get_variable(insert_from .. ".length") then break end
arg = wesnoth.tovconfig(wesnoth.get_variable(string.format("%s[%d]", insert_from, j)))
until false
else
cmd(arg)
end
end
end
-- Apply music alterations once all the commands have been processed.
wesnoth.set_music()
end
-- Splits the string argument on commas, excepting those commas that occur
-- within paired parentheses. The result is returned as a (non-empty) table.
-- (The table might have a single entry that is an empty string, though.)
-- Spaces around splitting commas are stripped (as in the C++ version).
-- Empty strings are not removed (unlike the C++ version).
function utils.parenthetical_split(str)
local t = {""}
-- To simplify some logic, end the string with paired parentheses.
local formatted = (str or "") .. ",()"
-- Isolate paired parentheses.
for prefix,paren in string.gmatch(formatted, "(.-)(%b())") do
-- Separate on commas
for comma,text in string.gmatch(prefix, "(,?)([^,]*)") do
if comma == "" then
-- We are continuing the last string found.
t[#t] = t[#t] .. text
else
-- We are starting the next string.
-- (Now that we know the last string is complete,
-- strip leading and trailing spaces from it.)
t[#t] = string.match(t[#t], "^%s*(.-)%s*$")
table.insert(t, text)
end
end
-- Add the parenthetical part to the last string found.
t[#t] = t[#t] .. paren
end
-- Remove the empty parentheses we had added to the end.
table.remove(t)
return t
end
--note: when using these, make sure that nothing can throw over the call to end_var_scope
function utils.start_var_scope(name)
local var = helper.get_variable_array(name) --containers and arrays
if #var == 0 then var = wesnoth.get_variable(name) end --scalars (and nil/empty)
wesnoth.set_variable(name)
return var
end
function utils.end_var_scope(name, var)
wesnoth.set_variable(name)
if type(var) == "table" then
helper.set_variable_array(name, var)
else
wesnoth.set_variable(name, var)
end
end
return utils

221
data/lua/wml/message.lua Normal file
View file

@ -0,0 +1,221 @@
local helper = wesnoth.require "lua/helper.lua"
local utils = wesnoth.require "lua/wml-utils.lua"
local location_set = wesnoth.require "lua/location_set.lua"
local function log(msg, level)
wesnoth.wml_actions.wml_message({
message = msg,
logger = level,
})
end
local function get_image(cfg, speaker)
local image = cfg.image
if speaker and image == nil then
image = speaker.__cfg.profile
end
if image == "none" or image == nil then
return ""
end
return image
end
local function get_caption(cfg, speaker)
local caption = cfg.caption
if not caption and speaker ~= nil then
caption = speaker.name or speaker.type_name
end
return caption
end
local function get_speaker(cfg)
local speaker
local context = wesnoth.current.event_context
if cfg.speaker == "narrator" then
speaker = "narrator"
elseif cfg.speaker == "unit" then
speaker = wesnoth.get_unit(context.x1, context.y1)
elseif cfg.speaker == "second_unit" then
speaker = wesnoth.get_unit(context.x2, context.y2)
else
speaker = wesnoth.get_units(cfg)[1]
end
return speaker
end
local function message_user_choice(cfg, speaker, options, text_input)
local image = get_image(cfg, speaker)
local caption = get_caption(cfg, speaker)
local left_side = true
-- If this doesn't work, might need tostring()
if image:find("~RIGHT()") then
left_side = false
-- The percent signs escape the parentheses for a literal match
image = image:gsub("~RIGHT%(%)", "")
end
local msg_cfg = {
left_side = left_side,
title = caption,
message = cfg.message,
portrait = image,
}
-- Parse input text, if not available all fields are empty
if text_input then
local input_max_size = tonumber(text_input.max_length) or 256
if input_max_size > 1024 or input_max_size < 1 then
log("Invalid maximum size for input " .. input_max_size, "warning")
input_max_size = 256
end
-- This roundabout method is because text_input starts out
-- as an immutable userdata value
text_input = {
label = text_input.label or "",
text = text_input.text or "",
max_length = input_max_size,
}
end
return function()
local option_chosen, ti_content = wesnoth.show_message_dialog(msg_cfg, options, text_input)
if option_chosen == -2 then -- Pressed Escape (only if no input)
wesnoth.skip_messages()
end
local result_cfg = {}
if #options > 0 then
result_cfg.value = option_chosen
end
if text_input ~= nil then
result_cfg.text = ti_content
end
return result_cfg
end
end
function wesnoth.wml_actions.message(cfg)
local show_if = helper.get_child(cfg, "show_if") or {}
if not wesnoth.eval_conditional(show_if) then
log("[message] skipped because [show_if] did not pass", "debug")
return
end
-- Only the first text_input tag is considered
local text_input
for cfg in helper.child_range(cfg, "text_input") do
if text_input ~= nil then
log("Too many [text_input] tags, only one accepted", "warning")
break
end
text_input = cfg
end
local options, option_events = {}, {}
for option in helper.child_range(cfg, "option") do
local condition = helper.get_child(cfg, "show_if") or {}
if wesnoth.eval_conditional(condition) then
table.insert(options, option.message)
table.insert(option_events, {})
for cmd in helper.child_range(option, "command") do
table.insert(option_events[#option_events], cmd)
end
end
end
-- Check if there is any input to be made, if not the message may be skipped
local has_input = text_input ~= nil or #options > 0
if not has_input and wesnoth.is_skipping_messages() then
-- No input to get and the user is not interested either
log("Skipping [message] because user not interested", "debug")
return
end
local sides_for = cfg.side_for
if sides_for and not has_input then
local show_for_side = false
-- Sanity checks on side number and controller
for side in utils.split(sides_for) do
side = tonumber(side)
if side > 0 and side < #wesnoth.sides and wesnoth.sides[side].controller == "human" then
show_for_side = true
break
end
end
if not show_for_side then
-- Player isn't controlling side which should see the message
log("Player isn't controlling side that should see [message]", "debug")
return
end
end
local speaker = get_speaker(cfg)
if not speaker then
-- No matching unit found, continue onto the next message
log("No speaker found for [message]", "debug")
return
elseif speaker == "narrator" then
-- Narrator, so deselect units
wesnoth.deselect_hex()
-- The speaker is expected to be either nil or a unit later
speaker = nil
else
-- Check ~= false, because the default if omitted should be true
if cfg.scroll ~= false then
wesnoth.scroll_to_tile(speaker.x, speaker.y)
end
wesnoth.select_hex(speaker.x, speaker.y, false)
end
if cfg.sound then wesnoth.play_sound(cfg.sound) end
local msg_dlg = message_user_choice(cfg, speaker, options, text_input)
local option_chosen
if not has_input then
-- Always show the dialog if it has no input, whether we are replaying or not
msg_dlg()
else
local choice = wesnoth.synchronize_choice(msg_dlg)
option_chosen = tonumber(choice.value)
if text_input ~= nil then
-- Implement the consequences of the choice
wesnoth.set_variable(text_input.variable or "input", choice.text)
end
end
if #options > 0 then
if option_chosen > #options then
log("invalid choice (" .. option_chosen .. ") was specified, choice 1 to " ..
#options .. " was expected", "debug")
return
end
for i, cmd in ipairs(option_events[option_chosen]) do
utils.handle_event_commands(cmd)
end
end
end

85
data/lua/wml/object.lua Normal file
View file

@ -0,0 +1,85 @@
local helper = wesnoth.require "lua/helper.lua"
local utils = wesnoth.require "lua/wml-utils.lua"
local T = helper.set_wml_tag_metatable {}
local wml_actions = wesnoth.wml_actions
local used_items = {}
function wml_actions.object(cfg)
local context = wesnoth.current.event_context
-- If this item has already been used
local obj_id = utils.check_key(cfg.id, "id", "object", true)
if obj_id and used_items[obj_id] then return end
local unit
local filter = helper.get_child(cfg, "filter")
if filter then
unit = wesnoth.get_units(filter)[1]
end
if not unit then
unit = wesnoth.get_unit(contxt.x, context.y)
end
local command_type, text
if unit then
text = tostring(cfg.description or "")
command_type = "then"
local dvs = cfg.delayed_variable_substitution
local add = cfg.no_write ~= true
if dvs then
wesnoth.add_modification(unit, "object", helper.literal(cfg), add)
else
wesnoth.add_modification(unit, "object", helper.parsed(cfg), add)
end
wesnoth.select_hex(unit.x, unit.y)
-- Mark this item as used up
if obj_id then used_items[obj_id] = true end
else
text = tostring(cfg.cannot_use_message or "")
command_type = "else"
end
-- Default to silent if object has no description
local silent = cfg.silent
if silent == nil then silent = (text:len() == 0) end
if not silent then
wml_actions.redraw{}
local name = tostring(cfg.name or "")
wesnoth.show_popup_dialog(name, text, cfg.image)
end
for cmd in helper.child_range(cfg, command_type) do
utils.handle_event_commands(cmd)
end
end
local old_on_load = wesnoth.game_events.on_load
function wesnoth.game_events.on_load(cfg)
for i = 1,#cfg do
if cfg[i][1] == "used_items" then
-- Not quite sure if this will work
-- Might need to loop through and copy each ID separately
used_items = cfg[i][2]
table.remove(cfg, i)
break
end
end
old_on_load(cfg)
end
local old_on_save = wesnoth.game_events.on_save
function wesnoth.game_events.on_save()
local cfg = old_on_save()
table.insert(cfg, T.used_items(used_items) )
return cfg
end
function wesnoth.wml_conditionals.found_item(cfg)
return used_items[utils.check_key(cfg.id, "id", "found_item", true)]
end

View file

@ -96,79 +96,6 @@ namespace game_events
// (So keep it at the rop of this file?)
wml_action::map wml_action::registry_;
namespace { // advance declarations
std::string get_caption(const vconfig& cfg, unit_map::iterator speaker);
std::string get_image(const vconfig& cfg, unit_map::iterator speaker);
}
namespace { // Types
struct message_user_choice : mp_sync::user_choice
{
vconfig cfg;
unit_map::iterator speaker;
vconfig text_input_element;
bool has_text_input;
const std::vector<std::string> &options;
message_user_choice(const vconfig &c, const unit_map::iterator &s,
const vconfig &t, bool ht, const std::vector<std::string> &o)
: cfg(c), speaker(s), text_input_element(t)
, has_text_input(ht), options(o)
{}
virtual config query_user(int /*side*/) const
{
std::string image = get_image(cfg, speaker);
std::string caption = get_caption(cfg, speaker);
size_t right_offset = image.find("~RIGHT()");
bool left_side = right_offset == std::string::npos;
if (!left_side) {
image.erase(right_offset);
}
// Parse input text, if not available all fields are empty
std::string text_input_label = text_input_element["label"];
std::string text_input_content = text_input_element["text"];
unsigned input_max_size = text_input_element["max_length"].to_int(256);
if (input_max_size > 1024 || input_max_size < 1) {
lg::wml_error << "invalid maximum size for input "
<< input_max_size << '\n';
input_max_size = 256;
}
int option_chosen = -1;
int dlg_result = gui2::show_wml_message(left_side,
resources::screen->video(), caption, cfg["message"],
image, false, has_text_input, text_input_label,
&text_input_content, input_max_size, options,
&option_chosen);
/* Since gui2::show_wml_message needs to do undrawing the
chatlines can get garbled and look dirty on screen. Force a
redraw to fix it. */
/** @todo This hack can be removed once gui2 is finished. */
resources::screen->invalidate_all();
resources::screen->draw(true,true);
if (dlg_result == gui2::twindow::CANCEL) {
resources::game_events->pump().context_skip_messages(true);
}
config cfg;
if (!options.empty()) cfg["value"] = option_chosen;
if (has_text_input) cfg["text"] = text_input_content;
return cfg;
}
virtual config random_choice(int /*side*/) const
{
return config();
}
};
} // end anonymous namespace (types)
namespace { // Support functions
/**
@ -277,103 +204,6 @@ namespace { // Support functions
return path;
}
/**
* Helper to handle the caption part of [message].
*
* @param cfg cfg of message.
* @param speaker The speaker of the message.
*
* @returns The caption to show.
*/
std::string get_caption(const vconfig& cfg, unit_map::iterator speaker)
{
std::string caption = cfg["caption"];
if (caption.empty() && speaker != resources::units->end()) {
caption = speaker->name();
if(caption.empty()) {
caption = speaker->type_name();
}
}
return caption;
}
/**
* Helper to handle the image part of [message].
*
* @param cfg cfg of message.
* @param speaker The speaker of the message.
*
* @returns The image to show.
*/
std::string get_image(const vconfig& cfg, unit_map::iterator speaker)
{
std::string image = cfg["image"];
if (image == "none") {
return "";
}
if (image.empty() && speaker != resources::units->end())
{
image = speaker->big_profile();
#ifndef LOW_MEM
if(image == speaker->absolute_image()) {
image += speaker->image_mods();
}
#endif
}
return image;
}
/**
* Helper to handle the speaker part of [message].
*
* @param event_info event_info of message.
* @param cfg cfg of message.
*
* @returns The unit who's the speaker or units->end().
*/
unit_map::iterator handle_speaker(const queued_event& event_info,
const vconfig& cfg, bool scroll)
{
unit_map *units = resources::units;
game_display &screen = *resources::screen;
unit_map::iterator speaker = units->end();
const std::string speaker_str = cfg["speaker"];
if(speaker_str == "unit") {
speaker = units->find(event_info.loc1);
} else if(speaker_str == "second_unit") {
speaker = units->find(event_info.loc2);
} else if(speaker_str != "narrator") {
const unit_filter ufilt(cfg, resources::filter_con);
for(speaker = units->begin(); speaker != units->end(); ++speaker){
if ( ufilt(*speaker) )
break;
}
}
if(speaker != units->end()) {
LOG_NG << "set speaker to '" << speaker->name() << "'\n";
const map_location &spl = speaker->get_location();
screen.highlight_hex(spl);
if(scroll) {
LOG_DP << "scrolling to speaker..\n";
screen.scroll_to_tile(spl);
}
screen.highlight_hex(spl);
} else if(speaker_str == "narrator") {
LOG_NG << "no speaker\n";
screen.highlight_hex(map_location::null_location());
} else {
return speaker;
}
screen.draw(false);
LOG_DP << "done scrolling to speaker...\n";
return speaker;
}
/**
* Implements the lifting and resetting of fog via WML.
* Keeping affect_normal_fog as false causes only the fog override to be affected.
@ -418,11 +248,6 @@ namespace { // Support functions
resources::screen->recalculate_minimap();
resources::screen->invalidate_all();
}
void handle_event_commands(const queued_event& event_info, const vconfig &cfg) {
assert(resources::lua_kernel);
resources::lua_kernel->run_wml_action("command", cfg, event_info);
}
} // end anonymous namespace (support functions)
void handle_deprecated_message(const config& cfg)
@ -562,141 +387,6 @@ WML_HANDLER_FUNCTION(lift_fog, /*event_info*/, cfg)
toggle_fog(true, cfg, !cfg["multiturn"].to_bool(false));
}
/// Display a message dialog
WML_HANDLER_FUNCTION(message, event_info, cfg)
{
// Check if there is any input to be made, if not the message may be skipped
const vconfig::child_list menu_items = cfg.get_children("option");
const vconfig::child_list text_input_elements = cfg.get_children("text_input");
const bool has_text_input = (text_input_elements.size() == 1);
bool has_input= (has_text_input || !menu_items.empty() );
// skip messages during quick replay
play_controller *controller = resources::controller;
if(!has_input && (
controller->is_skipping_replay() ||
resources::game_events->pump().context_skip_messages()
))
{
return;
}
// Check if this message is for this side
// handeling of side_for for messages with input is done below in the get_user_choice call
std::string side_for_raw = cfg["side_for"];
if (!side_for_raw.empty() && !has_input)
{
bool side_for_show = false;
std::vector<std::string> side_for =
utils::split(side_for_raw, ',', utils::STRIP_SPACES | utils::REMOVE_EMPTY);
std::vector<std::string>::iterator itSide;
size_t side;
// Check if any of side numbers are human controlled
for (itSide = side_for.begin(); itSide != side_for.end(); ++itSide)
{
side = lexical_cast_default<size_t>(*itSide);
// Make sanity check that side number is good
// then check if this side is human controlled.
if (side > 0 && side <= resources::teams->size() &&
(*resources::teams)[side-1].is_local_human())
{
side_for_show = true;
break;
}
}
if (!side_for_show)
{
DBG_NG << "player isn't controlling side which should get message\n";
return;
}
}
unit_map::iterator speaker = handle_speaker(event_info, cfg, cfg["scroll"].to_bool(true));
if (speaker == resources::units->end() && cfg["speaker"] != "narrator") {
// No matching unit found, so the dialog can't come up.
// Continue onto the next message.
WRN_NG << "cannot show message" << std::endl;
return;
}
std::vector<std::string> options;
std::vector<vconfig::child_list> option_events;
for(vconfig::child_list::const_iterator mi = menu_items.begin();
mi != menu_items.end(); ++mi) {
std::string msg_str = (*mi)["message"];
if (!mi->has_child("show_if")
|| conditional_passed(mi->child("show_if")))
{
options.push_back(msg_str);
option_events.push_back((*mi).get_children("command"));
}
}
has_input = !options.empty() || has_text_input;
if (!has_input && resources::controller->is_skipping_replay()) {
// No input to get and the user is not interested either.
return;
}
if (cfg.has_attribute("sound")) {
sound::play_sound(cfg["sound"]);
}
if(text_input_elements.size()>1) {
lg::wml_error << "too many text_input tags, only one accepted\n";
}
const vconfig text_input_element = has_text_input ?
text_input_elements.front() : vconfig::empty_vconfig();
int option_chosen = 0;
std::string text_input_result;
DBG_DP << "showing dialog...\n";
message_user_choice msg(cfg, speaker, text_input_element, has_text_input,
options);
if (!has_input)
{
/* Always show the dialog if it has no input, whether we are
replaying or not. */
msg.query_user(resources::controller->current_side());
}
else
{
config choice = mp_sync::get_user_choice("input", msg, cfg["side_for"].to_int(0));
option_chosen = choice["value"];
text_input_result = choice["text"].str();
}
// Implement the consequences of the choice
if(options.empty() == false) {
if(size_t(option_chosen) >= menu_items.size()) {
std::stringstream errbuf;
errbuf << "invalid choice (" << option_chosen
<< ") was specified, choice 0 to " << (menu_items.size() - 1)
<< " was expected.\n";
replay::process_error(errbuf.str());
return;
}
BOOST_FOREACH(const vconfig &cmd, option_events[option_chosen]) {
handle_event_commands(event_info, cmd);
}
}
if(has_text_input) {
std::string variable_name=text_input_element["variable"];
if(variable_name.empty())
variable_name="input";
resources::gamedata->set_variable(variable_name, text_input_result);
}
}
WML_HANDLER_FUNCTION(modify_turns, /*event_info*/, cfg)
{
config::attribute_value value = cfg["value"];
@ -797,81 +487,6 @@ WML_HANDLER_FUNCTION(move_units_fake, /*event_info*/, cfg)
LOG_NG << "Units moved\n";
}
WML_HANDLER_FUNCTION(object, event_info, cfg)
{
const vconfig & filter = cfg.child("filter");
boost::optional<unit_filter> ufilt;
if (!filter.null())
ufilt = unit_filter(filter, resources::filter_con);
std::string id = cfg["id"];
// If this item has already been used
assert(resources::game_events);
if ( resources::game_events->item_used(id) )
return;
std::string image = cfg["image"];
std::string caption = cfg["name"];
std::string text;
map_location loc;
if(ufilt) {
unit_const_ptr u_ptr = ufilt->first_match_on_map();
if (u_ptr) {
loc = u_ptr->get_location();
}
}
if(loc.valid() == false) {
loc = event_info.loc1;
}
const unit_map::iterator u = resources::units->find(loc);
std::string command_type = "then";
if ( u != resources::units->end() && (!ufilt || ufilt->matches(*u)) )
{
text = cfg["description"].str();
const bool no_add = cfg["no_write"].to_bool(false);
if(cfg["delayed_variable_substitution"].to_bool(false))
u->add_modification("object", cfg.get_config(), no_add);
else
u->add_modification("object", cfg.get_parsed_config(), no_add);
resources::screen->select_hex(event_info.loc1);
resources::screen->invalidate_unit();
// Mark this item as used up.
resources::game_events->item_used(id, true);
} else {
text = cfg["cannot_use_message"].str();
command_type = "else";
}
// Default to silent if object has no description
const bool silent = cfg.has_attribute("silent") ? cfg["silent"].to_bool() : !cfg.has_attribute("description");
if (!silent)
{
// Redraw the unit, with its new stats
resources::screen->draw();
try {
gui2::show_transient_message(resources::screen->video(), caption, text, image, true);
} catch(utf8::invalid_utf8_exception&) {
// we already had a warning so do nothing.
}
}
BOOST_FOREACH(const vconfig &cmd, cfg.get_children(command_type)) {
handle_event_commands(event_info, cmd);
}
}
/// If we should recall units that match a certain description.
WML_HANDLER_FUNCTION(recall, /*event_info*/, cfg)
{

View file

@ -64,28 +64,6 @@ void manager::add_event_handler(const config & handler, bool is_menu_item)
event_handlers_->add_event_handler(handler, *this, is_menu_item);
}
/**
* Checks if an item has been used.
* (An empty id will never be considered used.)
*/
bool manager::item_used(const std::string & id)
{
return !id.empty() && used_items_.count(id) > 0;
}
/** Records if an item has been used. */
void manager::item_used(const std::string & id, bool used)
{
// Empty IDs are not tracked.
if ( id.empty() )
return;
if ( used )
used_items_.insert(id);
else
used_items_.erase(id);
}
/** Removes an event handler. */
void manager::remove_event_handler(const std::string & id)
{
@ -98,7 +76,6 @@ void manager::remove_event_handler(const std::string & id)
manager::manager(const config& cfg, const boost::shared_ptr<t_context> & res)
: event_handlers_(new t_event_handlers())
, unit_wml_ids_()
, used_items_()
, pump_(new game_events::t_pump(*this, res))
, resources_(res)
, wml_menu_items_()
@ -122,14 +99,6 @@ manager::manager(const config& cfg, const boost::shared_ptr<t_context> & res)
resources_->lua_kernel->set_wml_action(action_cur->first, action_cur->second);
}
const std::string used = cfg["used_items"];
if(!used.empty()) {
const std::vector<std::string>& v = utils::split(used);
for(std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i) {
item_used(*i, true);
}
}
// Create the event handlers for menu items.
wml_menu_items_.init_handlers(me_);
}
@ -238,7 +207,6 @@ void manager::write_events(config& cfg)
cfg.add_child("event", eh->get_config());
}
cfg["used_items"] = utils::join(used_items_);
cfg["unit_wml_ids"] = utils::join(unit_wml_ids_);
wml_menu_items_.to_config(cfg);
}

View file

@ -112,7 +112,6 @@ namespace game_events {
boost::scoped_ptr<t_event_handlers> event_handlers_;
std::set<std::string> unit_wml_ids_;
std::set<std::string> used_items_;
boost::scoped_ptr<game_events::t_pump> pump_;
boost::shared_ptr<t_context> resources_;
@ -127,10 +126,6 @@ namespace game_events {
/// Create an event handler.
void add_event_handler(const config & handler, bool is_menu_item=false);
/// Checks if an item has been used.
bool item_used(const std::string & id);
/// Records if an item has been used.
void item_used(const std::string & id, bool used);
/// Removes an event handler.
void remove_event_handler(const std::string & id);

View file

@ -864,6 +864,16 @@ static void convert_old_saves_1_13_1(config& cfg)
}
}
}
BOOST_FOREACH(config& snapshot, cfg.child_range("snapshot")) {
if (snapshot.has_attribute("used_items")) {
config used_items;
BOOST_FOREACH(const std::string& item, utils::split(snapshot["used_items"])) {
used_items[item] = true;
}
snapshot.remove_attribute("used_items");
snapshot.add_child("used_items", used_items);
}
}
}
void convert_old_saves(config& cfg)

View file

@ -124,6 +124,10 @@
#include "lua/lauxlib.h" // for luaL_checkinteger, etc
#include "lua/lua.h" // for lua_setfield, etc
#include "resources.hpp"
#include "game_events/manager.hpp"
#include "game_events/pump.hpp"
class CVideo;
#ifdef DEBUG_LUA
@ -2850,6 +2854,48 @@ int game_lua_kernel::intf_select_hex(lua_State *L)
return 0;
}
/**
* Deselects any highlighted hex on the map.
* No arguments or return values
*/
int game_lua_kernel::intf_deselect_hex(lua_State*)
{
const map_location loc;
play_controller_.get_mouse_handler_base().select_hex(
loc, false, false, false);
if (game_display_) {
game_display_->highlight_hex(loc);
}
return 0;
}
/**
* Return true if a replay is in progress but the player has chosen to skip it
*/
int game_lua_kernel::intf_is_skipping_messages(lua_State *L)
{
bool skipping = play_controller_.is_skipping_replay();
if (!skipping && resources::game_events) {
skipping = resources::game_events->pump().context_skip_messages();
}
lua_pushboolean(L, skipping);
return 1;
}
/**
* Set whether to skip messages
* Arg 1 (optional) - boolean
*/
int game_lua_kernel::intf_skip_messages(lua_State *L)
{
bool skip = true;
if (!lua_isnone(L, 1)) {
skip = lua_toboolean(L, 1);
}
resources::game_events->pump().context_skip_messages(skip);
return 0;
}
namespace {
struct lua_synchronize : mp_sync::user_choice
{
@ -3283,6 +3329,7 @@ static int intf_get_traits(lua_State* L)
* - Arg 1: unit.
* - Arg 2: string.
* - Arg 3: WML table.
* - Arg 4: (optional) Whether to add to [modifications] - default true
*/
static int intf_add_modification(lua_State *L)
{
@ -3296,9 +3343,13 @@ static int intf_add_modification(lua_State *L)
if (sm != "advancement" && sm != "object" && sm != "trait") {
return luaL_argerror(L, 2, "unknown modification type");
}
bool write_to_mods = true;
if (!lua_isnone(L, 4)) {
write_to_mods = lua_toboolean(L, 4);
}
config cfg = luaW_checkconfig(L, 3);
u->add_modification(sm, cfg);
u->add_modification(sm, cfg, !write_to_mods);
return 0;
}
@ -4154,6 +4205,9 @@ game_lua_kernel::game_lua_kernel(CVideo * video, game_state & gs, play_controlle
{ "scroll", &dispatch<&game_lua_kernel::intf_scroll > },
{ "scroll_to_tile", &dispatch<&game_lua_kernel::intf_scroll_to_tile > },
{ "select_hex", &dispatch<&game_lua_kernel::intf_select_hex > },
{ "deselect_hex", &dispatch<&game_lua_kernel::intf_deselect_hex > },
{ "skip_messages", &dispatch<&game_lua_kernel::intf_skip_messages > },
{ "is_skipping_messages", &dispatch<&game_lua_kernel::intf_is_skipping_messages > },
{ "set_end_campaign_credits", &dispatch<&game_lua_kernel::intf_set_end_campaign_credits > },
{ "set_end_campaign_text", &dispatch<&game_lua_kernel::intf_set_end_campaign_text > },
{ "set_menu_item", &dispatch<&game_lua_kernel::intf_set_menu_item > },

View file

@ -129,6 +129,9 @@ class game_lua_kernel : public lua_kernel_base
int intf_simulate_combat(lua_State *L);
int intf_scroll_to_tile(lua_State *L);
int intf_select_hex(lua_State *L);
int intf_deselect_hex(lua_State *L);
int intf_is_skipping_messages(lua_State *L);
int intf_skip_messages(lua_State *L);
int intf_synchronize_choice(lua_State *L);
int intf_get_locations(lua_State *L);
int intf_get_villages(lua_State *L);

View file

@ -18,6 +18,8 @@
#include "gui/auxiliary/window_builder.hpp" // for twindow_builder, etc
#include "gui/dialogs/gamestate_inspector.hpp"
#include "gui/dialogs/lua_interpreter.hpp"
#include "gui/dialogs/wml_message.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "gui/widgets/clickable.hpp" // for tclickable_
#include "gui/widgets/control.hpp" // for tcontrol
#include "gui/widgets/multi_page.hpp" // for tmulti_page
@ -41,6 +43,7 @@
#include "scripting/lua_api.hpp" // for luaW_toboolean, etc
#include "scripting/lua_common.hpp"
#include "scripting/lua_types.hpp" // for getunitKey, dlgclbkKey, etc
#include "scripting/push_check.hpp"
#include "serialization/string_utils.hpp"
#include "tstring.hpp"
#include "video.hpp"
@ -239,6 +242,71 @@ int show_dialog(lua_State *L, CVideo & video)
return 1;
}
/**
* Displays a message window
* - Arg 1: Table describing the window
* - Arg 2: List of options (nil or empty table - no options)
* - Arg 3: Text input specifications (nil or empty table - no text input)
* - Ret 1: option chosen (if no options: 0 if there's text input, -2 if escape pressed, else -1)
* - Ret 2: string entered (empty if none, nil if no text input)
*/
int show_message_dialog(lua_State *L, CVideo & video)
{
config txt_cfg;
const bool has_input = !lua_isnoneornil(L, 3) && luaW_toconfig(L, 3, txt_cfg) && !txt_cfg.empty();
const std::string& input_caption = txt_cfg["label"];
std::string input_text = txt_cfg["text"].str();
unsigned int input_max_len = txt_cfg["max_length"].to_int(256);
std::vector<std::string> options;
int chosen_option = -1;
if (!lua_isnoneornil(L, 2)) {
options = lua_check<std::vector<std::string> >(L, 2);
}
const config& def_cfg = luaW_checkconfig(L, 1);
const std::string& title = def_cfg["title"];
const std::string& message = def_cfg["message"];
const std::string& portrait = def_cfg["portrait"];
const bool left_side = def_cfg["left_side"].to_bool(true);
const bool mirror = def_cfg["mirror"].to_bool(false);
int dlg_result = gui2::show_wml_message(
left_side, video, title, message, portrait, mirror,
has_input, input_caption, &input_text, input_max_len,
options, &chosen_option
);
if (!has_input and options.empty()) {
lua_pushinteger(L, dlg_result);
} else {
lua_pushinteger(L, chosen_option + 1);
}
if (has_input) {
lua_pushlstring(L, input_text.c_str(), input_text.length());
} else {
lua_pushnil(L);
}
return 2;
}
/**
* Displays a popup message
* - Arg 1: Title (allows Pango markup)
* - Arg 2: Message (allows Pango markup)
* - Arg 3: Image (optional)
*/
int show_popup_dialog(lua_State *L, CVideo & video) {
std::string title = luaL_checkstring(L, 1);
std::string msg = luaL_checkstring(L, 2);
std::string image = lua_isnoneornil(L, 3) ? "" : luaL_checkstring(L, 3);
gui2::show_transient_message(video, title, msg, image, true, true);
return 0;
}
/**
* Sets the value of a widget on the current dialog.
* - Arg 1: scalar.

View file

@ -32,6 +32,8 @@ int intf_set_dialog_active(lua_State *L);
int intf_set_dialog_visible(lua_State *L);
int intf_add_dialog_tree_node(lua_State *L);
int show_dialog(lua_State *L, CVideo & video);
int show_message_dialog(lua_State *L, CVideo & video);
int show_popup_dialog(lua_State *L, CVideo & video);
int show_lua_console(lua_State*L, CVideo & video, lua_kernel_base * lk);
int show_gamestate_inspector(CVideo & video, const vconfig & cfg);
int intf_remove_dialog_item(lua_State *L);

View file

@ -119,6 +119,28 @@ int lua_kernel_base::intf_show_dialog(lua_State *L)
return lua_gui2::show_dialog(L, *video_);
}
int lua_kernel_base::intf_show_message_dialog(lua_State *L)
{
if (!video_) {
ERR_LUA << "Cannot show dialog, no video object is available to this lua kernel.";
lua_error(L);
return 0;
}
return lua_gui2::show_message_dialog(L, *video_);
}
int lua_kernel_base::intf_show_popup_dialog(lua_State *L)
{
if (!video_) {
ERR_LUA << "Cannot show dialog, no video object is available to this lua kernel.";
lua_error(L);
return 0;
}
return lua_gui2::show_popup_dialog(L, *video_);
}
// The show lua console callback is similarly a method of lua kernel
int lua_kernel_base::intf_show_lua_console(lua_State *L)
{
@ -257,6 +279,8 @@ lua_kernel_base::lua_kernel_base(CVideo * video)
{ "dofile", &dispatch<&lua_kernel_base::intf_dofile> },
{ "require", &dispatch<&lua_kernel_base::intf_require> },
{ "show_dialog", &dispatch<&lua_kernel_base::intf_show_dialog> },
{ "show_message_dialog", &dispatch<&lua_kernel_base::intf_show_message_dialog> },
{ "show_popup_dialog", &dispatch<&lua_kernel_base::intf_show_popup_dialog> },
{ "show_lua_console", &dispatch<&lua_kernel_base::intf_show_lua_console> },
{ NULL, NULL }
};

View file

@ -103,6 +103,12 @@ protected:
// Show a dialog to the currently connected video object (if available)
int intf_show_dialog(lua_State * L);
// Show a message dialog, possibly with options
int intf_show_message_dialog(lua_State * L);
// Show a transient popup message
int intf_show_popup_dialog(lua_State * L);
// Show the interactive lua console (for debugging purposes)
int intf_show_lua_console(lua_State * L);