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