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