local helper = wesnoth.require "helper" local utils = wesnoth.require "wml-utils" local _ = wesnoth.textdomain "wesnoth" local function log(msg, level) wesnoth.log(level, msg, true) end local function get_image(cfg, speaker) local image = cfg.image local left_side = true if speaker and (image == nil or image == "") and (cfg.second_image == nil or cfg.second_image == "") then image = speaker.portrait end if image == "none" or image == nil then return "", true end -- Note: This is deprecated except for use to set default alignment in portraits -- (Move it into the first if statement later, with a nil check) if image:find("~RIGHT%(%)") then left_side = false -- The percent signs escape the parentheses for a literal match image = image:gsub("~RIGHT%(%)", "") end if cfg.image_pos == 'left' then left_side = true elseif cfg.image_pos == 'right' then left_side = false elseif cfg.image_pos ~= nil then helper.wml_error('Invalid [message]image_pos - should be left or right') end return image, left_side end local function get_caption(cfg, speaker) local caption = cfg.caption if not caption and speaker ~= nil then if speaker.name ~= nil and tostring(speaker.name):len() > 0 then caption = speaker.name else caption = speaker.__cfg.language_name end end return caption end local function get_pango_color(color) local pango_color = "#" -- if a hex color was passed in -- or if a color string was passed in - contains no non-letter characters -- just use that if string.sub(color, 1, 1) == "#" or not string.match(color, "%A+") then pango_color = color -- decimal color was passed in, convert to hex color for pango else for s in string.gmatch(color, "%d+") do pango_color = pango_color .. tonumber(s, 16) end end return pango_color end -- add formatting local function add_formatting(cfg, text) -- span tag local formatting = "" .. text .. "" end end -- or return unmodified message return text 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 or 0, context.y1 or 0) elseif cfg.speaker == "second_unit" then speaker = wesnoth.get_unit(context.x2 or 0, context.y2 or 0) else speaker = wesnoth.get_units(cfg)[1] end return speaker end local function message_user_choice(cfg, speaker, options, text_input, sound, voice) local image, left_side = get_image(cfg, speaker) local caption = get_caption(cfg, speaker) local msg_cfg = { left_side = left_side, title = caption, message = cfg.message, portrait = image, mirror = cfg.mirror, second_portrait = cfg.second_image, second_mirror = cfg.second_mirror, } if speaker ~= nil then if cfg.male_message ~= nil and speaker.gender == "male" then msg_cfg.message = cfg.male_message elseif cfg.female_message ~= nil and speaker.gender == "female" then msg_cfg.message = cfg.female_message end end -- add formatting msg_cfg.message = add_formatting(cfg, msg_cfg.message) -- 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() if sound then wesnoth.play_sound(sound) end if voice then local speech = { id = "wml_message_speaker", sounds = voice, loops = 0, delay = 0, } if speaker then speech.x = speaker.x speech.y = speaker.y end wesnoth.add_sound_source(speech) end local option_chosen, ti_content = wesnoth.show_message_dialog(msg_cfg, options, text_input) if voice then wesnoth.remove_sound_source("wml_message_speaker") end 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 = wml.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 text_input_cfg in wml.child_range(cfg, "text_input") do if text_input ~= nil then log("Too many [text_input] tags, only first one accepted", "warning") break end text_input = text_input_cfg end local options, option_events = {}, {} for option in wml.child_range(cfg, "option") do local condition = wml.get_child(option, "show_if") or {} if wesnoth.eval_conditional(condition) then if option.message and not option.image and not option.label then local message = tostring(option.message) wesnoth.deprecated_message("[option]message=", 3, "1.15.0", "Use label= instead."); -- Legacy format table.insert(options, option.message) else local opt = { label = option.label, description = option.description, image = option.image, default = option.default, value = option.value } if option.message then wesnoth.deprecated_message("[option]message=", 3, "1.15.0", "Use label= instead."); if not option.label then -- Support either message or description opt.label = option.message else log("[option] has both label= and message=, ignoring the latter", "warning") end end table.insert(options, opt) end table.insert(option_events, {}) for cmd in wml.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" and wesnoth.sides[side].is_local 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 cfg.highlight == false then -- Nothing to do here 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 wesnoth.fire("redraw") else -- Check ~= false, because the default if omitted should be true if cfg.scroll ~= false then wesnoth.scroll_to_tile(speaker.x, speaker.y, true, false, true) end wesnoth.highlight_hex(speaker.x, speaker.y) wesnoth.fire("redraw") end local msg_dlg = message_user_choice(cfg, speaker, options, text_input, cfg.sound, cfg.voice) 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 wait_description = cfg.wait_description or _("input") if type(sides_for) ~= "number" then -- 0 means currently playing side. sides_for = 0 end local choice = wesnoth.synchronize_choice(wait_description, msg_dlg, sides_for) option_chosen = tonumber(choice.value) if text_input ~= nil then -- Implement the consequences of the choice wml.variables[text_input.variable or "input"] = choice.text end end -- Unhilight the speaker if speaker and not cfg.highlight == false then wesnoth.deselect_hex() 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 if cfg.variable ~= nil then if options[option_chosen].value == nil then wml.variables[cfg.variable] = option_chosen else wml.variables[cfg.variable] = options[option_chosen].value end end for i, cmd in ipairs(option_events[option_chosen]) do local action = utils.handle_event_commands(cmd, "plain") if action ~= "none" then break end end end end