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