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