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