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