The Battle for Wesnoth  1.19.8+dev
game_lua_kernel.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2024
3  by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Provides a Lua interpreter, to be embedded in WML.
19  *
20  * @note Naming conventions:
21  * - intf_ functions are exported in the wesnoth domain,
22  * - impl_ functions are hidden inside metatables,
23  * - cfun_ functions are closures,
24  * - luaW_ functions are helpers in Lua style.
25  */
26 
28 
29 #include "actions/attack.hpp" // for battle_context_unit_stats, etc
30 #include "actions/advancement.hpp" // for advance_unit_at, etc
31 #include "actions/move.hpp" // for clear_shroud
32 #include "actions/vision.hpp" // for clear_shroud and create_jamming_map
33 #include "actions/undo.hpp" // for clear_shroud and create_jamming_map
34 #include "actions/undo_action.hpp" // for clear_shroud and create_jamming_map
35 #include "ai/composite/ai.hpp" // for ai_composite
36 #include "ai/composite/component.hpp" // for component, etc
37 #include "ai/composite/contexts.hpp" // for ai_context
38 #include "ai/lua/engine_lua.hpp" // for engine_lua
39 #include "ai/composite/rca.hpp" // for candidate_action
40 #include "ai/composite/stage.hpp" // for stage
41 #include "ai/configuration.hpp" // for configuration
42 #include "ai/lua/core.hpp" // for lua_ai_context, etc
43 #include "ai/manager.hpp" // for manager, holder
44 #include "attack_prediction.hpp" // for combatant
45 #include "chat_events.hpp" // for chat_handler, etc
46 #include "config.hpp" // for config, etc
47 #include "display_chat_manager.hpp" // for clear_chat_messages
48 #include "floating_label.hpp"
49 #include "formatter.hpp"
50 #include "game_board.hpp" // for game_board
51 #include "game_classification.hpp" // for game_classification, etc
52 #include "game_config.hpp" // for debug, base_income, etc
53 #include "game_config_manager.hpp" // for game_config_manager
54 #include "game_data.hpp" // for game_data, etc
55 #include "game_display.hpp" // for game_display
56 #include "game_errors.hpp" // for game_error
57 #include "game_events/conditional_wml.hpp" // for conditional_passed
59 #include "game_events/handlers.hpp"
60 #include "game_events/manager_impl.hpp" // for pending_event_handler
61 #include "game_events/pump.hpp" // for queued_event
62 #include "preferences/preferences.hpp" // for encountered_units
63 #include "log.hpp" // for LOG_STREAM, logger, etc
64 #include "map/map.hpp" // for gamemap
65 #include "map/label.hpp"
66 #include "map/location.hpp" // for map_location
67 #include "mouse_events.hpp" // for mouse_handler
68 #include "mp_game_settings.hpp" // for mp_game_settings
69 #include "overlay.hpp"
70 #include "pathfind/pathfind.hpp" // for full_cost_map, plain_route, etc
71 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
72 #include "play_controller.hpp" // for play_controller
74 #include "recall_list_manager.hpp" // for recall_list_manager
75 #include "replay.hpp" // for get_user_choice, etc
76 #include "reports.hpp" // for register_generator, etc
77 #include "resources.hpp" // for whiteboard
79 #include "scripting/lua_audio.hpp"
80 #include "scripting/lua_unit.hpp"
82 #include "scripting/lua_common.hpp"
84 #include "scripting/lua_gui2.hpp" // for show_gamestate_inspector
86 #include "scripting/lua_race.hpp"
87 #include "scripting/lua_team.hpp"
90 #include "scripting/push_check.hpp"
91 #include "synced_commands.hpp"
92 #include "color.hpp" // for surface
93 #include "side_filter.hpp" // for side_filter
94 #include "sound.hpp" // for commit_music_changes, etc
95 #include "synced_context.hpp" // for synced_context, etc
96 #include "synced_user_choice.hpp"
97 #include "team.hpp" // for team, village_owner
98 #include "terrain/terrain.hpp" // for terrain_type
99 #include "terrain/filter.hpp" // for terrain_filter
100 #include "terrain/translation.hpp" // for read_terrain_code, etc
101 #include "time_of_day.hpp" // for time_of_day
102 #include "tod_manager.hpp" // for tod_manager
103 #include "tstring.hpp" // for t_string, operator+
104 #include "units/unit.hpp" // for unit
105 #include "units/animation_component.hpp" // for unit_animation_component
106 #include "units/udisplay.hpp"
107 #include "units/filter.hpp"
108 #include "units/map.hpp" // for unit_map, etc
109 #include "units/ptr.hpp" // for unit_const_ptr, unit_ptr
110 #include "units/types.hpp" // for unit_type_data, unit_types, etc
111 #include "utils/scope_exit.hpp"
112 #include "variable.hpp" // for vconfig, etc
113 #include "variable_info.hpp"
114 #include "video.hpp" // only for faked
115 #include "whiteboard/manager.hpp" // for whiteboard
116 #include "deprecation.hpp"
117 
118 #include <functional> // for bind_t, bind
119 #include <array>
120 #include <cassert> // for assert
121 #include <cstring> // for strcmp
122 #include <iterator> // for distance, advance
123 #include <map> // for map, map<>::value_type, etc
124 #include <new> // for operator new
125 #include <set> // for set
126 #include <sstream> // for operator<<, basic_ostream, etc
127 #include <thread>
128 #include <utility> // for pair
129 #include <algorithm>
130 #include <vector> // for vector, etc
131 
132 #ifdef DEBUG_LUA
133 #include "scripting/debug_lua.hpp"
134 #endif
135 
136 static lg::log_domain log_scripting_lua("scripting/lua");
137 #define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
138 #define LOG_LUA LOG_STREAM(info, log_scripting_lua)
139 #define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
140 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
141 
142 static lg::log_domain log_wml("wml");
143 #define ERR_WML LOG_STREAM(err, log_wml)
144 
145 std::vector<config> game_lua_kernel::preload_scripts;
147 
148 // Template which allows to push member functions to the lua kernel base into lua as C functions, using a shim
149 typedef int (game_lua_kernel::*member_callback)(lua_State *);
150 
151 template <member_callback method>
152 int dispatch(lua_State *L) {
153  return ((lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)).*method)(L);
154 }
155 
156 // Pass a const bool also...
157 typedef int (game_lua_kernel::*member_callback2)(lua_State *, bool);
158 
159 template <member_callback2 method, bool b>
160 int dispatch2(lua_State *L) {
161  return ((lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)).*method)(L, b);
162 }
163 
165 {
166  map_locker(game_lua_kernel* kernel) : kernel_(kernel)
167  {
168  ++kernel_->map_locked_;
169  }
171  {
172  --kernel_->map_locked_;
173  }
175 };
176 
177 
179 {
181  for (const config& cfg : game_config.child_range("lua")) {
182  game_lua_kernel::preload_scripts.push_back(cfg);
183  }
184  game_lua_kernel::preload_config = game_config.mandatory_child("game_config");
185 }
186 
187 void game_lua_kernel::log_error(char const * msg, char const * context)
188 {
190  lua_chat(context, msg);
191 }
192 
193 void game_lua_kernel::lua_chat(const std::string& caption, const std::string& msg)
194 {
195  if (game_display_) {
196  game_display_->get_chat_manager().add_chat_message(std::time(nullptr), caption, 0, msg,
198  }
199 }
200 
201 /**
202  * Gets a vector of sides from side= attribute in a given config node.
203  * Promotes consistent behavior.
204  */
205 std::vector<int> game_lua_kernel::get_sides_vector(const vconfig& cfg)
206 {
207  const config::attribute_value sides = cfg["side"];
208  const vconfig &ssf = cfg.child("filter_side");
209 
210  if (!ssf.null()) {
211  if(!sides.empty()) { WRN_LUA << "ignoring duplicate side filter information (inline side=)"; }
213  return filter.get_teams();
214  }
215 
216  side_filter filter(sides.str(), &game_state_);
217  return filter.get_teams();
218 }
219 
220 namespace {
221  /**
222  * Temporary entry to a queued_event stack
223  */
224  struct queued_event_context
225  {
226  typedef game_events::queued_event qe;
227  std::stack<qe const *> & stack_;
228 
229  queued_event_context(qe const *new_qe, std::stack<qe const*> & stack)
230  : stack_(stack)
231  {
232  stack_.push(new_qe);
233  }
234 
235  ~queued_event_context()
236  {
237  stack_.pop();
238  }
239  };
240 }//unnamed namespace for queued_event_context
241 
242 /**
243  * Gets currently viewing side.
244  * - Ret 1: integer specifying the currently viewing side
245  * - Ret 2: Bool whether the vision is not limited to that team, this can for example be true during replays.
246  */
247 static int intf_get_viewing_side(lua_State *L)
248 {
249  if(const display* disp = display::get_singleton()) {
250  lua_pushinteger(L, disp->viewing_team().side());
251  lua_pushboolean(L, disp->show_everything());
252  return 2;
253  }
254  else {
255  return 0;
256  }
257 }
258 
259 static int intf_handle_user_interact(lua_State *)
260 {
262  return 0;
263 }
264 
265 static const char animatorKey[] = "unit animator";
266 
267 static int impl_animator_collect(lua_State* L) {
268  unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
269  anim.~unit_animator();
270  return 0;
271 }
272 
273 static int impl_add_animation(lua_State* L)
274 {
275  unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
276  unit_ptr up = luaW_checkunit_ptr(L, 2, false);
277  unit& u = *up;
278  std::string which = luaL_checkstring(L, 3);
279 
280  std::string hits_str = luaL_checkstring(L, 4);
281  strike_result::type hits = strike_result::get_enum(hits_str).value_or(strike_result::type::invalid);
282 
283  map_location dest;
284  int v1 = 0, v2 = 0;
285  bool bars = false;
286  t_string text;
287  color_t color{255, 255, 255};
288  const_attack_ptr primary, secondary;
289 
290  if(lua_istable(L, 5)) {
291  lua_getfield(L, 5, "target");
292  if(luaW_tolocation(L, -1, dest)) {
293  if(dest == u.get_location()) {
294  return luaL_argerror(L, 5, "target location must be different from animated unit's location");
295  } else if(!tiles_adjacent(dest, u.get_location())) {
296  return luaL_argerror(L, 5, "target location must be adjacent to the animated unit");
297  }
298  } else {
299  // luaW_tolocation may set the location to (0,0) if it fails
300  dest = map_location();
301  if(!lua_isnoneornil(L, -1)) {
302  return luaW_type_error(L, 5, "target", "location table");
303  }
304  }
305  lua_pop(L, 1);
306 
307  lua_getfield(L, 5, "value");
308  if(lua_isnumber(L, -1)) {
309  v1 = lua_tointeger(L, -1);
310  } else if(lua_istable(L, -1)) {
311  lua_rawgeti(L, -1, 1);
312  v1 = lua_tointeger(L, -1);
313  lua_pop(L, 1);
314  lua_rawgeti(L, -1, 2);
315  v2 = lua_tointeger(L, -1);
316  lua_pop(L, 1);
317  } else if(!lua_isnoneornil(L, -1)) {
318  return luaW_type_error(L, 5, "value", "number or array of two numbers");
319  }
320  lua_pop(L, 1);
321 
322  lua_getfield(L, 5, "with_bars");
323  if(lua_isboolean(L, -1)) {
324  bars = luaW_toboolean(L, -1);
325  } else if(!lua_isnoneornil(L, -1)) {
326  return luaW_type_error(L, 5, "with_bars", lua_typename(L, LUA_TBOOLEAN));
327  }
328  lua_pop(L, 1);
329 
330  lua_getfield(L, 5, "text");
331  if(lua_isstring(L, -1)) {
332  text = lua_tostring(L, -1);
333  } else if(luaW_totstring(L, -1, text)) {
334  // Do nothing; luaW_totstring already assigned the value
335  } else if(!lua_isnoneornil(L, -1)) {
336  return luaW_type_error(L, 5, "text", lua_typename(L, LUA_TSTRING));
337  }
338  lua_pop(L, 1);
339 
340  lua_getfield(L, 5, "color");
341  if(lua_istable(L, -1) && lua_rawlen(L, -1) == 3) {
342  int idx = lua_absindex(L, -1);
343  lua_rawgeti(L, idx, 1); // red @ -3
344  lua_rawgeti(L, idx, 2); // green @ -2
345  lua_rawgeti(L, idx, 3); // blue @ -1
346  color = color_t(lua_tointeger(L, -3), lua_tointeger(L, -2), lua_tointeger(L, -1));
347  lua_pop(L, 3);
348  } else if(!lua_isnoneornil(L, -1)) {
349  return luaW_type_error(L, 5, "color", "array of three numbers");
350  }
351  lua_pop(L, 1);
352 
353  lua_getfield(L, 5, "primary");
354  primary = luaW_toweapon(L, -1);
355  if(!primary && !lua_isnoneornil(L, -1)) {
356  return luaW_type_error(L, 5, "primary", "weapon");
357  }
358  lua_pop(L, 1);
359 
360  lua_getfield(L, 5, "secondary");
361  secondary = luaW_toweapon(L, -1);
362  if(!secondary && !lua_isnoneornil(L, -1)) {
363  return luaW_type_error(L, 5, "secondary", "weapon");
364  }
365  lua_pop(L, 1);
366  } else if(!lua_isnoneornil(L, 5)) {
367  return luaW_type_error(L, 5, "table of options");
368  }
369 
370  anim.add_animation(up, which, u.get_location(), dest, v1, bars, text, color, hits, primary, secondary, v2);
371  return 0;
372 }
373 
375 {
376  if(video::headless() || resources::controller->is_skipping_replay()) {
377  return 0;
378  }
379  events::command_disabler command_disabler;
380  unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
382  anim.start_animations();
383  anim.wait_for_end();
384  anim.set_all_standing();
385  anim.clear();
386  return 0;
387 }
388 
389 static int impl_clear_animation(lua_State* L)
390 {
391  unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
392  anim.clear();
393  return 0;
394 }
395 
396 static int impl_animator_get(lua_State* L)
397 {
398  const char* m = lua_tostring(L, 2);
399  return luaW_getmetafield(L, 1, m);
400 }
401 
403 {
404  new(L) unit_animator;
405  if(luaL_newmetatable(L, animatorKey)) {
406  luaL_Reg metafuncs[] {
407  {"__gc", impl_animator_collect},
408  {"__index", impl_animator_get},
409  {"add", impl_add_animation},
410  {"run", &dispatch<&game_lua_kernel::impl_run_animation>},
411  {"clear", impl_clear_animation},
412  {nullptr, nullptr},
413  };
414  luaL_setfuncs(L, metafuncs, 0);
415  lua_pushstring(L, "__metatable");
416  lua_setfield(L, -2, animatorKey);
417  }
418  lua_setmetatable(L, -2);
419  return 1;
420 }
421 
423 {
424  if (game_display_) {
426  std::string name;
427  if(luaW_tovconfig(L, 1, cfg)) {
428  name = cfg["name"].str();
429  deprecated_message("gui.show_inspector(cfg)", DEP_LEVEL::INDEFINITE, {1, 19, 0}, "Instead of {name = 'title' }, pass just 'title'.");
430  } else {
431  name = luaL_optstring(L, 1, "");
432  }
434  }
435  return 0;
436 }
437 
438 /**
439  * Gets the unit at the given location or with the given id.
440  * - Arg 1: location
441  * OR
442  * - Arg 1: string ID
443  * - Ret 1: full userdata with __index pointing to impl_unit_get and
444  * __newindex pointing to impl_unit_set.
445  */
447 {
449  if(lua_isstring(L, 1) && !lua_isnumber(L, 1)) {
450  std::string id = luaL_checkstring(L, 1);
451  for(const unit& u : units()) {
452  if(u.id() == id) {
453  luaW_pushunit(L, u.underlying_id());
454  return 1;
455  }
456  }
457  return 0;
458  }
459  if(!luaW_tolocation(L, 1, loc)) {
460  return luaL_argerror(L, 1, "expected string or location");
461  }
463 
464  if (!ui.valid()) return 0;
465 
466  luaW_pushunit(L, ui->underlying_id());
467  return 1;
468 }
469 
470 /**
471  * Gets the unit displayed in the sidebar.
472  * - Ret 1: full userdata with __index pointing to impl_unit_get and
473  * __newindex pointing to impl_unit_set.
474  */
476 {
477  if (!game_display_) {
478  return 0;
479  }
480 
485  if (!ui.valid()) return 0;
486 
487  luaW_pushunit(L, ui->underlying_id());
488  return 1;
489 }
490 
491 /**
492  * Gets all the units matching a given filter.
493  * - Arg 1: optional table containing a filter
494  * - Arg 2: optional location (to find all units that would match on that location)
495  * OR unit (to find all units that would match adjacent to that unit)
496  * - Ret 1: table containing full userdata with __index pointing to
497  * impl_unit_get and __newindex pointing to impl_unit_set.
498  */
500 {
501  vconfig filter = luaW_checkvconfig(L, 1, true);
502  unit_filter filt(filter);
503  std::vector<const unit*> units;
504 
505  if(unit* u_adj = luaW_tounit(L, 2)) {
506  if(!u_adj) {
507  return luaL_argerror(L, 2, "unit not found");
508  }
509  units = filt.all_matches_with_unit(*u_adj);
510  } else if(!lua_isnoneornil(L, 2)) {
512  luaW_tolocation(L, 2, loc);
513  if(!loc.valid()) {
514  return luaL_argerror(L, 2, "invalid location");
515  }
516  units = filt.all_matches_at(loc);
517  } else {
518  units = filt.all_matches_on_map();
519  }
520 
521  // Go through all the units while keeping the following stack:
522  // 1: return table, 2: userdata
523  lua_settop(L, 0);
524  lua_newtable(L);
525  int i = 1;
526 
527  for (const unit * ui : units) {
528  luaW_pushunit(L, ui->underlying_id());
529  lua_rawseti(L, 1, i);
530  ++i;
531  }
532  return 1;
533 }
534 
535 /**
536  * Matches a unit against the given filter.
537  * - Arg 1: full userdata.
538  * - Arg 2: table containing a filter
539  * - Arg 3: optional location OR optional "adjacent" unit
540  * - Ret 1: boolean.
541  */
543 {
544  lua_unit& u = *luaW_checkunit_ref(L, 1);
545 
546  vconfig filter = luaW_checkvconfig(L, 2, true);
547 
548  if (filter.null()) {
549  lua_pushboolean(L, true);
550  return 1;
551  }
552 
553  if(unit* u_adj = luaW_tounit(L, 3)) {
554  if(int side = u.on_recall_list()) {
555  WRN_LUA << "wesnoth.units.matches called with a secondary unit (3rd argument), ";
556  WRN_LUA << "but unit to match was on recall list. ";
557  WRN_LUA << "Thus the 3rd argument is ignored.";
558  team &t = board().get_team(side);
559  scoped_recall_unit auto_store("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id()));
560  lua_pushboolean(L, unit_filter(filter).matches(*u, map_location()));
561  return 1;
562  }
563  if (!u_adj) {
564  return luaL_argerror(L, 3, "unit not found");
565  }
566  lua_pushboolean(L, unit_filter(filter).matches(*u, *u_adj));
567  } else if(int side = u.on_recall_list()) {
569  luaW_tolocation(L, 3, loc); // If argument 3 isn't a location, loc is unchanged
570  team &t = board().get_team(side);
571  scoped_recall_unit auto_store("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id()));
572  lua_pushboolean(L, unit_filter(filter).matches(*u, loc));
573  return 1;
574  } else {
576  luaW_tolocation(L, 3, loc); // If argument 3 isn't a location, loc is unchanged
577  lua_pushboolean(L, unit_filter(filter).matches(*u, loc));
578  }
579  return 1;
580 }
581 
582 /**
583  * Gets the numeric ids of all the units matching a given filter on the recall lists.
584  * - Arg 1: optional table containing a filter
585  * - Ret 1: table containing full userdata with __index pointing to
586  * impl_unit_get and __newindex pointing to impl_unit_set.
587  */
589 {
590  vconfig filter = luaW_checkvconfig(L, 1, true);
591 
592  // Go through all the units while keeping the following stack:
593  // 1: return table, 2: userdata
594  lua_settop(L, 0);
595  lua_newtable(L);
596  int i = 1, s = 1;
597  const unit_filter ufilt(filter);
598  for (team &t : teams())
599  {
600  for (unit_ptr & u : t.recall_list())
601  {
602  if (!filter.null()) {
603  scoped_recall_unit auto_store("this_unit",
604  t.save_id_or_number(), t.recall_list().find_index(u->id()));
605  if (!ufilt( *u, map_location() ))
606  continue;
607  }
608  luaW_pushunit(L, s, u->underlying_id());
609  lua_rawseti(L, 1, i);
610  ++i;
611  }
612  ++s;
613  }
614  return 1;
615 }
616 
617 /**
618  * Fires an event.
619  * - Arg 1: string containing the event name or id.
620  * - Arg 2: optional first location.
621  * - Arg 3: optional second location.
622  * - Arg 4: optional WML table used used as the event data
623  * Typically this contains [first] as the [weapon] tag and [second] as the [second_weapon] tag.
624  * - Ret 1: boolean indicating whether the event was processed or not.
625  */
626 int game_lua_kernel::intf_fire_event(lua_State *L, const bool by_id)
627 {
628  char const *m = luaL_checkstring(L, 1);
629 
630  int pos = 2;
631  map_location l1, l2;
632  config data;
633 
634  if (luaW_tolocation(L, 2, l1)) {
635  if (luaW_tolocation(L, 3, l2)) {
636  pos = 4;
637  } else {
638  pos = 3;
639  }
640  }
641 
642  luaW_toconfig(L, pos, data);
643 
644  // Support WML names for some common data
645  if(data.has_child("primary_attack")) {
646  data.add_child("first", data.mandatory_child("primary_attack"));
647  data.remove_children("primary_attack");
648  }
649  if(data.has_child("secondary_attack")) {
650  data.add_child("second", data.mandatory_child("secondary_attack"));
651  data.remove_children("secondary_attack");
652  }
653 
654  bool b = false;
655 
656  if (by_id) {
657  b = std::get<0>(play_controller_.pump().fire("", m, l1, l2, data));
658  }
659  else {
660  b = std::get<0>(play_controller_.pump().fire(m, l1, l2, data));
661  }
662  lua_pushboolean(L, b);
663  return 1;
664 }
665 
666 
667 /**
668  * Fires a wml menu item.
669  * - Arg 1: id of the item. it is not possible to fire items that don't have ids with this function.
670  * - Arg 2: optional first location.
671  * - Ret 1: boolean, true indicating that the event was fired successfully
672  *
673  * NOTE: This is not an "official" feature, it may currently cause assertion failures if used with
674  * menu items which have "needs_select". It is not supported right now to use it this way.
675  * The purpose of this function right now is to make it possible to have automated sanity tests for
676  * the wml menu items system.
677  */
679 {
680  char const *m = luaL_checkstring(L, 1);
681 
682  map_location l1 = luaW_checklocation(L, 2);
683 
685  lua_pushboolean(L, b);
686  return 1;
687 }
688 
689 /**
690  * Gets a WML variable.
691  * - Arg 1: string containing the variable name.
692  * - Arg 2: optional bool indicating if tables for containers should be left empty.
693  * - Ret 1: value of the variable, if any.
694  */
696 {
697  char const *m = luaL_checkstring(L, 1);
699  return luaW_pushvariable(L, v) ? 1 : 0;
700 }
701 
702 /**
703  * Sets a WML variable.
704  * - Arg 1: string containing the variable name.
705  * - Arg 2: boolean/integer/string/table containing the value.
706  */
708 {
709  const std::string m = luaL_checkstring(L, 1);
710  if(m.empty()) return luaL_argerror(L, 1, "empty variable name");
711  if (lua_isnoneornil(L, 2)) {
713  return 0;
714  }
716  luaW_checkvariable(L, v, 2);
717  return 0;
718 }
719 
720 
722 {
723  config cfg = luaW_checkconfig(L, 1);
724  cfg["side"] = teams().size() + 1;
726  lua_pushinteger(L, teams().size());
727 
728  return 1;
729 }
730 
732 {
733  game_state_.get_wml_menu_items().set_item(luaL_checkstring(L, 1), luaW_checkvconfig(L,2));
734  return 0;
735 }
736 
738 {
739  std::string ids(luaL_checkstring(L, 1));
740  for(const std::string& id : utils::split(ids, ',', utils::STRIP_SPACES)) {
741  if(id.empty()) {
742  WRN_LUA << "[clear_menu_item] has been given an empty id=, ignoring";
743  continue;
744  }
746  }
747  return 0;
748 }
749 
750 /**
751  * Toggle shroud on some locations
752  * Arg 1: Side number
753  * Arg 2: List of locations on which to place/remove shroud
754  */
755 int game_lua_kernel::intf_toggle_shroud(lua_State *L, bool place_shroud)
756 {
757  team& t = luaW_checkteam(L, 1, board());
758 
759  if(lua_istable(L, 2)) {
760  std::set<map_location> locs = luaW_check_locationset(L, 2);
761 
762  for (const map_location& loc : locs)
763  {
764  if (place_shroud) {
765  t.place_shroud(loc);
766  } else {
767  t.clear_shroud(loc);
768  }
769  }
770  } else {
771  return luaL_argerror(L, 2, "expected list of locations");
772  }
773 
777 
778  return 0;
779 }
780 
781 /**
782  * Overrides the shroud entirely. All locations are shrouded, except for the ones passed in as argument 2.
783  * Arg 1: Side number
784  * Arg 2: List of locations that should be unshrouded
785  */
787 {
788  team& t = luaW_checkteam(L, 1, board());
789 
790  if(lua_istable(L, 2)) {
791  std::set<map_location> locs = luaW_check_locationset(L, 2);
792  t.reshroud();
793  for(const map_location& loc : locs) {
794  t.clear_shroud(loc);
795  }
796  } else {
797  return luaW_type_error(L, 2, "list of locations");
798  }
799 
803 
804  return 0;
805 }
806 
807 /**
808  * Highlights the given location on the map.
809  * - Arg 1: location.
810  */
812 {
813  if (!game_display_) {
814  return 0;
815  }
816 
817  const map_location loc = luaW_checklocation(L, 1);
818  if(!map().on_board(loc)) return luaL_argerror(L, 1, "not on board");
821 
822  return 0;
823 }
824 
825 /**
826  * Returns whether the first side is an enemy of the second one.
827  * - Args 1,2: side numbers.
828  * - Ret 1: boolean.
829  */
831 {
832  unsigned side_1, side_2;
833  if(team* t = luaW_toteam(L, 1)) {
834  side_1 = t->side();
835  } else {
836  side_1 = luaL_checkinteger(L, 1);
837  }
838  if(team* t = luaW_toteam(L, 2)) {
839  side_2 = t->side();
840  } else {
841  side_2 = luaL_checkinteger(L, 2);
842  }
843  if (side_1 > teams().size() || side_2 > teams().size()) return 0;
844  lua_pushboolean(L, board().get_team(side_1).is_enemy(side_2));
845  return 1;
846 }
847 
848 /**
849  * Gets whether gamemap scrolling is disabled for the user.
850  * - Ret 1: boolean.
851  */
853 {
854  if (!game_display_) {
855  return 0;
856  }
857 
858  lua_pushboolean(L, game_display_->view_locked());
859  return 1;
860 }
861 
862 /**
863  * Sets whether gamemap scrolling is disabled for the user.
864  * - Arg 1: boolean, specifying the new locked/unlocked status.
865  */
867 {
868  bool lock = luaW_toboolean(L, 1);
869  if (game_display_) {
871  }
872  return 0;
873 }
874 
875 static void luaW_push_tod(lua_State* L, const time_of_day& tod)
876 {
877  lua_newtable(L);
878  lua_pushstring(L, tod.id.c_str());
879  lua_setfield(L, -2, "id");
880  lua_pushinteger(L, tod.lawful_bonus);
881  lua_setfield(L, -2, "lawful_bonus");
882  lua_pushinteger(L, tod.bonus_modified);
883  lua_setfield(L, -2, "bonus_modified");
884  lua_pushstring(L, tod.image.c_str());
885  lua_setfield(L, -2, "image");
886  luaW_pushtstring(L, tod.name);
887  lua_setfield(L, -2, "name");
888  lua_pushstring(L, tod.sounds.c_str());
889  lua_setfield(L, -2, "sound");
890  lua_pushstring(L, tod.image_mask.c_str());
891  lua_setfield(L, -2, "mask");
892 
893  lua_pushinteger(L, tod.color.r);
894  lua_setfield(L, -2, "red");
895  lua_pushinteger(L, tod.color.g);
896  lua_setfield(L, -2, "green");
897  lua_pushinteger(L, tod.color.b);
898  lua_setfield(L, -2, "blue");
899 }
900 
901 // A schedule object is an index with a special metatable.
902 // The global schedule uses index -1
903 void game_lua_kernel::luaW_push_schedule(lua_State* L, int area_index)
904 {
905  lua_newuserdatauv(L, 0, 1);
906  lua_pushinteger(L, area_index);
907  lua_setiuservalue(L, -2, 1);
908  if(luaL_newmetatable(L, "schedule")) {
909  static luaL_Reg const schedule_meta[] {
910  {"__index", &dispatch<&game_lua_kernel::impl_schedule_get>},
911  {"__newindex", &dispatch<&game_lua_kernel::impl_schedule_set>},
912  {"__dir", &dispatch<&game_lua_kernel::impl_schedule_dir>},
913  {"__len", &dispatch<&game_lua_kernel::impl_schedule_len>},
914  { nullptr, nullptr }
915  };
916  luaL_setfuncs(L, schedule_meta, 0);
917  }
918  lua_setmetatable(L, -2);
919 }
920 
921 static int luaW_check_schedule(lua_State* L, int idx)
922 {
923  int save_top = lua_gettop(L);
924  luaL_checkudata(L, idx, "schedule");
925  lua_getiuservalue(L, idx, 1);
926  int i = luaL_checkinteger(L, -1);
927  lua_settop(L, save_top);
928  return i;
929 }
930 
931 struct schedule_tag {
935  auto& tod_man() const { return ref.tod_man(); }
936 };
937 #define SCHEDULE_GETTER(name, type) LATTR_GETTER(name, type, schedule_tag, sched)
938 #define SCHEDULE_SETTER(name, type) LATTR_SETTER(name, type, schedule_tag, sched)
939 #define SCHEDULE_VALID(name) LATTR_VALID(name, schedule_tag, sched)
941 
942 template<> struct lua_object_traits<schedule_tag> {
943  inline static auto metatable = "schedule";
944  inline static schedule_tag get(lua_State* L, int n) {
945  schedule_tag sched{lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)};
946  sched.area_index = luaW_check_schedule(L, n);
947  return sched;
948  }
949 };
950 
952 {
953  int area_index = luaW_check_schedule(L, 1);
954  if(lua_isnumber(L, 2)) {
955  const auto& times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
956  int i = lua_tointeger(L, 2) - 1;
957  if(i < 0 || i >= static_cast<int>(times.size())) {
958  return luaL_argerror(L, 2, "invalid time of day index");
959  }
960  luaW_push_tod(L, times[i]);
961  return 1;
962  }
963  return scheduleReg.get(L);
964 }
965 
967 {
968  int area_index = luaW_check_schedule(L, 1);
969  const auto& times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
970  lua_pushinteger(L, times.size());
971  return 1;
972 }
973 
975 {
976  int area_index = luaW_check_schedule(L, 1);
977  if(lua_isnumber(L, 2)) {
978  std::vector<time_of_day> times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
979  int i = lua_tointeger(L, 2) - 1;
980  if(i < 0 || i >= static_cast<int>(times.size())) {
981  return luaL_argerror(L, 2, "invalid time of day index");
982  }
983  config time_cfg = luaW_checkconfig(L, 3);
984  times[i] = time_of_day(time_cfg);
985  if(area_index < 0) {
986  tod_man().replace_schedule(times);
987  } else {
988  tod_man().replace_local_schedule(times, area_index);
989  }
990  }
991  return scheduleReg.set(L);
992 }
993 
995  return scheduleReg.dir(L);
996 }
997 
998 namespace {
999 SCHEDULE_GETTER("time_of_day", std::string) {
1000  if(sched.area_index >= 0) {
1001  return sched.tod_man().get_area_time_of_day(sched.area_index).id;
1002  }
1003  return sched.tod_man().get_time_of_day().id;
1004 }
1005 
1006 SCHEDULE_SETTER("time_of_day", std::string) {
1007  const auto& times = sched.area_index < 0 ? sched.tod_man().times() : sched.tod_man().times(sched.area_index);
1008  auto iter = std::find_if(times.begin(), times.end(), [&value](const time_of_day& tod) {
1009  return tod.id == value;
1010  });
1011  if(iter == times.end()) {
1012  std::ostringstream err;
1013  err << "invalid time of day ID for ";
1014  if(sched.area_index < 0) {
1015  err << "global schedule";
1016  } else {
1017  const std::string& id = sched.tod_man().get_area_id(sched.area_index);
1018  if(id.empty()) {
1019  const auto& hexes = sched.tod_man().get_area_by_index(sched.area_index);
1020  if(hexes.empty()) {
1021  err << "anonymous empty time area";
1022  } else {
1023  err << "anonymous time area at (" << hexes.begin()->wml_x() << ',' << hexes.begin()->wml_y() << ")";
1024  }
1025  } else {
1026  err << "time area with id=" << id;
1027  }
1028  }
1029  lua_push(L, err.str());
1030  throw lua_error(L);
1031  }
1032  int n = std::distance(times.begin(), iter);
1033  if(sched.area_index < 0) {
1034  sched.tod_man().set_current_time(n);
1035  } else {
1036  sched.tod_man().set_current_time(n, sched.area_index);
1037  }
1038 }
1039 
1040 SCHEDULE_VALID("liminal_bonus") {
1041  return sched.area_index < 0;
1042 }
1043 
1044 SCHEDULE_GETTER("liminal_bonus", utils::optional<int>) {
1045  if(sched.area_index >= 0) return utils::nullopt;
1046  return sched.tod_man().get_max_liminal_bonus();
1047 }
1048 
1049 SCHEDULE_SETTER("liminal_bonus", utils::optional<int>) {
1050  if(sched.area_index >= 0) {
1051  throw luaL_error(L, "liminal_bonus can only be set on the global schedule");
1052  }
1053  if(value) {
1054  sched.tod_man().set_max_liminal_bonus(*value);
1055  } else {
1056  sched.tod_man().reset_max_liminal_bonus();
1057  }
1058 }
1059 
1060 SCHEDULE_VALID("id") {
1061  return sched.area_index >= 0;
1062 }
1063 
1064 SCHEDULE_GETTER("id", utils::optional<std::string>) {
1065  if(sched.area_index < 0) return utils::nullopt;
1066  return sched.tod_man().get_area_id(sched.area_index);
1067 }
1068 
1069 SCHEDULE_SETTER("id", std::string) {
1070  if(sched.area_index < 0) {
1071  throw luaL_error(L, "can't set id of global schedule");
1072  }
1073  sched.tod_man().set_area_id(sched.area_index, value);
1074 }
1075 
1076 SCHEDULE_VALID("hexes") {
1077  return sched.area_index >= 0;
1078 }
1079 
1080 SCHEDULE_GETTER("hexes", utils::optional<std::set<map_location>>) {
1081  if(sched.area_index < 0) return utils::nullopt;
1082  return sched.tod_man().get_area_by_index(sched.area_index);
1083 }
1084 
1085 SCHEDULE_SETTER("hexes", std::set<map_location>) {
1086  if(sched.area_index < 0) {
1087  throw luaL_error(L, "can't set hexes of global schedule");
1088  }
1089  sched.tod_man().replace_area_locations(sched.area_index, value);
1090 }
1091 }
1092 
1093 /**
1094  * Gets details about a terrain.
1095  * - Arg 1: terrain code string.
1096  * - Ret 1: table.
1097  */
1099 {
1100  char const *m = luaL_checkstring(L, 2);
1102  if (t == t_translation::NONE_TERRAIN || !board().map().tdata()->is_known(t)) return 0;
1103  const terrain_type& info = board().map().tdata()->get_terrain_info(t);
1104 
1105  lua_newtable(L);
1106  lua_pushstring(L, info.id().c_str());
1107  lua_setfield(L, -2, "id");
1108  luaW_pushtstring(L, info.name());
1109  lua_setfield(L, -2, "name");
1110  luaW_pushtstring(L, info.editor_name());
1111  lua_setfield(L, -2, "editor_name");
1112  luaW_pushtstring(L, info.description());
1113  lua_setfield(L, -2, "description");
1114  lua_push(L, info.icon_image());
1115  lua_setfield(L, -2, "icon");
1116  lua_push(L, info.editor_image());
1117  lua_setfield(L, -2, "editor_image");
1118  lua_pushinteger(L, info.light_bonus(0));
1119  lua_setfield(L, -2, "light");
1120  lua_pushboolean(L, info.is_village());
1121  lua_setfield(L, -2, "village");
1122  lua_pushboolean(L, info.is_castle());
1123  lua_setfield(L, -2, "castle");
1124  lua_pushboolean(L, info.is_keep());
1125  lua_setfield(L, -2, "keep");
1126  lua_pushinteger(L, info.gives_healing());
1127  lua_setfield(L, -2, "healing");
1128 
1129  return 1;
1130 }
1131 
1132 /**
1133  * Gets a list of known terrain codes.
1134  * - Ret 1: array of terrain codes
1135  */
1137 {
1138  auto codes = board().map().tdata()->list();
1139  std::vector<std::string> terrains;
1140  terrains.reserve(codes.size());
1141  for(auto code : codes) {
1142  terrains.push_back(t_translation::write_terrain_code(code));
1143  }
1144  lua_push(L, terrains);
1145  return 1;
1146 }
1147 
1148 /**
1149  * Gets time of day information.
1150  * - Arg 1: schedule object, location, time area ID, or nil
1151  * - Arg 2: optional turn number
1152  * - Ret 1: table.
1153  */
1154 template<bool consider_illuminates>
1156 {
1157  int for_turn = tod_man().turn();
1159 
1160  if(luaW_tolocation(L, 1, loc)) {
1161  if(!board().map().on_board_with_border(loc)) {
1162  return luaL_argerror(L, 1, "coordinates are not on board");
1163  }
1164  } else if(lua_isstring(L, 1)) {
1165  auto area = tod_man().get_area_by_id(lua_tostring(L, 1));
1166  if(area.empty()) {
1167  return luaL_error(L, "invalid or empty time_area ID");
1168  }
1169  // We just need SOME location in that area, it doesn't matter which one.
1170  loc = *area.begin();
1171  } else if(!lua_isnil(L, 1)) {
1172  auto area = tod_man().get_area_by_index(luaW_check_schedule(L, 1));
1173  if(area.empty()) {
1174  return luaL_error(L, "empty time_area");
1175  }
1176  // We just need SOME location in that area, it doesn't matter which one.
1177  loc = *area.begin();
1178  }
1179 
1180  if(lua_isnumber(L, 2)) {
1181  for_turn = luaL_checkinteger(L, 2);
1182  int number_of_turns = tod_man().number_of_turns();
1183  if(for_turn < 1 || (number_of_turns != -1 && for_turn > number_of_turns)) {
1184  return luaL_argerror(L, 2, "turn number out of range");
1185  }
1186  }
1187 
1188  const time_of_day& tod = consider_illuminates ?
1189  tod_man().get_illuminated_time_of_day(board().units(), board().map(), loc, for_turn) :
1190  tod_man().get_time_of_day(loc, for_turn);
1191 
1192  luaW_push_tod(L, tod);
1193 
1194  return 1;
1195 }
1196 
1197 /**
1198  * Gets the side of a village owner.
1199  * - Arg 1: map location.
1200  * - Ret 1: integer.
1201  */
1203 {
1205  if (!board().map().is_village(loc))
1206  return 0;
1207 
1208  int side = board().village_owner(loc);
1209  if (!side) return 0;
1210  lua_pushinteger(L, side);
1211  return 1;
1212 }
1213 
1214 /**
1215  * Sets the owner of a village.
1216  * - Arg 1: map location.
1217  * - Arg 2: integer for the side or empty to remove ownership.
1218  */
1220 {
1222  if(!board().map().is_village(loc)) {
1223  return 0;
1224  }
1225 
1226  const int old_side_num = board().village_owner(loc);
1227  const int new_side_num = lua_isnoneornil(L, 2) ? 0 : luaL_checkinteger(L, 2);
1228 
1229  team* old_side = nullptr;
1230  team* new_side = nullptr;
1231 
1232  if(old_side_num == new_side_num) {
1233  return 0;
1234  }
1235 
1236  try {
1237  old_side = &board().get_team(old_side_num);
1238  } catch(const std::out_of_range&) {
1239  // old_side_num is invalid, most likely because the village wasn't captured.
1240  old_side = nullptr;
1241  }
1242 
1243  try {
1244  new_side = &board().get_team(new_side_num);
1245  } catch(const std::out_of_range&) {
1246  // new_side_num is invalid.
1247  new_side = nullptr;
1248  }
1249 
1250  // The new side was valid, but already defeated. Do nothing.
1251  if(new_side && board().team_is_defeated(*new_side)) {
1252  return 0;
1253  }
1254 
1255  // Even if the new side is not valid, we still want to remove the village from the old side.
1256  // This covers the case where new_side_num equals 0. The behavior in that case is to simply
1257  // un-assign the village from the old side, which of course we also want to happen if the new
1258  // side IS valid. If the village in question hadn't been captured, this won't fire (old_side
1259  // will be a nullptr).
1260  if(old_side) {
1261  old_side->lose_village(loc);
1262  }
1263 
1264  // If the new side was valid, re-assign the village.
1265  if(new_side) {
1266  new_side->get_village(loc, old_side_num, (luaW_toboolean(L, 3) ? &gamedata() : nullptr));
1267  }
1268 
1269  return 0;
1270 }
1271 
1272 /**
1273  * Returns the currently overed tile.
1274  * - Ret 1: x.
1275  * - Ret 2: y.
1276  */
1278 {
1279  if (!game_display_) {
1280  return 0;
1281  }
1282 
1284  if (!board().map().on_board(loc)) return 0;
1285  lua_pushinteger(L, loc.wml_x());
1286  lua_pushinteger(L, loc.wml_y());
1287  return 2;
1288 }
1289 
1290 /**
1291  * Returns the currently selected tile.
1292  * - Ret 1: x.
1293  * - Ret 2: y.
1294  */
1296 {
1297  if (!game_display_) {
1298  return 0;
1299  }
1300 
1302  if (!board().map().on_board(loc)) return 0;
1303  lua_pushinteger(L, loc.wml_x());
1304  lua_pushinteger(L, loc.wml_y());
1305  return 2;
1306 }
1307 
1308 
1309 /**
1310  * Gets a table for an resource tag.
1311  * - Arg 1: userdata (ignored).
1312  * - Arg 2: string containing id of the desired resource
1313  * - Ret 1: config for the era
1314  */
1315 static int intf_get_resource(lua_State *L)
1316 {
1317  std::string m = luaL_checkstring(L, 1);
1318  if(auto res = game_config_manager::get()->game_config().find_child("resource","id",m)) {
1319  luaW_pushconfig(L, *res);
1320  return 1;
1321  }
1322  else {
1323  return luaL_argerror(L, 1, ("Cannot find resource with id '" + m + "'").c_str());
1324  }
1325 }
1326 
1327 /**
1328  * Gets a table for an era tag.
1329  * - Arg 1: userdata (ignored).
1330  * - Arg 2: string containing id of the desired era
1331  * - Ret 1: config for the era
1332  */
1333 static int intf_get_era(lua_State *L)
1334 {
1335  std::string m = luaL_checkstring(L, 1);
1336  if(auto res = game_config_manager::get()->game_config().find_child("era","id",m)) {
1337  luaW_pushconfig(L, *res);
1338  return 1;
1339  }
1340  else {
1341  return luaL_argerror(L, 1, ("Cannot find era with id '" + m + "'").c_str());
1342  }
1343  return 1;
1344 }
1345 
1346 extern luaW_Registry& gameConfigReg();
1347 static auto& dummy = gameConfigReg(); // just to ensure it's constructed.
1348 
1352  auto& pc() const { return ref.play_controller_; }
1353  auto& gamedata() const { return ref.gamedata(); }
1354  auto& disp() const { return ref.game_display_; }
1355 };
1356 #define GAME_CONFIG_SIMPLE_SETTER(name) \
1357 GAME_CONFIG_SETTER(#name, decltype(game_config::name), game_lua_kernel) { \
1358  (void) k; \
1359  game_config::name = value; \
1360 }
1361 
1362 namespace {
1363 GAME_CONFIG_GETTER("do_healing", bool, game_lua_kernel) {
1364  game_config_glk_tag k2{k.ref};
1365  return k2.pc().gamestate().do_healing_;
1366 }
1367 
1368 GAME_CONFIG_SETTER("do_healing", bool, game_lua_kernel) {
1369  game_config_glk_tag k2{k.ref};
1370  k2.pc().gamestate().do_healing_ = value;}
1371 
1372 GAME_CONFIG_GETTER("theme", std::string, game_lua_kernel) {
1373  game_config_glk_tag k2{k.ref};
1374  return k2.gamedata().get_theme();
1375 }
1376 
1377 GAME_CONFIG_SETTER("theme", std::string, game_lua_kernel) {
1378  game_config_glk_tag k2{k.ref};
1379  k2.gamedata().set_theme(value);
1380  k2.disp()->set_theme(value);
1381 }
1382 
1383 using traits_map = std::map<std::string, config>;
1384 GAME_CONFIG_GETTER("global_traits", traits_map, game_lua_kernel) {
1385  (void)k;
1386  std::map<std::string, config> result;
1387  for(const config& trait : unit_types.traits()) {
1388  //It seems the engine never checks the id field for emptiness or duplicates
1389  //However, the worst that could happen is that the trait read later overwrites the older one,
1390  //and this is not the right place for such checks.
1391  result.emplace(trait["id"], trait);
1392  }
1393  return result;
1394 }
1395 
1404 }
1405 
1406 namespace {
1407  static config find_addon(const std::string& type, const std::string& id)
1408  {
1410  }
1411 }
1412 
1413 static int impl_end_level_data_get(lua_State* L)
1414 {
1415  const end_level_data& data = *static_cast<end_level_data*>(lua_touserdata(L, 1));
1416  const char* m = luaL_checkstring(L, 2);
1417 
1418  return_bool_attrib("linger_mode", data.transient.linger_mode);
1419  return_bool_attrib("reveal_map", data.transient.reveal_map);
1420  return_bool_attrib("carryover_report", data.transient.carryover_report);
1421  return_bool_attrib("prescenario_save", data.prescenario_save);
1422  return_bool_attrib("replay_save", data.replay_save);
1423  return_bool_attrib("proceed_to_next_level", data.proceed_to_next_level);
1424  return_bool_attrib("is_victory", data.is_victory);
1425  return_bool_attrib("is_loss", !data.is_victory);
1426  return_cstring_attrib("result", data.is_victory ? level_result::victory : "loss"); // to match wesnoth.end_level()
1427  return_string_attrib("test_result", data.test_result);
1428  return_cfg_attrib("__cfg", data.to_config_full());
1429 
1430  return 0;
1431 }
1432 
1433 namespace {
1434  struct end_level_committer {
1435  end_level_committer(end_level_data& data, play_controller& pc) : data_(data), pc_(pc) {}
1436  ~end_level_committer() {
1437  pc_.set_end_level_data(data_);
1438  }
1439  private:
1440  end_level_data& data_;
1441  play_controller& pc_;
1442  };
1443 }
1444 
1446 {
1447  end_level_data& data = *static_cast<end_level_data*>(lua_touserdata(L, 1));
1448  const char* m = luaL_checkstring(L, 2);
1449  end_level_committer commit(data, play_controller_);
1450 
1451  modify_bool_attrib("linger_mode", data.transient.linger_mode = value);
1452  modify_bool_attrib("reveal_map", data.transient.reveal_map = value);
1453  modify_bool_attrib("carryover_report", data.transient.carryover_report = value);
1454  modify_bool_attrib("prescenario_save", data.prescenario_save = value);
1455  modify_bool_attrib("replay_save", data.replay_save = value);
1456  modify_string_attrib("test_result", data.test_result = value);
1457 
1458  return 0;
1459 }
1460 
1461 static int impl_end_level_data_collect(lua_State* L)
1462 {
1463  end_level_data* data = static_cast<end_level_data*>(lua_touserdata(L, 1));
1464  data->~end_level_data();
1465  return 0;
1466 }
1467 
1468 static int impl_mp_settings_get(lua_State* L)
1469 {
1470  void* p = lua_touserdata(L, lua_upvalueindex(1));
1471  const mp_game_settings& settings = static_cast<play_controller*>(p)->get_mp_settings();
1472  if(lua_type(L, 2) == LUA_TNUMBER) {
1473  // Simulates a WML table with one [options] child and a variable number of [addon] children
1474  // TODO: Deprecate this -> mp_settings.options and mp_settings.addons
1475  size_t i = luaL_checkinteger(L, 2);
1476  if(i == 1) {
1477  lua_createtable(L, 2, 0);
1478  lua_pushstring(L, "options");
1479  lua_seti(L, -2, 1);
1480  luaW_pushconfig(L, settings.options);
1481  lua_seti(L, -2, 2);
1482  return 1;
1483  } else if(i >= 2) {
1484  i -= 2;
1485  if(i < settings.addons.size()) {
1486  auto iter = settings.addons.begin();
1487  std::advance(iter, i);
1488  config cfg;
1489  iter->second.write(cfg);
1490  cfg["id"] = iter->first;
1491 
1492  lua_createtable(L, 2, 0);
1493  lua_pushstring(L, "addon");
1494  lua_seti(L, -2, 1);
1495  luaW_pushconfig(L, cfg);
1496  lua_seti(L, -2, 2);
1497  return 1;
1498  }
1499  }
1500  } else {
1501  char const *m = luaL_checkstring(L, 2);
1502  return_string_attrib("scenario", settings.name);
1503  return_string_attrib("game_name", settings.name);
1504  return_string_attrib("hash", settings.hash);
1505  return_string_attrib("mp_era_name", settings.mp_era_name);
1506  return_string_attrib("mp_scenario", settings.mp_scenario);
1507  return_string_attrib("mp_scenario_name", settings.mp_scenario_name);
1508  return_string_attrib("mp_campaign", settings.mp_campaign);
1509  return_string_attrib("side_users", utils::join_map(settings.side_users));
1510  return_int_attrib("experience_modifier", settings.xp_modifier);
1511  return_bool_attrib("mp_countdown", settings.mp_countdown);
1512  return_int_attrib("mp_countdown_init_time", settings.mp_countdown_init_time.count());
1513  return_int_attrib("mp_countdown_turn_bonus", settings.mp_countdown_turn_bonus.count());
1514  return_int_attrib("mp_countdown_reservoir_bonus", settings.mp_countdown_reservoir_time.count());
1515  return_int_attrib("mp_countdown_action_bonus", settings.mp_countdown_action_bonus.count());
1516  return_int_attrib("mp_num_turns", settings.num_turns);
1517  return_int_attrib("mp_village_gold", settings.village_gold);
1518  return_int_attrib("mp_village_support", settings.village_support);
1519  return_bool_attrib("mp_fog", settings.fog_game);
1520  return_bool_attrib("mp_shroud", settings.shroud_game);
1521  return_bool_attrib("mp_use_map_settings", settings.use_map_settings);
1522  return_bool_attrib("mp_random_start_time", settings.random_start_time);
1523  return_bool_attrib("observer", settings.allow_observers);
1524  return_bool_attrib("allow_observers", settings.allow_observers);
1525  return_bool_attrib("private_replay", settings.private_replay);
1526  return_bool_attrib("shuffle_sides", settings.shuffle_sides);
1527  return_string_attrib("random_faction_mode", random_faction_mode::get_string(settings.mode));
1528  return_cfgref_attrib("options", settings.options);
1529  if(strcmp(m, "savegame") == 0) {
1530  auto savegame = settings.saved_game;
1531  if(savegame == saved_game_mode::type::no) {
1532  lua_pushboolean(L, false);
1533  } else {
1535  }
1536  return 1;
1537  }
1538  if(strcmp(m, "side_players") == 0) {
1539  lua_push(L, settings.side_users);
1540  return 1;
1541  }
1542  if(strcmp(m, "addons") == 0) {
1543  for(const auto& [id, addon] : settings.addons) {
1544  lua_createtable(L, 0, 4);
1545  lua_push(L, id);
1546  lua_setfield(L, -2, "id");
1547  lua_push(L, addon.name);
1548  lua_setfield(L, -2, "name");
1549  lua_pushboolean(L, addon.required);
1550  lua_setfield(L, -2, "required");
1551  if(addon.min_version) {
1552  luaW_getglobal(L, "wesnoth", "version");
1553  lua_push(L, addon.min_version->str());
1554  lua_call(L, 1, 1);
1555  lua_setfield(L, -2, "min_version");
1556  }
1557  if(addon.version) {
1558  luaW_getglobal(L, "wesnoth", "version");
1559  lua_push(L, addon.version->str());
1560  lua_call(L, 1, 1);
1561  lua_setfield(L, -2, "version");
1562  }
1563  lua_createtable(L, addon.content.size(), 0);
1564  for(const auto& content : addon.content) {
1565  lua_createtable(L, 0, 3);
1566  lua_push(L, content.id);
1567  lua_setfield(L, -2, "id");
1568  lua_push(L, content.name);
1569  lua_setfield(L, -2, "name");
1570  lua_push(L, content.type);
1571  lua_setfield(L, -2, "type");
1572  lua_seti(L, -2, lua_rawlen(L, -2) + 1);
1573  }
1574  lua_setfield(L, -2, "content");
1575  }
1576  return 1;
1577  }
1578  // Deprecated things that were moved out of mp_settings and into game_classification
1579  const game_classification& game = static_cast<play_controller*>(p)->get_classification();
1580  return_string_attrib_deprecated("mp_era", "wesnoth.scenario.mp_settings", INDEFINITE, "1.17", "Use wesnoth.scenario.era.id instead", game.era_id);
1581  return_string_attrib_deprecated("active_mods", "wesnoth.scenario.mp_settings", INDEFINITE, "1.17", "Use wesnoth.scenario.modifications instead (returns an array of modification tables)", utils::join(game.active_mods));
1582  // Expose the raw config; this is a way to ensure any new stuff can be accessed even if someone forgot to add it here.
1583  return_cfgref_attrib("__cfg", settings.to_config());
1584  }
1585  return 0;
1586 }
1587 
1588 static int impl_mp_settings_len(lua_State* L)
1589 {
1590  void* p = lua_touserdata(L, lua_upvalueindex(1));
1591  const mp_game_settings& settings = static_cast<play_controller*>(p)->get_mp_settings();
1592  lua_pushinteger(L, settings.addons.size() + 1);
1593  return 1;
1594 }
1595 
1599  auto& tod_man() const { return ref.tod_man(); }
1600  auto& gamedata() const { return ref.gamedata(); }
1601  auto& pc() const { return ref.play_controller_; }
1602  auto& cls() const { return ref.play_controller_.get_classification(); }
1603  auto end_level_set() const { return &dispatch<&game_lua_kernel::impl_end_level_data_set>; }
1604 };
1605 #define SCENARIO_GETTER(name, type) LATTR_GETTER(name, type, scenario_tag, k)
1606 #define SCENARIO_SETTER(name, type) LATTR_SETTER(name, type, scenario_tag, k)
1607 #define SCENARIO_VALID(name) LATTR_VALID(name, scenario_tag, k)
1609 
1610 template<> struct lua_object_traits<scenario_tag> {
1611  inline static auto metatable = "scenario";
1612  inline static scenario_tag get(lua_State* L, int) {
1613  return lua_kernel_base::get_lua_kernel<game_lua_kernel>(L);
1614  }
1615 };
1616 
1617 namespace {
1618 SCENARIO_GETTER("turns", int) {
1619  return k.tod_man().number_of_turns();
1620 }
1621 
1622 SCENARIO_SETTER("turns", int) {
1623  k.tod_man().set_number_of_turns_by_wml(value);
1624 }
1625 
1626 SCENARIO_GETTER("next", std::string) {
1627  return k.gamedata().next_scenario();
1628 }
1629 
1630 SCENARIO_SETTER("next", std::string) {
1631  k.gamedata().set_next_scenario(value);
1632 }
1633 
1634 SCENARIO_GETTER("id", std::string) {
1635  return k.gamedata().get_id();
1636 }
1637 
1638 SCENARIO_GETTER("name", t_string) {
1639  return k.pc().get_scenario_name();
1640 }
1641 
1642 SCENARIO_GETTER("defeat_music", std::vector<std::string>) {
1643  return k.gamedata().get_defeat_music();
1644 }
1645 
1646 SCENARIO_SETTER("defeat_music", std::vector<std::string>) {
1647  k.gamedata().set_defeat_music(value);
1648 }
1649 
1650 SCENARIO_GETTER("victory_music", std::vector<std::string>) {
1651  return k.gamedata().get_victory_music();
1652 }
1653 
1654 SCENARIO_SETTER("victory_music", std::vector<std::string>) {
1655  k.gamedata().set_victory_music(value);
1656 }
1657 
1658 SCENARIO_GETTER("resources", std::vector<config>) {
1659  std::vector<config> resources;
1660  for(const std::string& rsrc : utils::split(k.pc().get_loaded_resources())) {
1661  resources.push_back(find_addon("resource", rsrc));
1662  }
1663  return resources;
1664 }
1665 
1666 SCENARIO_GETTER("type", std::string) {
1667  return campaign_type::get_string(k.cls().type);
1668 }
1669 
1670 SCENARIO_GETTER("difficulty", std::string) {
1671  return k.cls().difficulty;
1672 }
1673 
1674 SCENARIO_GETTER("show_credits", bool) {
1675  return k.cls().end_credits;
1676 }
1677 
1678 SCENARIO_SETTER("show_credits", bool) {
1679  k.cls().end_credits = value;
1680 }
1681 
1682 SCENARIO_GETTER("end_text", t_string) {
1683  return k.cls().end_text;
1684 }
1685 
1686 SCENARIO_SETTER("end_text", t_string) {
1687  k.cls().end_text = value;
1688 }
1689 
1690 SCENARIO_GETTER("end_text_duration", int) {
1691  return k.cls().end_text_duration.count();
1692 }
1693 
1694 SCENARIO_SETTER("end_text_duration", int) {
1695  k.cls().end_text_duration = std::chrono::milliseconds{value};
1696 }
1697 
1698 SCENARIO_VALID("campaign") {
1699  return !k.cls().campaign.empty();
1700 }
1701 
1702 SCENARIO_GETTER("campaign", utils::optional<config>) {
1703  if(k.cls().campaign.empty()) return utils::nullopt;
1704  return find_addon("campaign", k.cls().campaign);
1705 }
1706 
1707 SCENARIO_GETTER("modifications", std::vector<config>) {
1708  std::vector<config> mods;
1709  for(const std::string& mod : k.cls().active_mods) {
1710  mods.push_back(find_addon("modification", mod));
1711  }
1712  return mods;
1713 }
1714 
1715 SCENARIO_GETTER("end_level_data", lua_index_raw) {
1716  if (!k.pc().is_regular_game_end()) {
1717  lua_pushnil(L);
1718  return lua_index_raw(L);
1719  }
1720  auto data = k.pc().get_end_level_data();
1721  new(L) end_level_data(data);
1722  if(luaL_newmetatable(L, "end level data")) {
1723  static luaL_Reg const callbacks[] {
1724  { "__index", &impl_end_level_data_get},
1725  { "__newindex", k.end_level_set()},
1726  { "__gc", &impl_end_level_data_collect},
1727  { nullptr, nullptr }
1728  };
1729  luaL_setfuncs(L, callbacks, 0);
1730  }
1731  lua_setmetatable(L, -2);
1732  return lua_index_raw(L);
1733 }
1734 
1735 SCENARIO_SETTER("end_level_data", vconfig) {
1737 
1738  data.proceed_to_next_level = value["proceed_to_next_level"].to_bool(true);
1739  data.transient.carryover_report = value["carryover_report"].to_bool(true);
1740  data.prescenario_save = value["save"].to_bool(true);
1741  data.replay_save = value["replay_save"].to_bool(true);
1742  data.transient.linger_mode = value["linger_mode"].to_bool(true) && !k.ref.teams().empty();
1743  data.transient.reveal_map = value["reveal_map"].to_bool(k.pc().reveal_map_default());
1744  data.is_victory = value["result"] == level_result::victory;
1745  data.test_result = value["test_result"].str();
1746  k.pc().set_end_level_data(data);
1747 }
1748 
1749 SCENARIO_VALID("mp_settings") {
1750  return k.cls().is_multiplayer();
1751 }
1752 
1753 SCENARIO_GETTER("mp_settings", lua_index_raw) {
1754  if(!k.cls().is_multiplayer()) {
1755  lua_pushnil(L);
1756  return lua_index_raw(L);
1757  }
1758  lua_newuserdatauv(L, 0, 0);
1759  if(luaL_newmetatable(L, "mp settings")) {
1760  lua_pushlightuserdata(L, &k.pc());
1761  lua_pushcclosure(L, impl_mp_settings_get, 1);
1762  lua_setfield(L, -2, "__index");
1763  lua_pushlightuserdata(L, &k.pc());
1764  lua_pushcclosure(L, impl_mp_settings_len, 1);
1765  lua_setfield(L, -2, "__len");
1766  lua_pushstring(L, "mp settings");
1767  lua_setfield(L, -2, "__metatable");
1768  }
1769  lua_setmetatable(L, -2);
1770  return lua_index_raw(L);
1771 }
1772 
1773 SCENARIO_VALID("era") {
1774  return k.cls().is_multiplayer();
1775 }
1776 
1777 SCENARIO_GETTER("era", utils::optional<config>) {
1778  if(!k.cls().is_multiplayer()) return utils::nullopt;
1779  return find_addon("era", k.cls().era_id);
1780 }
1781 }
1782 
1783 /**
1784  * Gets some scenario data (__index metamethod).
1785  * - Arg 1: userdata (ignored).
1786  * - Arg 2: string containing the name of the property.
1787  * - Ret 1: something containing the attribute.
1788  */
1790 {
1791  DBG_LUA << "impl_scenario_get";
1792  return scenarioReg.get(L);
1793 }
1794 
1795 /**
1796  * Sets some scenario data (__newindex metamethod).
1797  * - Arg 1: userdata (ignored).
1798  * - Arg 2: string containing the name of the property.
1799  * - Arg 3: something containing the attribute.
1800  */
1802 {
1803  DBG_LUA << "impl_scenario_set";
1804  return scenarioReg.set(L);
1805 }
1806 
1807 /**
1808  * Get a list of scenario data (__dir metamethod).
1809  */
1811 {
1812  DBG_LUA << "impl_scenario_dir";
1813  return scenarioReg.dir(L);
1814 }
1815 
1816 /**
1817  converts synced_context::get_synced_state() to a string.
1818 */
1820 {
1821  //maybe return "initial" for game_data::INITIAL?
1822  if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::INITIAL)
1823  {
1824  return "preload";
1825  }
1827  {
1829  return "local_choice";
1831  return "synced";
1833  return "unsynced";
1834  default:
1835  throw game::game_error("Found corrupt synced_context::synced_state");
1836  }
1837 }
1838 
1839 struct current_tag {
1842  auto& pc() const { return ref.play_controller_; }
1843  auto ss() const { return ref.synced_state(); }
1844  auto& gd() const { return ref.gamedata(); }
1845  auto& ev() const { return ref.get_event_info(); }
1846  void push_schedule(lua_State* L) const { ref.luaW_push_schedule(L, -1); }
1847 };
1848 #define CURRENT_GETTER(name, type) LATTR_GETTER(name, type, current_tag, k)
1850 
1851 template<> struct lua_object_traits<current_tag> {
1852  inline static auto metatable = "current";
1853  inline static game_lua_kernel& get(lua_State* L, int) {
1854  return lua_kernel_base::get_lua_kernel<game_lua_kernel>(L);
1855  }
1856 };
1857 
1858 namespace {
1859 CURRENT_GETTER("side", int) {
1860  return k.pc().current_side();
1861 }
1862 
1863 CURRENT_GETTER("turn", int) {
1864  return k.pc().turn();
1865 }
1866 
1867 CURRENT_GETTER("synced_state", std::string) {
1868  return k.ss();
1869 }
1870 
1871 CURRENT_GETTER("user_can_invoke_commands", bool) {
1872  return !events::commands_disabled && k.gd().phase() == game_data::TURN_PLAYING;
1873 }
1874 
1875 CURRENT_GETTER("map", lua_index_raw) {
1876  (void)k;
1878  return lua_index_raw(L);
1879 }
1880 
1881 CURRENT_GETTER("schedule", lua_index_raw) {
1882  k.push_schedule(L);
1883  return lua_index_raw(L);
1884 }
1885 
1886 CURRENT_GETTER("user_is_replaying", bool) {
1887  return k.pc().is_replay();
1888 }
1889 
1890 CURRENT_GETTER("event_context", config) {
1891  const game_events::queued_event &ev = k.ev();
1892  config cfg;
1893  cfg["name"] = ev.name;
1894  cfg["id"] = ev.id;
1895  cfg.add_child("data", ev.data);
1896  if (auto weapon = ev.data.optional_child("first")) {
1897  cfg.add_child("weapon", *weapon);
1898  }
1899  if (auto weapon = ev.data.optional_child("second")) {
1900  cfg.add_child("second_weapon", *weapon);
1901  }
1902 
1903  const config::attribute_value di = ev.data["damage_inflicted"];
1904  if(!di.empty()) {
1905  cfg["damage_inflicted"] = di;
1906  }
1907 
1908  if (ev.loc1.valid()) {
1909  cfg["x1"] = ev.loc1.filter_loc().wml_x();
1910  cfg["y1"] = ev.loc1.filter_loc().wml_y();
1911  // The position of the unit involved in this event, currently the only case where this is different from x1/y1 are enter/exit_hex events
1912  cfg["unit_x"] = ev.loc1.wml_x();
1913  cfg["unit_y"] = ev.loc1.wml_y();
1914  }
1915  if (ev.loc2.valid()) {
1916  cfg["x2"] = ev.loc2.filter_loc().wml_x();
1917  cfg["y2"] = ev.loc2.filter_loc().wml_y();
1918  }
1919  return cfg;
1920 }
1921 }
1922 
1923 /**
1924  * Gets some data about current point of game (__index metamethod).
1925  * - Arg 1: userdata (ignored).
1926  * - Arg 2: string containing the name of the property.
1927  * - Ret 1: something containing the attribute.
1928  */
1930 {
1931  return currentReg.get(L);
1932 }
1933 
1934 /**
1935  * Gets a list of date about current point of game (__dir metamethod).
1936  */
1938 {
1939  return currentReg.dir(L);
1940 }
1941 
1942 /**
1943  * Displays a message in the chat window and in the logs.
1944  * - Arg 1: optional message header.
1945  * - Arg 2 (or 1): message.
1946  */
1948 {
1949  t_string m = luaW_checktstring(L, 1);
1950  t_string h = m;
1951  if (lua_isnone(L, 2)) {
1952  h = "Lua";
1953  } else {
1954  m = luaW_checktstring(L, 2);
1955  }
1956  lua_chat(h, m);
1957  LOG_LUA << "Script says: \"" << m << "\"";
1958  return 0;
1959 }
1960 
1962 {
1963  if(!game_display_) {
1964  return 0;
1965  }
1966  double factor = luaL_checknumber(L, 1);
1967  bool relative = luaW_toboolean(L, 2);
1968  if(relative) {
1969  factor *= game_display_->get_zoom_factor();
1970  }
1971  // Passing true explicitly to avoid casting to int.
1972  // Without doing one of the two, the call is ambiguous.
1974  lua_pushnumber(L, game_display_->get_zoom_factor());
1975  return 1;
1976 }
1977 
1978 /**
1979  * Removes all messages from the chat window.
1980  */
1982 {
1983  if (game_display_) {
1985  }
1986  return 0;
1987 }
1988 
1990 {
1991  //note that next_player_number = 1, next_player_number = nteams+1 both set the next team to be the first team
1992  //but the later will make the turn counter change aswell fire turn end events accoringly etc.
1993  if (!lua_isnoneornil(L, 1)) {
1994  int max = 2 * teams().size();
1995  int npn = luaL_checkinteger(L, 1);
1996  if (npn <= 0 || npn > max) {
1997  return luaL_argerror(L, 1, "side number out of range");
1998  }
2000  }
2002  return 0;
2003 }
2004 
2005 /**
2006  * Evaluates a boolean WML conditional.
2007  * - Arg 1: WML table.
2008  * - Ret 1: boolean.
2009  */
2010 static int intf_eval_conditional(lua_State *L)
2011 {
2012  vconfig cond = luaW_checkvconfig(L, 1);
2013  bool b = game_events::conditional_passed(cond);
2014  lua_pushboolean(L, b);
2015  return 1;
2016 }
2017 
2018 
2019 /**
2020  * Finds a path between two locations.
2021  * - Arg 1: source location. (Or Arg 1: unit.)
2022  * - Arg 2: destination.
2023  * - Arg 3: optional cost function or
2024  * table (optional fields: ignore_units, ignore_teleport, max_cost, viewing_side).
2025  * - Ret 1: array of pairs containing path steps.
2026  * - Ret 2: path cost.
2027  */
2029 {
2030  int arg = 1;
2031  map_location src, dst;
2032  const unit* u = nullptr;
2033  int viewing_side = 0;
2034 
2035  if (lua_isuserdata(L, arg))
2036  {
2037  u = &luaW_checkunit(L, arg);
2038  src = u->get_location();
2039  viewing_side = u->side();
2040  ++arg;
2041  }
2042  else
2043  {
2044  src = luaW_checklocation(L, arg);
2046  if (ui.valid()) {
2047  u = ui.get_shared_ptr().get();
2048  viewing_side = u->side();
2049  }
2050  ++arg;
2051  }
2052 
2053  dst = luaW_checklocation(L, arg);
2054 
2055  if (!board().map().on_board(src))
2056  return luaL_argerror(L, 1, "invalid location");
2057  if (!board().map().on_board(dst))
2058  return luaL_argerror(L, arg, "invalid location");
2059  ++arg;
2060 
2061  const gamemap &map = board().map();
2062  bool ignore_units = false, see_all = false, ignore_teleport = false;
2063  double stop_at = 10000;
2064  std::unique_ptr<pathfind::cost_calculator> calc;
2065 
2066  if (lua_istable(L, arg))
2067  {
2068  ignore_units = luaW_table_get_def<bool>(L, arg, "ignore_units", false);
2069  see_all = luaW_table_get_def<bool>(L, arg, "ignore_visibility", false);
2070  ignore_teleport = luaW_table_get_def<bool>(L, arg, "ignore_teleport", false);
2071 
2072  stop_at = luaW_table_get_def<double>(L, arg, "max_cost", stop_at);
2073 
2074 
2075  lua_pushstring(L, "viewing_side");
2076  lua_rawget(L, arg);
2077  if (!lua_isnil(L, -1)) {
2078  int i = luaL_checkinteger(L, -1);
2079  if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
2080  else {
2081  // If there's a unit, we have a valid side, so fall back to legacy behaviour.
2082  // If we don't have a unit, legacy behaviour would be a crash, so let's not.
2083  if(u) see_all = true;
2084  deprecated_message("wesnoth.paths.find_path with viewing_side=0 (or an invalid side)", DEP_LEVEL::FOR_REMOVAL, {1, 17, 0}, "To consider fogged and hidden units, use ignore_visibility=true instead.");
2085  }
2086  }
2087  lua_pop(L, 1);
2088 
2089  lua_pushstring(L, "calculate");
2090  lua_rawget(L, arg);
2091  if(lua_isfunction(L, -1)) {
2092  calc.reset(new lua_pathfind_cost_calculator(L, lua_gettop(L)));
2093  }
2094  // Don't pop, the lua_pathfind_cost_calculator requires it to stay on the stack.
2095  }
2096  else if (lua_isfunction(L, arg))
2097  {
2098  deprecated_message("wesnoth.paths.find_path with cost_function as last argument", DEP_LEVEL::FOR_REMOVAL, {1, 17, 0}, "Use calculate=cost_function inside the path options table instead.");
2099  calc.reset(new lua_pathfind_cost_calculator(L, arg));
2100  }
2101 
2102  pathfind::teleport_map teleport_locations;
2103 
2104  if(!ignore_teleport) {
2105  if(viewing_side == 0) {
2106  lua_warning(L, "wesnoth.paths.find_path: ignore_teleport=false requires a valid viewing_side; continuing with ignore_teleport=true", false);
2107  ignore_teleport = true;
2108  } else {
2109  teleport_locations = pathfind::get_teleport_locations(*u, board().get_team(viewing_side), see_all, ignore_units);
2110  }
2111  }
2112 
2113  if (!calc) {
2114  if(!u) {
2115  return luaL_argerror(L, 1, "unit not found OR custom cost function not provided");
2116  }
2117 
2118  calc.reset(new pathfind::shortest_path_calculator(*u, board().get_team(viewing_side),
2119  teams(), map, ignore_units, false, see_all));
2120  }
2121 
2122  pathfind::plain_route res = pathfind::a_star_search(src, dst, stop_at, *calc, map.w(), map.h(),
2123  &teleport_locations);
2124 
2125  int nb = res.steps.size();
2126  lua_createtable(L, nb, 0);
2127  for (int i = 0; i < nb; ++i)
2128  {
2129  luaW_pushlocation(L, res.steps[i]);
2130  lua_rawseti(L, -2, i + 1);
2131  }
2132  lua_pushinteger(L, res.move_cost);
2133 
2134  return 2;
2135 }
2136 
2137 /**
2138  * Finds all the locations reachable by a unit.
2139  * - Arg 1: source location OR unit.
2140  * - Arg 2: optional table (optional fields: ignore_units, ignore_teleport, additional_turns, viewing_side).
2141  * - Ret 1: array of triples (coordinates + remaining movement).
2142  */
2144 {
2145  int arg = 1;
2146  const unit* u = nullptr;
2147 
2148  if (lua_isuserdata(L, arg))
2149  {
2150  u = &luaW_checkunit(L, arg);
2151  ++arg;
2152  }
2153  else
2154  {
2157  if (!ui.valid())
2158  return luaL_argerror(L, 1, "unit not found");
2159  u = ui.get_shared_ptr().get();
2160  ++arg;
2161  }
2162 
2163  int viewing_side = u->side();
2164  bool ignore_units = false, see_all = false, ignore_teleport = false;
2165  int additional_turns = 0;
2166 
2167  if (lua_istable(L, arg))
2168  {
2169  ignore_units = luaW_table_get_def<bool>(L, arg, "ignore_units", false);
2170  see_all = luaW_table_get_def<bool>(L, arg, "ignore_visibility", false);
2171  ignore_teleport = luaW_table_get_def<bool>(L, arg, "ignore_teleport", false);
2172  additional_turns = luaW_table_get_def<int>(L, arg, "max_cost", additional_turns);
2173 
2174  lua_pushstring(L, "viewing_side");
2175  lua_rawget(L, arg);
2176  if (!lua_isnil(L, -1)) {
2177  int i = luaL_checkinteger(L, -1);
2178  if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
2179  else {
2180  // If there's a unit, we have a valid side, so fall back to legacy behaviour.
2181  // If we don't have a unit, legacy behaviour would be a crash, so let's not.
2182  if(u) see_all = true;
2183  deprecated_message("wesnoth.find_reach with viewing_side=0 (or an invalid side)", DEP_LEVEL::FOR_REMOVAL, {1, 17, 0}, "To consider fogged and hidden units, use ignore_visibility=true instead.");
2184  }
2185  }
2186  lua_pop(L, 1);
2187  }
2188 
2189  const team& viewing_team = board().get_team(viewing_side);
2190 
2191  pathfind::paths res(*u, ignore_units, !ignore_teleport,
2192  viewing_team, additional_turns, see_all, ignore_units);
2193 
2194  int nb = res.destinations.size();
2195  lua_createtable(L, nb, 0);
2196  for (int i = 0; i < nb; ++i)
2197  {
2199  luaW_push_namedtuple(L, {"x", "y", "moves_left"});
2200  lua_pushinteger(L, s.curr.wml_x());
2201  lua_rawseti(L, -2, 1);
2202  lua_pushinteger(L, s.curr.wml_y());
2203  lua_rawseti(L, -2, 2);
2204  lua_pushinteger(L, s.move_left);
2205  lua_rawseti(L, -2, 3);
2206  lua_rawseti(L, -2, i + 1);
2207  }
2208 
2209  return 1;
2210 }
2211 
2212 /**
2213  * Finds all the locations for which a given unit would remove the fog (if there was fog on the map).
2214  *
2215  * - Arg 1: source location OR unit.
2216  * - Ret 1: array of triples (coordinates + remaining vision points).
2217  */
2219 {
2220  int arg = 1;
2221  const unit* u = nullptr;
2222 
2223  if (lua_isuserdata(L, arg))
2224  {
2225  u = &luaW_checkunit(L, arg);
2226  ++arg;
2227  }
2228  else
2229  {
2232  if (!ui.valid())
2233  return luaL_argerror(L, 1, "unit not found");
2234  u = ui.get_shared_ptr().get();
2235  ++arg;
2236  }
2237 
2238  if(!u)
2239  {
2240  return luaL_error(L, "wesnoth.find_vision_range: requires a valid unit");
2241  }
2242 
2243  std::map<map_location, int> jamming_map;
2244  actions::create_jamming_map(jamming_map, resources::gameboard->get_team(u->side()));
2245  pathfind::vision_path res(*u, u->get_location(), jamming_map);
2246 
2247  lua_createtable(L, res.destinations.size() + res.edges.size(), 0);
2248  for(const auto& d : res.destinations) {
2249  luaW_push_namedtuple(L, {"x", "y", "vision_left"});
2250  lua_pushinteger(L, d.curr.wml_x());
2251  lua_rawseti(L, -2, 1);
2252  lua_pushinteger(L, d.curr.wml_y());
2253  lua_rawseti(L, -2, 2);
2254  lua_pushinteger(L, d.move_left);
2255  lua_rawseti(L, -2, 3);
2256  lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
2257  }
2258  for(const auto& e : res.edges) {
2259  luaW_push_namedtuple(L, {"x", "y", "vision_left"});
2260  lua_pushinteger(L, e.wml_x());
2261  lua_rawseti(L, -2, 1);
2262  lua_pushinteger(L, e.wml_y());
2263  lua_rawseti(L, -2, 2);
2264  lua_pushinteger(L, -1);
2265  lua_rawseti(L, -2, 3);
2266  lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
2267  }
2268  return 1;
2269 }
2270 
2271 template<typename T> // This is only a template so I can avoid typing out the long typename. >_>
2272 static int load_fake_units(lua_State* L, int arg, T& fake_units)
2273 {
2274  for (int i = 1, i_end = lua_rawlen(L, arg); i <= i_end; ++i)
2275  {
2276  map_location src;
2277  lua_rawgeti(L, arg, i);
2278  int entry = lua_gettop(L);
2279  if (!lua_istable(L, entry)) {
2280  goto error;
2281  }
2282 
2283  if (!luaW_tolocation(L, entry, src)) {
2284  goto error;
2285  }
2286 
2287  lua_rawgeti(L, entry, 3);
2288  if (!lua_isnumber(L, -1)) {
2289  lua_getfield(L, entry, "side");
2290  if (!lua_isnumber(L, -1)) {
2291  goto error;
2292  }
2293  }
2294  int side = lua_tointeger(L, -1);
2295 
2296  lua_rawgeti(L, entry, 4);
2297  if (!lua_isstring(L, -1)) {
2298  lua_getfield(L, entry, "type");
2299  if (!lua_isstring(L, -1)) {
2300  goto error;
2301  }
2302  }
2303  std::string unit_type = lua_tostring(L, -1);
2304 
2305  fake_units.emplace_back(src, side, unit_type);
2306 
2307  lua_settop(L, entry - 1);
2308  }
2309  return 0;
2310 error:
2311  return luaL_argerror(L, arg, "unit type table malformed - each entry should be either array of 4 elements or table with keys x, y, side, type");
2312 }
2313 
2314 /**
2315  * Is called with one or more units and builds a cost map.
2316  * - Arg 1: source location. (Or Arg 1: unit. Or Arg 1: table containing a filter)
2317  * - Arg 2: optional array of tables with 4 elements (coordinates + side + unit type string)
2318  * - Arg 3: optional table (optional fields: ignore_units, ignore_teleport, viewing_side, debug).
2319  * - Arg 4: optional table: standard location filter.
2320  * - Ret 1: array of triples (coordinates + array of tuples(summed cost + reach counter)).
2321  */
2323 {
2324  int arg = 1;
2325  unit* unit = luaW_tounit(L, arg, true);
2327  luaW_tovconfig(L, arg, filter);
2328 
2329  std::vector<const ::unit*> real_units;
2330  typedef std::vector<std::tuple<map_location, int, std::string>> unit_type_vector;
2331  unit_type_vector fake_units;
2332 
2333 
2334  if (unit) // 1. arg - unit
2335  {
2336  real_units.push_back(unit);
2337  }
2338  else if (!filter.null()) // 1. arg - filter
2339  {
2340  for(const ::unit* match : unit_filter(filter).all_matches_on_map()) {
2341  if(match->get_location().valid()) {
2342  real_units.push_back(match);
2343  }
2344  }
2345  }
2346  else // 1. arg - coordinates
2347  {
2350  if (ui.valid())
2351  {
2352  real_units.push_back(&(*ui));
2353  }
2354  }
2355  ++arg;
2356 
2357  if (lua_istable(L, arg)) // 2. arg - optional types
2358  {
2359  load_fake_units(L, arg, fake_units);
2360  ++arg;
2361  }
2362 
2363  if(real_units.empty() && fake_units.empty())
2364  {
2365  return luaL_argerror(L, 1, "unit(s) not found");
2366  }
2367 
2368  int viewing_side = 0;
2369  bool ignore_units = true, see_all = true, ignore_teleport = false, debug = false, use_max_moves = false;
2370 
2371  if (lua_istable(L, arg)) // 4. arg - options
2372  {
2373  lua_pushstring(L, "ignore_units");
2374  lua_rawget(L, arg);
2375  if (!lua_isnil(L, -1))
2376  {
2377  ignore_units = luaW_toboolean(L, -1);
2378  }
2379  lua_pop(L, 1);
2380 
2381  lua_pushstring(L, "ignore_teleport");
2382  lua_rawget(L, arg);
2383  if (!lua_isnil(L, -1))
2384  {
2385  ignore_teleport = luaW_toboolean(L, -1);
2386  }
2387  lua_pop(L, 1);
2388 
2389  lua_pushstring(L, "viewing_side");
2390  lua_rawget(L, arg);
2391  if (!lua_isnil(L, -1))
2392  {
2393  int i = luaL_checkinteger(L, -1);
2394  if (i >= 1 && i <= static_cast<int>(teams().size()))
2395  {
2396  viewing_side = i;
2397  see_all = false;
2398  }
2399  }
2400 
2401  lua_pushstring(L, "debug");
2402  lua_rawget(L, arg);
2403  if (!lua_isnil(L, -1))
2404  {
2405  debug = luaW_toboolean(L, -1);
2406  }
2407  lua_pop(L, 1);
2408 
2409  lua_pushstring(L, "use_max_moves");
2410  lua_rawget(L, arg);
2411  if (!lua_isnil(L, -1))
2412  {
2413  use_max_moves = luaW_toboolean(L, -1);
2414  }
2415  lua_pop(L, 1);
2416  ++arg;
2417  }
2418 
2419  // 5. arg - location filter
2421  std::set<map_location> location_set;
2422  luaW_tovconfig(L, arg, filter);
2423  if (filter.null())
2424  {
2425  filter = vconfig(config(), true);
2426  }
2427  filter_context & fc = game_state_;
2428  const terrain_filter t_filter(filter, &fc, false);
2429  t_filter.get_locations(location_set, true);
2430  ++arg;
2431 
2432  // build cost_map
2433  const team& viewing_team = viewing_side
2434  ? board().get_team(viewing_side)
2435  : board().teams()[0];
2436 
2437  pathfind::full_cost_map cost_map(
2438  ignore_units, !ignore_teleport, viewing_team, see_all, ignore_units);
2439 
2440  for (const ::unit* const u : real_units)
2441  {
2442  cost_map.add_unit(*u, use_max_moves);
2443  }
2444  for (const unit_type_vector::value_type& fu : fake_units)
2445  {
2446  const unit_type* ut = unit_types.find(std::get<2>(fu));
2447  cost_map.add_unit(std::get<0>(fu), ut, std::get<1>(fu));
2448  }
2449 
2450  if (debug)
2451  {
2452  if (game_display_) {
2454  for (const map_location& loc : location_set)
2455  {
2456  std::stringstream s;
2457  s << cost_map.get_pair_at(loc).first;
2458  s << " / ";
2459  s << cost_map.get_pair_at(loc).second;
2460  game_display_->labels().set_label(loc, s.str());
2461  }
2462  }
2463  }
2464 
2465  // create return value
2466  lua_createtable(L, location_set.size(), 0);
2467  int counter = 1;
2468  for (const map_location& loc : location_set)
2469  {
2470  luaW_push_namedtuple(L, {"x", "y", "cost", "reach"});
2471 
2472  lua_pushinteger(L, loc.wml_x());
2473  lua_rawseti(L, -2, 1);
2474 
2475  lua_pushinteger(L, loc.wml_y());
2476  lua_rawseti(L, -2, 2);
2477 
2478  lua_pushinteger(L, cost_map.get_pair_at(loc).first);
2479  lua_rawseti(L, -2, 3);
2480 
2481  lua_pushinteger(L, cost_map.get_pair_at(loc).second);
2482  lua_rawseti(L, -2, 4);
2483 
2484  lua_rawseti(L, -2, counter);
2485  ++counter;
2486  }
2487  return 1;
2488 }
2489 
2490 const char* labelKey = "floating label";
2491 
2492 static int* luaW_check_floating_label(lua_State* L, int idx)
2493 {
2494  return reinterpret_cast<int*>(luaL_checkudata(L, idx, labelKey));
2495 }
2496 
2497 static int impl_floating_label_getmethod(lua_State* L)
2498 {
2499  const char* m = luaL_checkstring(L, 2);
2500  return_bool_attrib("valid", *luaW_check_floating_label(L, 1) != 0);
2501  return luaW_getmetafield(L, 1, m);
2502 }
2503 
2505 {
2506  int* handle = luaW_check_floating_label(L, 1);
2507  std::chrono::milliseconds fade{luaL_optinteger(L, 2, -1)};
2508  if(*handle != 0) {
2509  // Passing -1 as the second argument means it uses the fade time that was set when the label was created
2511  }
2512  *handle = 0;
2513  return 0;
2514 }
2515 
2517 {
2518  int* handle = luaW_check_floating_label(L, 1);
2519  if(*handle != 0) {
2520  font::move_floating_label(*handle, luaL_checknumber(L, 2), luaL_checknumber(L, 3));
2521  }
2522  return 0;
2523 }
2524 
2525 /**
2526  * Arg 1: text - string
2527  * Arg 2: options table
2528  * - size: font size
2529  * - max_width: max width for word wrapping
2530  * - color: font color
2531  * - bgcolor: background color
2532  * - bgalpha: background opacity
2533  * - duration: display duration (integer or the string "unlimited")
2534  * - fade_time: duration of fade-out
2535  * - location: screen offset
2536  * - valign: vertical alignment and anchoring - "top", "center", or "bottom"
2537  * - halign: horizontal alignment and anchoring - "left", "center", or "right"
2538  * Returns: label handle
2539  */
2540 int game_lua_kernel::intf_set_floating_label(lua_State* L, bool spawn)
2541 {
2542  t_string text = luaW_checktstring(L, 1);
2543  int size = font::SIZE_SMALL;
2544  int width = 0;
2545  double width_ratio = 0;
2546  color_t color = font::LABEL_COLOR, bgcolor{0, 0, 0, 0};
2547  int lifetime = 2'000, fadeout = 100;
2548  font::ALIGN alignment = font::ALIGN::CENTER_ALIGN, vertical_alignment = font::ALIGN::CENTER_ALIGN;
2549  // This is actually a relative screen location in pixels, but map_location already supports
2550  // everything needed to read in a pair of coordinates.
2551  // Depending on the chosen alignment, it may be relative to centre, an edge centre, or a corner.
2552  map_location loc{0, 0, wml_loc()};
2553  if(lua_istable(L, 2)) {
2554  if(luaW_tableget(L, 2, "size")) {
2555  size = luaL_checkinteger(L, -1);
2556  }
2557  if(luaW_tableget(L, 2, "max_width")) {
2558  int found_number;
2559  width = lua_tointegerx(L, -1, &found_number);
2560  if(!found_number) {
2561  auto value = luaW_tostring(L, -1);
2562  try {
2563  if(!value.empty() && value.back() == '%') {
2564  value.remove_suffix(1);
2565  width_ratio = std::stoi(std::string(value)) / 100.0;
2566  } else throw std::invalid_argument(value.data());
2567  } catch(std::invalid_argument&) {
2568  return luaL_argerror(L, -1, "max_width should be integer or percentage");
2569  }
2570 
2571  }
2572  }
2573  if(luaW_tableget(L, 2, "color")) {
2574  if(lua_isstring(L, -1)) {
2575  color = color_t::from_hex_string(lua_tostring(L, -1));
2576  } else {
2577  auto vec = lua_check<std::vector<int>>(L, -1);
2578  if(vec.size() != 3) {
2579  int idx = lua_absindex(L, -1);
2580  if(luaW_tableget(L, idx, "r") && luaW_tableget(L, idx, "g") && luaW_tableget(L, idx, "b")) {
2581  color.r = luaL_checkinteger(L, -3);
2582  color.g = luaL_checkinteger(L, -2);
2583  color.b = luaL_checkinteger(L, -1);
2584  } else {
2585  return luaL_error(L, "floating label text color should be a hex string, an array of 3 integers, or a table with r,g,b keys");
2586  }
2587  } else {
2588  color.r = vec[0];
2589  color.g = vec[1];
2590  color.b = vec[2];
2591  }
2592  }
2593  }
2594  if(luaW_tableget(L, 2, "bgcolor")) {
2595  if(lua_isstring(L, -1)) {
2596  bgcolor = color_t::from_hex_string(lua_tostring(L, -1));
2597  } else {
2598  auto vec = lua_check<std::vector<int>>(L, -1);
2599  if(vec.size() != 3) {
2600  int idx = lua_absindex(L, -1);
2601  if(luaW_tableget(L, idx, "r") && luaW_tableget(L, idx, "g") && luaW_tableget(L, idx, "b")) {
2602  bgcolor.r = luaL_checkinteger(L, -3);
2603  bgcolor.g = luaL_checkinteger(L, -2);
2604  bgcolor.b = luaL_checkinteger(L, -1);
2605  } else {
2606  return luaL_error(L, "floating label background color should be a hex string, an array of 3 integers, or a table with r,g,b keys");
2607  }
2608  } else {
2609  bgcolor.r = vec[0];
2610  bgcolor.g = vec[1];
2611  bgcolor.b = vec[2];
2612  }
2613  bgcolor.a = ALPHA_OPAQUE;
2614  }
2615  if(luaW_tableget(L, 2, "bgalpha")) {
2616  bgcolor.a = luaL_checkinteger(L, -1);
2617  }
2618  }
2619  if(luaW_tableget(L, 2, "duration")) {
2620  int found_number;
2621  lifetime = lua_tointegerx(L, -1, &found_number);
2622  if(!found_number) {
2623  auto value = luaW_tostring(L, -1);
2624  if(value == "unlimited") {
2625  lifetime = -1;
2626  } else {
2627  return luaL_argerror(L, -1, "duration should be integer or 'unlimited'");
2628  }
2629  }
2630  }
2631  if(luaW_tableget(L, 2, "fade_time")) {
2632  fadeout = lua_tointeger(L, -1);
2633  }
2634  if(luaW_tableget(L, 2, "location")) {
2635  loc = luaW_checklocation(L, -1);
2636  }
2637  if(luaW_tableget(L, 2, "halign")) {
2638  static const char* options[] = {"left", "center", "right"};
2639  alignment = font::ALIGN(luaL_checkoption(L, -1, nullptr, options));
2640  }
2641  if(luaW_tableget(L, 2, "valign")) {
2642  static const char* options[] = {"top", "center", "bottom"};
2643  vertical_alignment = font::ALIGN(luaL_checkoption(L, -1, nullptr, options));
2644  }
2645  }
2646 
2647  int* handle = nullptr;
2648  if(spawn) {
2649  // Creating a new label, allocate a new handle
2650  handle = new(L)int();
2651  } else {
2652  // First argument is the label handle
2654  }
2655  int handle_idx = lua_gettop(L);
2656 
2657  if(*handle != 0) {
2659  }
2660 
2661  const SDL_Rect rect = game_display_->map_outside_area();
2662  if(width_ratio > 0) {
2663  width = static_cast<int>(std::round(rect.w * width_ratio));
2664  }
2665  int x = 0, y = 0;
2666  switch(alignment) {
2668  x = rect.x + loc.wml_x();
2669  break;
2671  x = rect.x + rect.w / 2 + loc.wml_x();
2672  break;
2674  x = rect.x + rect.w - loc.wml_x();
2675  break;
2676  }
2677  switch(vertical_alignment) {
2678  case font::ALIGN::LEFT_ALIGN: // top
2679  y = rect.y + loc.wml_y();
2680  break;
2682  y = rect.y + rect.h / 2 + loc.wml_y();
2683  break;
2684  case font::ALIGN::RIGHT_ALIGN: // bottom
2685  // The size * 1.5 adjustment avoids the text being cut off if placed at y = 0
2686  // This is necessary because the text is positioned by the top edge but we want it to
2687  // seem like it's positioned by the bottom edge.
2688  // This wouldn't work for multiline text, but we don't expect that to be common in this API anyway.
2689  y = rect.y + rect.h - loc.wml_y() - static_cast<int>(size * 1.5);
2690  break;
2691  }
2692 
2693  using std::chrono::milliseconds;
2694  font::floating_label flabel(text);
2695  flabel.set_font_size(size);
2696  flabel.set_color(color);
2697  flabel.set_bg_color(bgcolor);
2698  flabel.set_alignment(alignment);
2699  flabel.set_position(x, y);
2700  flabel.set_lifetime(milliseconds{lifetime}, milliseconds{fadeout});
2701  flabel.set_clip_rect(rect);
2702 
2703  // Adjust fallback width (map area) to avoid text escaping when location != 0
2704  if(width > 0) {
2705  flabel.set_width(std::min(width, rect.w - loc.wml_x()));
2706  } else {
2707  flabel.set_width(rect.w - loc.wml_x());
2708  }
2709 
2710  *handle = font::add_floating_label(flabel);
2711  lua_settop(L, handle_idx);
2712  if(luaL_newmetatable(L, labelKey)) {
2713  // Initialize the metatable
2714  static const luaL_Reg methods[] = {
2715  {"remove", &dispatch<&game_lua_kernel::intf_remove_floating_label>},
2716  {"move", &dispatch<&game_lua_kernel::intf_move_floating_label>},
2717  {"replace", &dispatch2<&game_lua_kernel::intf_set_floating_label, false>},
2718  {"__index", &impl_floating_label_getmethod},
2719  { nullptr, nullptr }
2720  };
2721  luaL_setfuncs(L, methods, 0);
2722  luaW_table_set(L, -1, "__metatable", std::string(labelKey));
2723  }
2724  lua_setmetatable(L, handle_idx);
2725  lua_settop(L, handle_idx);
2726  return 1;
2727 }
2728 
2730 {
2731  if(game_display_) {
2733  }
2734 
2735  resources::whiteboard->on_kill_unit();
2736 }
2737 
2738 /**
2739  * Places a unit on the map.
2740  * - Arg 1: (optional) location.
2741  * - Arg 2: Unit (WML table or proxy), or nothing/nil to delete.
2742  * OR
2743  * - Arg 1: Unit (WML table or proxy)
2744  * - Arg 2: (optional) location
2745  * - Arg 3: (optional) boolean
2746  */
2748 {
2749  if(map_locked_) {
2750  return luaL_error(L, "Attempted to move a unit while the map is locked");
2751  }
2752 
2753  map_location loc;
2754  if (luaW_tolocation(L, 2, loc)) {
2755  if (!map().on_board(loc)) {
2756  return luaL_argerror(L, 2, "invalid location");
2757  }
2758  }
2759 
2760  if((luaW_isunit(L, 1))) {
2761  lua_unit& u = *luaW_checkunit_ref(L, 1);
2762  if(u.on_map() && u->get_location() == loc) {
2763  return 0;
2764  }
2765  if (!loc.valid()) {
2766  loc = u->get_location();
2767  if (!map().on_board(loc))
2768  return luaL_argerror(L, 1, "invalid location");
2769  }
2770 
2772  u.put_map(loc);
2773  u.get_shared()->anim_comp().set_standing();
2774  } else if(!lua_isnoneornil(L, 1)) {
2775  const vconfig* vcfg = nullptr;
2776  config cfg = luaW_checkconfig(L, 1, vcfg);
2777  if (!map().on_board(loc)) {
2778  loc.set_wml_x(cfg["x"].to_int());
2779  loc.set_wml_y(cfg["y"].to_int());
2780  if (!map().on_board(loc))
2781  return luaL_argerror(L, 2, "invalid location");
2782  }
2783 
2784  unit_ptr u = unit::create(cfg, true, vcfg);
2785  units().erase(loc);
2787  u->set_location(loc);
2788  units().insert(u);
2789  }
2790 
2791  // Fire event if using the deprecated version or if the final argument is not false
2792  // If the final boolean argument is omitted, the actual final argument (the unit or location) will always yield true.
2793  if(luaW_toboolean(L, -1)) {
2794  play_controller_.pump().fire("unit_placed", loc);
2795  }
2796  return 0;
2797 }
2798 
2799 /**
2800  * Erases a unit from the map
2801  * - Arg 1: Unit to erase OR Location to erase unit
2802  */
2804 {
2805  if(map_locked_) {
2806  return luaL_error(L, "Attempted to remove a unit while the map is locked");
2807  }
2808  map_location loc;
2809 
2810  if(luaW_isunit(L, 1)) {
2811  lua_unit& u = *luaW_checkunit_ref(L, 1);
2812  if (u.on_map()) {
2813  loc = u->get_location();
2814  if (!map().on_board(loc)) {
2815  return luaL_argerror(L, 1, "invalid location");
2816  }
2817  } else if (int side = u.on_recall_list()) {
2818  team &t = board().get_team(side);
2819  // Should it use underlying ID instead?
2820  t.recall_list().erase_if_matches_id(u->id());
2821  } else {
2822  return luaL_argerror(L, 1, "can't erase private units");
2823  }
2824  } else if (luaW_tolocation(L, 1, loc)) {
2825  if (!map().on_board(loc)) {
2826  return luaL_argerror(L, 1, "invalid location");
2827  }
2828  } else {
2829  return luaL_argerror(L, 1, "expected unit or location");
2830  }
2831 
2832  units().erase(loc);
2833  resources::whiteboard->on_kill_unit();
2834  return 0;
2835 }
2836 
2837 /**
2838  * Puts a unit on a recall list.
2839  * - Arg 1: WML table or unit.
2840  * - Arg 2: (optional) side.
2841  */
2843 {
2844  if(map_locked_) {
2845  return luaL_error(L, "Attempted to move a unit while the map is locked");
2846  }
2847  lua_unit *lu = nullptr;
2848  unit_ptr u = unit_ptr();
2849  int side = lua_tointeger(L, 2);
2850  if (static_cast<unsigned>(side) > teams().size()) side = 0;
2851 
2852  if(luaW_isunit(L, 1)) {
2853  lu = luaW_checkunit_ref(L, 1);
2854  u = lu->get_shared();
2855  if(lu->on_recall_list() && lu->on_recall_list() == side) {
2856  return luaL_argerror(L, 1, "unit already on recall list");
2857  }
2858  } else {
2859  const vconfig* vcfg = nullptr;
2860  config cfg = luaW_checkconfig(L, 1, vcfg);
2861  u = unit::create(cfg, true, vcfg);
2862  }
2863 
2864  if (!side) {
2865  side = u->side();
2866  } else {
2867  u->set_side(side);
2868  }
2869  team &t = board().get_team(side);
2870  // Avoid duplicates in the recall list.
2871  std::size_t uid = u->underlying_id();
2872  t.recall_list().erase_by_underlying_id(uid);
2873  t.recall_list().add(u);
2874  if (lu) {
2875  if (lu->on_map()) {
2876  units().erase(u->get_location());
2877  resources::whiteboard->on_kill_unit();
2878  u->anim_comp().clear_haloes();
2879  }
2880  lu->lua_unit::~lua_unit();
2881  new(lu) lua_unit(side, uid);
2882  }
2883 
2884  return 0;
2885 }
2886 
2887 /**
2888  * Extracts a unit from the map or a recall list and gives it to Lua.
2889  * - Arg 1: unit userdata.
2890  */
2892 {
2893  if(map_locked_) {
2894  return luaL_error(L, "Attempted to remove a unit while the map is locked");
2895  }
2896  lua_unit* lu = luaW_checkunit_ref(L, 1);
2897  unit_ptr u = lu->get_shared();
2898 
2899  if (lu->on_map()) {
2900  u = units().extract(u->get_location());
2901  assert(u);
2902  u->anim_comp().clear_haloes();
2903  } else if (int side = lu->on_recall_list()) {
2904  team &t = board().get_team(side);
2905  unit_ptr v = u->clone();
2906  t.recall_list().erase_if_matches_id(u->id());
2907  u = v;
2908  } else {
2909  return 0;
2910  }
2911 
2912  lu->lua_unit::~lua_unit();
2913  new(lu) lua_unit(u);
2914  return 0;
2915 }
2916 
2917 /**
2918  * Finds a vacant tile.
2919  * - Arg 1: location.
2920  * - Arg 2: optional unit for checking movement type.
2921  * - Rets 1,2: location.
2922  */
2924 {
2926 
2927  unit_ptr u;
2928  if (!lua_isnoneornil(L, 2)) {
2929  if(luaW_isunit(L, 2)) {
2930  u = luaW_checkunit_ptr(L, 2, false);
2931  } else {
2932  const vconfig* vcfg = nullptr;
2933  config cfg = luaW_checkconfig(L, 2, vcfg);
2934  u = unit::create(cfg, false, vcfg);
2935  }
2936  }
2937 
2939 
2940  if (!res.valid()) return 0;
2941  lua_pushinteger(L, res.wml_x());
2942  lua_pushinteger(L, res.wml_y());
2943  return 2;
2944 }
2945 
2946 /**
2947  * Floats some text on the map.
2948  * - Arg 1: location.
2949  * - Arg 2: string.
2950  * - Arg 3: color.
2951  */
2953 {
2955  color_t color = font::LABEL_COLOR;
2956 
2957  t_string text = luaW_checktstring(L, 2);
2958  if (!lua_isnoneornil(L, 3)) {
2959  color = color_t::from_rgb_string(luaL_checkstring(L, 3));
2960  }
2961 
2962  if (game_display_) {
2963  game_display_->float_label(loc, text, color);
2964  }
2965  return 0;
2966 }
2967 
2968 /**
2969  * Creates a unit from its WML description.
2970  * - Arg 1: WML table.
2971  * - Ret 1: unit userdata.
2972  */
2973 static int intf_create_unit(lua_State *L)
2974 {
2975  const vconfig* vcfg = nullptr;
2976  config cfg = luaW_checkconfig(L, 1, vcfg);
2977  unit_ptr u = unit::create(cfg, true, vcfg);
2978  luaW_pushunit(L, u);
2979  return 1;
2980 }
2981 
2982 /**
2983  * Copies a unit.
2984  * - Arg 1: unit userdata.
2985  * - Ret 1: unit userdata.
2986  */
2987 static int intf_copy_unit(lua_State *L)
2988 {
2989  unit& u = luaW_checkunit(L, 1);
2990  luaW_pushunit(L, u.clone());
2991  return 1;
2992 }
2993 
2994 /**
2995  * Returns unit resistance against a given attack type.
2996  * - Arg 1: unit userdata.
2997  * - Arg 2: string containing the attack type.
2998  * - Arg 3: boolean indicating if attacker.
2999  * - Arg 4: optional location.
3000  * - Ret 1: integer.
3001  */
3002 static int intf_unit_resistance(lua_State *L)
3003 {
3004  const unit& u = luaW_checkunit(L, 1);
3005  char const *m = luaL_checkstring(L, 2);
3006  bool a = false;
3008 
3009  if(lua_isboolean(L, 3)) {
3010  a = luaW_toboolean(L, 3);
3011  if(!lua_isnoneornil(L, 4)) {
3012  loc = luaW_checklocation(L, 4);
3013  }
3014  } else if(!lua_isnoneornil(L, 3)) {
3015  loc = luaW_checklocation(L, 3);
3016  }
3017 
3018  lua_pushinteger(L, 100 - u.resistance_against(m, a, loc));
3019  return 1;
3020 }
3021 
3022 /**
3023  * Returns unit movement cost on a given terrain.
3024  * - Arg 1: unit userdata.
3025  * - Arg 2: string containing the terrain type.
3026  * - Ret 1: integer.
3027  */
3028 static int intf_unit_movement_cost(lua_State *L)
3029 {
3030  const unit& u = luaW_checkunit(L, 1);
3032  map_location loc;
3033  if(luaW_tolocation(L, 2, loc)) {
3034  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
3035  } else if(lua_isstring(L, 2)) {
3036  char const *m = luaL_checkstring(L, 2);
3038  } else return luaW_type_error(L, 2, "location or terrain string");
3039  lua_pushinteger(L, u.movement_cost(t));
3040  return 1;
3041 }
3042 
3043 /**
3044  * Returns unit vision cost on a given terrain.
3045  * - Arg 1: unit userdata.
3046  * - Arg 2: string containing the terrain type.
3047  * - Ret 1: integer.
3048  */
3049 static int intf_unit_vision_cost(lua_State *L)
3050 {
3051  const unit& u = luaW_checkunit(L, 1);
3053  map_location loc;
3054  if(luaW_tolocation(L, 2, loc)) {
3055  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
3056  } else if(lua_isstring(L, 2)) {
3057  char const *m = luaL_checkstring(L, 2);
3059  } else return luaW_type_error(L, 2, "location or terrain string");
3060  lua_pushinteger(L, u.vision_cost(t));
3061  return 1;
3062 }
3063 
3064 /**
3065  * Returns unit jamming cost on a given terrain.
3066  * - Arg 1: unit userdata.
3067  * - Arg 2: string containing the terrain type.
3068  * - Ret 1: integer.
3069  */
3070 static int intf_unit_jamming_cost(lua_State *L)
3071 {
3072  const unit& u = luaW_checkunit(L, 1);
3074  map_location loc;
3075  if(luaW_tolocation(L, 2, loc)) {
3076  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
3077  } else if(lua_isstring(L, 2)) {
3078  char const *m = luaL_checkstring(L, 2);
3080  } else return luaW_type_error(L, 2, "location or terrain string");
3081  lua_pushinteger(L, u.jamming_cost(t));
3082  return 1;
3083 }
3084 
3085 /**
3086  * Returns unit defense on a given terrain.
3087  * - Arg 1: unit userdata.
3088  * - Arg 2: string containing the terrain type.
3089  * - Ret 1: integer.
3090  */
3091 static int intf_unit_defense(lua_State *L)
3092 {
3093  const unit& u = luaW_checkunit(L, 1);
3095  map_location loc;
3096  if(luaW_tolocation(L, 2, loc)) {
3097  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
3098  } else if(lua_isstring(L, 2)) {
3099  char const *m = luaL_checkstring(L, 2);
3101  } else return luaW_type_error(L, 2, "location or terrain string");
3102  lua_pushinteger(L, 100 - u.defense_modifier(t));
3103  return 1;
3104 }
3105 
3106 /**
3107  * Returns true if the unit has the given ability enabled.
3108  * - Arg 1: unit userdata.
3109  * - Arg 2: string.
3110  * - Ret 1: boolean.
3111  */
3113 {
3114  const unit& u = luaW_checkunit(L, 1);
3115  char const *m = luaL_checkstring(L, 2);
3116  lua_pushboolean(L, u.get_ability_bool(m));
3117  return 1;
3118 }
3119 
3120 /**
3121  * Changes a unit to the given unit type.
3122  * - Arg 1: unit userdata.
3123  * - Arg 2: unit type name
3124  * - Arg 3: (optional) unit variation name
3125  */
3126 static int intf_transform_unit(lua_State *L)
3127 {
3128  unit& u = luaW_checkunit(L, 1);
3129  char const *m = luaL_checkstring(L, 2);
3130  const unit_type *utp = unit_types.find(m);
3131  if (!utp) return luaL_argerror(L, 2, "unknown unit type");
3132  if(lua_isstring(L, 3)) {
3133  const std::string& m2 = lua_tostring(L, 3);
3134  if(!utp->has_variation(m2)) return luaL_argerror(L, 2, "unknown unit variation");
3135  utp = &utp->get_variation(m2);
3136  }
3137  u.advance_to(*utp);
3138 
3139  return 0;
3140 }
3141 
3142 /**
3143  * Puts a table at the top of the stack with some combat result.
3144  */
3145 static void luaW_pushsimdata(lua_State *L, const combatant &cmb)
3146 {
3147  int n = cmb.hp_dist.size();
3148  lua_createtable(L, 0, 4);
3149  lua_pushnumber(L, cmb.poisoned);
3150  lua_setfield(L, -2, "poisoned");
3151  lua_pushnumber(L, cmb.slowed);
3152  lua_setfield(L, -2, "slowed");
3153  lua_pushnumber(L, cmb.untouched);
3154  lua_setfield(L, -2, "untouched");
3155  lua_pushnumber(L, cmb.average_hp());
3156  lua_setfield(L, -2, "average_hp");
3157  lua_createtable(L, n, 0);
3158  for (int i = 0; i < n; ++i) {
3159  lua_pushnumber(L, cmb.hp_dist[i]);
3160  lua_rawseti(L, -2, i);
3161  }
3162  lua_setfield(L, -2, "hp_chance");
3163 }
3164 
3165 /**
3166  * Puts a table at the top of the stack with information about the combatants' weapons.
3167  */
3168 static void luaW_pushsimweapon(lua_State *L, const battle_context_unit_stats &bcustats)
3169 {
3170 
3171  lua_createtable(L, 0, 16);
3172 
3173  lua_pushnumber(L, bcustats.num_blows);
3174  lua_setfield(L, -2, "num_blows");
3175  lua_pushnumber(L, bcustats.damage);
3176  lua_setfield(L, -2, "damage");
3177  lua_pushnumber(L, bcustats.chance_to_hit);
3178  lua_setfield(L, -2, "chance_to_hit");
3179  lua_pushboolean(L, bcustats.poisons);
3180  lua_setfield(L, -2, "poisons");
3181  lua_pushboolean(L, bcustats.slows);
3182  lua_setfield(L, -2, "slows");
3183  lua_pushboolean(L, bcustats.petrifies);
3184  lua_setfield(L, -2, "petrifies");
3185  lua_pushboolean(L, bcustats.plagues);
3186  lua_setfield(L, -2, "plagues");
3187  lua_pushstring(L, bcustats.plague_type.c_str());
3188  lua_setfield(L, -2, "plague_type");
3189  lua_pushnumber(L, bcustats.rounds);
3190  lua_setfield(L, -2, "rounds");
3191  lua_pushboolean(L, bcustats.firststrike);
3192  lua_setfield(L, -2, "firststrike");
3193  lua_pushboolean(L, bcustats.drains);
3194  lua_setfield(L, -2, "drains");
3195  lua_pushnumber(L, bcustats.drain_constant);
3196  lua_setfield(L, -2, "drain_constant");
3197  lua_pushnumber(L, bcustats.drain_percent);
3198  lua_setfield(L, -2, "drain_percent");
3199 
3200 
3201  //if we called simulate_combat without giving an explicit weapon this can be useful.
3202  lua_pushnumber(L, bcustats.attack_num);
3203  lua_setfield(L, -2, "attack_num"); // DEPRECATED
3204  lua_pushnumber(L, bcustats.attack_num + 1);
3205  lua_setfield(L, -2, "number");
3206  //this is nullptr when there is no counter weapon
3207  if(bcustats.weapon != nullptr)
3208  {
3209  lua_pushstring(L, bcustats.weapon->id().c_str());
3210  lua_setfield(L, -2, "name");
3211  luaW_pushweapon(L, bcustats.weapon);
3212  lua_setfield(L, -2, "weapon");
3213  }
3214 
3215 }
3216 
3217 /**
3218  * Simulates a combat between two units.
3219  * - Arg 1: attacker userdata.
3220  * - Arg 2: optional weapon index.
3221  * - Arg 3: defender userdata.
3222  * - Arg 4: optional weapon index.
3223  *
3224  * - Ret 1: attacker results.
3225  * - Ret 2: defender results.
3226  * - Ret 3: info about the attacker weapon.
3227  * - Ret 4: info about the defender weapon.
3228  */
3230 {
3231  int arg_num = 1, att_w = -1, def_w = -1;
3232 
3233  unit_const_ptr att = luaW_checkunit(L, arg_num).shared_from_this();
3234  ++arg_num;
3235  if (lua_isnumber(L, arg_num)) {
3236  att_w = lua_tointeger(L, arg_num) - 1;
3237  if (att_w < 0 || att_w >= static_cast<int>(att->attacks().size()))
3238  return luaL_argerror(L, arg_num, "weapon index out of bounds");
3239  ++arg_num;
3240  }
3241 
3242  unit_const_ptr def = luaW_checkunit(L, arg_num).shared_from_this();
3243  ++arg_num;
3244  if (lua_isnumber(L, arg_num)) {
3245  def_w = lua_tointeger(L, arg_num) - 1;
3246  if (def_w < 0 || def_w >= static_cast<int>(def->attacks().size()))
3247  return luaL_argerror(L, arg_num, "weapon index out of bounds");
3248  ++arg_num;
3249  }
3250 
3251  battle_context context(units(), att->get_location(),
3252  def->get_location(), att_w, def_w, 0.0, nullptr, att, def);
3253 
3256  luaW_pushsimweapon(L, context.get_attacker_stats());
3257  luaW_pushsimweapon(L, context.get_defender_stats());
3258  return 4;
3259 }
3260 
3261 /**
3262  * Plays a sound, possibly repeated.
3263  * - Arg 1: string.
3264  * - Arg 2: optional integer.
3265  */
3267 {
3268  if (play_controller_.is_skipping_replay()) return 0;
3269  char const *m = luaL_checkstring(L, 1);
3270  int repeats = luaL_optinteger(L, 2, 0);
3271  sound::play_sound(m, sound::SOUND_FX, repeats);
3272  return 0;
3273 }
3274 
3275 /**
3276  * Sets an achievement as being completed.
3277  * - Arg 1: string - content_for.
3278  * - Arg 2: string - id.
3279  */
3281 {
3282  const char* content_for = luaL_checkstring(L, 1);
3283  const char* id = luaL_checkstring(L, 2);
3284 
3285  for(achievement_group& group : game_config_manager::get()->get_achievements()) {
3286  if(group.content_for_ == content_for) {
3287  for(achievement& achieve : group.achievements_) {
3288  if(achieve.id_ == id) {
3289  // already achieved
3290  if(achieve.achieved_) {
3291  return 0;
3292  }
3293  // found the achievement - mark it as completed
3294  if(!play_controller_.is_replay()) {
3295  prefs::get().set_achievement(content_for, id);
3296  }
3297  achieve.achieved_ = true;
3298  // progressable achievements can also check for current progress equals -1
3299  if(achieve.max_progress_ != 0) {
3300  achieve.current_progress_ = -1;
3301  }
3302  if(achieve.sound_path_ != "") {
3304  }
3305  // show the achievement popup
3306  luaW_getglobal(L, "gui", "show_popup");
3307  luaW_pushtstring(L, achieve.name_completed_);
3309  lua_pushstring(L, achieve.icon_completed_.c_str());
3310  luaW_pcall(L, 3, 0, 0);
3311  return 0;
3312  }
3313  }
3314  // achievement not found - existing achievement group but non-existing achievement id
3315  ERR_LUA << "Achievement " << id << " not found for achievement group " << content_for;
3316  return 0;
3317  }
3318  }
3319 
3320  // achievement group not found
3321  ERR_LUA << "Achievement group " << content_for << " not found";
3322  return 0;
3323 }
3324 
3325 /**
3326  * Returns whether an achievement has been completed.
3327  * - Arg 1: string - content_for.
3328  * - Arg 2: string - id.
3329  * - Ret 1: boolean.
3330  */
3332 {
3333  const char* content_for = luaL_checkstring(L, 1);
3334  const char* id = luaL_checkstring(L, 2);
3335 
3336  if(resources::controller->is_networked_mp() && synced_context::is_synced()) {
3337  ERR_LUA << "Returning false for whether a player has completed an achievement due to being networked multiplayer.";
3338  lua_pushboolean(L, false);
3339  } else {
3340  lua_pushboolean(L, prefs::get().achievement(content_for, id));
3341  }
3342 
3343  return 1;
3344 }
3345 
3346 /**
3347  * Returns information on a single achievement, or no data if the achievement is not found.
3348  * - Arg 1: string - content_for.
3349  * - Arg 2: string - id.
3350  * - Ret 1: WML table returned by the function.
3351  */
3353 {
3354  const char* content_for = luaL_checkstring(L, 1);
3355  const char* id = luaL_checkstring(L, 2);
3356 
3357  config cfg;
3358  for(const auto& group : game_config_manager::get()->get_achievements()) {
3359  if(group.content_for_ == content_for) {
3360  for(const auto& achieve : group.achievements_) {
3361  if(achieve.id_ == id) {
3362  // found the achievement - return it as a config
3363  cfg["id"] = achieve.id_;
3364  cfg["name"] = achieve.name_;
3365  cfg["name_completed"] = achieve.name_completed_;
3366  cfg["description"] = achieve.description_;
3367  cfg["description_completed"] = achieve.description_completed_;
3368  cfg["icon"] = achieve.icon_;
3369  cfg["icon_completed"] = achieve.icon_completed_;
3370  cfg["hidden"] = achieve.hidden_;
3371  cfg["achieved"] = achieve.achieved_;
3372  cfg["max_progress"] = achieve.max_progress_;
3373  cfg["current_progress"] = achieve.current_progress_;
3374 
3375  for(const auto& sub_ach : achieve.sub_achievements_) {
3376  config& sub = cfg.add_child("sub_achievement");
3377  sub["id"] = sub_ach.id_;
3378  sub["description"] = sub_ach.description_;
3379  sub["icon"] = sub_ach.icon_;
3380  sub["achieved"] = sub_ach.achieved_;
3381  }
3382 
3383  luaW_pushconfig(L, cfg);
3384  return 1;
3385  }
3386  }
3387  // return empty config - existing achievement group but non-existing achievement id
3388  ERR_LUA << "Achievement " << id << " not found for achievement group " << content_for;
3389  luaW_pushconfig(L, cfg);
3390  return 1;
3391  }
3392  }
3393  // return empty config - non-existing achievement group
3394  ERR_LUA << "Achievement group " << content_for << " not found";
3395  luaW_pushconfig(L, cfg);
3396  return 1;
3397 }
3398 
3399 /**
3400  * Progresses the provided achievement.
3401  * - Arg 1: string - content_for.
3402  * - Arg 2: string - achievement id.
3403  * - Arg 3: int - the amount to progress the achievement.
3404  * - Arg 4: int - the limit the achievement can progress by
3405  * - Ret 1: int - the achievement's current progress after adding amount or -1 if not a progressable achievement (including if it's already achieved)
3406  * - Ret 2: int - the achievement's max progress or -1 if not a progressable achievement
3407  */
3409 {
3410  const char* content_for = luaL_checkstring(L, 1);
3411  const char* id = luaL_checkstring(L, 2);
3412  int amount = luaL_checkinteger(L, 3);
3413  int limit = luaL_optinteger(L, 4, 999999999);
3414 
3415  for(achievement_group& group : game_config_manager::get()->get_achievements()) {
3416  if(group.content_for_ == content_for) {
3417  for(achievement& achieve : group.achievements_) {
3418  if(achieve.id_ == id) {
3419  // check that this is a progressable achievement
3420  if(achieve.max_progress_ == 0 || achieve.sub_achievements_.size() > 0) {
3421  ERR_LUA << "Attempted to progress achievement " << id << " for achievement group " << content_for << ", is not a progressible achievement.";
3422  lua_pushinteger(L, -1);
3423  lua_pushinteger(L, -1);
3424  return 2;
3425  }
3426 
3427  if(!achieve.achieved_) {
3428  int progress = 0;
3429  if(!play_controller_.is_replay()) {
3430  progress = prefs::get().progress_achievement(content_for, id, limit, achieve.max_progress_, amount);
3431  }
3432  if(progress >= achieve.max_progress_) {
3434  achieve.current_progress_ = -1;
3435  } else {
3436  achieve.current_progress_ = progress;
3437  }
3438  lua_pushinteger(L, progress);
3439  } else {
3440  lua_pushinteger(L, -1);
3441  }
3442  lua_pushinteger(L, achieve.max_progress_);
3443 
3444  return 2;
3445  }
3446  }
3447  // achievement not found - existing achievement group but non-existing achievement id
3448  lua_push(L, "Achievement " + std::string(id) + " not found for achievement group " + content_for);
3449  return lua_error(L);
3450  }
3451  }
3452 
3453  // achievement group not found
3454  lua_push(L, "Achievement group " + std::string(content_for) + " not found");
3455  return lua_error(L);
3456 }
3457 
3458 /**
3459  * Returns whether an achievement has been completed.
3460  * - Arg 1: string - content_for.
3461  * - Arg 2: string - achievement id.
3462  * - Arg 3: string - sub-achievement id
3463  * - Ret 1: boolean.
3464  */
3466 {
3467  const char* content_for = luaL_checkstring(L, 1);
3468  const char* id = luaL_checkstring(L, 2);
3469  const char* sub_id = luaL_checkstring(L, 3);
3470 
3471  if(resources::controller->is_networked_mp() && synced_context::is_synced()) {
3472  ERR_LUA << "Returning false for whether a player has completed an achievement due to being networked multiplayer.";
3473  lua_pushboolean(L, false);
3474  } else {
3475  lua_pushboolean(L, prefs::get().sub_achievement(content_for, id, sub_id));
3476  }
3477 
3478  return 1;
3479 }
3480 
3481 /**
3482  * Marks a single sub-achievement as completed.
3483  * - Arg 1: string - content_for.
3484  * - Arg 2: string - achievement id.
3485  * - Arg 3: string - sub-achievement id
3486  */
3488 {
3489  const char* content_for = luaL_checkstring(L, 1);
3490  const char* id = luaL_checkstring(L, 2);
3491  const char* sub_id = luaL_checkstring(L, 3);
3492 
3493  for(achievement_group& group : game_config_manager::get()->get_achievements()) {
3494  if(group.content_for_ == content_for) {
3495  for(achievement& achieve : group.achievements_) {
3496  if(achieve.id_ == id) {
3497  // the whole achievement is already completed
3498  if(achieve.achieved_) {
3499  return 0;
3500  }
3501 
3502  for(sub_achievement& sub_ach : achieve.sub_achievements_) {
3503  if(sub_ach.id_ == sub_id) {
3504  // this particular sub-achievement is already achieved
3505  if(sub_ach.achieved_) {
3506  return 0;
3507  } else {
3508  if(!play_controller_.is_replay()) {
3509  prefs::get().set_sub_achievement(content_for, id, sub_id);
3510  }
3511  sub_ach.achieved_ = true;
3512  achieve.current_progress_++;
3513  if(achieve.current_progress_ == achieve.max_progress_) {
3515  }
3516  return 0;
3517  }
3518  }
3519  }
3520  // sub-achievement not found - existing achievement group and achievement but non-existing sub-achievement id
3521  lua_push(L, "Sub-achievement " + std::string(id) + " not found for achievement" + id + " in achievement group " + content_for);
3522  return lua_error(L);
3523  }
3524  }
3525  // achievement not found - existing achievement group but non-existing achievement id
3526  lua_push(L, "Achievement " + std::string(id) + " not found for achievement group " + content_for);
3527  return lua_error(L);
3528  }
3529  }
3530 
3531  // achievement group not found
3532  lua_push(L, "Achievement group " + std::string(content_for) + " not found");
3533  return lua_error(L);
3534 }
3535 
3536 /**
3537  * Scrolls to given tile.
3538  * - Arg 1: location.
3539  * - Arg 2: boolean preventing scroll to fog.
3540  * - Arg 3: boolean specifying whether to warp instantly.
3541  * - Arg 4: boolean specifying whether to skip if already onscreen
3542  */
3544 {
3546  bool check_fogged = luaW_toboolean(L, 2);
3548  ? luaW_toboolean(L, 3)
3551  : luaW_toboolean(L, 3)
3554  ;
3555  if (game_display_) {
3556  game_display_->scroll_to_tile(loc, scroll, check_fogged);
3557  }
3558  return 0;
3559 }
3560 
3561 /**
3562  * Selects and highlights the given location on the map.
3563  * - Arg 1: location.
3564  * - Args 2,3: booleans
3565  */
3567 {
3568  events::command_disabler command_disabler;
3569  if(lua_isnoneornil(L, 1)) {
3571  return 0;
3572  }
3573  const map_location loc = luaW_checklocation(L, 1);
3574  if(!map().on_board(loc)) return luaL_argerror(L, 1, "not on board");
3575  bool highlight = true;
3576  if(!lua_isnoneornil(L, 2))
3577  highlight = luaW_toboolean(L, 2);
3578  const bool fire_event = luaW_toboolean(L, 3);
3580  loc, false, highlight, fire_event);
3581  return 0;
3582 }
3583 
3584 /**
3585  * Deselects any highlighted hex on the map.
3586  * No arguments or return values
3587  */
3589 {
3590  if(game_display_) {
3592  }
3593 
3594  return 0;
3595 }
3596 
3597 /**
3598  * Return true if a replay is in progress but the player has chosen to skip it
3599  */
3601 {
3603  if (!skipping) {
3604  skipping = game_state_.events_manager_->pump().context_skip_messages();
3605  }
3606  lua_pushboolean(L, skipping);
3607  return 1;
3608 }
3609 
3610 /**
3611  * Set whether to skip messages
3612  * Arg 1 (optional) - boolean
3613  */
3615 {
3616  bool skip = true;
3617  if (!lua_isnone(L, 1)) {
3618  skip = luaW_toboolean(L, 1);
3619  }
3620  game_state_.events_manager_->pump().context_skip_messages(skip);
3621  return 0;
3622 }
3623 
3624 namespace
3625 {
3626  struct lua_synchronize : mp_sync::user_choice
3627  {
3628  lua_State *L;
3629  int user_choice_index;
3630  int random_choice_index;
3631  int ai_choice_index;
3632  std::string desc;
3633  lua_synchronize(lua_State *l, const std::string& descr, int user_index, int random_index = 0, int ai_index = 0)
3634  : L(l)
3635  , user_choice_index(user_index)
3636  , random_choice_index(random_index)
3637  , ai_choice_index(ai_index != 0 ? ai_index : user_index)
3638  , desc(descr)
3639  {}
3640 
3641  virtual config query_user(int side) const override
3642  {
3643  bool is_local_ai = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).board().get_team(side).is_local_ai();
3644  config cfg;
3645  query_lua(side, is_local_ai ? ai_choice_index : user_choice_index, cfg);
3646  return cfg;
3647  }
3648 
3649  virtual config random_choice(int side) const override
3650  {
3651  config cfg;
3652  if(random_choice_index != 0 && lua_isfunction(L, random_choice_index)) {
3653  query_lua(side, random_choice_index, cfg);
3654  }
3655  return cfg;
3656  }
3657 
3658  virtual std::string description() const override
3659  {
3660  return desc;
3661  }
3662 
3663  void query_lua(int side, int function_index, config& cfg) const
3664  {
3665  lua_pushvalue(L, function_index);
3666  lua_pushnumber(L, side);
3667  if (luaW_pcall(L, 1, 1, false)) {
3668  if(!luaW_toconfig(L, -1, cfg)) {
3669  static const char* msg = "function returned to wesnoth.sync.[multi_]evaluate a table which was partially invalid";
3670  lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).log_error(msg);
3671  lua_warning(L, msg, false);
3672  }
3673  }
3674  }
3675  //Although lua's sync_choice can show a dialog, (and will in most cases)
3676  //we return false to enable other possible things that do not contain UI things.
3677  //it's in the responsibility of the umc dev to not show dialogs during prestart events.
3678  virtual bool is_visible() const override { return false; }
3679  };
3680 }//unnamed namespace for lua_synchronize
3681 
3682 /**
3683  * Ensures a value is synchronized among all the clients.
3684  * - Arg 1: optional string specifying the type id of the choice.
3685  * - Arg 2: function to compute the value, called if the client is the master.
3686  * - Arg 3: optional function, called instead of the first function if the user is not human.
3687  * - Arg 4: optional integer specifying, on which side the function should be evaluated.
3688  * - Ret 1: WML table returned by the function.
3689  */
3690 static int intf_synchronize_choice(lua_State *L)
3691 {
3692  std::string tagname = "input";
3693  t_string desc = _("input");
3694  int human_func = 0;
3695  int ai_func = 0;
3696  int side_for;
3697 
3698  int nextarg = 1;
3699  if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) {
3700  ++nextarg;
3701  }
3702  if(lua_isfunction(L, nextarg)) {
3703  human_func = nextarg++;
3704  }
3705  else {
3706  return luaL_argerror(L, nextarg, "expected a function");
3707  }
3708  if(lua_isfunction(L, nextarg)) {
3709  ai_func = nextarg++;
3710  }
3711  side_for = lua_tointeger(L, nextarg);
3712 
3713  config cfg = mp_sync::get_user_choice(tagname, lua_synchronize(L, desc, human_func, 0, ai_func), side_for);
3714  luaW_pushconfig(L, cfg);
3715  return 1;
3716 }
3717 /**
3718  * Ensures a value is synchronized among all the clients.
3719  * - Arg 1: optional string the id of this type of user input, may only contain characters a-z and '_'
3720  * - Arg 2: function to compute the value, called if the client is the master.
3721  * - Arg 3: an optional function to compute the value, if the side was null/empty controlled.
3722  * - Arg 4: an array of integers specifying, on which side the function should be evaluated.
3723  * - Ret 1: a map int -> WML tabls.
3724  */
3725 static int intf_synchronize_choices(lua_State *L)
3726 {
3727  std::string tagname = "input";
3728  t_string desc = _("input");
3729  int human_func = 0;
3730  int null_func = 0;
3731  std::vector<int> sides_for;
3732 
3733  int nextarg = 1;
3734  if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) {
3735  ++nextarg;
3736  }
3737  if(lua_isfunction(L, nextarg)) {
3738  human_func = nextarg++;
3739  }
3740  else {
3741  return luaL_argerror(L, nextarg, "expected a function");
3742  }
3743  if(lua_isfunction(L, nextarg)) {
3744  null_func = nextarg++;
3745  };
3746  sides_for = lua_check<std::vector<int>>(L, nextarg++);
3747 
3748  lua_push(L, mp_sync::get_user_choice_multiple_sides(tagname, lua_synchronize(L, desc, human_func, null_func), std::set<int>(sides_for.begin(), sides_for.end())));
3749  return 1;
3750 }
3751 
3752 
3753 /**
3754  * Calls a function in an unsynced context (this specially means that all random calls used by that function will be unsynced).
3755  * This is usually used together with an unsynced if like 'if controller != network'
3756  * - Arg 1: function that will be called during the unsynced context.
3757  */
3758 static int intf_do_unsynced(lua_State *L)
3759 {
3760  set_scontext_unsynced sync;
3761  lua_pushvalue(L, 1);
3762  luaW_pcall(L, 0, 0, false);
3763  return 0;
3764 }
3765 
3766 /**
3767  * Gets all the locations matching a given filter.
3768  * - Arg 1: WML table.
3769  * - Arg 2: Optional reference unit (teleport_unit)
3770  * - Ret 1: array of integer pairs.
3771  */
3773 {
3775 
3776  std::set<map_location> res;
3777  filter_context & fc = game_state_;
3778  const terrain_filter t_filter(filter, &fc, false);
3779  if(luaW_isunit(L, 2)) {
3780  t_filter.get_locations(res, *luaW_tounit(L, 2), true);
3781  } else {
3782  t_filter.get_locations(res, true);
3783  }
3784 
3785  luaW_push_locationset(L, res);
3786  return 1;
3787 }
3788 
3789 /**
3790  * Matches a location against the given filter.
3791  * - Arg 1: location.
3792  * - Arg 2: WML table.
3793  * - Arg 3: Optional reference unit (teleport_unit)
3794  * - Ret 1: boolean.
3795  */
3797 {
3799  vconfig filter = luaW_checkvconfig(L, 2, true);
3800 
3801  if (filter.null()) {
3802  lua_pushboolean(L, true);
3803  return 1;
3804  }
3805 
3806  filter_context & fc = game_state_;
3807  const terrain_filter t_filter(filter, &fc, false);
3808  if(luaW_isunit(L, 3)) {
3809  lua_pushboolean(L, t_filter.match(loc, *luaW_tounit(L, 3)));
3810  } else {
3811  lua_pushboolean(L, t_filter.match(loc));
3812  }
3813  return 1;
3814 }
3815 
3816 
3817 
3818 /**
3819  * Matches a side against the given filter.
3820  * - Args 1: side number.
3821  * - Arg 2: WML table.
3822  * - Ret 1: boolean.
3823  */
3825 {
3826  vconfig filter = luaW_checkvconfig(L, 2, true);
3827 
3828  if (filter.null()) {
3829  lua_pushboolean(L, true);
3830  return 1;
3831  }
3832 
3833  filter_context & fc = game_state_;
3834  side_filter s_filter(filter, &fc);
3835 
3836  if(team* t = luaW_toteam(L, 1)) {
3837  lua_pushboolean(L, s_filter.match(*t));
3838  } else {
3839  unsigned side = luaL_checkinteger(L, 1) - 1;
3840  if (side >= teams().size()) return 0;
3841  lua_pushboolean(L, s_filter.match(side + 1));
3842  }
3843  return 1;
3844 }
3845 
3847 {
3848  int team_i;
3849  if(team* t = luaW_toteam(L, 1)) {
3850  team_i = t->side();
3851  } else {
3852  team_i = luaL_checkinteger(L, 1);
3853  }
3854  std::string flag = luaL_optlstring(L, 2, "", nullptr);
3855  std::string color = luaL_optlstring(L, 3, "", nullptr);
3856 
3857  if(flag.empty() && color.empty()) {
3858  return 0;
3859  }
3860  if(team_i < 1 || static_cast<std::size_t>(team_i) > teams().size()) {
3861  return luaL_error(L, "set_side_id: side number %d out of range", team_i);
3862  }
3863  team& side = board().get_team(team_i);
3864 
3865  if(!color.empty()) {
3866  side.set_color(color);
3867  }
3868  if(!flag.empty()) {
3869  side.set_flag(flag);
3870  }
3871 
3873  return 0;
3874 }
3875 
3876 static int intf_modify_ai(lua_State *L, const char* action)
3877 {
3878  int side_num;
3879  if(team* t = luaW_toteam(L, 1)) {
3880  side_num = t->side();
3881  } else {
3882  side_num = luaL_checkinteger(L, 1);
3883  }
3884  std::string path = luaL_checkstring(L, 2);
3885  config cfg {
3886  "action", action,
3887  "path", path
3888  };
3889  if(strcmp(action, "delete") == 0) {
3891  return 0;
3892  }
3893  config component = luaW_checkconfig(L, 3);
3894  std::size_t len = std::string::npos, open_brak = path.find_last_of('[');
3895  std::size_t dot = path.find_last_of('.');
3896  if(open_brak != len) {
3897  len = open_brak - dot - 1;
3898  }
3899  cfg.add_child(path.substr(dot + 1, len), component);
3901  return 0;
3902 }
3903 
3904 static int intf_switch_ai(lua_State *L)
3905 {
3906  int side_num;
3907  if(team* t = luaW_toteam(L, 1)) {
3908  side_num = t->side();
3909  } else {
3910  side_num = luaL_checkinteger(L, 1);
3911  }
3912  if(lua_isstring(L, 2)) {
3913  std::string file = luaL_checkstring(L, 2);
3914  if(!ai::manager::get_singleton().add_ai_for_side_from_file(side_num, file)) {
3915  std::string err = formatter() << "Could not load AI for side " << side_num << " from file " << file;
3916  lua_pushlstring(L, err.c_str(), err.length());
3917  return lua_error(L);
3918  }
3919  } else {
3921  }
3922  return 0;
3923 }
3924 
3925 static int intf_append_ai(lua_State *L)
3926 {
3927  int side_num;
3928  if(team* t = luaW_toteam(L, 1)) {
3929  side_num = t->side();
3930  } else {
3931  side_num = luaL_checkinteger(L, 1);
3932  }
3933  config cfg = luaW_checkconfig(L, 2);
3934  if(!cfg.has_child("ai")) {
3935  cfg = config {"ai", cfg};
3936  }
3937  bool added_dummy_stage = false;
3938  if(!cfg.mandatory_child("ai").has_child("stage")) {
3939  added_dummy_stage = true;
3940  cfg.mandatory_child("ai").add_child("stage", config {"name", "empty"});
3941  }
3943  if(added_dummy_stage) {
3944  cfg.remove_children("stage", [](const config& stage_cfg) { return stage_cfg["name"] == "empty"; });
3945  }
3947  return 0;
3948 }
3949 
3951 {
3952  unsigned i = luaL_checkinteger(L, 1);
3953  if(i < 1 || i > teams().size()) return 0;
3954  luaW_pushteam(L, board().get_team(i));
3955  return 1;
3956 }
3957 
3958 /**
3959  * Returns a proxy table array for all sides matching the given SSF.
3960  * - Arg 1: SSF
3961  * - Ret 1: proxy table array
3962  */
3964 {
3965  LOG_LUA << "intf_get_sides called: this = " << std::hex << this << std::dec << " myname = " << my_name();
3966  std::vector<int> sides;
3967  const vconfig ssf = luaW_checkvconfig(L, 1, true);
3968  if(ssf.null()) {
3969  for(const team& t : teams()) {
3970  sides.push_back(t.side());
3971  }
3972  } else {
3973  filter_context & fc = game_state_;
3974 
3975  side_filter filter(ssf, &fc);
3976  sides = filter.get_teams();
3977  }
3978 
3979  lua_settop(L, 0);
3980  lua_createtable(L, sides.size(), 0);
3981  unsigned index = 1;
3982  for(int side : sides) {
3983  luaW_pushteam(L, board().get_team(side));
3984  lua_rawseti(L, -2, index);
3985  ++index;
3986  }
3987 
3988  return 1;
3989 }
3990 
3991 /**
3992  * Adds a modification to a unit.
3993  * - Arg 1: unit.
3994  * - Arg 2: string.
3995  * - Arg 3: WML table.
3996  * - Arg 4: (optional) Whether to add to [modifications] - default true
3997  */
3998 static int intf_add_modification(lua_State *L)
3999 {
4000  unit& u = luaW_checkunit(L, 1);
4001  char const *m = luaL_checkstring(L, 2);
4002  std::string sm = m;
4003  if (sm == "advance") { // Maintain backwards compatibility
4004  sm = "advancement";
4005  deprecated_message("\"advance\" modification type", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use \"advancement\" instead.");
4006  }
4007  if (sm != "advancement" && sm != "object" && sm != "trait") {
4008  return luaL_argerror(L, 2, "unknown modification type");
4009  }
4010  bool write_to_mods = true;
4011  if (!lua_isnone(L, 4)) {
4012  write_to_mods = luaW_toboolean(L, 4);
4013  }
4014  if(sm.empty()) {
4015  write_to_mods = false;
4016  }
4017 
4018  config cfg = luaW_checkconfig(L, 3);
4019  u.add_modification(sm, cfg, !write_to_mods);
4020  return 0;
4021 }
4022 
4023 /**
4024  * Removes modifications from a unit
4025  * - Arg 1: unit
4026  * - Arg 2: table (filter as [filter_wml])
4027  * - Arg 3: type of modification (default "object")
4028  */
4029 static int intf_remove_modifications(lua_State *L)
4030 {
4031  unit& u = luaW_checkunit(L, 1);
4032  config filter = luaW_checkconfig(L, 2);
4033  std::vector<std::string> tags;
4034  if(lua_isstring(L, 3)) {
4035  tags.push_back(lua_check<std::string>(L, 3));
4036  } else if (lua_istable(L, 3)){
4037  tags = lua_check<std::vector<std::string>>(L, 3);
4038  } else {
4039  tags.push_back("object");
4040  }
4041  //TODO
4042  if(filter.attribute_count() == 1 && filter.all_children_count() == 0 && filter.attribute_range().front().first == "duration") {
4043  u.expire_modifications(filter["duration"]);
4044  } else {
4045  for(const std::string& tag : tags) {
4046  for(config& obj : u.get_modifications().child_range(tag)) {
4047  if(obj.matches(filter)) {
4048  obj["duration"] = "now";
4049  }
4050  }
4051  }
4052  u.expire_modifications("now");
4053  }
4054  return 0;
4055 }
4056 
4057 /**
4058  * Advances a unit if the unit has enough xp.
4059  * - Arg 1: unit.
4060  * - Arg 2: optional boolean whether to animate the advancement.
4061  * - Arg 3: optional boolean whether to fire advancement events.
4062  */
4063 static int intf_advance_unit(lua_State *L)
4064 {
4065  events::command_disabler command_disabler;
4066  unit& u = luaW_checkunit(L, 1, true);
4068  if(lua_isboolean(L, 2)) {
4069  par.animate(luaW_toboolean(L, 2));
4070  }
4071  if(lua_isboolean(L, 3)) {
4072  par.fire_events(luaW_toboolean(L, 3));
4073  }
4074  advance_unit_at(par);
4075  return 0;
4076 }
4077 
4078 
4079 /**
4080  * Adds a new known unit type to the help system.
4081  * - Arg 1: string.
4082  */
4083 static int intf_add_known_unit(lua_State *L)
4084 {
4085  char const *ty = luaL_checkstring(L, 1);
4086  if(!unit_types.find(ty))
4087  {
4088  std::stringstream ss;
4089  ss << "unknown unit type: '" << ty << "'";
4090  return luaL_argerror(L, 1, ss.str().c_str());
4091  }
4092  prefs::get().encountered_units().insert(ty);
4093  return 0;
4094 }
4095 
4096 /**
4097  * Adds an overlay on a tile.
4098  * - Arg 1: location.
4099  * - Arg 2: WML table.
4100  */
4102 {
4104  vconfig cfg = luaW_checkvconfig(L, 2);
4105  const vconfig &ssf = cfg.child("filter_team");
4106 
4107  std::string team_name;
4108  if (!ssf.null()) {
4109  const std::vector<int>& teams = side_filter(ssf, &game_state_).get_teams();
4110  std::vector<std::string> team_names;
4111  std::transform(teams.begin(), teams.end(), std::back_inserter(team_names),
4112  [&](int team) { return game_state_.get_disp_context().get_team(team).team_name(); });
4113  team_name = utils::join(team_names);
4114  } else {
4115  team_name = cfg["team_name"].str();
4116  }
4117 
4118  if (game_display_) {
4120  cfg["image"],
4121  cfg["halo"],
4122  team_name,
4123  cfg["name"], // Name is treated as the ID
4124  cfg["visible_in_fog"].to_bool(true),
4125  cfg["submerge"].to_double(0),
4126  cfg["z_order"].to_double(0)
4127  ));
4128  }
4129  return 0;
4130 }
4131 
4132 /**
4133  * Removes an overlay from a tile.
4134  * - Arg 1: location.
4135  * - Arg 2: optional string.
4136  */
4138 {
4140  char const *m = lua_tostring(L, 2);
4141 
4142  if (m) {
4143  if (game_display_) {
4145  }
4146  } else {
4147  if (game_display_) {
4149  }
4150  }
4151  return 0;
4152 }
4153 
4155 {
4157  const int nargs = lua_gettop(L);
4158  if(nargs < 2 || nargs > 3) {
4159  return luaL_error(L, "Wrong number of arguments to ai.log_replay() - should be 2 or 3 arguments.");
4160  }
4161  const std::string key = nargs == 2 ? luaL_checkstring(L, 1) : luaL_checkstring(L, 2);
4162  config cfg;
4163  if(nargs == 2) {
4164  recorder.add_log_data(key, luaL_checkstring(L, 2));
4165  } else if(luaW_toconfig(L, 3, cfg)) {
4166  recorder.add_log_data(luaL_checkstring(L, 1), key, cfg);
4167  } else if(!lua_isstring(L, 3)) {
4168  return luaL_argerror(L, 3, "accepts only string or config");
4169  } else {
4170  recorder.add_log_data(luaL_checkstring(L, 1), key, luaL_checkstring(L, 3));
4171  }
4172  return 0;
4173 }
4174 
4176 {
4177  lua_event_filter(game_lua_kernel& lk, int idx, const config& args) : lk(lk), args_(args)
4178  {
4179  ref_ = lk.save_wml_event(idx);
4180  }
4181  bool operator()(const game_events::queued_event& event_info) const override
4182  {
4183  bool result;
4184  return lk.run_wml_event(ref_, args_, event_info, &result) && result;
4185  }
4187  {
4189  }
4190  void serialize(config& cfg) const override {
4191  cfg.add_child("filter_lua")["code"] = "<function>";
4192  }
4193 private:
4195  int ref_;
4197 };
4198 
4199 static std::string read_event_name(lua_State* L, int idx)
4200 {
4201  if(lua_isstring(L, idx)) {
4202  return lua_tostring(L, idx);
4203  } else {
4204  return utils::join(lua_check<std::vector<std::string>>(L, idx));
4205  }
4206 }
4207 
4208 /**
4209  * Add undo actions for the current active event
4210  * Arg 1: Either a table of ActionWML or a function to call
4211  * Arg 2: (optional) If Arg 1 is a function, this is a WML table that will be passed to it
4212  */
4214 {
4215  config cfg;
4216  if(luaW_toconfig(L, 1, cfg)) {
4218  } else {
4219  luaW_toconfig(L, 2, cfg);
4221  }
4222  return 0;
4223 }
4224 
4225 /** Add a new event handler
4226  * Arg 1: Table of options.
4227  * name: Event to handle, as a string or list of strings
4228  * id: Event ID
4229  * menu_item: True if this is a menu item (an ID is required); this means removing the menu item will automatically remove this event. Default false.
4230  * first_time_only: Whether this event should fire again after the first time; default true.
4231  * priority: Number that determines execution order. Events execute in order of decreasing priority, and secondarily in order of addition.
4232  * filter: Event filters as a config with filter tags, a table of the form {filter_type = filter_contents}, or a function
4233  * filter_args: Arbitrary data that will be passed to the filter, if it is a function. Ignored if the filter is specified as WML or a table.
4234  * content: The content of the event. This is a WML table passed verbatim into the event when it fires. If no function is specified, it will be interpreted as ActionWML.
4235  * action: The function to call when the event triggers. Defaults to wesnoth.wml_actions.command.
4236  *
4237  * Lua API: wesnoth.game_events.add
4238  */
4240 {
4242  using namespace std::literals;
4243  std::string name, id = luaW_table_get_def(L, 1, "id", ""s);
4244  bool repeat = !luaW_table_get_def(L, 1, "first_time_only", true), is_menu_item = luaW_table_get_def(L, 1, "menu_item", false);
4245  double priority = luaW_table_get_def(L, 1, "priority", 0.);
4246  if(luaW_tableget(L, 1, "name")) {
4247  name = read_event_name(L, -1);
4248  } else if(is_menu_item) {
4249  if(id.empty()) {
4250  return luaL_argerror(L, 1, "non-empty id is required for a menu item");
4251  }
4252  name = "menu item " + id;
4253  }
4254  if(id.empty() && name.empty()) {
4255  return luaL_argerror(L, 1, "either a name or id is required");
4256  }
4257  auto new_handler = man.add_event_handler_from_lua(name, id, repeat, priority, is_menu_item);
4258  if(new_handler.valid()) {
4259  bool has_lua_filter = false;
4260  new_handler->set_arguments(luaW_table_get_def(L, 1, "content", config{"__empty_lua_event", true}));
4261 
4262  if(luaW_tableget(L, 1, "filter")) {
4263  int filterIdx = lua_gettop(L);
4264  config filters;
4265  if(!luaW_toconfig(L, filterIdx, filters)) {
4266  if(lua_isfunction(L, filterIdx)) {
4267  int fcnIdx = lua_absindex(L, -1);
4268  new_handler->add_filter(std::make_unique<lua_event_filter>(*this, fcnIdx, luaW_table_get_def(L, 1, "filter_args", config())));
4269  has_lua_filter = true;
4270  } else {
4271 #define READ_ONE_FILTER(key, tag) \
4272  do { \
4273  if(luaW_tableget(L, filterIdx, key)) { \
4274  if(lua_isstring(L, -1)) { \
4275  filters.add_child("insert_tag", config{ \
4276  "name", tag, \
4277  "variable", luaL_checkstring(L, -1) \
4278  }); \
4279  } else { \
4280  filters.add_child(tag, luaW_checkconfig(L, -1)); \
4281  } \
4282  } \
4283  } while(false);
4284  READ_ONE_FILTER("condition", "filter_condition");
4285  READ_ONE_FILTER("side", "filter_side");
4286  READ_ONE_FILTER("unit", "filter");
4287  READ_ONE_FILTER("attack", "filter_attack");
4288  READ_ONE_FILTER("second_unit", "filter_second");
4289  READ_ONE_FILTER("second_attack", "filter_second_attack");
4290 #undef READ_ONE_FILTER
4291  if(luaW_tableget(L, filterIdx, "formula")) {
4292  filters["filter_formula"] = luaL_checkstring(L, -1);
4293  }
4294  }
4295  }
4296  new_handler->read_filters(filters);
4297  }
4298 
4299  if(luaW_tableget(L, 1, "action")) {
4300  new_handler->set_event_ref(save_wml_event(-1), has_preloaded_);
4301  } else {
4302  if(has_lua_filter) {
4303  // This just sets the appropriate flags so the engine knows it cannot be serialized.
4304  // The register_wml_event call will override the actual event_ref so just pass LUA_NOREF here.
4305  new_handler->set_event_ref(LUA_NOREF, has_preloaded_);
4306  }
4307  new_handler->register_wml_event(*this);
4308  }
4309  }
4310  return 0;
4311 }
4312 
4313 /**
4314  * Upvalue 1: The event function
4315  * Upvalue 2: The undo function
4316  * Arg 1: The event content
4317  */
4319 {
4320  lua_pushvalue(L, lua_upvalueindex(1));
4321  lua_push(L, 1);
4322  luaW_pcall(L, 1, 0);
4323  game_state_.undo_stack_->add_custom<actions::undo_event>(lua_upvalueindex(2), config(), get_event_info());
4324  return 0;
4325 }
4326 
4327 /** Add a new event handler
4328  * Arg 1: Event to handle, as a string or list of strings; or menu item ID if this is a menu item
4329  * Arg 2: The function to call when the event triggers
4330  * Arg 3: (optional) Event priority
4331  * Arg 4: (optional, non-menu-items only) The function to call when the event is undone
4332  *
4333  * Lua API:
4334  * - wesnoth.game_events.add_repeating
4335  * - wesnoth.game_events.add_menu
4336  */
4337 template<bool is_menu_item>
4339 {
4341  bool repeat = true;
4342  std::string name = read_event_name(L, 1), id;
4343  double priority = luaL_optnumber(L, 3, 0.);
4344  if(name.empty()) {
4345  return luaL_argerror(L, 1, "must not be empty");
4346  }
4347  if(is_menu_item) {
4348  id = name;
4349  name = "menu item " + name;
4350  } else if(lua_absindex(L, -1) > 2 && lua_isfunction(L, -1)) {
4351  // If undo is provided as a separate function, link them together into a single function
4352  // The function can be either the 3rd or 4th argument.
4353  lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_undoable_event>, 2);
4354  }
4355  auto new_handler = man.add_event_handler_from_lua(name, id, repeat, priority, is_menu_item);
4356  if(new_handler.valid()) {
4357  // An event with empty arguments is not added, so set some dummy arguments
4358  new_handler->set_arguments(config{"__quick_lua_event", true});
4359  new_handler->set_event_ref(save_wml_event(2), has_preloaded_);
4360  }
4361  return 0;
4362 }
4363 
4364 /** Add a new event handler
4365  * Arg: A full event specification as a WML config
4366  *
4367  * WML API: [event]
4368  */
4370 {
4372  vconfig cfg(luaW_checkvconfig(L, 1));
4373  bool delayed_variable_substitution = cfg["delayed_variable_substitution"].to_bool(true);
4374  if(delayed_variable_substitution) {
4375  man.add_event_handler_from_wml(cfg.get_config(), *this);
4376  } else {
4378  }
4379  return 0;
4380 }
4381 
4383 {
4384  game_state_.events_manager_->remove_event_handler(luaL_checkstring(L, 1));
4385  return 0;
4386 }
4387 
4389 {
4390  if (game_display_) {
4391  game_display_->adjust_color_overlay(luaL_checkinteger(L, 1), luaL_checkinteger(L, 2), luaL_checkinteger(L, 3));
4393  }
4394  return 0;
4395 }
4396 
4398 {
4399  if(game_display_) {
4400  auto color = game_display_->get_color_overlay();
4401  lua_pushinteger(L, color.r);
4402  lua_pushinteger(L, color.g);
4403  lua_pushinteger(L, color.b);
4404  return 3;
4405  }
4406  return 0;
4407 }
4408 
4410 {
4411  if(game_display_) {
4412  auto vec = lua_check<std::vector<uint8_t>>(L, 1);
4413  if(vec.size() != 4) {
4414  return luaW_type_error(L, 1, "array of 4 integers");
4415  }
4416  color_t fade{vec[0], vec[1], vec[2], vec[3]};
4417  game_display_->fade_to(fade, std::chrono::milliseconds{luaL_checkinteger(L, 2)});
4418  }
4419  return 0;
4420 }
4421 
4422 /**
4423  * Delays engine for a while.
4424  * - Arg 1: integer.
4425  * - Arg 2: boolean (optional).
4426  */
4428 {
4429  if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::PRESTART || gamedata().phase() == game_data::INITIAL) {
4430  //don't call play_slice if the game ui is not active yet.
4431  return 0;
4432  }
4433  events::command_disabler command_disabler;
4434  using namespace std::chrono_literals;
4435  std::chrono::milliseconds delay{luaL_checkinteger(L, 1)};
4436  if(delay == 0ms) {
4438  return 0;
4439  }
4440  if(luaW_toboolean(L, 2) && game_display_ && game_display_->turbo_speed() > 0) {
4441  delay /= game_display_->turbo_speed();
4442  }
4443  const auto end_time = std::chrono::steady_clock::now() + delay;
4444  do {
4446  std::this_thread::sleep_for(10ms);
4447  } while (std::chrono::steady_clock::now() < end_time);
4448  return 0;
4449 }
4450 
4452 {
4453  // TODO: Support color = {r = 0, g = 0, b = 0}
4454  if (game_display_) {
4455  vconfig cfg(luaW_checkvconfig(L, 1));
4456 
4457  game_display &screen = *game_display_;
4458 
4459  terrain_label label(screen.labels(), cfg.get_config());
4460 
4461  screen.labels().set_label(label.location(), label.text(), label.creator(), label.team_name(), label.color(),
4462  label.visible_in_fog(), label.visible_in_shroud(), label.immutable(), label.category(), label.tooltip());
4463  }
4464  return 0;
4465 }
4466 
4468 {
4469  if (game_display_) {
4471  std::string team_name;
4472 
4473  // If there's only one parameter and it's a table, check if it contains team_name
4474  if(lua_gettop(L) == 1 && lua_istable(L, 1)) {
4475  using namespace std::literals;
4476  team_name = luaW_table_get_def(L, 1, "team_name", ""sv);
4477  } else {
4478  team_name = luaL_optstring(L, 2, "");
4479  }
4480 
4481  game_display_->labels().set_label(loc, "", -1, team_name);
4482  }
4483  return 0;
4484 }
4485 
4487 {
4488  if(game_display_) {
4489  game_display &screen = *game_display_;
4490  auto loc = luaW_checklocation(L, 1);
4491  const terrain_label* label = nullptr;
4492  switch(lua_type(L, 2)) {
4493  // Missing 2nd argument - get global label
4494  case LUA_TNONE: case LUA_TNIL:
4495  label = screen.labels().get_label(loc, "");
4496  break;
4497  // Side number - get label belonging to that side's team
4498  case LUA_TNUMBER:
4499  if(size_t n = luaL_checkinteger(L, 2); n > 0 && n <= teams().size()) {
4500  label = screen.labels().get_label(loc, teams().at(n - 1).team_name());
4501  }
4502  break;
4503  // String - get label belonging to the team with that name
4504  case LUA_TSTRING:
4505  label = screen.labels().get_label(loc, luaL_checkstring(L, 2));
4506  break;
4507  // Side userdata - get label belonging to that side's team
4508  case LUA_TUSERDATA:
4509  label = screen.labels().get_label(loc, luaW_checkteam(L, 2).team_name());
4510  break;
4511  }
4512  if(label) {
4513  config cfg;
4514  label->write(cfg);
4515  luaW_pushconfig(L, cfg);
4516  return 1;
4517  }
4518  }
4519  return 0;
4520 }
4521 
4523 {
4524  if (game_display_) {
4525  game_display & screen = *game_display_;
4526 
4527  vconfig cfg(luaW_checkvconfig(L, 1));
4528  bool clear_shroud(luaW_toboolean(L, 2));
4529 
4530  // We do this twice so any applicable redraws happen both before and after
4531  // any events caused by redrawing shroud are fired
4532  bool result = screen.maybe_rebuild();
4533  if (!result) {
4534  screen.invalidate_all();
4535  }
4536 
4537  if (clear_shroud) {
4539  for (const int side : filter.get_teams()){
4540  actions::clear_shroud(side);
4541  }
4542  screen.recalculate_minimap();
4543  }
4544 
4545  result = screen.maybe_rebuild();
4546  if (!result) {
4547  screen.invalidate_all();
4548  }
4549  }
4550  return 0;
4551 }
4552 
4553 /**
4554  * Lua frontend to the modify_ai functionality
4555  * - Arg 1: config.
4556  */
4557 static int intf_modify_ai_old(lua_State *L)
4558 {
4559  config cfg;
4560  luaW_toconfig(L, 1, cfg);
4561  int side = cfg["side"].to_int();
4563  return 0;
4564 }
4565 
4566 static int cfun_exec_candidate_action(lua_State *L)
4567 {
4568  bool exec = luaW_toboolean(L, -1);
4569  lua_pop(L, 1);
4570 
4571  lua_getfield(L, -1, "ca_ptr");
4572 
4573  ai::candidate_action *ca = static_cast<ai::candidate_action*>(lua_touserdata(L, -1));
4574  lua_pop(L, 2);
4575  if (exec) {
4576  ca->execute();
4577  return 0;
4578  }
4579  lua_pushnumber(L, ca->evaluate());
4580  return 1;
4581 }
4582 
4583 static int cfun_exec_stage(lua_State *L)
4584 {
4585  lua_getfield(L, -1, "stg_ptr");
4586  ai::stage *stg = static_cast<ai::stage*>(lua_touserdata(L, -1));
4587  lua_pop(L, 2);
4588  stg->play_stage();
4589  return 0;
4590 }
4591 
4592 static void push_component(lua_State *L, ai::component* c, const std::string &ct = "")
4593 {
4594  lua_createtable(L, 0, 0); // Table for a component
4595 
4596  lua_pushstring(L, "name");
4597  lua_pushstring(L, c->get_name().c_str());
4598  lua_rawset(L, -3);
4599 
4600  lua_pushstring(L, "engine");
4601  lua_pushstring(L, c->get_engine().c_str());
4602  lua_rawset(L, -3);
4603 
4604  lua_pushstring(L, "id");
4605  lua_pushstring(L, c->get_id().c_str());
4606  lua_rawset(L, -3);
4607 
4608  if (ct == "candidate_action") {
4609  lua_pushstring(L, "ca_ptr");
4610  lua_pushlightuserdata(L, c);
4611  lua_rawset(L, -3);
4612 
4613  lua_pushstring(L, "exec");
4614  lua_pushcclosure(L, &cfun_exec_candidate_action, 0);
4615  lua_rawset(L, -3);
4616  }
4617 
4618  if (ct == "stage") {
4619  lua_pushstring(L, "stg_ptr");
4620  lua_pushlightuserdata(L, c);
4621  lua_rawset(L, -3);
4622 
4623  lua_pushstring(L, "exec");
4624  lua_pushcclosure(L, &cfun_exec_stage, 0);
4625  lua_rawset(L, -3);
4626  }
4627 
4628 
4629  std::vector<std::string> c_types = c->get_children_types();
4630 
4631  for (std::vector<std::string>::const_iterator t = c_types.begin(); t != c_types.end(); ++t)
4632  {
4633  std::vector<ai::component*> children = c->get_children(*t);
4634  std::string type = *t;
4635  if (type == "aspect" || type == "goal" || type == "engine")
4636  {
4637  continue;
4638  }
4639 
4640  lua_pushstring(L, type.c_str());
4641  lua_createtable(L, 0, 0); // this table will be on top of the stack during recursive calls
4642 
4643  for (std::vector<ai::component*>::const_iterator i = children.begin(); i != children.end(); ++i)
4644  {
4645  lua_pushstring(L, (*i)->get_name().c_str());
4646  push_component(L, *i, type);
4647  lua_rawset(L, -3);
4648 
4649  //if (type == "candidate_action")
4650  //{
4651  // ai::candidate_action *ca = dynamic_cast<ai::candidate_action*>(*i);
4652  // ca->execute();
4653  //}
4654  }
4655 
4656  lua_rawset(L, -3); // setting the child table
4657  }
4658 
4659 
4660 }
4661 
4662 /**
4663  * Debug access to the ai tables
4664  * - Arg 1: int
4665  * - Ret 1: ai table
4666  */
4667 static int intf_debug_ai(lua_State *L)
4668 {
4669  if (!game_config::debug) { // This function works in debug mode only
4670  return 0;
4671  }
4672  int side;
4673  if(team* t = luaW_toteam(L, 1)) {
4674  side = t->side();
4675  } else {
4676  side = luaL_checkinteger(L, 1);
4677  }
4678  lua_pop(L, 1);
4679 
4681 
4682  // Bad, but works
4683  std::vector<ai::component*> engines = c->get_children("engine");
4684  ai::engine_lua* lua_engine = nullptr;
4685  for (std::vector<ai::component*>::const_iterator i = engines.begin(); i != engines.end(); ++i)
4686  {
4687  if ((*i)->get_name() == "lua")
4688  {
4689  lua_engine = dynamic_cast<ai::engine_lua *>(*i);
4690  }
4691  }
4692 
4693  // Better way, but doesn't work
4694  //ai::component* e = ai::manager::get_singleton().get_active_ai_holder_for_side_dbg(side).get_component(c, "engine[lua]");
4695  //ai::engine_lua* lua_engine = dynamic_cast<ai::engine_lua *>(e);
4696 
4697  if (lua_engine == nullptr)
4698  {
4699  //no lua engine is defined for this side.
4700  //so set up a dummy engine
4701 
4702  ai::ai_composite * ai_ptr = dynamic_cast<ai::ai_composite *>(c);
4703 
4704  assert(ai_ptr);
4705 
4706  ai::ai_context& ai_context = ai_ptr->get_ai_context();
4708 
4709  lua_engine = new ai::engine_lua(ai_context, cfg);
4710  LOG_LUA << "Created new dummy lua-engine for debug_ai().";
4711 
4712  //and add the dummy engine as a component
4713  //to the manager, so we could use it later
4714  cfg.add_child("engine", lua_engine->to_config());
4715  ai::component_manager::add_component(c, "engine[]", cfg);
4716  }
4717 
4718  lua_engine->push_ai_table(); // stack: [-1: ai_context]
4719 
4720  lua_pushstring(L, "components");
4721  push_component(L, c); // stack: [-1: component tree; -2: ai context]
4722  lua_rawset(L, -3);
4723 
4724  return 1;
4725 }
4726 
4727 /** Allow undo sets the flag saying whether the event has mutated the game to false. */
4729 {
4730  bool allow;
4731  t_string reason;
4732  // The extra iststring is required to prevent totstring from converting a bool value
4733  if(luaW_iststring(L, 1) && luaW_totstring(L, 1, reason)) {
4734  allow = false;
4735  } else {
4736  allow = luaW_toboolean(L, 1);
4737  luaW_totstring(L, 2, reason);
4738  }
4739  gamedata().set_allow_end_turn(allow, reason);
4740  return 0;
4741 }
4742 
4743 /** Allow undo sets the flag saying whether the event has mutated the game to false. */
4745 {
4746  if(lua_isboolean(L, 1)) {
4748  }
4749  else {
4751  }
4752  return 0;
4753 }
4754 
4756 {
4758  return 0;
4759 }
4760 
4761 /** Adding new time_areas dynamically with Standard Location Filters.
4762  * Arg 1: Area ID
4763  * Arg 2: Area locations (either a filter or a list of locations)
4764  * Arg 3: (optional) Area schedule - WML table with [time] tags and optional current_time=
4765  */
4767 {
4768  log_scope("time_area");
4769 
4770  std::string id;
4771  std::set<map_location> locs;
4772  config times;
4773 
4774  if(lua_gettop(L) == 1) {
4775  vconfig cfg = luaW_checkvconfig(L, 1);
4776  deprecated_message("Single-argument wesnoth.map.place_area is deprecated. Instead, pass ID, filter, and schedule as three separate arguments.", DEP_LEVEL::INDEFINITE, {1, 17, 0});
4777  id = cfg["id"].str();
4778  const terrain_filter filter(cfg, &game_state_, false);
4779  filter.get_locations(locs, true);
4780  times = cfg.get_parsed_config();
4781  } else {
4782  id = luaL_checkstring(L, 1);
4783  if(!lua_isnoneornil(L, 3))
4784  times = luaW_checkconfig(L, 3);
4785  vconfig cfg{config()};
4786  if(luaW_tovconfig(L, 2, cfg)) {
4787  // Second argument is a location filter
4788  const terrain_filter filter(cfg, &game_state_, false);
4789  filter.get_locations(locs, true);
4790  } else {
4791  // Second argument is an array of locations
4792  luaW_check_locationset(L, 2);
4793  }
4794  }
4795 
4796  tod_man().add_time_area(id, locs, times);
4797  LOG_LUA << "Lua inserted time_area '" << id << "'";
4798  return 0;
4799 }
4800 
4801 /** Removing new time_areas dynamically with Standard Location Filters. */
4803 {
4804  log_scope("remove_time_area");
4805 
4806  const char * id = luaL_checkstring(L, 1);
4807  tod_man().remove_time_area(id);
4808  LOG_LUA << "Lua removed time_area '" << id << "'";
4809 
4810  return 0;
4811 }
4812 
4814 {
4815  map_location loc;
4816  if(luaW_tolocation(L, 1, loc)) {
4817  int area_index = tod_man().get_area_on_hex(loc).first;
4818  if(area_index < 0) {
4819  lua_pushnil(L);
4820  return 1;
4821  }
4822  luaW_push_schedule(L, area_index);
4823  return 1;
4824  } else {
4825  std::string area_id = luaL_checkstring(L, 1);
4826  const auto& area_ids = tod_man().get_area_ids();
4827  if(auto iter = std::find(area_ids.begin(), area_ids.end(), area_id); iter == area_ids.end()) {
4828  lua_pushnil(L);
4829  return 1;
4830  } else {
4831  luaW_push_schedule(L, std::distance(area_ids.begin(), iter));
4832  return 1;
4833  }
4834  }
4835 }
4836 
4837 /** Replacing the current time of day schedule. */
4839 {
4840  map_location loc;
4841  if(luaL_testudata(L, 1, "schedule")) {
4842  // Replace the global schedule with a time area's schedule
4843  // Replacing the global schedule with the global schedule
4844  // is also supported but obviously a no-op
4845  int area = luaW_check_schedule(L, 1);
4846  if(area >= 0) tod_man().replace_schedule(tod_man().times(area));
4847  } else {
4848  vconfig cfg = luaW_checkvconfig(L, 1);
4849 
4850  if(cfg.get_children("time").empty()) {
4851  ERR_LUA << "attempted to to replace ToD schedule with empty schedule";
4852  } else {
4854  if (game_display_) {
4856  }
4857  LOG_LUA << "replaced ToD schedule";
4858  }
4859  }
4860  return 0;
4861 }
4862 
4864 {
4865  if (game_display_) {
4866  point scroll_to(luaL_checkinteger(L, 1), luaL_checkinteger(L, 2));
4867  game_display_->scroll(scroll_to, true);
4868 
4869  lua_remove(L, 1);
4870  lua_remove(L, 1);
4871  lua_push(L, 25);
4872  intf_delay(L);
4873  }
4874 
4875  return 0;
4876 }
4877 
4878 namespace {
4879  struct lua_report_generator : reports::generator
4880  {
4881  lua_State *mState;
4882  std::string name;
4883  lua_report_generator(lua_State *L, const std::string &n)
4884  : mState(L), name(n) {}
4885  virtual config generate(const reports::context & rc);
4886  };
4887 
4888  config lua_report_generator::generate(const reports::context & /*rc*/)
4889  {
4890  lua_State *L = mState;
4891  config cfg;
4892  if (!luaW_getglobal(L, "wesnoth", "interface", "game_display", name))
4893  return cfg;
4894  if (!luaW_pcall(L, 0, 1)) return cfg;
4895  luaW_toconfig(L, -1, cfg);
4896  lua_pop(L, 1);
4897  return cfg;
4898  }
4899 }//unnamed namespace for lua_report_generator
4900 
4901 /**
4902  * Executes its upvalue as a theme item generator.
4903  */
4904 int game_lua_kernel::impl_theme_item(lua_State *L, const std::string& m)
4905 {
4907  luaW_pushconfig(L, reports_.generate_report(m.c_str(), temp_context , true));
4908  return 1;
4909 }
4910 
4911 /**
4912  * Creates a field of the theme_items table and returns it (__index metamethod).
4913  */
4915 {
4916  char const *m = luaL_checkstring(L, 2);
4917  lua_cpp::push_closure(L, std::bind(&game_lua_kernel::impl_theme_item, this, std::placeholders::_1, std::string(m)), 0);
4918  lua_pushvalue(L, 2);
4919  lua_pushvalue(L, -2);
4920  lua_rawset(L, 1);
4921  reports_.register_generator(m, new lua_report_generator(L, m));
4922  return 1;
4923 }
4924 
4925 /**
4926  * Sets a field of the theme_items table (__newindex metamethod).
4927  */
4929 {
4930  char const *m = luaL_checkstring(L, 2);
4931  lua_pushvalue(L, 2);
4932  lua_pushvalue(L, 3);
4933  lua_rawset(L, 1);
4934  reports_.register_generator(m, new lua_report_generator(L, m));
4935  return 0;
4936 }
4937 
4938 /**
4939  * Get all available theme_items (__dir metamethod).
4940  */
4942 {
4944  return 1;
4945 }
4946 
4947 /**
4948  * Gets all the WML variables currently set.
4949  * - Ret 1: WML table
4950  */
4952  luaW_pushconfig(L, gamedata().get_variables());
4953  return 1;
4954 }
4955 
4956 /**
4957  * Teeleports a unit to a location.
4958  * Arg 1: unit
4959  * Arg 2: target location
4960  * Arg 3: bool (ignore_passability)
4961  * Arg 4: bool (clear_shroud)
4962  * Arg 5: bool (animate)
4963  */
4965 {
4966  events::command_disabler command_disabler;
4967  unit_ptr u = luaW_checkunit_ptr(L, 1, true);
4969  bool check_passability = !luaW_toboolean(L, 3);
4970  bool clear_shroud = luaW_toboolean(L, 4);
4971  bool animate = luaW_toboolean(L, 5);
4972 
4973  if (dst == u->get_location() || !map().on_board(dst)) {
4974  return 0;
4975  }
4976  const map_location vacant_dst = find_vacant_tile(dst, pathfind::VACANT_ANY, check_passability ? u.get() : nullptr);
4977  if (!map().on_board(vacant_dst)) {
4978  return 0;
4979  }
4980  // Clear the destination hex before the move (so the animation can be seen).
4981  actions::shroud_clearer clearer;
4982  if ( clear_shroud ) {
4983  clearer.clear_dest(vacant_dst, *u);
4984  }
4985 
4986  map_location src_loc = u->get_location();
4987 
4988  std::vector<map_location> teleport_path;
4989  teleport_path.push_back(src_loc);
4990  teleport_path.push_back(vacant_dst);
4991  unit_display::move_unit(teleport_path, u, animate);
4992 
4993  units().move(src_loc, vacant_dst);
4995 
4996  u = units().find(vacant_dst).get_shared_ptr();
4997  u->anim_comp().set_standing();
4998 
4999  if ( clear_shroud ) {
5000  // Now that the unit is visibly in position, clear the shroud.
5001  clearer.clear_unit(vacant_dst, *u);
5002  }
5003 
5004  if (map().is_village(vacant_dst)) {
5005  actions::get_village(vacant_dst, u->side());
5006  }
5007 
5008  game_display_->invalidate_unit_after_move(src_loc, vacant_dst);
5009 
5010  // Sighted events.
5011  clearer.fire_events();
5012  return 0;
5013 }
5014 
5015 /**
5016  * Logs a message
5017  * Arg 1: (optional) Logger; "wml" for WML errors or deprecations
5018  * Arg 2: Message
5019  * Arg 3: Whether to print to chat (always true if arg 1 is "wml")
5020  */
5021 int game_lua_kernel::intf_log(lua_State *L)
5022 {
5023  const std::string& logger = lua_isstring(L, 2) ? luaL_checkstring(L, 1) : "";
5024  const std::string& msg = lua_isstring(L, 2) ? luaL_checkstring(L, 2) : luaL_checkstring(L, 1);
5025 
5026  if(logger == "wml" || logger == "WML") {
5027  lg::log_to_chat() << msg << '\n';
5028  ERR_WML << msg;
5029  } else {
5030  bool in_chat = luaW_toboolean(L, -1);
5031  game_state_.events_manager_->pump().put_wml_message(logger,msg,in_chat);
5032  }
5033  return 0;
5034 }
5035 
5036 int game_lua_kernel::intf_get_fog_or_shroud(lua_State *L, bool fog)
5037 {
5038  team& t = luaW_checkteam(L, 1, board());
5040  lua_pushboolean(L, fog ? t.fogged(loc) : t.shrouded(loc));
5041  return 1;
5042 }
5043 
5044 /**
5045  * Implements the lifting and resetting of fog via WML.
5046  * Keeping affect_normal_fog as false causes only the fog override to be affected.
5047  * Otherwise, fog lifting will be implemented similar to normal sight (cannot be
5048  * individually reset and ends at the end of the turn), and fog resetting will, in
5049  * addition to removing overrides, extend the specified teams' normal fog to all
5050  * hexes.
5051  *
5052  * Arg 1: (optional) Side number, or list of side numbers
5053  * Arg 2: List of locations; each is a two-element array or a table with x and y keys
5054  * Arg 3: (optional) boolean
5055  */
5056 int game_lua_kernel::intf_toggle_fog(lua_State *L, const bool clear)
5057 {
5058  bool affect_normal_fog = false;
5059  if(lua_isboolean(L, -1)) {
5060  affect_normal_fog = luaW_toboolean(L, -1);
5061  }
5062  std::set<int> sides;
5063  if(team* t = luaW_toteam(L, 1)) {
5064  sides.insert(t->side());
5065  } else if(lua_isnumber(L, 1)) {
5066  sides.insert(lua_tointeger(L, 1));
5067  } else if(lua_istable(L, 1) && lua_istable(L, 2)) {
5068  const auto& v = lua_check<std::vector<int>>(L, 1);
5069  sides.insert(v.begin(), v.end());
5070  } else {
5071  for(const team& t : teams()) {
5072  sides.insert(t.side()+1);
5073  }
5074  }
5075  const auto& locs = luaW_check_locationset(L, lua_istable(L, 2) ? 2 : 1);
5076 
5077  for(const int &side_num : sides) {
5078  if(side_num < 1 || static_cast<std::size_t>(side_num) > teams().size()) {
5079  continue;
5080  }
5081  team &t = board().get_team(side_num);
5082  if(!clear) {
5083  // Extend fog.
5084  t.remove_fog_override(locs);
5085  if(affect_normal_fog) {
5086  t.refog();
5087  }
5088  } else if(!affect_normal_fog) {
5089  // Force the locations clear of fog.
5090  t.add_fog_override(locs);
5091  } else {
5092  // Simply clear fog from the locations.
5093  for(const map_location &hex : locs) {
5094  t.clear_fog(hex);
5095  }
5096  }
5097  }
5098 
5099  // Flag a screen update.
5102  return 0;
5103 }
5104 
5105 // Invokes a synced command
5106 static int intf_invoke_synced_command(lua_State* L)
5107 {
5108  const std::string name = luaL_checkstring(L, 1);
5109  auto it = synced_command::registry().find(name);
5110  config cmd;
5111  if(it == synced_command::registry().end()) {
5112  // Custom command
5113  if(!luaW_getglobal(L, "wesnoth", "custom_synced_commands", name)) {
5114  return luaL_argerror(L, 1, "Unknown synced command");
5115  }
5116  config& cmd_tag = cmd.child_or_add("custom_command");
5117  cmd_tag["name"] = name;
5118  if(!lua_isnoneornil(L, 2)) {
5119  cmd_tag.add_child("data", luaW_checkconfig(L, 2));
5120  }
5121  } else {
5122  // Built-in command
5123  cmd.add_child(name, luaW_checkconfig(L, 2));
5124  }
5125  // Now just forward to the WML action.
5126  luaW_getglobal(L, "wesnoth", "wml_actions", "do_command");
5127  luaW_pushconfig(L, cmd);
5128  luaW_pcall(L, 1, 0);
5129  return 0;
5130 }
5131 
5135 };
5136 #define CALLBACK_GETTER(name, type) LATTR_GETTER(name, lua_index_raw, callbacks_tag, ) { lua_pushcfunction(L, &impl_null_callback<type>); return lua_index_raw(L); }
5138 
5139 template<typename Ret>
5140 static int impl_null_callback(lua_State* L) {
5141  if constexpr(std::is_same_v<Ret, void>) return 0;
5142  else lua_push(L, Ret());
5143  return 1;
5144 };
5145 
5146 template<> struct lua_object_traits<callbacks_tag> {
5147  inline static auto metatable = "game_events";
5148  inline static game_lua_kernel& get(lua_State* L, int) {
5149  return lua_kernel_base::get_lua_kernel<game_lua_kernel>(L);
5150  }
5151 };
5152 
5153 namespace {
5154 CALLBACK_GETTER("on_event", void);
5155 CALLBACK_GETTER("on_load", void);
5156 CALLBACK_GETTER("on_save", config);
5157 CALLBACK_GETTER("on_mouse_action", void);
5158 CALLBACK_GETTER("on_mouse_button", bool);
5159 CALLBACK_GETTER("on_mouse_move", void);
5160 }
5161 
5162 static int impl_game_events_dir(lua_State* L) {
5163  return callbacksReg.dir(L);
5164 }
5165 
5166 static int impl_game_events_get(lua_State* L) {
5167  return callbacksReg.get(L);
5168 }
5169 
5170 template<typename Ret = void>
5171 static bool impl_get_callback(lua_State* L, const std::string& name) {
5172  int top = lua_gettop(L);
5173  if(!luaW_getglobal(L, "wesnoth", "game_events")) {
5174  return false;
5175  }
5176  lua_getfield(L, -1, name.c_str()); // calls impl_game_events_get
5177  lua_pushcfunction(L, &impl_null_callback<Ret>);
5178  if(lua_rawequal(L, -1, -2)) {
5179  lua_settop(L, top);
5180  return false;
5181  }
5182  lua_pop(L, 1);
5183  lua_remove(L, -2);
5184  return true;
5185 }
5186 
5187 // END CALLBACK IMPLEMENTATION
5188 
5190  return game_state_.board_;
5191 }
5192 
5194  return game_state_.board_.units();
5195 }
5196 
5197 std::vector<team> & game_lua_kernel::teams() {
5198  return game_state_.board_.teams();
5199 }
5200 
5202  return game_state_.board_.map();
5203 }
5204 
5206  return game_state_.gamedata_;
5207 }
5208 
5210  return game_state_.tod_manager_;
5211 }
5212 
5214  return *queued_events_.top();
5215 }
5216 
5217 
5219  : lua_kernel_base()
5220  , game_display_(nullptr)
5221  , game_state_(gs)
5222  , play_controller_(pc)
5223  , reports_(reports_object)
5224  , level_lua_()
5225  , EVENT_TABLE(LUA_NOREF)
5226  , queued_events_()
5227  , map_locked_(0)
5228 {
5229  static game_events::queued_event default_queued_event("_from_lua", "", map_location(), map_location(), config());
5230  queued_events_.push(&default_queued_event);
5231 
5232  lua_State *L = mState;
5233 
5234  cmd_log_ << "Registering game-specific wesnoth lib functions...\n";
5235 
5236  // Put some callback functions in the scripting environment.
5237  static luaL_Reg const callbacks[] {
5238  { "add_known_unit", &intf_add_known_unit },
5239  { "get_era", &intf_get_era },
5240  { "get_resource", &intf_get_resource },
5241  { "modify_ai", &intf_modify_ai_old },
5242  { "cancel_action", &dispatch<&game_lua_kernel::intf_cancel_action > },
5243  { "log_replay", &dispatch<&game_lua_kernel::intf_log_replay > },
5244  { "log", &dispatch<&game_lua_kernel::intf_log > },
5245  { "redraw", &dispatch<&game_lua_kernel::intf_redraw > },
5246  { "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > },
5247  { nullptr, nullptr }
5248  };lua_getglobal(L, "wesnoth");
5249  if (!lua_istable(L,-1)) {
5250  lua_newtable(L);
5251  }
5252  luaL_setfuncs(L, callbacks, 0);
5253 
5254  lua_setglobal(L, "wesnoth");
5255 
5256  lua_getglobal(L, "gui");
5257  lua_pushcfunction(L, &dispatch<&game_lua_kernel::intf_gamestate_inspector>);
5258  lua_setfield(L, -2, "show_inspector");
5259  lua_pushcfunction(L, &lua_gui2::intf_show_recruit_dialog);
5260  lua_setfield(L, -2, "show_recruit_dialog");
5261  lua_pushcfunction(L, &lua_gui2::intf_show_recall_dialog);
5262  lua_setfield(L, -2, "show_recall_dialog");
5263  lua_pop(L, 1);
5264 
5266  // Create the unit_test module
5267  lua_newtable(L);
5268  static luaL_Reg const test_callbacks[] {
5269  { "fire_wml_menu_item", &dispatch<&game_lua_kernel::intf_fire_wml_menu_item> },
5270  { nullptr, nullptr }
5271  };
5272  luaL_setfuncs(L, test_callbacks, 0);
5273  lua_setglobal(L, "unit_test");
5274  }
5275 
5276  // Create the getside metatable.
5278 
5279  // Create the gettype metatable.
5281 
5282  //Create the getrace metatable
5284 
5285  //Create the unit metatables
5288 
5289  // Create the vconfig metatable.
5291 
5292  // Create the unit_types table
5294 
5295  // Create the unit_types table
5297 
5298  // Create the unit_types table
5299  cmd_log_ << "Adding terrain_types table...\n";
5300  lua_getglobal(L, "wesnoth");
5301  lua_newuserdatauv(L, 0, 0);
5302  lua_createtable(L, 0, 2);
5303  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_get_terrain_info>);
5304  lua_setfield(L, -2, "__index");
5305  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_get_terrain_list>);
5306  lua_setfield(L, -2, "__dir");
5307  lua_pushstring(L, "terrain types");
5308  lua_setfield(L, -2, "__metatable");
5309  lua_setmetatable(L, -2);
5310  lua_setfield(L, -2, "terrain_types");
5311  lua_pop(L, 1);
5312 
5313  // Create the ai elements table.
5314  cmd_log_ << "Adding ai elements table...\n";
5315 
5317 
5318  // Create the current variable with its metatable.
5319  cmd_log_ << "Adding wesnoth current table...\n";
5320 
5321  lua_getglobal(L, "wesnoth");
5322  lua_newuserdatauv(L, 0, 0);
5323  lua_createtable(L, 0, 2);
5324  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_current_get>);
5325  lua_setfield(L, -2, "__index");
5326  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_current_dir>);
5327  lua_setfield(L, -2, "__dir");
5328  lua_pushboolean(L, true);
5329  lua_setfield(L, -2, "__dir_tablelike");
5330  lua_pushstring(L, "current config");
5331  lua_setfield(L, -2, "__metatable");
5332  lua_setmetatable(L, -2);
5333  lua_setfield(L, -2, "current");
5334  lua_pop(L, 1);
5335 
5336  // Add functions to the WML module
5337  lua_getglobal(L, "wml");
5338  static luaL_Reg const wml_callbacks[] {
5339  {"tovconfig", &lua_common::intf_tovconfig},
5340  {"eval_conditional", &intf_eval_conditional},
5341  // These aren't actually part of the API - they're used internally by the variable metatable.
5342  { "get_variable", &dispatch<&game_lua_kernel::intf_get_variable>},
5343  { "set_variable", &dispatch<&game_lua_kernel::intf_set_variable>},
5344  { "get_all_vars", &dispatch<&game_lua_kernel::intf_get_all_vars>},
5345  { nullptr, nullptr }
5346  };
5347  luaL_setfuncs(L, wml_callbacks, 0);
5348  lua_pop(L, 1);
5349 
5350  // Add functions to the map module
5351  luaW_getglobal(L, "wesnoth", "map");
5352  static luaL_Reg const map_callbacks[] {
5353  // Map methods
5354  {"terrain_mask", &intf_terrain_mask},
5355  {"on_board", &intf_on_board},
5356  {"on_border", &intf_on_border},
5357  {"iter", &intf_terrainmap_iter},
5358  // Village operations
5359  {"get_owner", &dispatch<&game_lua_kernel::intf_get_village_owner>},
5360  {"set_owner", &dispatch<&game_lua_kernel::intf_set_village_owner>},
5361  // Label operations
5362  {"add_label", &dispatch<&game_lua_kernel::intf_add_label>},
5363  {"remove_label", &dispatch<&game_lua_kernel::intf_remove_label>},
5364  {"get_label", &dispatch<&game_lua_kernel::intf_get_label>},
5365  // Time area operations
5366  {"place_area", &dispatch<&game_lua_kernel::intf_add_time_area>},
5367  {"remove_area", &dispatch<&game_lua_kernel::intf_remove_time_area>},
5368  {"get_area", &dispatch<&game_lua_kernel::intf_get_time_area>},
5369  // Filters
5370  {"find", &dispatch<&game_lua_kernel::intf_get_locations>},
5371  {"matches", &dispatch<&game_lua_kernel::intf_match_location>},
5372  {"replace_if_failed", intf_replace_if_failed},
5373  { nullptr, nullptr }
5374  };
5375  luaL_setfuncs(L, map_callbacks, 0);
5376  lua_pop(L, 1);
5377 
5378  // Create the units module
5379  cmd_log_ << "Adding units module...\n";
5380  static luaL_Reg const unit_callbacks[] {
5381  {"advance", &intf_advance_unit},
5382  {"clone", &intf_copy_unit},
5383  {"erase", &dispatch<&game_lua_kernel::intf_erase_unit>},
5384  {"extract", &dispatch<&game_lua_kernel::intf_extract_unit>},
5385  {"matches", &dispatch<&game_lua_kernel::intf_match_unit>},
5386  {"select", &dispatch<&game_lua_kernel::intf_select_unit>},
5387  {"to_map", &dispatch<&game_lua_kernel::intf_put_unit>},
5388  {"to_recall", &dispatch<&game_lua_kernel::intf_put_recall_unit>},
5389  {"transform", &intf_transform_unit},
5390  {"teleport", &dispatch<&game_lua_kernel::intf_teleport>},
5391 
5392  {"ability", &dispatch<&game_lua_kernel::intf_unit_ability>},
5393  {"defense_on", &intf_unit_defense},
5394  {"jamming_on", &intf_unit_jamming_cost},
5395  {"movement_on", &intf_unit_movement_cost},
5396  {"resistance_against", intf_unit_resistance},
5397  {"vision_on", &intf_unit_vision_cost},
5398 
5399  {"add_modification", &intf_add_modification},
5400  {"remove_modifications", &intf_remove_modifications},
5401  // Static functions
5402  {"create", &intf_create_unit},
5403  {"find_on_map", &dispatch<&game_lua_kernel::intf_get_units>},
5404  {"find_on_recall", &dispatch<&game_lua_kernel::intf_get_recall_units>},
5405  {"get", &dispatch<&game_lua_kernel::intf_get_unit>},
5406  {"get_hovered", &dispatch<&game_lua_kernel::intf_get_displayed_unit>},
5407  {"create_animator", &dispatch<&game_lua_kernel::intf_create_animator>},
5408  {"create_weapon", intf_create_attack},
5409 
5410  { nullptr, nullptr }
5411  };
5412  lua_getglobal(L, "wesnoth");
5413  lua_newtable(L);
5414  luaL_setfuncs(L, unit_callbacks, 0);
5415  lua_setfield(L, -2, "units");
5416  lua_pop(L, 1);
5417 
5418  // Create sides module
5419  cmd_log_ << "Adding sides module...\n";
5420  static luaL_Reg const side_callbacks[] {
5421  { "is_enemy", &dispatch<&game_lua_kernel::intf_is_enemy> },
5422  { "matches", &dispatch<&game_lua_kernel::intf_match_side> },
5423  { "set_id", &dispatch<&game_lua_kernel::intf_set_side_id> },
5424  { "append_ai", &intf_append_ai },
5425  { "debug_ai", &intf_debug_ai },
5426  { "switch_ai", &intf_switch_ai },
5427  // Static functions
5428  { "find", &dispatch<&game_lua_kernel::intf_get_sides> },
5429  { "get", &dispatch<&game_lua_kernel::intf_get_side> },
5430  { "create", &dispatch<&game_lua_kernel::intf_create_side> },
5431  // Shroud operations
5432  {"place_shroud", &dispatch2<&game_lua_kernel::intf_toggle_shroud, true>},
5433  {"remove_shroud", &dispatch2<&game_lua_kernel::intf_toggle_shroud, false>},
5434  {"override_shroud", &dispatch<&game_lua_kernel::intf_override_shroud>},
5435  {"is_shrouded", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, false>},
5436  // Fog operations
5437  {"place_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, false>},
5438  {"remove_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, true>},
5439  {"is_fogged", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, true>},
5440  { nullptr, nullptr }
5441  };
5442  std::vector<lua_cpp::Reg> const cpp_side_callbacks {
5443  {"add_ai_component", std::bind(intf_modify_ai, std::placeholders::_1, "add")},
5444  {"delete_ai_component", std::bind(intf_modify_ai, std::placeholders::_1, "delete")},
5445  {"change_ai_component", std::bind(intf_modify_ai, std::placeholders::_1, "change")},
5446  {nullptr, nullptr}
5447  };
5448 
5449  lua_getglobal(L, "wesnoth");
5450  lua_newtable(L);
5451  luaL_setfuncs(L, side_callbacks, 0);
5452  lua_cpp::set_functions(L, cpp_side_callbacks);
5453  lua_setfield(L, -2, "sides");
5454  lua_pop(L, 1);
5455 
5456  // Create the interface module
5457  cmd_log_ << "Adding interface module...\n";
5458  static luaL_Reg const intf_callbacks[] {
5459  {"add_hex_overlay", &dispatch<&game_lua_kernel::intf_add_tile_overlay>},
5460  {"remove_hex_overlay", &dispatch<&game_lua_kernel::intf_remove_tile_overlay>},
5461  {"get_color_adjust", &dispatch<&game_lua_kernel::intf_get_color_adjust>},
5462  {"color_adjust", &dispatch<&game_lua_kernel::intf_color_adjust>},
5463  {"screen_fade", &dispatch<&game_lua_kernel::intf_screen_fade>},
5464  {"delay", &dispatch<&game_lua_kernel::intf_delay>},
5465  {"deselect_hex", &dispatch<&game_lua_kernel::intf_deselect_hex>},
5466  {"highlight_hex", &dispatch<&game_lua_kernel::intf_highlight_hex>},
5467  {"float_label", &dispatch<&game_lua_kernel::intf_float_label>},
5468  {"get_displayed_unit", &dispatch<&game_lua_kernel::intf_get_displayed_unit>},
5469  {"get_hovered_hex", &dispatch<&game_lua_kernel::intf_get_mouseover_tile>},
5470  {"get_selected_hex", &dispatch<&game_lua_kernel::intf_get_selected_tile>},
5471  {"lock", &dispatch<&game_lua_kernel::intf_lock_view>},
5472  {"is_locked", &dispatch<&game_lua_kernel::intf_view_locked>},
5473  {"scroll", &dispatch<&game_lua_kernel::intf_scroll>},
5474  {"scroll_to_hex", &dispatch<&game_lua_kernel::intf_scroll_to_tile>},
5475  {"skip_messages", &dispatch<&game_lua_kernel::intf_skip_messages>},
5476  {"is_skipping_messages", &dispatch<&game_lua_kernel::intf_is_skipping_messages>},
5477  {"zoom", &dispatch<&game_lua_kernel::intf_zoom>},
5478  {"clear_menu_item", &dispatch<&game_lua_kernel::intf_clear_menu_item>},
5479  {"set_menu_item", &dispatch<&game_lua_kernel::intf_set_menu_item>},
5480  {"allow_end_turn", &dispatch<&game_lua_kernel::intf_allow_end_turn>},
5481  {"clear_chat_messages", &dispatch<&game_lua_kernel::intf_clear_messages>},
5482  {"end_turn", &dispatch<&game_lua_kernel::intf_end_turn>},
5483  {"get_viewing_side", &intf_get_viewing_side},
5484  {"add_chat_message", &dispatch<&game_lua_k