1-- Note: This file is loaded automatically by the engine. 2 3function wesnoth.alert(title, msg) 4 if not msg then 5 msg = title; 6 title = ""; 7 end 8 wesnoth.show_message_box(title, msg, "ok", true) 9end 10 11function wesnoth.confirm(title, msg) 12 if not msg then 13 msg = title; 14 title = ""; 15 end 16 return wesnoth.show_message_box(title, msg, "yes_no", true) 17end 18 19 20--[========[Config Manipulation Functions]========] 21 22wml = {} 23wml.tovconfig = wesnoth.tovconfig 24wml.tostring = wesnoth.debug 25 26local function ensure_config(cfg) 27 if type(cfg) == 'table' then 28 return true 29 end 30 if type(cfg) == 'userdata' then 31 if getmetatable(cfg) == 'wml object' then return true end 32 error("Expected a table or wml object but got " .. getmetatable(cfg), 3) 33 else 34 error("Expected a table or wml object but got " .. type(cfg), 3) 35 end 36 return false 37end 38 39--! Returns the first subtag of @a cfg with the given @a name. 40--! If @a id is not nil, the "id" attribute of the subtag has to match too. 41--! The function also returns the index of the subtag in the array. 42function wml.get_child(cfg, name, id) 43 ensure_config(cfg) 44 for i,v in ipairs(cfg) do 45 if v[1] == name then 46 local w = v[2] 47 if not id or w.id == id then return w, i end 48 end 49 end 50end 51 52--! Returns the nth subtag of @a cfg with the given @a name. 53--! (Indices start at 1, as always with Lua.) 54--! The function also returns the index of the subtag in the array. 55function wml.get_nth_child(cfg, name, n) 56 ensure_config(cfg) 57 for i,v in ipairs(cfg) do 58 if v[1] == name then 59 n = n - 1 60 if n == 0 then return v[2], i end 61 end 62 end 63end 64 65--! Returns the number of subtags of @a with the given @a name. 66function wml.child_count(cfg, name) 67 ensure_config(cfg) 68 local n = 0 69 for i,v in ipairs(cfg) do 70 if v[1] == name then 71 n = n + 1 72 end 73 end 74 return n 75end 76 77--! Returns an iterator over all the subtags of @a cfg with the given @a name. 78function wml.child_range(cfg, tag) 79 ensure_config(cfg) 80 local iter, state, i = ipairs(cfg) 81 local function f(s) 82 local c 83 repeat 84 i,c = iter(s,i) 85 if not c then return end 86 until c[1] == tag 87 return c[2] 88 end 89 return f, state 90end 91 92--! Returns an array from the subtags of @a cfg with the given @a name 93function wml.child_array(cfg, tag) 94 ensure_config(cfg) 95 local result = {} 96 for val in wml.child_range(cfg, tag) do 97 table.insert(result, val) 98 end 99 return result 100end 101 102--! Removes the first matching child tag from @a cfg 103function wml.remove_child(cfg, tag) 104 ensure_config(cfg) 105 for i,v in ipairs(cfg) do 106 if v[1] == tag then 107 table.remove(cfg, i) 108 return 109 end 110 end 111end 112 113--! Removes all matching child tags from @a cfg 114function wml.remove_children(cfg, ...) 115 for i = #cfg, 1, -1 do 116 for _,v in ipairs(...) do 117 if cfg[i] == v then 118 table.remove(cfg, i) 119 end 120 end 121 end 122end 123 124--[========[WML Tag Creation Table]========] 125 126local create_tag_mt = { 127 __metatable = "WML tag builder", 128 __index = function(self, n) 129 return function(cfg) return { n, cfg } end 130 end 131} 132 133wml.tag = setmetatable({}, create_tag_mt) 134 135--[========[Config / Vconfig Unified Handling]========] 136 137function wml.literal(cfg) 138 if type(cfg) == "userdata" then 139 return cfg.__literal 140 else 141 return cfg or {} 142 end 143end 144 145function wml.parsed(cfg) 146 if type(cfg) == "userdata" then 147 return cfg.__parsed 148 else 149 return cfg or {} 150 end 151end 152 153function wml.shallow_literal(cfg) 154 if type(cfg) == "userdata" then 155 return cfg.__shallow_literal 156 else 157 return cfg or {} 158 end 159end 160 161function wml.shallow_parsed(cfg) 162 if type(cfg) == "userdata" then 163 return cfg.__shallow_parsed 164 else 165 return cfg or {} 166 end 167end 168 169--[========[Deprecation Helpers]========] 170 171-- Need a textdomain for translatable deprecation strings. 172local _ = wesnoth.textdomain "wesnoth" 173 174-- Marks a function or subtable as deprecated. 175-- Parameters: 176---- elem_name: the full name of the element being deprecated (including the module) 177---- replacement: the name of the element that will replace it (including the module) 178---- level: deprecation level (1-4) 179---- version: the version at which the element may be removed (level 2 or 3 only) 180---- Set to nil if deprecation level is 1 or 4 181---- elem: The actual element being deprecated 182---- detail_msg: An optional message to add to the deprecation message 183function wesnoth.deprecate_api(elem_name, replacement, level, version, elem, detail_msg) 184 local message = detail_msg or '' 185 if replacement then 186 message = message .. " " .. wesnoth.format( 187 _"(Note: You should use $replacement instead in new code)", 188 {replacement = replacement}) 189 end 190 if type(level) ~= "number" or level < 1 or level > 4 then 191 local err_params = {level = level} 192 -- Note: This message is duplicated in src/deprecation.cpp 193 -- Any changes should be mirrorred there. 194 error(wesnoth.format(_"Invalid deprecation level $level (should be 1-4)", err_params)) 195 end 196 local msg_shown = false 197 if type(elem) == "function" then 198 return function(...) 199 if not msg_shown then 200 msg_shown = true 201 wesnoth.deprecated_message(elem_name, level, version, message) 202 end 203 return elem(...) 204 end 205 elseif type(elem) == "table" then 206 -- Don't clobber the old metatable. 207 local old_mt = getmetatable(elem) or {} 208 local mt = {} 209 for k,v in pairs(old_mt) do 210 mt[k] = old_mt[v] 211 end 212 mt.__index = function(self, key) 213 if not msg_shown then 214 msg_shown = true 215 wesnoth.deprecated_message(elem_name, level, version, message) 216 end 217 if type(old_mt) == "table" then 218 if type(old_mt.__index) == 'function' then 219 return old_mt.__index(self, key) 220 elseif type(old_mt.__index) == 'table' then 221 return old_mt.__index[key] 222 else 223 -- As of 2019, __index must be either a function or a table. If you ever run into this error, 224 -- add an elseif branch for wrapping old_mt.__index appropriately. 225 wml.error('The wrapped __index metamethod of a deprecated object is neither a function nor a table') 226 end 227 end 228 return elem[key] 229 end 230 mt.__newindex = function(self, key, val) 231 if not msg_shown then 232 msg_shown = true 233 wesnoth.deprecated_message(elem_name, level, version, message) 234 end 235 if type(old_mt) == "table" and old_mt.__newindex ~= nil then 236 return old_mt.__newindex(self, key, val) 237 end 238 elem[key] = val 239 end 240 return setmetatable({}, mt) 241 else 242 wesnoth.log('warn', "Attempted to deprecate something that is not a table or function: " .. 243 elem_name .. " -> " .. replacement .. ", where " .. elem_name .. " = " .. tostring(elem)) 244 end 245 return elem 246end 247 248if wesnoth.kernel_type() == "Game Lua Kernel" then 249 --[========[Basic variable access]========] 250 251 -- Get all variables via wml.all_variables (read-only) 252 local get_all_vars_local = wesnoth.get_all_vars 253 setmetatable(wml, { 254 __metatable = "WML module", 255 __index = function(self, key) 256 if key == 'all_variables' then 257 return get_all_vars_local() 258 end 259 return rawget(self, key) 260 end, 261 __newindex = function(self, key, value) 262 if key == 'all_variables' then 263 error("all_variables is read-only") 264 -- TODO Implement writing? 265 end 266 rawset(self, key, value) 267 end 268 }) 269 270 -- So that definition of wml.variables does not cause deprecation warnings: 271 local get_variable_local = wesnoth.get_variable 272 local set_variable_local = wesnoth.set_variable 273 274 -- Get and set variables via wml.variables[variable_path] 275 wml.variables = setmetatable({}, { 276 __metatable = "WML variables", 277 __index = function(_, key) 278 return get_variable_local(key) 279 end, 280 __newindex = function(_, key, value) 281 set_variable_local(key, value) 282 end 283 }) 284 285 --[========[Variable Proxy Table]========] 286 287 local variable_mt = { 288 __metatable = "WML variable proxy" 289 } 290 291 local function get_variable_proxy(k) 292 local v = wml.variables[k] 293 if type(v) == "table" then 294 v = setmetatable({ __varname = k }, variable_mt) 295 end 296 return v 297 end 298 299 local function set_variable_proxy(k, v) 300 if getmetatable(v) == "WML variable proxy" then 301 v = wml.variables[v.__varname] 302 end 303 wml.variables[k] = v 304 end 305 306 function variable_mt.__index(t, k) 307 local i = tonumber(k) 308 if i then 309 k = t.__varname .. '[' .. i .. ']' 310 else 311 k = t.__varname .. '.' .. k 312 end 313 return get_variable_proxy(k) 314 end 315 316 function variable_mt.__newindex(t, k, v) 317 local i = tonumber(k) 318 if i then 319 k = t.__varname .. '[' .. i .. ']' 320 else 321 k = t.__varname .. '.' .. k 322 end 323 set_variable_proxy(k, v) 324 end 325 326 local root_variable_mt = { 327 __metatable = "WML variables proxy", 328 __index = function(t, k) return get_variable_proxy(k) end, 329 __newindex = function(t, k, v) 330 if type(v) == "function" then 331 -- User-friendliness when _G is overloaded early. 332 -- FIXME: It should be disabled outside the "preload" event. 333 rawset(t, k, v) 334 else 335 set_variable_proxy(k, v) 336 end 337 end 338 } 339 340 wml.variables_proxy = setmetatable({}, root_variable_mt) 341 342 --[========[Variable Array Access]========] 343 344 local function resolve_variable_context(ctx, err_hint) 345 if ctx == nil then 346 return {get = get_variable_local, set = set_variable_local} 347 elseif type(ctx) == 'number' and ctx > 0 and ctx <= #wesnoth.sides then 348 return resolve_variable_context(wesnoth.sides[ctx]) 349 elseif type(ctx) == 'string' then 350 -- TODO: Treat it as a namespace for a global (persistent) variable 351 -- (Need Lua API for accessing them first, though.) 352 elseif getmetatable(ctx) == "unit" then 353 return { 354 get = function(path) return ctx.variables[path] end, 355 set = function(path, val) ctx.variables[path] = val end, 356 } 357 elseif getmetatable(ctx) == "side" then 358 return { 359 get = function(path) return wesnoth.get_side_variable(ctx.side, path) end, 360 set = function(path, val) wesnoth.set_side_variable(ctx.side, path, val) end, 361 } 362 elseif getmetatable(ctx) == "unit variables" or getmetatable(ctx) == "side variables" then 363 return { 364 get = function(path) return ctx[path] end, 365 set = function(path, val) ctx[path] = val end, 366 } 367 end 368 error(string.format("Invalid context for %s: expected nil, side, or unit", err_hint), 3) 369 end 370 371 wml.array_access = {} 372 373 --! Fetches all the WML container variables with name @a var. 374 --! @returns a table containing all the variables (starting at index 1). 375 function wml.array_access.get(var, context) 376 context = resolve_variable_context(context, "get_variable_array") 377 local result = {} 378 for i = 1, context.get(var .. ".length") do 379 result[i] = context.get(string.format("%s[%d]", var, i - 1)) 380 end 381 return result 382 end 383 384 --! Puts all the elements of table @a t inside a WML container with name @a var. 385 function wml.array_access.set(var, t, context) 386 context = resolve_variable_context(context, "set_variable_array") 387 context.set(var) 388 for i, v in ipairs(t) do 389 context.set(string.format("%s[%d]", var, i - 1), v) 390 end 391 end 392 393 --! Creates proxies for all the WML container variables with name @a var. 394 --! This is similar to wml.array_access.get, except that the elements 395 --! can be used for writing too. 396 --! @returns a table containing all the variable proxies (starting at index 1). 397 function wml.array_access.get_proxy(var) 398 local result = {} 399 for i = 1, wml.variables[var .. ".length"] do 400 result[i] = get_variable_proxy(string.format("%s[%d]", var, i - 1)) 401 end 402 return result 403 end 404 405 -- More convenient when accessing global variables 406 wml.array_variables = setmetatable({}, { 407 __metatable = "WML variables", 408 __index = function(_, key) 409 return wml.array_access.get(key) 410 end, 411 __newindex = function(_, key, value) 412 wml.array_access.set(key, value) 413 end 414 }) 415 416 wesnoth.persistent_tags = setmetatable({}, { 417 -- This just makes assignment of the read/write funtions more convenient 418 __index = function(t,k) 419 rawset(t,k,{}) 420 return t[k] 421 end 422 }) 423 424 -- Note: We don't save the old on_load and on_save here. 425 -- It's not necessary because we know this will be the first one registered. 426 function wesnoth.game_events.on_load(cfg) 427 local warned_tags = {} 428 for i = 1, #cfg do 429 local name = cfg[i][1] 430 -- Use rawget so as not to trigger the auto-adding mechanism 431 local tag = rawget(wesnoth.persistent_tags, name) 432 if type(tag) == 'table' and type(tag.read) == 'function' then 433 tag.read(cfg[i][2]) 434 elseif tag ~= nil and not warned_tags[name] then 435 error(string.format("Invalid persistent tag [%s], should be a table containing read and write functions.", name)) 436 warned_tags[name] = true 437 else 438 error(string.format("[%s] not supported at scenario toplevel", name)) 439 warned_tags[name] = true 440 end 441 end 442 end 443 444 function wesnoth.game_events.on_save() 445 local data_to_save = {} 446 for name, tag in pairs(wesnoth.persistent_tags) do 447 if type(tag) == 'table' and type(tag.write) == 'function' then 448 local function add(data) 449 table.insert(data_to_save, wml.tag[name](data)) 450 end 451 tag.write(add) 452 end 453 end 454 return data_to_save 455 end 456end 457 458-- Some C++ functions are deprecated; apply the messages here. 459-- Note: It must happen AFTER the C++ functions are reassigned above to their new location. 460-- These deprecated functions will probably never be removed. 461if wesnoth.kernel_type() == "Game Lua Kernel" then 462 wesnoth.get_variable = wesnoth.deprecate_api('wesnoth.get_variable', 'wml.variables', 1, nil, wesnoth.get_variable) 463 wesnoth.set_variable = wesnoth.deprecate_api('wesnoth.set_variable', 'wml.variables', 1, nil, wesnoth.set_variable) 464 wesnoth.get_all_vars = wesnoth.deprecate_api('wesnoth.get_all_vars', 'wml.all_variables', 1, nil, wesnoth.get_all_vars) 465end 466wesnoth.tovconfig = wesnoth.deprecate_api('wesnoth.tovconfig', 'wml.tovconfig', 1, nil, wesnoth.tovconfig) 467wesnoth.debug = wesnoth.deprecate_api('wesnoth.debug', 'wml.tostring', 1, nil, wesnoth.debug) 468