The Battle for Wesnoth  1.17.8+dev
game_lua_kernel.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2022
3  by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Provides a Lua interpreter, to be embedded in WML.
19  *
20  * @note Naming conventions:
21  * - intf_ functions are exported in the wesnoth domain,
22  * - impl_ functions are hidden inside metatables,
23  * - cfun_ functions are closures,
24  * - luaW_ functions are helpers in Lua style.
25  */
26 
28 
29 #include "actions/attack.hpp" // for battle_context_unit_stats, etc
30 #include "actions/advancement.hpp" // for advance_unit_at, etc
31 #include "actions/move.hpp" // for clear_shroud
32 #include "actions/vision.hpp" // for clear_shroud and create_jamming_map
33 #include "ai/composite/ai.hpp" // for ai_composite
34 #include "ai/composite/component.hpp" // for component, etc
35 #include "ai/composite/contexts.hpp" // for ai_context
36 #include "ai/lua/engine_lua.hpp" // for engine_lua
37 #include "ai/composite/rca.hpp" // for candidate_action
38 #include "ai/composite/stage.hpp" // for stage
39 #include "ai/configuration.hpp" // for configuration
40 #include "ai/lua/core.hpp" // for lua_ai_context, etc
41 #include "ai/manager.hpp" // for manager, holder
42 #include "attack_prediction.hpp" // for combatant
43 #include "chat_events.hpp" // for chat_handler, etc
44 #include "config.hpp" // for config, etc
45 #include "display_chat_manager.hpp" // for clear_chat_messages
46 #include "floating_label.hpp"
47 #include "formatter.hpp"
48 #include "game_board.hpp" // for game_board
49 #include "game_classification.hpp" // for game_classification, etc
50 #include "game_config.hpp" // for debug, base_income, etc
51 #include "game_config_manager.hpp" // for game_config_manager
52 #include "game_data.hpp" // for game_data, etc
53 #include "game_display.hpp" // for game_display
54 #include "game_errors.hpp" // for game_error
55 #include "game_events/conditional_wml.hpp" // for conditional_passed
57 #include "game_events/handlers.hpp"
58 #include "game_events/manager_impl.hpp" // for pending_event_handler
59 #include "game_events/pump.hpp" // for queued_event
60 #include "preferences/game.hpp" // for encountered_units
61 #include "log.hpp" // for LOG_STREAM, logger, etc
62 #include "map/map.hpp" // for gamemap
63 #include "map/label.hpp"
64 #include "map/location.hpp" // for map_location
65 #include "mouse_events.hpp" // for mouse_handler
66 #include "mp_game_settings.hpp" // for mp_game_settings
67 #include "pathfind/pathfind.hpp" // for full_cost_map, plain_route, etc
68 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
69 #include "play_controller.hpp" // for play_controller
70 #include "recall_list_manager.hpp" // for recall_list_manager
71 #include "replay.hpp" // for get_user_choice, etc
72 #include "reports.hpp" // for register_generator, etc
73 #include "resources.hpp" // for whiteboard
74 #include "scripting/lua_audio.hpp"
75 #include "scripting/lua_unit.hpp"
77 #include "scripting/lua_common.hpp"
79 #include "scripting/lua_gui2.hpp" // for show_gamestate_inspector
81 #include "scripting/lua_race.hpp"
82 #include "scripting/lua_team.hpp"
85 #include "scripting/push_check.hpp"
86 #include "synced_commands.hpp"
87 #include "color.hpp" // for surface
88 #include "sdl/surface.hpp" // for surface
89 #include "side_filter.hpp" // for side_filter
90 #include "sound.hpp" // for commit_music_changes, etc
91 #include "soundsource.hpp"
92 #include "synced_context.hpp" // for synced_context, etc
93 #include "synced_user_choice.hpp"
94 #include "team.hpp" // for team, village_owner
95 #include "terrain/terrain.hpp" // for terrain_type
96 #include "terrain/filter.hpp" // for terrain_filter
97 #include "terrain/translation.hpp" // for read_terrain_code, etc
98 #include "time_of_day.hpp" // for time_of_day
99 #include "tod_manager.hpp" // for tod_manager
100 #include "tstring.hpp" // for t_string, operator+
101 #include "units/unit.hpp" // for unit
102 #include "units/animation_component.hpp" // for unit_animation_component
103 #include "units/udisplay.hpp"
104 #include "units/filter.hpp"
105 #include "units/map.hpp" // for unit_map, etc
106 #include "units/ptr.hpp" // for unit_const_ptr, unit_ptr
107 #include "units/types.hpp" // for unit_type_data, unit_types, etc
108 #include "utils/scope_exit.hpp"
109 #include "variable.hpp" // for vconfig, etc
110 #include "variable_info.hpp"
111 #include "video.hpp" // only for faked
112 #include "whiteboard/manager.hpp" // for whiteboard
113 #include "wml_exception.hpp"
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 {
178  game_lua_kernel::preload_scripts.clear();
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.child("game_config");
183 }
184 
185 void game_lua_kernel::log_error(char const * msg, char const * context)
186 {
187  lua_kernel_base::log_error(msg, context);
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));
379  play_controller_.play_slice(false);
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_) {
423  return lua_gui2::show_gamestate_inspector(luaW_checkvconfig(L, 1), gamedata(), game_state_);
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 
471  unit_map::const_iterator ui = board().find_visible_unit(
472  game_display_->displayed_unit_hex(),
473  teams()[game_display_->viewing_team()],
474  game_display_->show_everything());
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.child("primary_attack"));
637  data.remove_children("primary_attack");
638  }
639  if(data.has_child("secondary_attack")) {
640  data.add_child("second", data.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 
674  bool b = game_state_.get_wml_menu_items().fire_item(m, l1, gamedata(), game_state_, units());
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);
688  variable_access_const v = gamedata().get_variable_access_read(m);
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)) {
702  gamedata().clear_variable(m);
703  return 0;
704  }
705  variable_access_create v = gamedata().get_variable_access_write(m);
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;
715  game_state_.add_side_wml(cfg);
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  }
735  game_state_.get_wml_menu_items().erase(id);
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 
764  game_display_->labels().recalculate_shroud();
765  game_display_->recalculate_minimap();
766  game_display_->invalidate_all();
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 
790  game_display_->labels().recalculate_shroud();
791  game_display_->recalculate_minimap();
792  game_display_->invalidate_all();
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");
809  game_display_->highlight_hex(loc);
810  game_display_->display_unit_hex(loc);
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_) {
860  game_display_->set_view_locked(lock);
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) {
1008  tod_man().set_current_time(n);
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) {
1023  tod_man().reset_max_liminal_bonus();
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(const config& 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  char const *m = luaL_checkstring(L, 1);
1259  luaW_pushconfig(L, game_config_manager::get()->game_config().find_child("era","id",m));
1260  return 1;
1261 }
1262 
1263 /**
1264  * Gets some game_config data (__index metamethod).
1265  * - Arg 1: userdata (ignored).
1266  * - Arg 2: string containing the name of the property.
1267  * - Ret 1: something containing the attribute.
1268  */
1270 {
1271  LOG_LUA << "impl_game_config_get";
1272  char const *m = luaL_checkstring(L, 2);
1273 
1274  // Find the corresponding attribute.
1275  return_int_attrib_deprecated("last_turn", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.turns instead", tod_man().number_of_turns());
1276  return_bool_attrib("do_healing", play_controller_.gamestate().do_healing_);
1277  return_string_attrib_deprecated("next_scenario", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.next instead", gamedata().next_scenario());
1278  return_string_attrib("theme", gamedata().get_theme());
1279  return_string_attrib_deprecated("scenario_id", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.id instead", gamedata().get_id());
1280  return_vector_string_attrib_deprecated("defeat_music", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.defeat_music instead",
1281  gamedata().get_defeat_music());
1282  return_vector_string_attrib_deprecated("victory_music", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.victory_music instead",
1283  gamedata().get_victory_music());
1284  return_vector_string_attrib_deprecated("active_resources", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.resources instead",
1285  utils::split(play_controller_.get_loaded_resources()));
1286 
1287  if(strcmp(m, "global_traits") == 0) {
1288  lua_newtable(L);
1289  for(const config& trait : unit_types.traits()) {
1290  const std::string& id = trait["id"];
1291  //It seems the engine never checks the id field for emptiness or duplicates
1292  //However, the worst that could happen is that the trait read later overwrites the older one,
1293  //and this is not the right place for such checks.
1294  lua_pushstring(L, id.c_str());
1295  luaW_pushconfig(L, trait);
1296  lua_rawset(L, -3);
1297  }
1298  return 1;
1299  }
1300 
1301  const mp_game_settings& mp_settings = play_controller_.get_mp_settings();
1302  const game_classification & classification = play_controller_.get_classification();
1303 
1304  return_string_attrib_deprecated("campaign_type", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.type instead", campaign_type::get_string(classification.type));
1305  if(classification.type==campaign_type::type::multiplayer) {
1306  return_cfgref_attrib_deprecated("mp_settings", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.mp_settings instead", mp_settings.to_config());
1307  return_cfgref_attrib_deprecated("era", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.era instead",
1308  game_config_manager::get()->game_config().find_child("era","id",classification.era_id));
1309  //^ finds the era with name matching mp_era, and creates a lua reference from the config of that era.
1310  }
1311 
1313 }
1314 
1315 /**
1316  * Sets some game_config data (__newindex metamethod).
1317  * - Arg 1: userdata (ignored).
1318  * - Arg 2: string containing the name of the property.
1319  * - Arg 3: something containing the attribute.
1320  */
1322 {
1323  LOG_LUA << "impl_game_config_set";
1324  char const *m = luaL_checkstring(L, 2);
1325 
1326  // Find the corresponding attribute.
1327  modify_int_attrib("base_income", game_config::base_income = value);
1328  modify_int_attrib("village_income", game_config::village_income = value);
1329  modify_int_attrib("village_support", game_config::village_support = value);
1330  modify_int_attrib("poison_amount", game_config::poison_amount = value);
1331  modify_int_attrib("rest_heal_amount", game_config::rest_heal_amount = value);
1332  modify_int_attrib("recall_cost", game_config::recall_cost = value);
1333  modify_int_attrib("kill_experience", game_config::kill_experience = value);
1334  modify_int_attrib("combat_experience", game_config::combat_experience = value);
1335  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));
1336  modify_bool_attrib("do_healing", play_controller_.gamestate().do_healing_ = value);
1337  modify_string_attrib_deprecated("next_scenario", "wesnoth.game_config", INDEFINITE, "1.17", "Use wesnoth.scenario.next instead", gamedata().set_next_scenario(value));
1338  modify_string_attrib("theme",
1339  gamedata().set_theme(value);
1340  game_display_->set_theme(value);
1341  );
1342  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)));
1343  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)));
1345 }
1346 
1347 namespace {
1348  static config find_addon(const std::string& type, const std::string& id)
1349  {
1350  return game_config_manager::get()->game_config().find_child(type, "id", id);
1351  }
1352 }
1353 
1354 static int impl_end_level_data_get(lua_State* L)
1355 {
1356  const end_level_data& data = *static_cast<end_level_data*>(lua_touserdata(L, 1));
1357  const char* m = luaL_checkstring(L, 2);
1358 
1359  return_bool_attrib("linger_mode", data.transient.linger_mode);
1360  return_bool_attrib("reveal_map", data.transient.reveal_map);
1361  return_bool_attrib("carryover_report", data.transient.carryover_report);
1362  return_bool_attrib("prescenario_save", data.prescenario_save);
1363  return_bool_attrib("replay_save", data.replay_save);
1364  return_bool_attrib("proceed_to_next_level", data.proceed_to_next_level);
1365  return_bool_attrib("is_victory", data.is_victory);
1366  return_bool_attrib("is_loss", !data.is_victory);
1367  return_cstring_attrib("result", data.is_victory ? level_result::victory : "loss"); // to match wesnoth.end_level()
1368  return_string_attrib("test_result", data.test_result);
1369  return_cfg_attrib("__cfg", data.to_config_full());
1370 
1371  return 0;
1372 }
1373 
1374 namespace {
1375  struct end_level_committer {
1376  end_level_committer(end_level_data& data, play_controller& pc) : data_(data), pc_(pc) {}
1377  ~end_level_committer() {
1378  pc_.set_end_level_data(data_);
1379  }
1380  private:
1381  end_level_data& data_;
1382  play_controller& pc_;
1383  };
1384 }
1385 
1387 {
1388  end_level_data& data = *static_cast<end_level_data*>(lua_touserdata(L, 1));
1389  const char* m = luaL_checkstring(L, 2);
1390  end_level_committer commit(data, play_controller_);
1391 
1392  modify_bool_attrib("linger_mode", data.transient.linger_mode = value);
1393  modify_bool_attrib("reveal_map", data.transient.reveal_map = value);
1394  modify_bool_attrib("carryover_report", data.transient.carryover_report = value);
1395  modify_bool_attrib("prescenario_save", data.prescenario_save = value);
1396  modify_bool_attrib("replay_save", data.replay_save = value);
1397  modify_string_attrib("test_result", data.test_result = value);
1398 
1399  return 0;
1400 }
1401 
1402 static int impl_end_level_data_collect(lua_State* L)
1403 {
1404  end_level_data* data = static_cast<end_level_data*>(lua_touserdata(L, 1));
1405  data->~end_level_data();
1406  return 0;
1407 }
1408 
1409 static int impl_mp_settings_get(lua_State* L)
1410 {
1411  void* p = lua_touserdata(L, lua_upvalueindex(1));
1412  const mp_game_settings& settings = static_cast<play_controller*>(p)->get_mp_settings();
1413  if(lua_type(L, 2) == LUA_TNUMBER) {
1414  // Simulates a WML table with one [options] child and a variable number of [addon] children
1415  // TODO: Deprecate this -> mp_settings.options and mp_settings.addons
1416  size_t i = luaL_checkinteger(L, 2);
1417  if(i == 1) {
1418  lua_createtable(L, 2, 0);
1419  lua_pushstring(L, "options");
1420  lua_seti(L, -2, 1);
1421  luaW_pushconfig(L, settings.options);
1422  lua_seti(L, -2, 2);
1423  return 1;
1424  } else if(i >= 2) {
1425  i -= 2;
1426  if(i < settings.addons.size()) {
1427  auto iter = settings.addons.begin();
1428  std::advance(iter, i);
1429  config cfg;
1430  iter->second.write(cfg);
1431  cfg["id"] = iter->first;
1432 
1433  lua_createtable(L, 2, 0);
1434  lua_pushstring(L, "addon");
1435  lua_seti(L, -2, 1);
1436  luaW_pushconfig(L, cfg);
1437  lua_seti(L, -2, 2);
1438  return 1;
1439  }
1440  }
1441  } else {
1442  char const *m = luaL_checkstring(L, 2);
1443  return_string_attrib("scenario", settings.name);
1444  return_string_attrib("game_name", settings.name);
1445  return_string_attrib("hash", settings.hash);
1446  return_string_attrib("mp_era_name", settings.mp_era_name);
1447  return_string_attrib("mp_scenario", settings.mp_scenario);
1448  return_string_attrib("mp_scenario_name", settings.mp_scenario_name);
1449  return_string_attrib("mp_campaign", settings.mp_campaign);
1450  return_string_attrib("side_users", utils::join_map(settings.side_users));
1451  return_int_attrib("experience_modifier", settings.xp_modifier);
1452  return_bool_attrib("mp_countdown", settings.mp_countdown);
1453  return_int_attrib("mp_countdown_init_time", settings.mp_countdown_init_time);
1454  return_int_attrib("mp_countdown_turn_bonus", settings.mp_countdown_turn_bonus);
1455  return_int_attrib("mp_countdown_reservoir_bonus", settings.mp_countdown_reservoir_time);
1456  return_int_attrib("mp_countdown_action_bonus", settings.mp_countdown_action_bonus);
1457  return_int_attrib("mp_num_turns", settings.num_turns);
1458  return_int_attrib("mp_village_gold", settings.village_gold);
1459  return_int_attrib("mp_village_support", settings.village_support);
1460  return_bool_attrib("mp_fog", settings.fog_game);
1461  return_bool_attrib("mp_shroud", settings.shroud_game);
1462  return_bool_attrib("mp_use_map_settings", settings.use_map_settings);
1463  return_bool_attrib("mp_random_start_time", settings.random_start_time);
1464  return_bool_attrib("observer", settings.allow_observers);
1465  return_bool_attrib("allow_observers", settings.allow_observers);
1466  return_bool_attrib("private_replay", settings.private_replay);
1467  return_bool_attrib("shuffle_sides", settings.shuffle_sides);
1468  return_string_attrib("random_faction_mode", random_faction_mode::get_string(settings.mode));
1469  return_cfgref_attrib("options", settings.options);
1470  if(strcmp(m, "savegame") == 0) {
1471  auto savegame = settings.saved_game;
1472  if(savegame == saved_game_mode::type::no) {
1473  lua_pushboolean(L, false);
1474  } else {
1476  }
1477  return 1;
1478  }
1479  if(strcmp(m, "side_players") == 0) {
1480  lua_push(L, settings.side_users);
1481  return 1;
1482  }
1483  if(strcmp(m, "addons") == 0) {
1484  for(const auto& [id, addon] : settings.addons) {
1485  lua_createtable(L, 0, 4);
1486  lua_push(L, id);
1487  lua_setfield(L, -2, "id");
1488  lua_push(L, addon.name);
1489  lua_setfield(L, -2, "name");
1490  lua_pushboolean(L, addon.required);
1491  lua_setfield(L, -2, "required");
1492  if(addon.min_version) {
1493  luaW_getglobal(L, "wesnoth", "version");
1494  lua_push(L, addon.min_version->str());
1495  lua_call(L, 1, 1);
1496  lua_setfield(L, -2, "min_version");
1497  }
1498  if(addon.version) {
1499  luaW_getglobal(L, "wesnoth", "version");
1500  lua_push(L, addon.version->str());
1501  lua_call(L, 1, 1);
1502  lua_setfield(L, -2, "version");
1503  }
1504  lua_createtable(L, addon.content.size(), 0);
1505  for(const auto& content : addon.content) {
1506  lua_createtable(L, 0, 3);
1507  lua_push(L, content.id);
1508  lua_setfield(L, -2, "id");
1509  lua_push(L, content.name);
1510  lua_setfield(L, -2, "name");
1511  lua_push(L, content.type);
1512  lua_setfield(L, -2, "type");
1513  lua_seti(L, -2, lua_rawlen(L, -2) + 1);
1514  }
1515  lua_setfield(L, -2, "content");
1516  }
1517  return 1;
1518  }
1519  // Deprecated things that were moved out of mp_settings and into game_classification
1520  const game_classification& game = static_cast<play_controller*>(p)->get_classification();
1521  return_string_attrib_deprecated("mp_era", "wesnoth.scenario.mp_settings", INDEFINITE, "1.17", "Use wesnoth.scenario.era.id instead", game.era_id);
1522  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));
1523  // Expose the raw config; this is a way to ensure any new stuff can be accessed even if someone forgot to add it here.
1524  return_cfgref_attrib("__cfg", settings.to_config());
1525  }
1526  return 0;
1527 }
1528 
1529 static int impl_mp_settings_len(lua_State* L)
1530 {
1531  void* p = lua_touserdata(L, lua_upvalueindex(1));
1532  const mp_game_settings& settings = static_cast<play_controller*>(p)->get_mp_settings();
1533  lua_pushinteger(L, settings.addons.size() + 1);
1534  return 1;
1535 }
1536 
1537 /**
1538  * Gets some scenario data (__index metamethod).
1539  * - Arg 1: userdata (ignored).
1540  * - Arg 2: string containing the name of the property.
1541  * - Ret 1: something containing the attribute.
1542  */
1544 {
1545  LOG_LUA << "impl_scenario_get";
1546  char const *m = luaL_checkstring(L, 2);
1547 
1548  // Find the corresponding attribute.
1549  return_int_attrib("turns", tod_man().number_of_turns());
1551  return_string_attrib("id", gamedata().get_id());
1552  return_tstring_attrib("name", play_controller_.get_scenario_name());
1553  return_vector_string_attrib("defeat_music", gamedata().get_defeat_music());
1554  return_vector_string_attrib("victory_music", gamedata().get_victory_music());
1555  if(strcmp(m, "resources") == 0) {
1556  std::vector<config> resources;
1557  for(const std::string& rsrc : utils::split(play_controller_.get_loaded_resources())) {
1558  resources.push_back(find_addon("resource", rsrc));
1559  }
1560  lua_push(L, resources);
1561  return 1;
1562  }
1563 
1564  const game_classification& classification = play_controller_.get_classification();
1565  return_string_attrib("type", campaign_type::get_string(classification.type));
1566  return_string_attrib("difficulty", classification.difficulty);
1567  return_bool_attrib("show_credits", classification.end_credits);
1568  return_tstring_attrib("end_text", classification.end_text);
1569  return_int_attrib("end_text_duration", classification.end_text_duration);
1570  if(!classification.campaign.empty()) {
1571  return_cfgref_attrib("campaign", find_addon("campaign", classification.campaign));
1572  }
1573  if(strcmp(m, "modifications") == 0) {
1574  std::vector<config> mods;
1575  for(const std::string& mod : classification.active_mods) {
1576  mods.push_back(find_addon("modification", mod));
1577  }
1578  lua_push(L, mods);
1579  return 1;
1580  }
1581  if(strcmp(m, "end_level_data") == 0) {
1582  if (!play_controller_.is_regular_game_end()) {
1583  return 0;
1584  }
1585  auto data = play_controller_.get_end_level_data();
1586  new(L) end_level_data(data);
1587  if(luaL_newmetatable(L, "end level data")) {
1588  static luaL_Reg const callbacks[] {
1589  { "__index", &impl_end_level_data_get},
1590  { "__newindex", &dispatch<&game_lua_kernel::impl_end_level_data_set>},
1591  { "__gc", &impl_end_level_data_collect},
1592  { nullptr, nullptr }
1593  };
1594  luaL_setfuncs(L, callbacks, 0);
1595  }
1596  lua_setmetatable(L, -2);
1597 
1598  return 1;
1599  }
1600 
1601  if(classification.is_multiplayer()) {
1602  if(strcmp(m, "mp_settings") == 0) {
1603  lua_newuserdatauv(L, 0, 0);
1604  if(luaL_newmetatable(L, "mp settings")) {
1605  lua_pushlightuserdata(L, &play_controller_);
1606  lua_pushcclosure(L, impl_mp_settings_get, 1);
1607  lua_setfield(L, -2, "__index");
1608  lua_pushlightuserdata(L, &play_controller_);
1609  lua_pushcclosure(L, impl_mp_settings_len, 1);
1610  lua_setfield(L, -2, "__len");
1611  lua_pushstring(L, "mp settings");
1612  lua_setfield(L, -2, "__metatable");
1613  }
1614  lua_setmetatable(L, -2);
1615  return 1;
1616  }
1617  return_cfgref_attrib("era", find_addon("era", classification.era_id));
1618  }
1619  return 0;
1620 }
1621 
1622 /**
1623  * Sets some scenario data (__newindex metamethod).
1624  * - Arg 1: userdata (ignored).
1625  * - Arg 2: string containing the name of the property.
1626  * - Arg 3: something containing the attribute.
1627  */
1629 {
1630  LOG_LUA << "impl_scenario_set";
1631  char const *m = luaL_checkstring(L, 2);
1632 
1633  // Find the corresponding attribute.
1634  modify_int_attrib("turns", tod_man().set_number_of_turns_by_wml(value));
1635  modify_string_attrib("next", gamedata().set_next_scenario(value));
1636  modify_vector_string_attrib("defeat_music", gamedata().set_defeat_music(std::move(value)));
1637  modify_vector_string_attrib("victory_music", gamedata().set_victory_music(std::move(value)));
1638 
1639  game_classification& classification = play_controller_.get_classification();
1640  modify_bool_attrib("show_credits", classification.end_credits = value);
1641  modify_tstring_attrib("end_text", classification.end_text = value);
1642  modify_int_attrib("end_text_duration", classification.end_text_duration = value);
1643  if(strcmp(m, "end_level_data") == 0) {
1644  vconfig cfg(luaW_checkvconfig(L, 3));
1646 
1647  data.proceed_to_next_level = cfg["proceed_to_next_level"].to_bool(true);
1648  data.transient.carryover_report = cfg["carryover_report"].to_bool(true);
1649  data.prescenario_save = cfg["save"].to_bool(true);
1650  data.replay_save = cfg["replay_save"].to_bool(true);
1651  data.transient.linger_mode = cfg["linger_mode"].to_bool(true) && !teams().empty();
1652  data.transient.reveal_map = cfg["reveal_map"].to_bool(true);
1653  data.is_victory = cfg["result"] == level_result::victory;
1654  data.test_result = cfg["test_result"].str(level_result::result_not_set);
1655  play_controller_.set_end_level_data(data);
1656 
1657  return 1;
1658  }
1659  return 0;
1660 }
1661 
1662 /**
1663  converts synced_context::get_synced_state() to a string.
1664 */
1666 {
1667  //maybe return "initial" for game_data::INITIAL?
1668  if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::INITIAL)
1669  {
1670  return "preload";
1671  }
1673  {
1675  return "local_choice";
1677  return "synced";
1679  return "unsynced";
1680  default:
1681  throw game::game_error("Found corrupt synced_context::synced_state");
1682  }
1683 }
1684 
1685 
1686 /**
1687  * Gets some data about current point of game (__index metamethod).
1688  * - Arg 1: userdata (ignored).
1689  * - Arg 2: string containing the name of the property.
1690  * - Ret 1: something containing the attribute.
1691  */
1693 {
1694  char const *m = luaL_checkstring(L, 2);
1695 
1696  // Find the corresponding attribute.
1697  return_int_attrib("side", play_controller_.current_side());
1698  return_int_attrib("turn", play_controller_.turn());
1699  return_string_attrib("synced_state", synced_state());
1700  return_bool_attrib("user_can_invoke_commands", !play_controller_.is_lingering() && play_controller_.gamestate().init_side_done() && !events::commands_disabled && gamedata().phase() == game_data::PLAY);
1701 
1702  if(strcmp(m, "map") == 0) {
1703  return intf_terrainmap_get(L);
1704  }
1705  if(strcmp(m, "schedule") == 0) {
1706  luaW_push_schedule(L, -1);
1707  return 1;
1708  }
1709 
1710  if (strcmp(m, "event_context") == 0)
1711  {
1712  const game_events::queued_event &ev = get_event_info();
1713  config cfg;
1714  cfg["name"] = ev.name;
1715  cfg["id"] = ev.id;
1716  cfg.add_child("data", ev.data);
1717  if (const config &weapon = ev.data.child("first")) {
1718  cfg.add_child("weapon", weapon);
1719  }
1720  if (const config &weapon = ev.data.child("second")) {
1721  cfg.add_child("second_weapon", weapon);
1722  }
1723 
1724  const config::attribute_value di = ev.data["damage_inflicted"];
1725  if(!di.empty()) {
1726  cfg["damage_inflicted"] = di;
1727  }
1728 
1729  if (ev.loc1.valid()) {
1730  cfg["x1"] = ev.loc1.filter_loc().wml_x();
1731  cfg["y1"] = ev.loc1.filter_loc().wml_y();
1732  // 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
1733  cfg["unit_x"] = ev.loc1.wml_x();
1734  cfg["unit_y"] = ev.loc1.wml_y();
1735  }
1736  if (ev.loc2.valid()) {
1737  cfg["x2"] = ev.loc2.filter_loc().wml_x();
1738  cfg["y2"] = ev.loc2.filter_loc().wml_y();
1739  }
1740  luaW_pushconfig(L, cfg);
1741  return 1;
1742  }
1743 
1744  return 0;
1745 }
1746 
1747 /**
1748  * Displays a message in the chat window and in the logs.
1749  * - Arg 1: optional message header.
1750  * - Arg 2 (or 1): message.
1751  */
1753 {
1754  t_string m = luaW_checktstring(L, 1);
1755  t_string h = m;
1756  if (lua_isnone(L, 2)) {
1757  h = "Lua";
1758  } else {
1759  m = luaW_checktstring(L, 2);
1760  }
1761  lua_chat(h, m);
1762  LOG_LUA << "Script says: \"" << m << "\"";
1763  return 0;
1764 }
1765 
1767 {
1768  if(!game_display_) {
1769  return 0;
1770  }
1771  double factor = luaL_checknumber(L, 1);
1772  bool relative = luaW_toboolean(L, 2);
1773  if(relative) {
1774  factor *= game_display_->get_zoom_factor();
1775  }
1776  // Passing true explicitly to avoid casting to int.
1777  // Without doing one of the two, the call is ambiguous.
1778  game_display_->set_zoom(factor * game_config::tile_size, true);
1779  lua_pushnumber(L, game_display_->get_zoom_factor());
1780  return 1;
1781 }
1782 
1783 /**
1784  * Removes all messages from the chat window.
1785  */
1787 {
1788  if (game_display_) {
1789  game_display_->get_chat_manager().clear_chat_messages();
1790  }
1791  return 0;
1792 }
1793 
1795 {
1796  //note that next_player_number = 1, next_player_number = nteams+1 both set the next team to be the first team
1797  //but the later will make the turn counter change aswell fire turn end events accoringly etc.
1798  if (!lua_isnoneornil(L, 1)) {
1799  int max = 2 * teams().size();
1800  int npn = luaL_checkinteger(L, 1);
1801  if (npn <= 0 || npn > max) {
1802  return luaL_argerror(L, 1, "side number out of range");
1803  }
1805  }
1806  play_controller_.force_end_turn();
1807  return 0;
1808 }
1809 
1810 /**
1811  * Evaluates a boolean WML conditional.
1812  * - Arg 1: WML table.
1813  * - Ret 1: boolean.
1814  */
1815 static int intf_eval_conditional(lua_State *L)
1816 {
1817  vconfig cond = luaW_checkvconfig(L, 1);
1818  bool b = game_events::conditional_passed(cond);
1819  lua_pushboolean(L, b);
1820  return 1;
1821 }
1822 
1823 
1824 /**
1825  * Finds a path between two locations.
1826  * - Arg 1: source location. (Or Arg 1: unit.)
1827  * - Arg 2: destination.
1828  * - Arg 3: optional cost function or
1829  * table (optional fields: ignore_units, ignore_teleport, max_cost, viewing_side).
1830  * - Ret 1: array of pairs containing path steps.
1831  * - Ret 2: path cost.
1832  */
1834 {
1835  int arg = 1;
1836  map_location src, dst;
1837  const unit* u = nullptr;
1838  int viewing_side = 0;
1839 
1840  if (lua_isuserdata(L, arg))
1841  {
1842  u = &luaW_checkunit(L, arg);
1843  src = u->get_location();
1844  viewing_side = u->side();
1845  ++arg;
1846  }
1847  else
1848  {
1849  src = luaW_checklocation(L, arg);
1850  unit_map::const_unit_iterator ui = units().find(src);
1851  if (ui.valid()) {
1852  u = ui.get_shared_ptr().get();
1853  viewing_side = u->side();
1854  }
1855  ++arg;
1856  }
1857 
1858  dst = luaW_checklocation(L, arg);
1859 
1860  if (!board().map().on_board(src))
1861  return luaL_argerror(L, 1, "invalid location");
1862  if (!board().map().on_board(dst))
1863  return luaL_argerror(L, arg, "invalid location");
1864  ++arg;
1865 
1866  const gamemap &map = board().map();
1867  bool ignore_units = false, see_all = false, ignore_teleport = false;
1868  double stop_at = 10000;
1869  std::unique_ptr<pathfind::cost_calculator> calc;
1870 
1871  if (lua_istable(L, arg))
1872  {
1873  ignore_units = luaW_table_get_def<bool>(L, arg, "ignore_units", false);
1874  see_all = luaW_table_get_def<bool>(L, arg, "ignore_visibility", false);
1875  ignore_teleport = luaW_table_get_def<bool>(L, arg, "ignore_teleport", false);
1876 
1877  stop_at = luaW_table_get_def<double>(L, arg, "max_cost", stop_at);
1878 
1879 
1880  lua_pushstring(L, "viewing_side");
1881  lua_rawget(L, arg);
1882  if (!lua_isnil(L, -1)) {
1883  int i = luaL_checkinteger(L, -1);
1884  if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
1885  else {
1886  // If there's a unit, we have a valid side, so fall back to legacy behaviour.
1887  // If we don't have a unit, legacy behaviour would be a crash, so let's not.
1888  if(u) see_all = true;
1889  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.");
1890  }
1891  }
1892  lua_pop(L, 1);
1893 
1894  lua_pushstring(L, "calculate");
1895  lua_rawget(L, arg);
1896  if(lua_isfunction(L, -1)) {
1897  calc.reset(new lua_pathfind_cost_calculator(L, lua_gettop(L)));
1898  }
1899  // Don't pop, the lua_pathfind_cost_calculator requires it to stay on the stack.
1900  }
1901  else if (lua_isfunction(L, arg))
1902  {
1903  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.");
1904  calc.reset(new lua_pathfind_cost_calculator(L, arg));
1905  }
1906 
1907  pathfind::teleport_map teleport_locations;
1908 
1909  if(!ignore_teleport) {
1910  if(viewing_side == 0) {
1911  lua_warning(L, "wesnoth.paths.find_path: ignore_teleport=false requires a valid viewing_side; continuing with ignore_teleport=true", false);
1912  ignore_teleport = true;
1913  } else {
1914  teleport_locations = pathfind::get_teleport_locations(*u, board().get_team(viewing_side), see_all, ignore_units);
1915  }
1916  }
1917 
1918  if (!calc) {
1919  if(!u) {
1920  return luaL_argerror(L, 1, "unit not found OR custom cost function not provided");
1921  }
1922 
1923  calc.reset(new pathfind::shortest_path_calculator(*u, board().get_team(viewing_side),
1924  teams(), map, ignore_units, false, see_all));
1925  }
1926 
1927  pathfind::plain_route res = pathfind::a_star_search(src, dst, stop_at, *calc, map.w(), map.h(),
1928  &teleport_locations);
1929 
1930  int nb = res.steps.size();
1931  lua_createtable(L, nb, 0);
1932  for (int i = 0; i < nb; ++i)
1933  {
1934  luaW_pushlocation(L, res.steps[i]);
1935  lua_rawseti(L, -2, i + 1);
1936  }
1937  lua_pushinteger(L, res.move_cost);
1938 
1939  return 2;
1940 }
1941 
1942 /**
1943  * Finds all the locations reachable by a unit.
1944  * - Arg 1: source location OR unit.
1945  * - Arg 2: optional table (optional fields: ignore_units, ignore_teleport, additional_turns, viewing_side).
1946  * - Ret 1: array of triples (coordinates + remaining movement).
1947  */
1949 {
1950  int arg = 1;
1951  const unit* u = nullptr;
1952 
1953  if (lua_isuserdata(L, arg))
1954  {
1955  u = &luaW_checkunit(L, arg);
1956  ++arg;
1957  }
1958  else
1959  {
1960  map_location src = luaW_checklocation(L, arg);
1961  unit_map::const_unit_iterator ui = units().find(src);
1962  if (!ui.valid())
1963  return luaL_argerror(L, 1, "unit not found");
1964  u = ui.get_shared_ptr().get();
1965  ++arg;
1966  }
1967 
1968  int viewing_side = u->side();
1969  bool ignore_units = false, see_all = false, ignore_teleport = false;
1970  int additional_turns = 0;
1971 
1972  if (lua_istable(L, arg))
1973  {
1974  ignore_units = luaW_table_get_def<bool>(L, arg, "ignore_units", false);
1975  see_all = luaW_table_get_def<bool>(L, arg, "ignore_visibility", false);
1976  ignore_teleport = luaW_table_get_def<bool>(L, arg, "ignore_teleport", false);
1977  additional_turns = luaW_table_get_def<int>(L, arg, "max_cost", additional_turns);
1978 
1979  lua_pushstring(L, "viewing_side");
1980  lua_rawget(L, arg);
1981  if (!lua_isnil(L, -1)) {
1982  int i = luaL_checkinteger(L, -1);
1983  if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
1984  else {
1985  // If there's a unit, we have a valid side, so fall back to legacy behaviour.
1986  // If we don't have a unit, legacy behaviour would be a crash, so let's not.
1987  if(u) see_all = true;
1988  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.");
1989  }
1990  }
1991  lua_pop(L, 1);
1992  }
1993 
1994  const team& viewing_team = board().get_team(viewing_side);
1995 
1996  pathfind::paths res(*u, ignore_units, !ignore_teleport,
1997  viewing_team, additional_turns, see_all, ignore_units);
1998 
1999  int nb = res.destinations.size();
2000  lua_createtable(L, nb, 0);
2001  for (int i = 0; i < nb; ++i)
2002  {
2004  luaW_push_namedtuple(L, {"x", "y", "moves_left"});
2005  lua_pushinteger(L, s.curr.wml_x());
2006  lua_rawseti(L, -2, 1);
2007  lua_pushinteger(L, s.curr.wml_y());
2008  lua_rawseti(L, -2, 2);
2009  lua_pushinteger(L, s.move_left);
2010  lua_rawseti(L, -2, 3);
2011  lua_rawseti(L, -2, i + 1);
2012  }
2013 
2014  return 1;
2015 }
2016 
2017 /**
2018  * Finds all the locations for which a given unit would remove the fog (if there was fog on the map).
2019  *
2020  * - Arg 1: source location OR unit.
2021  * - Ret 1: array of triples (coordinates + remaining vision points).
2022  */
2024 {
2025  int arg = 1;
2026  const unit* u = nullptr;
2027 
2028  if (lua_isuserdata(L, arg))
2029  {
2030  u = &luaW_checkunit(L, arg);
2031  ++arg;
2032  }
2033  else
2034  {
2035  map_location src = luaW_checklocation(L, arg);
2036  unit_map::const_unit_iterator ui = units().find(src);
2037  if (!ui.valid())
2038  return luaL_argerror(L, 1, "unit not found");
2039  u = ui.get_shared_ptr().get();
2040  ++arg;
2041  }
2042 
2043  if(!u)
2044  {
2045  return luaL_error(L, "wesnoth.find_vision_range: requires a valid unit");
2046  }
2047 
2048  std::map<map_location, int> jamming_map;
2049  actions::create_jamming_map(jamming_map, resources::gameboard->get_team(u->side()));
2050  pathfind::vision_path res(*u, u->get_location(), jamming_map);
2051 
2052  lua_createtable(L, res.destinations.size() + res.edges.size(), 0);
2053  for(const auto& d : res.destinations) {
2054  luaW_push_namedtuple(L, {"x", "y", "vision_left"});
2055  lua_pushinteger(L, d.curr.wml_x());
2056  lua_rawseti(L, -2, 1);
2057  lua_pushinteger(L, d.curr.wml_y());
2058  lua_rawseti(L, -2, 2);
2059  lua_pushinteger(L, d.move_left);
2060  lua_rawseti(L, -2, 3);
2061  lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
2062  }
2063  for(const auto& e : res.edges) {
2064  luaW_push_namedtuple(L, {"x", "y", "vision_left"});
2065  lua_pushinteger(L, e.wml_x());
2066  lua_rawseti(L, -2, 1);
2067  lua_pushinteger(L, e.wml_y());
2068  lua_rawseti(L, -2, 2);
2069  lua_pushinteger(L, -1);
2070  lua_rawseti(L, -2, 3);
2071  lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
2072  }
2073  return 1;
2074 }
2075 
2076 template<typename T> // This is only a template so I can avoid typing out the long typename. >_>
2077 static int load_fake_units(lua_State* L, int arg, T& fake_units)
2078 {
2079  for (int i = 1, i_end = lua_rawlen(L, arg); i <= i_end; ++i)
2080  {
2081  map_location src;
2082  lua_rawgeti(L, arg, i);
2083  int entry = lua_gettop(L);
2084  if (!lua_istable(L, entry)) {
2085  goto error;
2086  }
2087 
2088  if (!luaW_tolocation(L, entry, src)) {
2089  goto error;
2090  }
2091 
2092  lua_rawgeti(L, entry, 3);
2093  if (!lua_isnumber(L, -1)) {
2094  lua_getfield(L, entry, "side");
2095  if (!lua_isnumber(L, -1)) {
2096  goto error;
2097  }
2098  }
2099  int side = lua_tointeger(L, -1);
2100 
2101  lua_rawgeti(L, entry, 4);
2102  if (!lua_isstring(L, -1)) {
2103  lua_getfield(L, entry, "type");
2104  if (!lua_isstring(L, -1)) {
2105  goto error;
2106  }
2107  }
2108  std::string unit_type = lua_tostring(L, -1);
2109 
2110  fake_units.emplace_back(src, side, unit_type);
2111 
2112  lua_settop(L, entry - 1);
2113  }
2114  return 0;
2115 error:
2116  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");
2117 }
2118 
2119 /**
2120  * Is called with one or more units and builds a cost map.
2121  * - Arg 1: source location. (Or Arg 1: unit. Or Arg 1: table containing a filter)
2122  * - Arg 2: optional array of tables with 4 elements (coordinates + side + unit type string)
2123  * - Arg 3: optional table (optional fields: ignore_units, ignore_teleport, viewing_side, debug).
2124  * - Arg 4: optional table: standard location filter.
2125  * - Ret 1: array of triples (coordinates + array of tuples(summed cost + reach counter)).
2126  */
2128 {
2129  int arg = 1;
2130  unit* unit = luaW_tounit(L, arg, true);
2132  luaW_tovconfig(L, arg, filter);
2133 
2134  std::vector<const ::unit*> real_units;
2135  typedef std::vector<std::tuple<map_location, int, std::string>> unit_type_vector;
2136  unit_type_vector fake_units;
2137 
2138 
2139  if (unit) // 1. arg - unit
2140  {
2141  real_units.push_back(unit);
2142  }
2143  else if (!filter.null()) // 1. arg - filter
2144  {
2145  for(const ::unit* match : unit_filter(filter).all_matches_on_map()) {
2146  if(match->get_location().valid()) {
2147  real_units.push_back(match);
2148  }
2149  }
2150  }
2151  else // 1. arg - coordinates
2152  {
2153  map_location src = luaW_checklocation(L, arg);
2154  unit_map::const_unit_iterator ui = units().find(src);
2155  if (ui.valid())
2156  {
2157  real_units.push_back(&(*ui));
2158  }
2159  }
2160  ++arg;
2161 
2162  if (lua_istable(L, arg)) // 2. arg - optional types
2163  {
2164  load_fake_units(L, arg, fake_units);
2165  ++arg;
2166  }
2167 
2168  if(real_units.empty() && fake_units.empty())
2169  {
2170  return luaL_argerror(L, 1, "unit(s) not found");
2171  }
2172 
2173  int viewing_side = 0;
2174  bool ignore_units = true, see_all = true, ignore_teleport = false, debug = false, use_max_moves = false;
2175 
2176  if (lua_istable(L, arg)) // 4. arg - options
2177  {
2178  lua_pushstring(L, "ignore_units");
2179  lua_rawget(L, arg);
2180  if (!lua_isnil(L, -1))
2181  {
2182  ignore_units = luaW_toboolean(L, -1);
2183  }
2184  lua_pop(L, 1);
2185 
2186  lua_pushstring(L, "ignore_teleport");
2187  lua_rawget(L, arg);
2188  if (!lua_isnil(L, -1))
2189  {
2190  ignore_teleport = luaW_toboolean(L, -1);
2191  }
2192  lua_pop(L, 1);
2193 
2194  lua_pushstring(L, "viewing_side");
2195  lua_rawget(L, arg);
2196  if (!lua_isnil(L, -1))
2197  {
2198  int i = luaL_checkinteger(L, -1);
2199  if (i >= 1 && i <= static_cast<int>(teams().size()))
2200  {
2201  viewing_side = i;
2202  see_all = false;
2203  }
2204  }
2205 
2206  lua_pushstring(L, "debug");
2207  lua_rawget(L, arg);
2208  if (!lua_isnil(L, -1))
2209  {
2210  debug = luaW_toboolean(L, -1);
2211  }
2212  lua_pop(L, 1);
2213 
2214  lua_pushstring(L, "use_max_moves");
2215  lua_rawget(L, arg);
2216  if (!lua_isnil(L, -1))
2217  {
2218  use_max_moves = luaW_toboolean(L, -1);
2219  }
2220  lua_pop(L, 1);
2221  ++arg;
2222  }
2223 
2224  // 5. arg - location filter
2225  filter = vconfig::unconstructed_vconfig();
2226  std::set<map_location> location_set;
2227  luaW_tovconfig(L, arg, filter);
2228  if (filter.null())
2229  {
2230  filter = vconfig(config(), true);
2231  }
2232  filter_context & fc = game_state_;
2233  const terrain_filter t_filter(filter, &fc, false);
2234  t_filter.get_locations(location_set, true);
2235  ++arg;
2236 
2237  // build cost_map
2238  const team& viewing_team = viewing_side
2239  ? board().get_team(viewing_side)
2240  : board().teams()[0];
2241 
2242  pathfind::full_cost_map cost_map(
2243  ignore_units, !ignore_teleport, viewing_team, see_all, ignore_units);
2244 
2245  for (const ::unit* const u : real_units)
2246  {
2247  cost_map.add_unit(*u, use_max_moves);
2248  }
2249  for (const unit_type_vector::value_type& fu : fake_units)
2250  {
2251  const unit_type* ut = unit_types.find(std::get<2>(fu));
2252  cost_map.add_unit(std::get<0>(fu), ut, std::get<1>(fu));
2253  }
2254 
2255  if (debug)
2256  {
2257  if (game_display_) {
2258  game_display_->labels().clear_all();
2259  for (const map_location& loc : location_set)
2260  {
2261  std::stringstream s;
2262  s << cost_map.get_pair_at(loc).first;
2263  s << " / ";
2264  s << cost_map.get_pair_at(loc).second;
2265  game_display_->labels().set_label(loc, s.str());
2266  }
2267  }
2268  }
2269 
2270  // create return value
2271  lua_createtable(L, location_set.size(), 0);
2272  int counter = 1;
2273  for (const map_location& loc : location_set)
2274  {
2275  luaW_push_namedtuple(L, {"x", "y", "cost", "reach"});
2276 
2277  lua_pushinteger(L, loc.wml_x());
2278  lua_rawseti(L, -2, 1);
2279 
2280  lua_pushinteger(L, loc.wml_y());
2281  lua_rawseti(L, -2, 2);
2282 
2283  lua_pushinteger(L, cost_map.get_pair_at(loc).first);
2284  lua_rawseti(L, -2, 3);
2285 
2286  lua_pushinteger(L, cost_map.get_pair_at(loc).second);
2287  lua_rawseti(L, -2, 4);
2288 
2289  lua_rawseti(L, -2, counter);
2290  ++counter;
2291  }
2292  return 1;
2293 }
2294 
2295 const char* labelKey = "floating label";
2296 
2297 static int* luaW_check_floating_label(lua_State* L, int idx)
2298 {
2299  return reinterpret_cast<int*>(luaL_checkudata(L, idx, labelKey));
2300 }
2301 
2302 static int impl_floating_label_getmethod(lua_State* L)
2303 {
2304  const char* m = luaL_checkstring(L, 2);
2305  return_bool_attrib("valid", *luaW_check_floating_label(L, 1) != 0);
2306  return luaW_getmetafield(L, 1, m);
2307 }
2308 
2310 {
2311  int* handle = luaW_check_floating_label(L, 1);
2312  int fade = luaL_optinteger(L, 2, -1);
2313  if(*handle != 0) {
2314  // Passing -1 as the second argument means it uses the fade time that was set when the label was created
2315  font::remove_floating_label(*handle, fade);
2316  }
2317  *handle = 0;
2318  return 0;
2319 }
2320 
2322 {
2323  int* handle = luaW_check_floating_label(L, 1);
2324  if(*handle != 0) {
2325  font::move_floating_label(*handle, luaL_checknumber(L, 2), luaL_checknumber(L, 3));
2326  }
2327  return 0;
2328 }
2329 
2330 /**
2331  * Arg 1: text - string
2332  * Arg 2: options table
2333  * - size: font size
2334  * - max_width: max width for word wrapping
2335  * - color: font color
2336  * - bgcolor: background color
2337  * - bgalpha: background opacity
2338  * - duration: display duration (integer or the string "unlimited")
2339  * - fade_time: duration of fade-out
2340  * - location: screen offset
2341  * - valign: vertical alignment and anchoring - "top", "center", or "bottom"
2342  * - halign: horizontal alignment and anchoring - "left", "center", or "right"
2343  * Returns: label handle
2344  */
2345 int game_lua_kernel::intf_set_floating_label(lua_State* L, bool spawn)
2346 {
2347  t_string text = luaW_checktstring(L, 1);
2348  int size = font::SIZE_SMALL;
2349  int width = 0;
2350  double width_ratio = 0;
2351  color_t color = font::LABEL_COLOR, bgcolor{0, 0, 0, 0};
2352  int lifetime = 2'000, fadeout = 100;
2353  font::ALIGN alignment = font::ALIGN::CENTER_ALIGN, vertical_alignment = font::ALIGN::CENTER_ALIGN;
2354  // This is actually a relative screen location in pixels, but map_location already supports
2355  // everything needed to read in a pair of coordinates.
2356  // Depending on the chosen alignment, it may be relative to centre, an edge centre, or a corner.
2357  map_location loc{0, 0, wml_loc()};
2358  if(lua_istable(L, 2)) {
2359  if(luaW_tableget(L, 2, "size")) {
2360  size = luaL_checkinteger(L, -1);
2361  }
2362  if(luaW_tableget(L, 2, "max_width")) {
2363  int found_number;
2364  width = lua_tointegerx(L, -1, &found_number);
2365  if(!found_number) {
2366  auto value = luaW_tostring(L, -1);
2367  try {
2368  if(!value.empty() && value.back() == '%') {
2369  value.remove_suffix(1);
2370  width_ratio = std::stoi(std::string(value)) / 100.0;
2371  } else throw std::invalid_argument(value.data());
2372  } catch(std::invalid_argument&) {
2373  return luaL_argerror(L, -1, "max_width should be integer or percentage");
2374  }
2375 
2376  }
2377  }
2378  if(luaW_tableget(L, 2, "color")) {
2379  if(lua_isstring(L, -1)) {
2380  color = color_t::from_hex_string(lua_tostring(L, -1));
2381  } else {
2382  auto vec = lua_check<std::vector<int>>(L, -1);
2383  if(vec.size() != 3) {
2384  return luaL_error(L, "floating label text color should be a hex string or an array of 3 integers");
2385  }
2386  color.r = vec[0];
2387  color.g = vec[1];
2388  color.b = vec[2];
2389  }
2390  }
2391  if(luaW_tableget(L, 2, "bgcolor")) {
2392  if(lua_isstring(L, -1)) {
2393  bgcolor = color_t::from_hex_string(lua_tostring(L, -1));
2394  } else {
2395  auto vec = lua_check<std::vector<int>>(L, -1);
2396  if(vec.size() != 3) {
2397  return luaL_error(L, "floating label background color should be a hex string or an array of 3 integers");
2398  }
2399  bgcolor.r = vec[0];
2400  bgcolor.g = vec[1];
2401  bgcolor.b = vec[2];
2402  bgcolor.a = ALPHA_OPAQUE;
2403  }
2404  if(luaW_tableget(L, 2, "bgalpha")) {
2405  bgcolor.a = luaL_checkinteger(L, -1);
2406  }
2407  }
2408  if(luaW_tableget(L, 2, "duration")) {
2409  int found_number;
2410  lifetime = lua_tointegerx(L, -1, &found_number);
2411  if(!found_number) {
2412  auto value = luaW_tostring(L, -1);
2413  if(value == "unlimited") {
2414  lifetime = -1;
2415  } else {
2416  return luaL_argerror(L, -1, "duration should be integer or 'unlimited'");
2417  }
2418  }
2419  }
2420  if(luaW_tableget(L, 2, "fade_time")) {
2421  fadeout = lua_tointeger(L, -1);
2422  }
2423  if(luaW_tableget(L, 2, "location")) {
2424  loc = luaW_checklocation(L, -1);
2425  }
2426  if(luaW_tableget(L, 2, "halign")) {
2427  static const char* options[] = {"left", "center", "right"};
2428  alignment = font::ALIGN(luaL_checkoption(L, -1, nullptr, options));
2429  }
2430  if(luaW_tableget(L, 2, "valign")) {
2431  static const char* options[] = {"top", "center", "bottom"};
2432  vertical_alignment = font::ALIGN(luaL_checkoption(L, -1, nullptr, options));
2433  }
2434  }
2435 
2436  int* handle = nullptr;
2437  if(spawn) {
2438  // Creating a new label, allocate a new handle
2439  handle = new(L)int();
2440  } else {
2441  // First argument is the label handle
2442  handle = luaW_check_floating_label(L, 1);
2443  }
2444  int handle_idx = lua_gettop(L);
2445 
2446  if(*handle != 0) {
2447  font::remove_floating_label(*handle);
2448  }
2449 
2450  SDL_Rect rect = game_display_->map_outside_area();
2451  if(width_ratio > 0) {
2452  width = static_cast<int>(std::round(rect.w * width_ratio));
2453  }
2454  int x = 0, y = 0;
2455  switch(alignment) {
2456  case font::ALIGN::LEFT_ALIGN:
2457  x = rect.x + loc.wml_x();
2458  if(width > 0) {
2459  rect.w = std::min(rect.w, width);
2460  }
2461  break;
2462  case font::ALIGN::CENTER_ALIGN:
2463  x = rect.x + rect.w / 2 + loc.wml_x();
2464  if(width > 0) {
2465  rect.w = std::min(rect.w, width);
2466  rect.x = x - rect.w / 2;
2467  }
2468  break;
2469  case font::ALIGN::RIGHT_ALIGN:
2470  x = rect.x + rect.w - loc.wml_x();
2471  if(width > 0) {
2472  rect.x = (rect.x + rect.w) - std::min(rect.w, width);
2473  rect.w = std::min(rect.w, width);
2474  }
2475  break;
2476  }
2477  switch(vertical_alignment) {
2478  case font::ALIGN::LEFT_ALIGN: // top
2479  y = rect.y + loc.wml_y();
2480  break;
2481  case font::ALIGN::CENTER_ALIGN:
2482  y = rect.y + rect.h / 2 + loc.wml_y();
2483  break;
2484  case font::ALIGN::RIGHT_ALIGN: // bottom
2485  // The size * 1.5 adjustment avoids the text being cut off if placed at y = 0
2486  // This is necessary because the text is positioned by the top edge but we want it to
2487  // seem like it's positioned by the bottom edge.
2488  // This wouldn't work for multiline text, but we don't expect that to be common in this API anyway.
2489  y = rect.y + rect.h - loc.wml_y() - static_cast<int>(size * 1.5);
2490  break;
2491  }
2492 
2493  font::floating_label flabel(text);
2494  flabel.set_font_size(size);
2495  flabel.set_color(color);
2496  flabel.set_bg_color(bgcolor);
2497  flabel.set_alignment(alignment);
2498  flabel.set_position(x, y);
2499  flabel.set_lifetime(lifetime, fadeout);
2500  flabel.set_clip_rect(rect);
2501 
2502  *handle = font::add_floating_label(flabel);
2503  lua_settop(L, handle_idx);
2504  if(luaL_newmetatable(L, labelKey)) {
2505  // Initialize the metatable
2506  static const luaL_Reg methods[] = {
2507  {"remove", &dispatch<&game_lua_kernel::intf_remove_floating_label>},
2508  {"move", &dispatch<&game_lua_kernel::intf_move_floating_label>},
2509  {"replace", &dispatch2<&game_lua_kernel::intf_set_floating_label, false>},
2510  {"__index", &impl_floating_label_getmethod},
2511  { nullptr, nullptr }
2512  };
2513  luaL_setfuncs(L, methods, 0);
2514  luaW_table_set(L, -1, "__metatable", std::string(labelKey));
2515  }
2516  lua_setmetatable(L, handle_idx);
2517  lua_settop(L, handle_idx);
2518  return 1;
2519 }
2520 
2522 {
2523  if(game_display_) {
2524  game_display_->invalidate(loc);
2525  }
2526 
2527  units().erase(loc);
2528  resources::whiteboard->on_kill_unit();
2529 }
2530 
2531 /**
2532  * Places a unit on the map.
2533  * - Arg 1: (optional) location.
2534  * - Arg 2: Unit (WML table or proxy), or nothing/nil to delete.
2535  * OR
2536  * - Arg 1: Unit (WML table or proxy)
2537  * - Arg 2: (optional) location
2538  * - Arg 3: (optional) boolean
2539  */
2541 {
2542  if(map_locked_) {
2543  return luaL_error(L, "Attempted to move a unit while the map is locked");
2544  }
2545 
2546  map_location loc;
2547  if (luaW_tolocation(L, 2, loc)) {
2548  if (!map().on_board(loc)) {
2549  return luaL_argerror(L, 2, "invalid location");
2550  }
2551  }
2552 
2553  if((luaW_isunit(L, 1))) {
2554  lua_unit& u = *luaW_checkunit_ref(L, 1);
2555  if(u.on_map() && u->get_location() == loc) {
2556  return 0;
2557  }
2558  if (!loc.valid()) {
2559  loc = u->get_location();
2560  if (!map().on_board(loc))
2561  return luaL_argerror(L, 1, "invalid location");
2562  }
2563 
2564  put_unit_helper(loc);
2565  u.put_map(loc);
2566  u.get_shared()->anim_comp().set_standing();
2567  } else if(!lua_isnoneornil(L, 1)) {
2568  const vconfig* vcfg = nullptr;
2569  config cfg = luaW_checkconfig(L, 1, vcfg);
2570  if (!map().on_board(loc)) {
2571  loc.set_wml_x(cfg["x"]);
2572  loc.set_wml_y(cfg["y"]);
2573  if (!map().on_board(loc))
2574  return luaL_argerror(L, 2, "invalid location");
2575  }
2576 
2577  unit_ptr u = unit::create(cfg, true, vcfg);
2578  put_unit_helper(loc);
2579  u->set_location(loc);
2580  units().insert(u);
2581  }
2582 
2583  // Fire event if using the deprecated version or if the final argument is not false
2584  // If the final boolean argument is omitted, the actual final argument (the unit or location) will always yield true.
2585  if(luaW_toboolean(L, -1)) {
2586  play_controller_.pump().fire("unit_placed", loc);
2587  }
2588  return 0;
2589 }
2590 
2591 /**
2592  * Erases a unit from the map
2593  * - Arg 1: Unit to erase OR Location to erase unit
2594  */
2596 {
2597  if(map_locked_) {
2598  return luaL_error(L, "Attempted to remove a unit while the map is locked");
2599  }
2600  map_location loc;
2601 
2602  if(luaW_isunit(L, 1)) {
2603  lua_unit& u = *luaW_checkunit_ref(L, 1);
2604  if (u.on_map()) {
2605  loc = u->get_location();
2606  if (!map().on_board(loc)) {
2607  return luaL_argerror(L, 1, "invalid location");
2608  }
2609  } else if (int side = u.on_recall_list()) {
2610  team &t = board().get_team(side);
2611  // Should it use underlying ID instead?
2613  } else {
2614  return luaL_argerror(L, 1, "can't erase private units");
2615  }
2616  } else if (luaW_tolocation(L, 1, loc)) {
2617  if (!map().on_board(loc)) {
2618  return luaL_argerror(L, 1, "invalid location");
2619  }
2620  } else {
2621  return luaL_argerror(L, 1, "expected unit or location");
2622  }
2623 
2624  units().erase(loc);
2625  resources::whiteboard->on_kill_unit();
2626  return 0;
2627 }
2628 
2629 /**
2630  * Puts a unit on a recall list.
2631  * - Arg 1: WML table or unit.
2632  * - Arg 2: (optional) side.
2633  */
2635 {
2636  if(map_locked_) {
2637  return luaL_error(L, "Attempted to move a unit while the map is locked");
2638  }
2639  lua_unit *lu = nullptr;
2640  unit_ptr u = unit_ptr();
2641  int side = lua_tointeger(L, 2);
2642  if (static_cast<unsigned>(side) > teams().size()) side = 0;
2643 
2644  if(luaW_isunit(L, 1)) {
2645  lu = luaW_checkunit_ref(L, 1);
2646  u = lu->get_shared();
2647  if(lu->on_recall_list() && lu->on_recall_list() == side) {
2648  return luaL_argerror(L, 1, "unit already on recall list");
2649  }
2650  } else {
2651  const vconfig* vcfg = nullptr;
2652  config cfg = luaW_checkconfig(L, 1, vcfg);
2653  u = unit::create(cfg, true, vcfg);
2654  }
2655 
2656  if (!side) {
2657  side = u->side();
2658  } else {
2659  u->set_side(side);
2660  }
2661  team &t = board().get_team(side);
2662  // Avoid duplicates in the recall list.
2663  std::size_t uid = u->underlying_id();
2665  t.recall_list().add(u);
2666  if (lu) {
2667  if (lu->on_map()) {
2668  units().erase(u->get_location());
2669  resources::whiteboard->on_kill_unit();
2670  u->anim_comp().clear_haloes();
2671  }
2672  lu->lua_unit::~lua_unit();
2673  new(lu) lua_unit(side, uid);
2674  }
2675 
2676  return 0;
2677 }
2678 
2679 /**
2680  * Extracts a unit from the map or a recall list and gives it to Lua.
2681  * - Arg 1: unit userdata.
2682  */
2684 {
2685  if(map_locked_) {
2686  return luaL_error(L, "Attempted to remove a unit while the map is locked");
2687  }
2688  lua_unit* lu = luaW_checkunit_ref(L, 1);
2689  unit_ptr u = lu->get_shared();
2690 
2691  if (lu->on_map()) {
2692  u = units().extract(u->get_location());
2693  assert(u);
2694  u->anim_comp().clear_haloes();
2695  } else if (int side = lu->on_recall_list()) {
2696  team &t = board().get_team(side);
2697  unit_ptr v = u->clone();
2698  t.recall_list().erase_if_matches_id(u->id());
2699  u = v;
2700  } else {
2701  return 0;
2702  }
2703 
2704  lu->lua_unit::~lua_unit();
2705  new(lu) lua_unit(u);
2706  return 0;
2707 }
2708 
2709 /**
2710  * Finds a vacant tile.
2711  * - Arg 1: location.
2712  * - Arg 2: optional unit for checking movement type.
2713  * - Rets 1,2: location.
2714  */
2716 {
2717  map_location loc = luaW_checklocation(L, 1);
2718 
2719  unit_ptr u;
2720  if (!lua_isnoneornil(L, 2)) {
2721  if(luaW_isunit(L, 2)) {
2722  u = luaW_checkunit_ptr(L, 2, false);
2723  } else {
2724  const vconfig* vcfg = nullptr;
2725  config cfg = luaW_checkconfig(L, 2, vcfg);
2726  u = unit::create(cfg, false, vcfg);
2727  }
2728  }
2729 
2730  map_location res = find_vacant_tile(loc, pathfind::VACANT_ANY, u.get());
2731 
2732  if (!res.valid()) return 0;
2733  lua_pushinteger(L, res.wml_x());
2734  lua_pushinteger(L, res.wml_y());
2735  return 2;
2736 }
2737 
2738 /**
2739  * Floats some text on the map.
2740  * - Arg 1: location.
2741  * - Arg 2: string.
2742  * - Arg 3: color.
2743  */
2745 {
2746  map_location loc = luaW_checklocation(L, 1);
2747  color_t color = font::LABEL_COLOR;
2748 
2749  t_string text = luaW_checktstring(L, 2);
2750  if (!lua_isnoneornil(L, 3)) {
2751  color = color_t::from_rgb_string(luaL_checkstring(L, 3));
2752  }
2753 
2754  if (game_display_) {
2755  game_display_->float_label(loc, text, color);
2756  }
2757  return 0;
2758 }
2759 
2760 /**
2761  * Creates a unit from its WML description.
2762  * - Arg 1: WML table.
2763  * - Ret 1: unit userdata.
2764  */
2765 static int intf_create_unit(lua_State *L)
2766 {
2767  const vconfig* vcfg = nullptr;
2768  config cfg = luaW_checkconfig(L, 1, vcfg);
2769  unit_ptr u = unit::create(cfg, true, vcfg);
2770  luaW_pushunit(L, u);
2771  return 1;
2772 }
2773 
2774 /**
2775  * Copies a unit.
2776  * - Arg 1: unit userdata.
2777  * - Ret 1: unit userdata.
2778  */
2779 static int intf_copy_unit(lua_State *L)
2780 {
2781  unit& u = luaW_checkunit(L, 1);
2782  luaW_pushunit(L, u.clone());
2783  return 1;
2784 }
2785 
2786 /**
2787  * Returns unit resistance against a given attack type.
2788  * - Arg 1: unit userdata.
2789  * - Arg 2: string containing the attack type.
2790  * - Arg 3: boolean indicating if attacker.
2791  * - Arg 4: optional location.
2792  * - Ret 1: integer.
2793  */
2794 static int intf_unit_resistance(lua_State *L)
2795 {
2796  const unit& u = luaW_checkunit(L, 1);
2797  char const *m = luaL_checkstring(L, 2);
2798  bool a = false;
2799  map_location loc = u.get_location();
2800 
2801  if(lua_isboolean(L, 3)) {
2802  a = luaW_toboolean(L, 3);
2803  if(!lua_isnoneornil(L, 4)) {
2804  loc = luaW_checklocation(L, 4);
2805  }
2806  } else if(!lua_isnoneornil(L, 3)) {
2807  loc = luaW_checklocation(L, 3);
2808  }
2809 
2810  lua_pushinteger(L, 100 - u.resistance_against(m, a, loc));
2811  return 1;
2812 }
2813 
2814 /**
2815  * Returns unit movement cost on a given terrain.
2816  * - Arg 1: unit userdata.
2817  * - Arg 2: string containing the terrain type.
2818  * - Ret 1: integer.
2819  */
2820 static int intf_unit_movement_cost(lua_State *L)
2821 {
2822  const unit& u = luaW_checkunit(L, 1);
2824  map_location loc;
2825  if(luaW_tolocation(L, 2, loc)) {
2826  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
2827  } else if(lua_isstring(L, 2)) {
2828  char const *m = luaL_checkstring(L, 2);
2830  } else return luaW_type_error(L, 2, "location or terrain string");
2831  lua_pushinteger(L, u.movement_cost(t));
2832  return 1;
2833 }
2834 
2835 /**
2836  * Returns unit vision cost on a given terrain.
2837  * - Arg 1: unit userdata.
2838  * - Arg 2: string containing the terrain type.
2839  * - Ret 1: integer.
2840  */
2841 static int intf_unit_vision_cost(lua_State *L)
2842 {
2843  const unit& u = luaW_checkunit(L, 1);
2845  map_location loc;
2846  if(luaW_tolocation(L, 2, loc)) {
2847  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
2848  } else if(lua_isstring(L, 2)) {
2849  char const *m = luaL_checkstring(L, 2);
2851  } else return luaW_type_error(L, 2, "location or terrain string");
2852  lua_pushinteger(L, u.vision_cost(t));
2853  return 1;
2854 }
2855 
2856 /**
2857  * Returns unit jamming cost on a given terrain.
2858  * - Arg 1: unit userdata.
2859  * - Arg 2: string containing the terrain type.
2860  * - Ret 1: integer.
2861  */
2862 static int intf_unit_jamming_cost(lua_State *L)
2863 {
2864  const unit& u = luaW_checkunit(L, 1);
2866  map_location loc;
2867  if(luaW_tolocation(L, 2, loc)) {
2868  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
2869  } else if(lua_isstring(L, 2)) {
2870  char const *m = luaL_checkstring(L, 2);
2872  } else return luaW_type_error(L, 2, "location or terrain string");
2873  lua_pushinteger(L, u.jamming_cost(t));
2874  return 1;
2875 }
2876 
2877 /**
2878  * Returns unit defense on a given terrain.
2879  * - Arg 1: unit userdata.
2880  * - Arg 2: string containing the terrain type.
2881  * - Ret 1: integer.
2882  */
2883 static int intf_unit_defense(lua_State *L)
2884 {
2885  const unit& u = luaW_checkunit(L, 1);
2887  map_location loc;
2888  if(luaW_tolocation(L, 2, loc)) {
2889  t = static_cast<const game_board*>(resources::gameboard)->map()[loc];
2890  } else if(lua_isstring(L, 2)) {
2891  char const *m = luaL_checkstring(L, 2);
2893  } else return luaW_type_error(L, 2, "location or terrain string");
2894  lua_pushinteger(L, 100 - u.defense_modifier(t));
2895  return 1;
2896 }
2897 
2898 /**
2899  * Returns true if the unit has the given ability enabled.
2900  * - Arg 1: unit userdata.
2901  * - Arg 2: string.
2902  * - Ret 1: boolean.
2903  */
2905 {
2906  const unit& u = luaW_checkunit(L, 1);
2907  char const *m = luaL_checkstring(L, 2);
2908  lua_pushboolean(L, u.get_ability_bool(m));
2909  return 1;
2910 }
2911 
2912 /**
2913  * Changes a unit to the given unit type.
2914  * - Arg 1: unit userdata.
2915  * - Arg 2: unit type name
2916  * - Arg 3: (optional) unit variation name
2917  */
2918 static int intf_transform_unit(lua_State *L)
2919 {
2920  unit& u = luaW_checkunit(L, 1);
2921  char const *m = luaL_checkstring(L, 2);
2922  const unit_type *utp = unit_types.find(m);
2923  if (!utp) return luaL_argerror(L, 2, "unknown unit type");
2924  if(lua_isstring(L, 3)) {
2925  const std::string& m2 = lua_tostring(L, 3);
2926  if(!utp->has_variation(m2)) return luaL_argerror(L, 2, "unknown unit variation");
2927  utp = &utp->get_variation(m2);
2928  }
2929  u.advance_to(*utp);
2930 
2931  return 0;
2932 }
2933 
2934 /**
2935  * Puts a table at the top of the stack with some combat result.
2936  */
2937 static void luaW_pushsimdata(lua_State *L, const combatant &cmb)
2938 {
2939  int n = cmb.hp_dist.size();
2940  lua_createtable(L, 0, 4);
2941  lua_pushnumber(L, cmb.poisoned);
2942  lua_setfield(L, -2, "poisoned");
2943  lua_pushnumber(L, cmb.slowed);
2944  lua_setfield(L, -2, "slowed");
2945  lua_pushnumber(L, cmb.untouched);
2946  lua_setfield(L, -2, "untouched");
2947  lua_pushnumber(L, cmb.average_hp());
2948  lua_setfield(L, -2, "average_hp");
2949  lua_createtable(L, n, 0);
2950  for (int i = 0; i < n; ++i) {
2951  lua_pushnumber(L, cmb.hp_dist[i]);
2952  lua_rawseti(L, -2, i);
2953  }
2954  lua_setfield(L, -2, "hp_chance");
2955 }
2956 
2957 /**
2958  * Puts a table at the top of the stack with information about the combatants' weapons.
2959  */
2960 static void luaW_pushsimweapon(lua_State *L, const battle_context_unit_stats &bcustats)
2961 {
2962 
2963  lua_createtable(L, 0, 16);
2964 
2965  lua_pushnumber(L, bcustats.num_blows);
2966  lua_setfield(L, -2, "num_blows");
2967  lua_pushnumber(L, bcustats.damage);
2968  lua_setfield(L, -2, "damage");
2969  lua_pushnumber(L, bcustats.chance_to_hit);
2970  lua_setfield(L, -2, "chance_to_hit");
2971  lua_pushboolean(L, bcustats.poisons);
2972  lua_setfield(L, -2, "poisons");
2973  lua_pushboolean(L, bcustats.slows);
2974  lua_setfield(L, -2, "slows");
2975  lua_pushboolean(L, bcustats.petrifies);
2976  lua_setfield(L, -2, "petrifies");
2977  lua_pushboolean(L, bcustats.plagues);
2978  lua_setfield(L, -2, "plagues");
2979  lua_pushstring(L, bcustats.plague_type.c_str());
2980  lua_setfield(L, -2, "plague_type");
2981  lua_pushboolean(L, bcustats.backstab_pos);
2982  lua_setfield(L, -2, "backstabs");
2983  lua_pushnumber(L, bcustats.rounds);
2984  lua_setfield(L, -2, "rounds");
2985  lua_pushboolean(L, bcustats.firststrike);
2986  lua_setfield(L, -2, "firststrike");
2987  lua_pushboolean(L, bcustats.drains);
2988  lua_setfield(L, -2, "drains");
2989  lua_pushnumber(L, bcustats.drain_constant);
2990  lua_setfield(L, -2, "drain_constant");
2991  lua_pushnumber(L, bcustats.drain_percent);
2992  lua_setfield(L, -2, "drain_percent");
2993 
2994 
2995  //if we called simulate_combat without giving an explicit weapon this can be useful.
2996  lua_pushnumber(L, bcustats.attack_num);
2997  lua_setfield(L, -2, "attack_num"); // DEPRECATED
2998  lua_pushnumber(L, bcustats.attack_num + 1);
2999  lua_setfield(L, -2, "number");
3000  //this is nullptr when there is no counter weapon
3001  if(bcustats.weapon != nullptr)
3002  {
3003  lua_pushstring(L, bcustats.weapon->id().c_str());
3004  lua_setfield(L, -2, "name");
3005  luaW_pushweapon(L, bcustats.weapon);
3006  lua_setfield(L, -2, "weapon");
3007  }
3008 
3009 }
3010 
3011 /**
3012  * Simulates a combat between two units.
3013  * - Arg 1: attacker userdata.
3014  * - Arg 2: optional weapon index.
3015  * - Arg 3: defender userdata.
3016  * - Arg 4: optional weapon index.
3017  *
3018  * - Ret 1: attacker results.
3019  * - Ret 2: defender results.
3020  * - Ret 3: info about the attacker weapon.
3021  * - Ret 4: info about the defender weapon.
3022  */
3024 {
3025  int arg_num = 1, att_w = -1, def_w = -1;
3026 
3027  unit_const_ptr att = luaW_checkunit(L, arg_num).shared_from_this();
3028  ++arg_num;
3029  if (lua_isnumber(L, arg_num)) {
3030  att_w = lua_tointeger(L, arg_num) - 1;
3031  if (att_w < 0 || att_w >= static_cast<int>(att->attacks().size()))
3032  return luaL_argerror(L, arg_num, "weapon index out of bounds");
3033  ++arg_num;
3034  }
3035 
3036  unit_const_ptr def = luaW_checkunit(L, arg_num).shared_from_this();
3037  ++arg_num;
3038  if (lua_isnumber(L, arg_num)) {
3039  def_w = lua_tointeger(L, arg_num) - 1;
3040  if (def_w < 0 || def_w >= static_cast<int>(def->attacks().size()))
3041  return luaL_argerror(L, arg_num, "weapon index out of bounds");
3042  ++arg_num;
3043  }
3044 
3045  battle_context context(units(), att->get_location(),
3046  def->get_location(), att_w, def_w, 0.0, nullptr, att, def);
3047 
3048  luaW_pushsimdata(L, context.get_attacker_combatant());
3049  luaW_pushsimdata(L, context.get_defender_combatant());
3050  luaW_pushsimweapon(L, context.get_attacker_stats());
3051  luaW_pushsimweapon(L, context.get_defender_stats());
3052  return 4;
3053 }
3054 
3055 /**
3056  * Plays a sound, possibly repeated.
3057  * - Arg 1: string.
3058  * - Arg 2: optional integer.
3059  */
3061 {
3062  if (play_controller_.is_skipping_replay()) return 0;
3063  char const *m = luaL_checkstring(L, 1);
3064  int repeats = luaL_optinteger(L, 2, 0);
3065  sound::play_sound(m, sound::SOUND_FX, repeats);
3066  return 0;
3067 }
3068 
3069 /**
3070  * Scrolls to given tile.
3071  * - Arg 1: location.
3072  * - Arg 2: boolean preventing scroll to fog.
3073  * - Arg 3: boolean specifying whether to warp instantly.
3074  * - Arg 4: boolean specifying whether to skip if already onscreen
3075  */
3077 {
3078  map_location loc = luaW_checklocation(L, 1);
3079  bool check_fogged = luaW_toboolean(L, 2);
3081  ? luaW_toboolean(L, 3)
3084  : luaW_toboolean(L, 3)
3087  ;
3088  if (game_display_) {
3089  game_display_->scroll_to_tile(loc, scroll, check_fogged);
3090  }
3091  return 0;
3092 }
3093 
3094 /**
3095  * Selects and highlights the given location on the map.
3096  * - Arg 1: location.
3097  * - Args 2,3: booleans
3098  */
3100 {
3101  events::command_disabler command_disabler;
3102  if(lua_isnoneornil(L, 1)) {
3103  play_controller_.get_mouse_handler_base().select_hex(map_location::null_location(), false, false, false);
3104  return 0;
3105  }
3106  const map_location loc = luaW_checklocation(L, 1);
3107  if(!map().on_board(loc)) return luaL_argerror(L, 1, "not on board");
3108  bool highlight = true;
3109  if(!lua_isnoneornil(L, 2))
3110  highlight = luaW_toboolean(L, 2);
3111  const bool fire_event = luaW_toboolean(L, 3);
3112  play_controller_.get_mouse_handler_base().select_hex(
3113  loc, false, highlight, fire_event);
3114  return 0;
3115 }
3116 
3117 /**
3118  * Deselects any highlighted hex on the map.
3119  * No arguments or return values
3120  */
3122 {
3123  if(game_display_) {
3124  game_display_->highlight_hex(map_location::null_location());
3125  }
3126 
3127  return 0;
3128 }
3129 
3130 /**
3131  * Return true if a replay is in progress but the player has chosen to skip it
3132  */
3134 {
3135  bool skipping = play_controller_.is_skipping_replay() || play_controller_.is_skipping_story();
3136  if (!skipping) {
3137  skipping = game_state_.events_manager_->pump().context_skip_messages();
3138  }
3139  lua_pushboolean(L, skipping);
3140  return 1;
3141 }
3142 
3143 /**
3144  * Set whether to skip messages
3145  * Arg 1 (optional) - boolean
3146  */
3148 {
3149  bool skip = true;
3150  if (!lua_isnone(L, 1)) {
3151  skip = luaW_toboolean(L, 1);
3152  }
3153  game_state_.events_manager_->pump().context_skip_messages(skip);
3154  return 0;
3155 }
3156 
3157 namespace
3158 {
3159  struct lua_synchronize : mp_sync::user_choice
3160  {
3161  lua_State *L;
3162  int user_choice_index;
3163  int random_choice_index;
3164  int ai_choice_index;
3165  std::string desc;
3166  lua_synchronize(lua_State *l, const std::string& descr, int user_index, int random_index = 0, int ai_index = 0)
3167  : L(l)
3168  , user_choice_index(user_index)
3169  , random_choice_index(random_index)
3170  , ai_choice_index(ai_index != 0 ? ai_index : user_index)
3171  , desc(descr)
3172  {}
3173 
3174  virtual config query_user(int side) const override
3175  {
3176  bool is_local_ai = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).board().get_team(side).is_local_ai();
3177  config cfg;
3178  query_lua(side, is_local_ai ? ai_choice_index : user_choice_index, cfg);
3179  return cfg;
3180  }
3181 
3182  virtual config random_choice(int side) const override
3183  {
3184  config cfg;
3185  if(random_choice_index != 0 && lua_isfunction(L, random_choice_index)) {
3186  query_lua(side, random_choice_index, cfg);
3187  }
3188  return cfg;
3189  }
3190 
3191  virtual std::string description() const override
3192  {
3193  return desc;
3194  }
3195 
3196  void query_lua(int side, int function_index, config& cfg) const
3197  {
3198  lua_pushvalue(L, function_index);
3199  lua_pushnumber(L, side);
3200  if (luaW_pcall(L, 1, 1, false)) {
3201  if(!luaW_toconfig(L, -1, cfg)) {
3202  static const char* msg = "function returned to wesnoth.sync.[multi_]evaluate a table which was partially invalid";
3203  lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).log_error(msg);
3204  lua_warning(L, msg, false);
3205  }
3206  }
3207  }
3208  //Although lua's sync_choice can show a dialog, (and will in most cases)
3209  //we return false to enable other possible things that do not contain UI things.
3210  //it's in the responsibility of the umc dev to not show dialogs during prestart events.
3211  virtual bool is_visible() const override { return false; }
3212  };
3213 }//unnamed namespace for lua_synchronize
3214 
3215 /**
3216  * Ensures a value is synchronized among all the clients.
3217  * - Arg 1: optional string specifying the type id of the choice.
3218  * - Arg 2: function to compute the value, called if the client is the master.
3219  * - Arg 3: optional function, called instead of the first function if the user is not human.
3220  * - Arg 4: optional integer specifying, on which side the function should be evaluated.
3221  * - Ret 1: WML table returned by the function.
3222  */
3223 static int intf_synchronize_choice(lua_State *L)
3224 {
3225  std::string tagname = "input";
3226  t_string desc = _("input");
3227  int human_func = 0;
3228  int ai_func = 0;
3229  int side_for;
3230 
3231  int nextarg = 1;
3232  if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) {
3233  ++nextarg;
3234  }
3235  if(lua_isfunction(L, nextarg)) {
3236  human_func = nextarg++;
3237  }
3238  else {
3239  return luaL_argerror(L, nextarg, "expected a function");
3240  }
3241  if(lua_isfunction(L, nextarg)) {
3242  ai_func = nextarg++;
3243  }
3244  side_for = lua_tointeger(L, nextarg);
3245 
3246  config cfg = mp_sync::get_user_choice(tagname, lua_synchronize(L, desc, human_func, 0, ai_func), side_for);
3247  luaW_pushconfig(L, cfg);
3248  return 1;
3249 }
3250 /**
3251  * Ensures a value is synchronized among all the clients.
3252  * - Arg 1: optional string the id of this type of user input, may only contain characters a-z and '_'
3253  * - Arg 2: function to compute the value, called if the client is the master.
3254  * - Arg 3: an optional function to compute the value, if the side was null/empty controlled.
3255  * - Arg 4: an array of integers specifying, on which side the function should be evaluated.
3256  * - Ret 1: a map int -> WML tabls.
3257  */
3258 static int intf_synchronize_choices(lua_State *L)
3259 {
3260  std::string tagname = "input";
3261  t_string desc = _("input");
3262  int human_func = 0;
3263  int null_func = 0;
3264  std::vector<int> sides_for;
3265 
3266  int nextarg = 1;
3267  if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) {
3268  ++nextarg;
3269  }
3270  if(lua_isfunction(L, nextarg)) {
3271  human_func = nextarg++;
3272  }
3273  else {
3274  return luaL_argerror(L, nextarg, "expected a function");
3275  }
3276  if(lua_isfunction(L, nextarg)) {
3277  null_func = nextarg++;
3278  };
3279  sides_for = lua_check<std::vector<int>>(L, nextarg++);
3280 
3281  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())));
3282  return 1;
3283 }
3284 
3285 
3286 /**
3287  * Calls a function in an unsynced context (this specially means that all random calls used by that function will be unsynced).
3288  * This is usually used together with an unsynced if like 'if controller != network'
3289  * - Arg 1: function that will be called during the unsynced context.
3290  */
3291 static int intf_do_unsynced(lua_State *L)
3292 {
3293  set_scontext_unsynced sync;
3294  lua_pushvalue(L, 1);
3295  luaW_pcall(L, 0, 0, false);
3296  return 0;
3297 }
3298 
3299 /**
3300  * Gets all the locations matching a given filter.
3301  * - Arg 1: WML table.
3302  * - Arg 2: Optional reference unit (teleport_unit)
3303  * - Ret 1: array of integer pairs.
3304  */
3306 {
3307  vconfig filter = luaW_checkvconfig(L, 1);
3308 
3309  std::set<map_location> res;
3310  filter_context & fc = game_state_;
3311  const terrain_filter t_filter(filter, &fc, false);
3312  if(luaW_isunit(L, 2)) {
3313  t_filter.get_locations(res, *luaW_tounit(L, 2), true);
3314  } else {
3315  t_filter.get_locations(res, true);
3316  }
3317 
3318  luaW_push_locationset(L, res);
3319  return 1;
3320 }
3321 
3322 /**
3323  * Matches a location against the given filter.
3324  * - Arg 1: location.
3325  * - Arg 2: WML table.
3326  * - Arg 3: Optional reference unit (teleport_unit)
3327  * - Ret 1: boolean.
3328  */
3330 {
3331  map_location loc = luaW_checklocation(L, 1);
3332  vconfig filter = luaW_checkvconfig(L, 2, true);
3333 
3334  if (filter.null()) {
3335  lua_pushboolean(L, true);
3336  return 1;
3337  }
3338 
3339  filter_context & fc = game_state_;
3340  const terrain_filter t_filter(filter, &fc, false);
3341  if(luaW_isunit(L, 3)) {
3342  lua_pushboolean(L, t_filter.match(loc, *luaW_tounit(L, 3)));
3343  } else {
3344  lua_pushboolean(L, t_filter.match(loc));
3345  }
3346  return 1;
3347 }
3348 
3349 
3350 
3351 /**
3352  * Matches a side against the given filter.
3353  * - Args 1: side number.
3354  * - Arg 2: WML table.
3355  * - Ret 1: boolean.
3356  */
3358 {
3359  vconfig filter = luaW_checkvconfig(L, 2, true);
3360 
3361  if (filter.null()) {
3362  lua_pushboolean(L, true);
3363  return 1;
3364  }
3365 
3366  filter_context & fc = game_state_;
3367  side_filter s_filter(filter, &fc);
3368 
3369  if(team* t = luaW_toteam(L, 1)) {
3370  lua_pushboolean(L, s_filter.match(*t));
3371  } else {
3372  unsigned side = luaL_checkinteger(L, 1) - 1;
3373  if (side >= teams().size()) return 0;
3374  lua_pushboolean(L, s_filter.match(side + 1));
3375  }
3376  return 1;
3377 }
3378 
3380 {
3381  int team_i;
3382  if(team* t = luaW_toteam(L, 1)) {
3383  team_i = t->side();
3384  } else {
3385  team_i = luaL_checkinteger(L, 1);
3386  }
3387  std::string flag = luaL_optlstring(L, 2, "", nullptr);
3388  std::string color = luaL_optlstring(L, 3, "", nullptr);
3389 
3390  if(flag.empty() && color.empty()) {
3391  return 0;
3392  }
3393  if(team_i < 1 || static_cast<std::size_t>(team_i) > teams().size()) {
3394  return luaL_error(L, "set_side_id: side number %d out of range", team_i);
3395  }
3396  team& side = board().get_team(team_i);
3397 
3398  if(!color.empty()) {
3399  side.set_color(color);
3400  }
3401  if(!flag.empty()) {
3402  side.set_flag(flag);
3403  }
3404 
3405  game_display_->reinit_flags_for_team(side);
3406  return 0;
3407 }
3408 
3409 static int intf_modify_ai(lua_State *L, const char* action)
3410 {
3411  int side_num;
3412  if(team* t = luaW_toteam(L, 1)) {
3413  side_num = t->side();
3414  } else {
3415  side_num = luaL_checkinteger(L, 1);
3416  }
3417  std::string path = luaL_checkstring(L, 2);
3418  config cfg {
3419  "action", action,
3420  "path", path
3421  };
3422  if(strcmp(action, "delete") == 0) {
3424  return 0;
3425  }
3426  config component = luaW_checkconfig(L, 3);
3427  std::size_t len = std::string::npos, open_brak = path.find_last_of('[');
3428  std::size_t dot = path.find_last_of('.');
3429  if(open_brak != len) {
3430  len = open_brak - dot - 1;
3431  }
3432  cfg.add_child(path.substr(dot + 1, len), component);
3434  return 0;
3435 }
3436 
3437 static int intf_switch_ai(lua_State *L)
3438 {
3439  int side_num;
3440  if(team* t = luaW_toteam(L, 1)) {
3441  side_num = t->side();
3442  } else {
3443  side_num = luaL_checkinteger(L, 1);
3444  }
3445  if(lua_isstring(L, 2)) {
3446  std::string file = luaL_checkstring(L, 2);
3447  if(!ai::manager::get_singleton().add_ai_for_side_from_file(side_num, file)) {
3448  std::string err = formatter() << "Could not load AI for side " << side_num << " from file " << file;
3449  lua_pushlstring(L, err.c_str(), err.length());
3450  return lua_error(L);
3451  }
3452  } else {
3454  }
3455  return 0;
3456 }
3457 
3458 static int intf_append_ai(lua_State *L)
3459 {
3460  int side_num;
3461  if(team* t = luaW_toteam(L, 1)) {
3462  side_num = t->side();
3463  } else {
3464  side_num = luaL_checkinteger(L, 1);
3465  }
3466  config cfg = luaW_checkconfig(L, 2);
3467  if(!cfg.has_child("ai")) {
3468  cfg = config {"ai", cfg};
3469  }
3470  bool added_dummy_stage = false;
3471  if(!cfg.child("ai").has_child("stage")) {
3472  added_dummy_stage = true;
3473  cfg.child("ai").add_child("stage", config {"name", "empty"});
3474  }
3476  if(added_dummy_stage) {
3477  for(auto iter = cfg.ordered_begin(); iter != cfg.ordered_end(); iter++) {
3478  if(iter->key == "stage" && iter->cfg["name"] == "empty") {
3479  iter = cfg.erase(iter);
3480  }
3481  }
3482  }
3484  return 0;
3485 }
3486 
3488 {
3489  unsigned i = luaL_checkinteger(L, 1);
3490  if(i < 1 || i > teams().size()) return 0;
3491  luaW_pushteam(L, board().get_team(i));
3492  return 1;
3493 }
3494 
3495 /**
3496  * Returns a proxy table array for all sides matching the given SSF.
3497  * - Arg 1: SSF
3498  * - Ret 1: proxy table array
3499  */
3501 {
3502  LOG_LUA << "intf_get_sides called: this = " << std::hex << this << std::dec << " myname = " << my_name();
3503  std::vector<int> sides;
3504  const vconfig ssf = luaW_checkvconfig(L, 1, true);
3505  if(ssf.null()) {
3506  for (unsigned side_number = 1; side_number <= teams().size(); ++side_number) {
3507  sides.push_back(side_number);
3508  }
3509  } else {
3510  filter_context & fc = game_state_;
3511 
3512  side_filter filter(ssf, &fc);
3513  sides = filter.get_teams();
3514  }
3515 
3516  lua_settop(L, 0);
3517  lua_createtable(L, sides.size(), 0);
3518  unsigned index = 1;
3519  for(int side : sides) {
3520  luaW_pushteam(L, board().get_team(side));
3521  lua_rawseti(L, -2, index);
3522  ++index;
3523  }
3524 
3525  return 1;
3526 }
3527 
3528 /**
3529  * Adds a modification to a unit.
3530  * - Arg 1: unit.
3531  * - Arg 2: string.
3532  * - Arg 3: WML table.
3533  * - Arg 4: (optional) Whether to add to [modifications] - default true
3534  */
3535 static int intf_add_modification(lua_State *L)
3536 {
3537  unit& u = luaW_checkunit(L, 1);
3538  char const *m = luaL_checkstring(L, 2);
3539  std::string sm = m;
3540  if (sm == "advance") { // Maintain backwards compatibility
3541  sm = "advancement";
3542  deprecated_message("\"advance\" modification type", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use \"advancement\" instead.");
3543  }
3544  if (sm != "advancement" && sm != "object" && sm != "trait") {
3545  return luaL_argerror(L, 2, "unknown modification type");
3546  }
3547  bool write_to_mods = true;
3548  if (!lua_isnone(L, 4)) {
3549  write_to_mods = luaW_toboolean(L, 4);
3550  }
3551  if(sm.empty()) {
3552  write_to_mods = false;
3553  }
3554 
3555  config cfg = luaW_checkconfig(L, 3);
3556  u.add_modification(sm, cfg, !write_to_mods);
3557  return 0;
3558 }
3559 
3560 /**
3561  * Removes modifications from a unit
3562  * - Arg 1: unit
3563  * - Arg 2: table (filter as [filter_wml])
3564  * - Arg 3: type of modification (default "object")
3565  */
3566 static int intf_remove_modifications(lua_State *L)
3567 {
3568  unit& u = luaW_checkunit(L, 1);
3569  config filter = luaW_checkconfig(L, 2);
3570  std::string tag = luaL_optstring(L, 3, "object");
3571  //TODO
3572  if(filter.attribute_count() == 1 && filter.all_children_count() == 0 && filter.attribute_range().front().first == "duration") {
3573  u.expire_modifications(filter["duration"]);
3574  } else {
3575  for(config& obj : u.get_modifications().child_range(tag)) {
3576  if(obj.matches(filter)) {
3577  obj["duration"] = "now";
3578  }
3579  }
3580  u.expire_modifications("now");
3581  }
3582  return 0;
3583 }
3584 
3585 /**
3586  * Advances a unit if the unit has enough xp.
3587  * - Arg 1: unit.
3588  * - Arg 2: optional boolean whether to animate the advancement.
3589  * - Arg 3: optional boolean whether to fire advancement events.
3590  */
3591 static int intf_advance_unit(lua_State *L)
3592 {
3593  events::command_disabler command_disabler;
3594  unit& u = luaW_checkunit(L, 1, true);
3596  if(lua_isboolean(L, 2)) {
3597  par.animate(luaW_toboolean(L, 2));
3598  }
3599  if(lua_isboolean(L, 3)) {
3600  par.fire_events(luaW_toboolean(L, 3));
3601  }
3602  advance_unit_at(par);
3603  return 0;
3604 }
3605 
3606 
3607 /**
3608  * Adds a new known unit type to the help system.
3609  * - Arg 1: string.
3610  */
3611 static int intf_add_known_unit(lua_State *L)
3612 {
3613  char const *ty = luaL_checkstring(L, 1);
3614  if(!unit_types.find(ty))
3615  {
3616  std::stringstream ss;
3617  ss << "unknown unit type: '" << ty << "'";
3618  return luaL_argerror(L, 1, ss.str().c_str());
3619  }
3620  preferences::encountered_units().insert(ty);
3621  return 0;
3622 }
3623 
3624 /**
3625  * Adds an overlay on a tile.
3626  * - Arg 1: location.
3627  * - Arg 2: WML table.
3628  */
3630 {
3631  map_location loc = luaW_checklocation(L, 1);
3632  vconfig cfg = luaW_checkvconfig(L, 2);
3633  const vconfig &ssf = cfg.child("filter_team");
3634 
3635  std::string team_name;
3636  if (!ssf.null()) {
3637  const std::vector<int>& teams = side_filter(ssf, &game_state_).get_teams();
3638  std::vector<std::string> team_names;
3639  std::transform(teams.begin(), teams.end(), std::back_inserter(team_names),
3640  [&](int team) { return game_state_.get_disp_context().get_team(team).team_name(); });
3641  team_name = utils::join(team_names);
3642  } else {
3643  team_name = cfg["team_name"].str();
3644  }
3645 
3646  if (game_display_) {
3647  game_display_->add_overlay(loc, cfg["image"], cfg["halo"],
3648  team_name, cfg["name"], cfg["visible_in_fog"].to_bool(true),
3649  cfg["submerge"].to_double(0), cfg["z_order"].to_double(0));
3650  }
3651  return 0;
3652 }
3653 
3654 /**
3655  * Removes an overlay from a tile.
3656  * - Arg 1: location.
3657  * - Arg 2: optional string.
3658  */
3660 {
3661  map_location loc = luaW_checklocation(L, 1);
3662  char const *m = lua_tostring(L, 2);
3663 
3664  if (m) {
3665  if (game_display_) {
3666  game_display_->remove_single_overlay(loc, m);
3667  }
3668  } else {
3669  if (game_display_) {
3670  game_display_->remove_overlay(loc);
3671  }
3672  }
3673  return 0;
3674 }
3675 
3677 {
3678  replay& recorder = play_controller_.get_replay();
3679  const int nargs = lua_gettop(L);
3680  if(nargs < 2 || nargs > 3) {
3681  return luaL_error(L, "Wrong number of arguments to ai.log_replay() - should be 2 or 3 arguments.");
3682  }
3683  const std::string key = nargs == 2 ? luaL_checkstring(L, 1) : luaL_checkstring(L, 2);
3684  config cfg;
3685  if(nargs == 2) {
3686  recorder.add_log_data(key, luaL_checkstring(L, 2));
3687  } else if(luaW_toconfig(L, 3, cfg)) {
3688  recorder.add_log_data(luaL_checkstring(L, 1), key, cfg);
3689  } else if(!lua_isstring(L, 3)) {
3690  return luaL_argerror(L, 3, "accepts only string or config");
3691  } else {
3692  recorder.add_log_data(luaL_checkstring(L, 1), key, luaL_checkstring(L, 3));
3693  }
3694  return 0;
3695 }
3696 
3698 {
3699  lua_event_filter(game_lua_kernel& lk, int idx, const config& args) : lk(lk), args_(args)
3700  {
3701  ref_ = lk.save_wml_event(idx);
3702  }
3703  bool operator()(const game_events::queued_event& event_info) const override
3704  {
3705  bool result;
3706  return lk.run_wml_event(ref_, args_, event_info, &result) && result;
3707  }
3709  {
3710  lk.clear_wml_event(ref_);
3711  }
3712  void serialize(config& cfg) const override {
3713  cfg.add_child("filter_lua")["code"] = "<function>";
3714  }
3715 private:
3717  int ref_;
3719 };
3720 
3721 static std::string read_event_name(lua_State* L, int idx)
3722 {
3723  if(lua_isstring(L, idx)) {
3724  return lua_tostring(L, idx);
3725  } else {
3726  return utils::join(lua_check<std::vector<std::string>>(L, idx));
3727  }
3728 }
3729 
3730 /** Add a new event handler
3731  * Arg 1: Table of options.
3732  * name: Event to handle, as a string or list of strings
3733  * id: Event ID
3734  * 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.
3735  * first_time_only: Whether this event should fire again after the first time; default true.
3736  * filter: Event filters as a config with filter tags, a table of the form {filter_type = filter_contents}, or a function
3737  * 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.
3738  * action: The function to call when the event triggers. Defaults to wesnoth.wml_actions.command.
3739  */
3741 {
3742  game_events::manager & man = *game_state_.events_manager_;
3743  using namespace std::literals;
3744  std::string name, id = luaW_table_get_def(L, 1, "id", ""s);
3745  bool repeat = !luaW_table_get_def(L, 1, "first_time_only", true), is_menu_item = luaW_table_get_def(L, 1, "menu_item", false);
3746  if(luaW_tableget(L, 1, "name")) {
3747  name = read_event_name(L, -1);
3748  } else if(is_menu_item) {
3749  if(id.empty()) {
3750  return luaL_argerror(L, 1, "non-empty id is required for a menu item");
3751  }
3752  name = "menu item " + id;
3753  }
3754  if(id.empty() && name.empty()) {
3755  return luaL_argerror(L, 1, "either a name or id is required");
3756  }
3757  auto new_handler = man.add_event_handler_from_lua(name, id, repeat, is_menu_item);
3758  if(new_handler.valid()) {
3759  bool has_lua_filter = false;
3760  new_handler->set_arguments(luaW_table_get_def(L, 1, "content", config{"__empty_lua_event", true}));
3761 
3762  if(luaW_tableget(L, 1, "filter")) {
3763  int filterIdx = lua_gettop(L);
3764  config filters;
3765  if(!luaW_toconfig(L, filterIdx, filters)) {
3766  if(lua_isfunction(L, filterIdx)) {
3767  int fcnIdx = lua_absindex(L, -1);
3768  new_handler->add_filter(std::make_unique<lua_event_filter>(*this, fcnIdx, luaW_table_get_def(L, 1, "filter_args", config())));
3769  has_lua_filter = true;
3770  } else {
3771 #define READ_ONE_FILTER(key) \
3772  do { \
3773  if(luaW_tableget(L, filterIdx, key)) { \
3774  if(lua_isstring(L, -1)) { \
3775  filters.add_child("insert_tag", config{ \
3776  "name", "filter_" key, \
3777  "variable", luaL_checkstring(L, -1) \
3778  }); \
3779  } else { \
3780  filters.add_child("filter_" key, luaW_checkconfig(L, -1)); \
3781  } \
3782  } \
3783  } while(false);
3784  READ_ONE_FILTER("condition");
3785  READ_ONE_FILTER("side");
3786  READ_ONE_FILTER("unit");
3787  READ_ONE_FILTER("attack");
3788  READ_ONE_FILTER("second_unit");
3789  READ_ONE_FILTER("second_attack");
3790 #undef READ_ONE_FILTER
3791  if(luaW_tableget(L, filterIdx, "formula")) {
3792  filters["filter_formula"] = luaL_checkstring(L, -1);
3793  }
3794  }
3795  }
3796  new_handler->read_filters(filters);
3797  }
3798 
3799  if(luaW_tableget(L, 1, "action")) {
3800  new_handler->set_event_ref(save_wml_event(-1), has_preloaded_);
3801  } else {
3802  if(has_lua_filter) {
3803  // This just sets the appropriate flags so the engine knows it cannot be serialized.
3804  // The register_wml_event call will override the actual event_ref so just pass LUA_NOREF here.
3805  new_handler->set_event_ref(LUA_NOREF, has_preloaded_);
3806  }
3807  new_handler->register_wml_event(*this);
3808  }
3809  }
3810  return 0;
3811 }
3812 
3813 /** Add a new event handler
3814  * Arg 1: Event to handle, as a string or list of strings; or menu item ID if this is a menu item
3815  * Arg 2: The function to call when the event triggers
3816  */
3817 template<bool is_menu_item>
3819 {
3820  game_events::manager & man = *game_state_.events_manager_;
3821  bool repeat = true;
3822  std::string name = read_event_name(L, 1), id;
3823  if(name.empty()) {
3824  return luaL_argerror(L, 1, "must not be empty");
3825  }
3826  if(is_menu_item) {
3827  id = name;
3828  name = "menu item " + name;
3829  }
3830  auto new_handler = man.add_event_handler_from_lua(name, id, repeat, is_menu_item);
3831  if(new_handler.valid()) {
3832  // An event with empty arguments is not added, so set some dummy arguments
3833  new_handler->set_arguments(config{"__quick_lua_event", true});
3834  new_handler->set_event_ref(save_wml_event(2), has_preloaded_);
3835  }
3836  return 0;
3837 }
3838 
3839 /** Add a new event handler
3840  * Arg: A full event specification as a WML config
3841  */
3843 {
3844  game_events::manager & man = *game_state_.events_manager_;
3845  vconfig cfg(luaW_checkvconfig(L, 1));
3846  bool delayed_variable_substitution = cfg["delayed_variable_substitution"].to_bool(true);
3847  if(delayed_variable_substitution) {
3848  man.add_event_handler_from_wml(cfg.get_config(), *this);
3849  } else {
3851  }
3852  return 0;
3853 }
3854 
3856 {
3857  game_state_.events_manager_->remove_event_handler(luaL_checkstring(L, 1));
3858  return 0;
3859 }
3860 
3862 {
3863  if (game_display_) {
3864  game_display_->adjust_color_overlay(luaL_checkinteger(L, 1), luaL_checkinteger(L, 2), luaL_checkinteger(L, 3));
3865  game_display_->invalidate_all();
3866  }
3867  return 0;
3868 }
3869 
3871 {
3872  if(game_display_) {
3873  auto color = game_display_->get_color_overlay();
3874  lua_pushinteger(L, color.r);
3875  lua_pushinteger(L, color.g);
3876  lua_pushinteger(L, color.b);
3877  return 3;
3878  }
3879  return 0;
3880 }
3881 
3883 {
3884  if(game_display_) {
3885  auto vec = lua_check<std::vector<uint8_t>>(L, 1);
3886  if(vec.size() != 4) {
3887  return luaW_type_error(L, 1, "array of 4 integers");
3888  }
3889  color_t fade{vec[0], vec[1], vec[2], vec[3]};
3890  game_display_->fade_to(fade, luaL_checkinteger(L, 2));
3891  }
3892  return 0;
3893 }
3894 
3895 /**
3896  * Delays engine for a while.
3897  * - Arg 1: integer.
3898  * - Arg 2: boolean (optional).
3899  */
3901 {
3902  if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::PRESTART || gamedata().phase() == game_data::INITIAL) {
3903  //don't call play_slice if the game ui is not active yet.
3904  return 0;
3905  }
3906  events::command_disabler command_disabler;
3907  lua_Integer delay = luaL_checkinteger(L, 1);
3908  if(delay == 0) {
3909  play_controller_.play_slice(false);
3910  return 0;
3911  }
3912  if(luaW_toboolean(L, 2) && game_display_ && game_display_->turbo_speed() > 0) {
3913  delay /= game_display_->turbo_speed();
3914  }
3915  const unsigned final = SDL_GetTicks() + delay;
3916  do {
3917  play_controller_.play_slice(false);
3918  SDL_Delay(10);
3919  } while (static_cast<int>(final - SDL_GetTicks()) > 0);
3920  return 0;
3921 }
3922 
3924 {
3925  // TODO: Support color = {r = 0, g = 0, b = 0}
3926  if (game_display_) {
3927  vconfig cfg(luaW_checkvconfig(L, 1));
3928 
3929  game_display &screen = *game_display_;
3930 
3931  terrain_label label(screen.labels(), cfg.get_config());
3932 
3933  screen.labels().set_label(label.location(), label.text(), label.creator(), label.team_name(), label.color(),
3934  label.visible_in_fog(), label.visible_in_shroud(), label.immutable(), label.category(), label.tooltip());
3935  }
3936  return 0;
3937 }
3938 
3940 {
3941  if (game_display_) {
3942  map_location loc = luaW_checklocation(L, 1);
3943  std::string team_name;
3944 
3945  // If there's only one parameter and it's a table, check if it contains team_name
3946  if(lua_gettop(L) == 1 && lua_istable(L, 1)) {
3947  using namespace std::literals;
3948  team_name = luaW_table_get_def(L, 1, "team_name", ""sv);
3949  } else {
3950  team_name = luaL_optstring(L, 2, "");
3951  }
3952 
3953  game_display_->labels().set_label(loc, "", -1, team_name);
3954  }
3955  return 0;
3956 }
3957 
3959 {
3960  if(game_display_) {
3961  game_display &screen = *game_display_;
3962  auto loc = luaW_checklocation(L, 1);
3963  const terrain_label* label = nullptr;
3964  switch(lua_type(L, 2)) {
3965  // Missing 2nd argument - get global label
3966  case LUA_TNONE: case LUA_TNIL:
3967  label = screen.labels().get_label(loc, "");
3968  break;
3969  // Side number - get label belonging to that side's team
3970  case LUA_TNUMBER:
3971  if(size_t n = luaL_checkinteger(L, 2); n > 0 && n <= teams().size()) {
3972  label = screen.labels().get_label(loc, teams().at(n - 1).team_name());
3973  }
3974  break;
3975  // String - get label belonging to the team with that name
3976  case LUA_TSTRING:
3977  label = screen.labels().get_label(loc, luaL_checkstring(L, 2));
3978  break;
3979  // Side userdata - get label belonging to that side's team
3980  case LUA_TUSERDATA:
3981  label = screen.labels().get_label(loc, luaW_checkteam(L, 2).team_name());
3982  break;
3983  }
3984  if(label) {
3985  config cfg;
3986  label->write(cfg);
3987  luaW_pushconfig(L, cfg);
3988  return 1;
3989  }
3990  }
3991  return 0;
3992 }
3993 
3995 {
3996  if (game_display_) {
3997  game_display & screen = *game_display_;
3998 
3999  vconfig cfg(luaW_checkvconfig(L, 1));
4000  bool clear_shroud(luaW_toboolean(L, 2));
4001 
4002  // We do this twice so any applicable redraws happen both before and after
4003  // any events caused by redrawing shroud are fired
4004  bool result = screen.maybe_rebuild();
4005  if (!result) {
4006  screen.invalidate_all();
4007  }
4008 
4009  if (clear_shroud) {
4010  side_filter filter(cfg, &game_state_);
4011  for (const int side : filter.get_teams()){
4012  actions::clear_shroud(side);
4013  }
4014  screen.recalculate_minimap();
4015  }
4016 
4017  result = screen.maybe_rebuild();
4018  if (!result) {
4019  screen.invalidate_all();
4020  }
4021  }
4022  return 0;
4023 }
4024 
4025 /**
4026  * Lua frontend to the modify_ai functionality
4027  * - Arg 1: config.
4028  */
4029 static int intf_modify_ai_old(lua_State *L)
4030 {
4031  config cfg;
4032  luaW_toconfig(L, 1, cfg);
4033  int side = cfg["side"];
4035  return 0;
4036 }
4037 
4038 static int cfun_exec_candidate_action(lua_State *L)
4039 {
4040  bool exec = luaW_toboolean(L, -1);
4041  lua_pop(L, 1);
4042 
4043  lua_getfield(L, -1, "ca_ptr");
4044 
4045  ai::candidate_action *ca = static_cast<ai::candidate_action*>(lua_touserdata(L, -1));
4046  lua_pop(L, 2);
4047  if (exec) {
4048  ca->execute();
4049  return 0;
4050  }
4051  lua_pushnumber(L, ca->evaluate());
4052  return 1;
4053 }
4054 
4055 static int cfun_exec_stage(lua_State *L)
4056 {
4057  lua_getfield(L, -1, "stg_ptr");
4058  ai::stage *stg = static_cast<ai::stage*>(lua_touserdata(L, -1));
4059  lua_pop(L, 2);
4060  stg->play_stage();
4061  return 0;
4062 }
4063 
4064 static void push_component(lua_State *L, ai::component* c, const std::string &ct = "")
4065 {
4066  lua_createtable(L, 0, 0); // Table for a component
4067 
4068  lua_pushstring(L, "name");
4069  lua_pushstring(L, c->get_name().c_str());
4070  lua_rawset(L, -3);
4071 
4072  lua_pushstring(L, "engine");
4073  lua_pushstring(L, c->get_engine().c_str());
4074  lua_rawset(L, -3);
4075 
4076  lua_pushstring(L, "id");
4077  lua_pushstring(L, c->get_id().c_str());
4078  lua_rawset(L, -3);
4079 
4080  if (ct == "candidate_action") {
4081  lua_pushstring(L, "ca_ptr");
4082  lua_pushlightuserdata(L, c);
4083  lua_rawset(L, -3);
4084 
4085  lua_pushstring(L, "exec");
4086  lua_pushcclosure(L, &cfun_exec_candidate_action, 0);
4087  lua_rawset(L, -3);
4088  }
4089 
4090  if (ct == "stage") {
4091  lua_pushstring(L, "stg_ptr");
4092  lua_pushlightuserdata(L, c);
4093  lua_rawset(L, -3);
4094 
4095  lua_pushstring(L, "exec");
4096  lua_pushcclosure(L, &cfun_exec_stage, 0);
4097  lua_rawset(L, -3);
4098  }
4099 
4100 
4101  std::vector<std::string> c_types = c->get_children_types();
4102 
4103  for (std::vector<std::string>::const_iterator t = c_types.begin(); t != c_types.end(); ++t)
4104  {
4105  std::vector<ai::component*> children = c->get_children(*t);
4106  std::string type = *t;
4107  if (type == "aspect" || type == "goal" || type == "engine")
4108  {
4109  continue;
4110  }
4111 
4112  lua_pushstring(L, type.c_str());
4113  lua_createtable(L, 0, 0); // this table will be on top of the stack during recursive calls
4114 
4115  for (std::vector<ai::component*>::const_iterator i = children.begin(); i != children.end(); ++i)
4116  {
4117  lua_pushstring(L, (*i)->get_name().c_str());
4118  push_component(L, *i, type);
4119  lua_rawset(L, -3);
4120 
4121  //if (type == "candidate_action")
4122  //{
4123  // ai::candidate_action *ca = dynamic_cast<ai::candidate_action*>(*i);
4124  // ca->execute();
4125  //}
4126  }
4127 
4128  lua_rawset(L, -3); // setting the child table
4129  }
4130 
4131 
4132 }
4133 
4134 /**
4135  * Debug access to the ai tables
4136  * - Arg 1: int
4137  * - Ret 1: ai table
4138  */
4139 static int intf_debug_ai(lua_State *L)
4140 {
4141  if (!game_config::debug) { // This function works in debug mode only
4142  return 0;
4143  }
4144  int side;
4145  if(team* t = luaW_toteam(L, 1)) {
4146  side = t->side();
4147  } else {
4148  side = luaL_checkinteger(L, 1);
4149  }
4150  lua_pop(L, 1);
4151 
4153 
4154  // Bad, but works
4155  std::vector<ai::component*> engines = c->get_children("engine");
4156  ai::engine_lua* lua_engine = nullptr;
4157  for (std::vector<ai::component*>::const_iterator i = engines.begin(); i != engines.end(); ++i)
4158  {
4159  if ((*i)->get_name() == "lua")
4160  {
4161  lua_engine = dynamic_cast<ai::engine_lua *>(*i);
4162  }
4163  }
4164 
4165  // Better way, but doesn't work
4166  //ai::component* e = ai::manager::get_singleton().get_active_ai_holder_for_side_dbg(side).get_component(c, "engine[lua]");
4167  //ai::engine_lua* lua_engine = dynamic_cast<ai::engine_lua *>(e);
4168 
4169  if (lua_engine == nullptr)
4170  {
4171  //no lua engine is defined for this side.
4172  //so set up a dummy engine
4173 
4174  ai::ai_composite * ai_ptr = dynamic_cast<ai::ai_composite *>(c);
4175 
4176  assert(ai_ptr);
4177 
4178  ai::ai_context& ai_context = ai_ptr->get_ai_context();
4180 
4181  lua_engine = new ai::engine_lua(ai_context, cfg);
4182  LOG_LUA << "Created new dummy lua-engine for debug_ai().";
4183 
4184  //and add the dummy engine as a component
4185  //to the manager, so we could use it later
4186  cfg.add_child("engine", lua_engine->to_config());
4187  ai::component_manager::add_component(c, "engine[]", cfg);
4188  }
4189 
4190  lua_engine->push_ai_table(); // stack: [-1: ai_context]
4191 
4192  lua_pushstring(L, "components");
4193  push_component(L, c); // stack: [-1: component tree; -2: ai context]
4194  lua_rawset(L, -3);
4195 
4196  return 1;
4197 }
4198 
4199 /** Allow undo sets the flag saying whether the event has mutated the game to false. */
4201 {
4202  bool allow;
4203  t_string reason;
4204  // The extra iststring is required to prevent totstring from converting a bool value
4205  if(luaW_iststring(L, 1) && luaW_totstring(L, 1, reason)) {
4206  allow = false;
4207  } else {
4208  allow = luaW_toboolean(L, 1);
4209  luaW_totstring(L, 2, reason);
4210  }
4211  gamedata().set_allow_end_turn(allow, reason);
4212  return 0;
4213 }
4214 
4215 /** Allow undo sets the flag saying whether the event has mutated the game to false. */
4217 {
4218  if(lua_isboolean(L, 1)) {
4219  play_controller_.pump().set_undo_disabled(!luaW_toboolean(L, 1));
4220  }
4221  else {
4222  play_controller_.pump().set_undo_disabled(false);
4223  }
4224  return 0;
4225 }
4226 
4228 {
4229  play_controller_.pump().set_action_canceled();
4230  return 0;
4231 }
4232 
4233 /** Adding new time_areas dynamically with Standard Location Filters.
4234  * Arg 1: Area ID
4235  * Arg 2: Area locations (either a filter or a list of locations)
4236  * Arg 3: (optional) Area schedule - WML table with [time] tags and optional current_time=
4237  */
4239 {
4240  log_scope("time_area");
4241 
4242  std::string id;
4243  std::set<map_location> locs;
4244  config times;
4245 
4246  if(lua_gettop(L) == 1) {
4247  vconfig cfg = luaW_checkvconfig(L, 1);
4248  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});
4249  id = cfg["id"].str();
4250  const terrain_filter filter(cfg, &game_state_, false);
4251  filter.get_locations(locs, true);
4252  times = cfg.get_parsed_config();
4253  } else {
4254  id = luaL_checkstring(L, 1);
4255  if(!lua_isnoneornil(L, 3))
4256  times = luaW_checkconfig(L, 3);
4257  vconfig cfg{config()};
4258  if(luaW_tovconfig(L, 2, cfg)) {
4259  // Second argument is a location filter
4260  const terrain_filter filter(cfg, &game_state_, false);
4261  filter.get_locations(locs, true);
4262  } else {
4263  // Second argument is an array of locations
4264  luaW_check_locationset(L, 2);
4265  }
4266  }
4267 
4268  tod_man().add_time_area(id, locs, times);
4269  LOG_LUA << "Lua inserted time_area '" << id << "'";
4270  return 0;
4271 }
4272 
4273 /** Removing new time_areas dynamically with Standard Location Filters. */
4275 {
4276  log_scope("remove_time_area");
4277 
4278  const char * id = luaL_checkstring(L, 1);
4279  tod_man().remove_time_area(id);
4280  LOG_LUA << "Lua removed time_area '" << id << "'";
4281 
4282  return 0;
4283 }
4284 
4286 {
4287  map_location loc;
4288  if(luaW_tolocation(L, 1, loc)) {
4289  int area_index = tod_man().get_area_on_hex(loc).first;
4290  if(area_index < 0) {
4291  lua_pushnil(L);
4292  return 1;
4293  }
4294  luaW_push_schedule(L, area_index);
4295  return 1;
4296  } else {
4297  std::string area_id = luaL_checkstring(L, 1);
4298  const auto& area_ids = tod_man().get_area_ids();
4299  if(auto iter = std::find(area_ids.begin(), area_ids.end(), area_id); iter == area_ids.end()) {
4300  lua_pushnil(L);
4301  return 1;
4302  } else {
4303  luaW_push_schedule(L, std::distance(area_ids.begin(), iter));
4304  return 1;
4305  }
4306  }
4307 }
4308 
4309 /** Replacing the current time of day schedule. */
4311 {
4312  map_location loc;
4313  if(luaL_testudata(L, 1, "schedule")) {
4314  // Replace the global schedule with a time area's schedule
4315  // Replacing the global schedule with the global schedule
4316  // is also supported but obviously a no-op
4317  int area = luaW_check_schedule(L, 1);
4318  if(area >= 0) tod_man().replace_schedule(tod_man().times(area));
4319  } else {
4320  vconfig cfg = luaW_checkvconfig(L, 1);
4321 
4322  if(cfg.get_children("time").empty()) {
4323  ERR_LUA << "attempted to to replace ToD schedule with empty schedule";
4324  } else {
4325  tod_man().replace_schedule(cfg.get_parsed_config());
4326  if (game_display_) {
4327  game_display_->new_turn();
4328  }
4329  LOG_LUA << "replaced ToD schedule";
4330  }
4331  }
4332  return 0;
4333 }
4334 
4336 {
4337  int x = luaL_checkinteger(L, 1), y = luaL_checkinteger(L, 2);
4338 
4339  if (game_display_) {
4340  game_display_->scroll(x, y, true);
4341  }
4342 
4343  return 0;
4344 }
4345 
4346 namespace {
4347  struct lua_report_generator : reports::generator
4348  {
4349  lua_State *mState;
4350  std::string name;
4351  lua_report_generator(lua_State *L, const std::string &n)
4352  : mState(L), name(n) {}
4353  virtual config generate(reports::context & rc);
4354  };
4355 
4356  config lua_report_generator::generate(reports::context & /*rc*/)
4357  {
4358  lua_State *L = mState;
4359  config cfg;
4360  if (!luaW_getglobal(L, "wesnoth", "interface", "game_display", name))
4361  return cfg;
4362  if (!luaW_pcall(L, 0, 1)) return cfg;
4363  luaW_toconfig(L, -1, cfg);
4364  lua_pop(L, 1);
4365  return cfg;
4366  }
4367 }//unnamed namespace for lua_report_generator
4368 
4369 /**
4370  * Executes its upvalue as a theme item generator.
4371  */
4372 int game_lua_kernel::impl_theme_item(lua_State *L, std::string m)
4373 {
4374  reports::context temp_context = reports::context(board(), *game_display_, tod_man(), play_controller_.get_whiteboard(), play_controller_.get_mouse_handler_base());
4375  luaW_pushconfig(L, reports_.generate_report(m.c_str(), temp_context , true));
4376  return 1;
4377 }
4378 
4379 /**
4380  * Creates a field of the theme_items table and returns it (__index metamethod).
4381  */
4383 {
4384  char const *m = luaL_checkstring(L, 2);
4385  lua_cpp::push_closure(L, std::bind(&game_lua_kernel::impl_theme_item, this, std::placeholders::_1, std::string(m)), 0);
4386  lua_pushvalue(L, 2);
4387  lua_pushvalue(L, -2);
4388  lua_rawset(L, 1);
4389  reports_.register_generator(m, new lua_report_generator(L, m));
4390  return 1;
4391 }
4392 
4393 /**
4394  * Sets a field of the theme_items table (__newindex metamethod).
4395  */
4397 {
4398  char const *m = luaL_checkstring(L, 2);
4399  lua_pushvalue(L, 2);
4400  lua_pushvalue(L, 3);
4401  lua_rawset(L, 1);
4402  reports_.register_generator(m, new lua_report_generator(L, m));
4403  return 0;
4404 }
4405 
4406 /**
4407  * Gets all the WML variables currently set.
4408  * - Ret 1: WML table
4409  */
4411  luaW_pushconfig(L, gamedata().get_variables());
4412  return 1;
4413 }
4414 
4415 /**
4416  * Teeleports a unit to a location.
4417  * Arg 1: unit
4418  * Arg 2: target location
4419  * Arg 3: bool (ignore_passability)
4420  * Arg 4: bool (clear_shroud)
4421  * Arg 5: bool (animate)
4422  */
4424 {
4425  events::command_disabler command_disabler;
4426  unit_ptr u = luaW_checkunit_ptr(L, 1, true);
4427  map_location dst = luaW_checklocation(L, 2);
4428  bool check_passability = !luaW_toboolean(L, 3);
4429  bool clear_shroud = luaW_toboolean(L, 4);
4430  bool animate = luaW_toboolean(L, 5);
4431 
4432  if (dst == u->get_location() || !map().on_board(dst)) {
4433  return 0;
4434  }
4435  const map_location vacant_dst = find_vacant_tile(dst, pathfind::VACANT_ANY, check_passability ? u.get() : nullptr);
4436  if (!map().on_board(vacant_dst)) {
4437  return 0;
4438  }
4439  // Clear the destination hex before the move (so the animation can be seen).
4440  actions::shroud_clearer clearer;
4441  if ( clear_shroud ) {
4442  clearer.clear_dest(vacant_dst, *u);
4443  }
4444 
4445  map_location src_loc = u->get_location();
4446 
4447  std::vector<map_location> teleport_path;
4448  teleport_path.push_back(src_loc);
4449  teleport_path.push_back(vacant_dst);
4450  unit_display::move_unit(teleport_path, u, animate);
4451 
4452  units().move(src_loc, vacant_dst);
4454 
4455  u = units().find(vacant_dst).get_shared_ptr();
4456  u->anim_comp().set_standing();
4457 
4458  if ( clear_shroud ) {
4459  // Now that the unit is visibly in position, clear the shroud.
4460  clearer.clear_unit(vacant_dst, *u);
4461  }
4462 
4463  if (map().is_village(vacant_dst)) {
4464  actions::get_village(vacant_dst, u->side());
4465  }
4466 
4467  game_display_->invalidate_unit_after_move(src_loc, vacant_dst);
4468 
4469  // Sighted events.
4470  clearer.fire_events();
4471  return 0;
4472 }
4473 
4474 /**
4475  * Logs a message
4476  * Arg 1: (optional) Logger; "wml" for WML errors or deprecations
4477  * Arg 2: Message
4478  * Arg 3: Whether to print to chat (always true if arg 1 is "wml")
4479  */
4480 int game_lua_kernel::intf_log(lua_State *L)
4481 {
4482  const std::string& logger = lua_isstring(L, 2) ? luaL_checkstring(L, 1) : "";
4483  const std::string& msg = lua_isstring(L, 2) ? luaL_checkstring(L, 2) : luaL_checkstring(L, 1);
4484 
4485  if(logger == "wml" || logger == "WML") {
4486  lg::log_to_chat() << msg << '\n';
4487  ERR_WML << msg;
4488  } else {
4489  bool in_chat = luaW_toboolean(L, -1);
4490  game_state_.events_manager_->pump().put_wml_message(logger,msg,in_chat);
4491  }
4492  return 0;
4493 }
4494 
4496 {
4497  team& t = luaW_checkteam(L, 1, board());
4498  map_location loc = luaW_checklocation(L, 2);
4499  lua_pushboolean(L, fog ? t.fogged(loc) : t.shrouded(loc));
4500  return 1;
4501 }
4502 
4503 /**
4504  * Implements the lifting and resetting of fog via WML.
4505  * Keeping affect_normal_fog as false causes only the fog override to be affected.
4506  * Otherwise, fog lifting will be implemented similar to normal sight (cannot be
4507  * individually reset and ends at the end of the turn), and fog resetting will, in
4508  * addition to removing overrides, extend the specified teams' normal fog to all
4509  * hexes.
4510  *
4511  * Arg 1: (optional) Side number, or list of side numbers
4512  * Arg 2: List of locations; each is a two-element array or a table with x and y keys
4513  * Arg 3: (optional) boolean
4514  */
4515 int game_lua_kernel::intf_toggle_fog(lua_State *L, const bool clear)
4516 {
4517  bool affect_normal_fog = false;
4518  if(lua_isboolean(L, -1)) {
4519  affect_normal_fog = luaW_toboolean(L, -1);
4520  }
4521  std::set<int> sides;
4522  if(team* t = luaW_toteam(L, 1)) {
4523  sides.insert(t->side());
4524  } else if(lua_isnumber(L, 1)) {
4525  sides.insert(lua_tointeger(L, 1));
4526  } else if(lua_istable(L, 1) && lua_istable(L, 2)) {
4527  const auto& v = lua_check<std::vector<int>>(L, 1);
4528  sides.insert(v.begin(), v.end());
4529  } else {
4530  for(const team& t : teams()) {
4531  sides.insert(t.side()+1);
4532  }
4533  }
4534  const auto& locs = luaW_check_locationset(L, lua_istable(L, 2) ? 2 : 1);
4535 
4536  for(const int &side_num : sides) {
4537  if(side_num < 1 || static_cast<std::size_t>(side_num) > teams().size()) {
4538  continue;
4539  }
4540  team &t = board().get_team(side_num);
4541  if(!clear) {
4542  // Extend fog.
4543  t.remove_fog_override(locs);
4544  if(affect_normal_fog) {
4545  t.refog();
4546  }
4547  } else if(!affect_normal_fog) {
4548  // Force the locations clear of fog.
4549  t.add_fog_override(locs);
4550  } else {
4551  // Simply clear fog from the locations.
4552  for(const map_location &hex : locs) {
4553  t.clear_fog(hex);
4554  }
4555  }
4556  }
4557 
4558  // Flag a screen update.
4559  game_display_->recalculate_minimap();
4560  game_display_->invalidate_all();
4561  return 0;
4562 }
4563 
4564 // Invokes a synced command
4565 static int intf_invoke_synced_command(lua_State* L)
4566 {
4567  const std::string name = luaL_checkstring(L, 1);
4568  auto it = synced_command::registry().find(name);
4569  config cmd;
4570  if(it == synced_command::registry().end()) {
4571  // Custom command
4572  if(!luaW_getglobal(L, "wesnoth", "custom_synced_commands", name)) {
4573  return luaL_argerror(L, 1, "Unknown synced command");
4574  }
4575  config& cmd_tag = cmd.child_or_add("custom_command");
4576  cmd_tag["name"] = name;
4577  if(!lua_isnoneornil(L, 2)) {
4578  cmd_tag.add_child("data", luaW_checkconfig(L, 2));
4579  }
4580  } else {
4581  // Built-in command
4582  cmd.add_child(name, luaW_checkconfig(L, 2));
4583  }
4584  // Now just forward to the WML action.
4585  luaW_getglobal(L, "wesnoth", "wml_actions", "do_command");
4586  luaW_pushconfig(L, cmd);
4587  luaW_pcall(L, 1, 0);
4588  return 0;
4589 }
4590 
4591 // END CALLBACK IMPLEMENTATION
4592 
4594  return game_state_.board_;
4595 }
4596 
4598  return game_state_.board_.units();
4599 }
4600 
4601 std::vector<team> & game_lua_kernel::teams() {
4602  return game_state_.board_.teams();
4603 }
4604 
4606  return game_state_.board_.map();
4607 }
4608 
4610  return game_state_.gamedata_;
4611 }
4612 
4614  return game_state_.tod_manager_;
4615 }
4616 
4618  return *queued_events_.top();
4619 }
4620 
4621 
4623  : lua_kernel_base()
4624  , game_display_(nullptr)
4625  , game_state_(gs)
4626  , play_controller_(pc)
4627  , reports_(reports_object)
4628  , level_lua_()
4629  , EVENT_TABLE(LUA_NOREF)
4630  , queued_events_()
4631  , map_locked_(0)
4632 {
4633  static game_events::queued_event default_queued_event("_from_lua", "", map_location(), map_location(), config());
4634  queued_events_.push(&default_queued_event);
4635 
4636  lua_State *L = mState;
4637 
4638  cmd_log_ << "Registering game-specific wesnoth lib functions...\n";
4639 
4640  // Put some callback functions in the scripting environment.
4641  static luaL_Reg const callbacks[] {
4642  { "add_known_unit", &intf_add_known_unit },
4643  { "get_era", &intf_get_era },
4644  { "get_resource", &intf_get_resource },
4645  { "modify_ai", &intf_modify_ai_old },
4646  { "allow_undo", &dispatch<&game_lua_kernel::intf_allow_undo > },
4647  { "cancel_action", &dispatch<&game_lua_kernel::intf_cancel_action > },
4648  { "log_replay", &dispatch<&game_lua_kernel::intf_log_replay > },
4649  { "log", &dispatch<&game_lua_kernel::intf_log > },
4650  { "redraw", &dispatch<&game_lua_kernel::intf_redraw > },
4651  { "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > },
4652  { nullptr, nullptr }
4653  };lua_getglobal(L, "wesnoth");
4654  if (!lua_istable(L,-1)) {
4655  lua_newtable(L);
4656  }
4657  luaL_setfuncs(L, callbacks, 0);
4658 
4659  lua_setglobal(L, "wesnoth");
4660 
4661  lua_getglobal(L, "gui");
4662  lua_pushcfunction(L, &dispatch<&game_lua_kernel::intf_gamestate_inspector>);
4663  lua_setfield(L, -2, "show_inspector");
4664  lua_pop(L, 1);
4665 
4667  // Create the unit_test module
4668  lua_newtable(L);
4669  static luaL_Reg const test_callbacks[] {
4670  { "fire_wml_menu_item", &dispatch<&game_lua_kernel::intf_fire_wml_menu_item> },
4671  { nullptr, nullptr }
4672  };
4673  luaL_setfuncs(L, test_callbacks, 0);
4674  lua_setglobal(L, "unit_test");
4675  }
4676 
4677  // Create the getside metatable.
4679 
4680  // Create the gettype metatable.
4682 
4683  //Create the getrace metatable
4685 
4686  //Create the unit metatables
4689 
4690  // Create the vconfig metatable.
4692 
4693  // Create the unit_types table
4695 
4696  // Create the unit_types table
4698 
4699  // Create the unit_types table
4700  cmd_log_ << "Adding terrain_types table...\n";
4701  lua_getglobal(L, "wesnoth");
4702  lua_newuserdatauv(L, 0, 0);
4703  lua_createtable(L, 0, 2);
4704  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_get_terrain_info>);
4705  lua_setfield(L, -2, "__index");
4706  lua_pushstring(L, "terrain types");
4707  lua_setfield(L, -2, "__metatable");
4708  lua_setmetatable(L, -2);
4709  lua_setfield(L, -2, "terrain_types");
4710  lua_pop(L, 1);
4711 
4712  // Create the ai elements table.
4713  cmd_log_ << "Adding ai elements table...\n";
4714 
4716 
4717  // Create the current variable with its metatable.
4718  cmd_log_ << "Adding wesnoth current table...\n";
4719 
4720  lua_getglobal(L, "wesnoth");
4721  lua_newuserdatauv(L, 0, 0);
4722  lua_createtable(L, 0, 2);
4723  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_current_get>);
4724  lua_setfield(L, -2, "__index");
4725  lua_pushstring(L, "current config");
4726  lua_setfield(L, -2, "__metatable");
4727  lua_setmetatable(L, -2);
4728  lua_setfield(L, -2, "current");
4729  lua_pop(L, 1);
4730 
4731  // Add functions to the WML module
4732  lua_getglobal(L, "wml");
4733  static luaL_Reg const wml_callbacks[] {
4734  {"tovconfig", &lua_common::intf_tovconfig},
4735  {"eval_conditional", &intf_eval_conditional},
4736  // These aren't actually part of the API - they're used internally by the variable metatable.
4737  { "get_variable", &dispatch<&game_lua_kernel::intf_get_variable>},
4738  { "set_variable", &dispatch<&game_lua_kernel::intf_set_variable>},
4739  { "get_all_vars", &dispatch<&game_lua_kernel::intf_get_all_vars>},
4740  { nullptr, nullptr }
4741  };
4742  luaL_setfuncs(L, wml_callbacks, 0);
4743  lua_pop(L, 1);
4744 
4745  // Add functions to the map module
4746  luaW_getglobal(L, "wesnoth", "map");
4747  static luaL_Reg const map_callbacks[] {
4748  // Map methods
4749  {"terrain_mask", &intf_terrain_mask},
4750  {"on_board", &intf_on_board},
4751  {"on_border", &intf_on_border},
4752  {"iter", &intf_terrainmap_iter},
4753  // Village operations
4754  {"get_owner", &dispatch<&game_lua_kernel::intf_get_village_owner>},
4755  {"set_owner", &dispatch<&game_lua_kernel::intf_set_village_owner>},
4756  // Label operations
4757  {"add_label", &dispatch<&game_lua_kernel::intf_add_label>},
4758  {"remove_label", &dispatch<&game_lua_kernel::intf_remove_label>},
4759  {"get_label", &dispatch<&game_lua_kernel::intf_get_label>},
4760  // Time area operations
4761  {"place_area", &dispatch<&game_lua_kernel::intf_add_time_area>},
4762  {"remove_area", &dispatch<&game_lua_kernel::intf_remove_time_area>},
4763  {"get_area", &dispatch<&game_lua_kernel::intf_get_time_area>},
4764  // Filters
4765  {"find", &dispatch<&game_lua_kernel::intf_get_locations>},
4766  {"matches", &dispatch<&game_lua_kernel::intf_match_location>},
4767  {"replace_if_failed", intf_replace_if_failed},
4768  { nullptr, nullptr }
4769  };
4770  luaL_setfuncs(L, map_callbacks, 0);
4771  lua_pop(L, 1);
4772 
4773  // Create the units module
4774  cmd_log_ << "Adding units module...\n";
4775  static luaL_Reg const unit_callbacks[] {
4776  {"advance", &intf_advance_unit},
4777  {"clone", &intf_copy_unit},
4778  {"erase", &dispatch<&game_lua_kernel::intf_erase_unit>},
4779  {"extract", &dispatch<&game_lua_kernel::intf_extract_unit>},
4780  {"matches", &dispatch<&game_lua_kernel::intf_match_unit>},
4781  {"select", &dispatch<&game_lua_kernel::intf_select_unit>},
4782  {"to_map", &dispatch<&game_lua_kernel::intf_put_unit>},
4783  {"to_recall", &dispatch<&game_lua_kernel::intf_put_recall_unit>},
4784  {"transform", &intf_transform_unit},
4785  {"teleport", &dispatch<&game_lua_kernel::intf_teleport>},
4786 
4787  {"ability", &dispatch<&game_lua_kernel::intf_unit_ability>},
4788  {"defense_on", &intf_unit_defense},
4789  {"jamming_on", &intf_unit_jamming_cost},
4790  {"movement_on", &intf_unit_movement_cost},
4791  {"resistance_against", intf_unit_resistance},
4792  {"vision_on", &intf_unit_vision_cost},
4793 
4794  {"add_modification", &intf_add_modification},
4795  {"remove_modifications", &intf_remove_modifications},
4796  // Static functions
4797  {"create", &intf_create_unit},
4798  {"find_on_map", &dispatch<&game_lua_kernel::intf_get_units>},
4799  {"find_on_recall", &dispatch<&game_lua_kernel::intf_get_recall_units>},
4800  {"get", &dispatch<&game_lua_kernel::intf_get_unit>},
4801  {"get_hovered", &dispatch<&game_lua_kernel::intf_get_displayed_unit>},
4802  {"create_animator", &dispatch<&game_lua_kernel::intf_create_animator>},
4803  {"create_weapon", intf_create_attack},
4804 
4805  { nullptr, nullptr }
4806  };
4807  lua_getglobal(L, "wesnoth");
4808  lua_newtable(L);
4809  luaL_setfuncs(L, unit_callbacks, 0);
4810  lua_setfield(L, -2, "units");
4811  lua_pop(L, 1);
4812 
4813  // Create sides module
4814  cmd_log_ << "Adding sides module...\n";
4815  static luaL_Reg const side_callbacks[] {
4816  { "is_enemy", &dispatch<&game_lua_kernel::intf_is_enemy> },
4817  { "matches", &dispatch<&game_lua_kernel::intf_match_side> },
4818  { "set_id", &dispatch<&game_lua_kernel::intf_set_side_id> },
4819  { "append_ai", &intf_append_ai },
4820  { "debug_ai", &intf_debug_ai },
4821  { "switch_ai", &intf_switch_ai },
4822  // Static functions
4823  { "find", &dispatch<&game_lua_kernel::intf_get_sides> },
4824  { "get", &dispatch<&game_lua_kernel::intf_get_side> },
4825  { "create", &dispatch<&game_lua_kernel::intf_create_side> },
4826  // Shroud operations
4827  {"place_shroud", &dispatch2<&game_lua_kernel::intf_toggle_shroud, true>},
4828  {"remove_shroud", &dispatch2<&game_lua_kernel::intf_toggle_shroud, false>},
4829  {"override_shroud", &dispatch<&game_lua_kernel::intf_override_shroud>},
4830  {"is_shrouded", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, false>},
4831  // Fog operations
4832  {"place_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, false>},
4833  {"remove_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, true>},
4834  {"is_fogged", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, true>},
4835  { nullptr, nullptr }
4836  };
4837  std::vector<lua_cpp::Reg> const cpp_side_callbacks {
4838  {"add_ai_component", std::bind(intf_modify_ai, std::placeholders::_1, "add")},
4839  {"delete_ai_component", std::bind(intf_modify_ai, std::placeholders::_1, "delete")},
4840  {"change_ai_component", std::bind(intf_modify_ai, std::placeholders::_1, "change")},
4841  {nullptr, nullptr}
4842  };
4843 
4844  lua_getglobal(L, "wesnoth");
4845  lua_newtable(L);
4846  luaL_setfuncs(L, side_callbacks, 0);
4847  lua_cpp::set_functions(L, cpp_side_callbacks);
4848  lua_setfield(L, -2, "sides");
4849  lua_pop(L, 1);
4850 
4851  // Create the interface module
4852  cmd_log_ << "Adding interface module...\n";
4853  static luaL_Reg const intf_callbacks[] {
4854  {"add_hex_overlay", &dispatch<&game_lua_kernel::intf_add_tile_overlay>},
4855  {"remove_hex_overlay", &dispatch<&game_lua_kernel::intf_remove_tile_overlay>},
4856  {"get_color_adjust", &dispatch<&game_lua_kernel::intf_get_color_adjust>},
4857  {"color_adjust", &dispatch<&game_lua_kernel::intf_color_adjust>},
4858  {"screen_fade", &dispatch<&game_lua_kernel::intf_screen_fade>},
4859  {"delay", &dispatch<&game_lua_kernel::intf_delay>},
4860  {"deselect_hex", &dispatch<&game_lua_kernel::intf_deselect_hex>},
4861  {"highlight_hex", &dispatch<&game_lua_kernel::intf_highlight_hex>},
4862  {"float_label", &dispatch<&game_lua_kernel::intf_float_label>},
4863  {"get_displayed_unit", &dispatch<&game_lua_kernel::intf_get_displayed_unit>},
4864  {"get_hovered_hex", &dispatch<&game_lua_kernel::intf_get_mouseover_tile>},
4865  {"get_selected_hex", &dispatch<&game_lua_kernel::intf_get_selected_tile>},
4866  {"lock", &dispatch<&game_lua_kernel::intf_lock_view>},
4867  {"is_locked", &dispatch<&game_lua_kernel::intf_view_locked>},
4868  {"scroll", &dispatch<&game_lua_kernel::intf_scroll>},
4869  {"scroll_to_hex", &dispatch<&game_lua_kernel::intf_scroll_to_tile>},
4870  {"skip_messages", &dispatch<&game_lua_kernel::intf_skip_messages>},
4871  {"is_skipping_messages", &dispatch<&game_lua_kernel::intf_is_skipping_messages>},
4872  {"zoom", &dispatch<&game_lua_kernel::intf_zoom>},
4873  {"clear_menu_item", &dispatch<&game_lua_kernel::intf_clear_menu_item>},
4874  {"set_menu_item", &dispatch<&game_lua_kernel::intf_set_menu_item>},
4875  {"allow_end_turn", &dispatch<&game_lua_kernel::intf_allow_end_turn>},
4876  {"clear_chat_messages", &dispatch<&game_lua_kernel::intf_clear_messages>},
4877  {"end_turn", &dispatch<&game_lua_kernel::intf_end_turn>},
4878  {"get_viewing_side", &intf_get_viewing_side},
4879  {"add_chat_message", &dispatch<&game_lua_kernel::intf_message>},
4880  {"add_overlay_text", &dispatch2<&game_lua_kernel::intf_set_floating_label, true>},
4881  {"handle_user_interact", &intf_handle_user_interact},
4882  { nullptr, nullptr }
4883  };
4884  lua_getglobal(L, "wesnoth");
4885  lua_newtable(L);
4886  luaL_setfuncs(L, intf_callbacks, 0);
4887  lua_setfield(L, -2, "interface");
4888  lua_pop(L, 1);
4889 
4890  // Create the audio module
4891  cmd_log_ << "Adding audio module...\n";
4892  static luaL_Reg const audio_callbacks[] {
4893  { "play", &dispatch<&game_lua_kernel::intf_play_sound > },
4894  { nullptr, nullptr }
4895  };
4896  lua_getglobal(L, "wesnoth");
4897  lua_newtable(L);
4898  luaL_setfuncs(L, audio_callbacks, 0);
4899  lua_setfield(L, -2, "audio");
4900  lua_pop(L, 1);
4901 
4902  // Create the paths module
4903  cmd_log_ << "Adding paths module...\n";
4904  static luaL_Reg const path_callbacks[] {
4905  { "find_cost_map", &dispatch<&game_lua_kernel::intf_find_cost_map > },
4906  { "find_path", &dispatch<&game_lua_kernel::intf_find_path > },
4907  { "find_reach", &dispatch<&game_lua_kernel::intf_find_reach > },
4908  { "find_vacant_hex", &dispatch<&game_lua_kernel::intf_find_vacant_tile > },
4909  { "find_vision_range", &dispatch<&game_lua_kernel::intf_find_vision_range > },
4910  { nullptr, nullptr }
4911  };
4912  lua_getglobal(L, "wesnoth");
4913  lua_newtable(L);
4914  luaL_setfuncs(L, path_callbacks, 0);
4915  lua_setfield(L, -2, "paths");
4916  lua_pop(L, 1);
4917 
4918  // Create the sync module
4919  cmd_log_ << "Adding sync module...\n";
4920  static luaL_Reg const sync_callbacks[] {
4921  { "invoke_command", &intf_invoke_synced_command },
4922  { "run_unsynced", &intf_do_unsynced },
4923  { "evaluate_single", &intf_synchronize_choice },
4924  { "evaluate_multiple", &intf_synchronize_choices },
4925  { nullptr, nullptr }
4926  };
4927  lua_getglobal(L, "wesnoth");
4928  lua_newtable(L);
4929  luaL_setfuncs(L, sync_callbacks, 0);
4930  lua_setfield(L, -2, "sync");
4931  lua_pop(L, 1);
4932 
4933  // Create the schedule module
4934  cmd_log_ << "Adding schedule module...\n";
4935  static luaL_Reg const schedule_callbacks[] {
4936  { "get_time_of_day", &dispatch<&game_lua_kernel::intf_get_time_of_day<false>>},
4937  { "get_illumination", &dispatch<&game_lua_kernel::intf_get_time_of_day<true>>},
4938  { "replace", &dispatch<&game_lua_kernel::intf_replace_schedule>},
4939  { nullptr, nullptr }
4940  };
4941  lua_getglobal(L, "wesnoth");
4942  lua_newtable(L);
4943  luaL_setfuncs(L, schedule_callbacks, 0);
4944  lua_createtable(L, 0, 2);
4945  lua_setmetatable(L, -2);
4946  lua_setfield(L, -2, "schedule");
4947  lua_pop(L, 1);
4948 
4949  // Create the playlist table with its metatable
4951 
4952  // Create the wml_actions table.
4953  cmd_log_ << "Adding wml_actions table...\n";
4954 
4955  lua_getglobal(L, "wesnoth");
4956  lua_newtable(L);
4957  lua_setfield(L, -2, "wml_actions");
4958  lua_pop(L, 1);
4959 
4960  // Create the wml_conditionals table.
4961  cmd_log_ << "Adding wml_conditionals table...\n";
4962 
4963  lua_getglobal(L, "wesnoth");
4964  lua_newtable(L);
4965  lua_setfield(L, -2, "wml_conditionals");
4966  lua_pop(L, 1);
4970 
4971  // Create the effects table.
4972  cmd_log_ << "Adding effects table...\n";
4973 
4974  lua_getglobal(L, "wesnoth");
4975  lua_newtable(L);
4976  lua_setfield(L, -2, "effects");
4977  lua_pop(L, 1);
4978 
4979  // Create the custom_synced_commands table.
4980  cmd_log_ << "Adding custom_synced_commands table...\n";
4981 
4982  lua_getglobal(L, "wesnoth");
4983  lua_newtable(L);
4984  lua_setfield(L, -2, "custom_synced_commands");
4985  lua_pop(L, 1);
4986 
4987  // Create the game_events table.
4988  cmd_log_ << "Adding game_events module...\n";
4989  static luaL_Reg const event_callbacks[] {
4990  { "add", &dispatch<&game_lua_kernel::intf_add_event> },
4991  { "add_repeating", &dispatch<&game_lua_kernel::intf_add_event_simple<false>> },
4992  { "add_menu", &dispatch<&game_lua_kernel::intf_add_event_simple<true>> },
4993  { "add_wml", &dispatch<&game_lua_kernel::intf_add_event_wml> },
4994  { "remove", &dispatch<&game_lua_kernel::intf_remove_event> },
4995  { "fire", &dispatch2<&game_lua_kernel::intf_fire_event, false> },
4996  { "fire_by_id", &dispatch2<&game_lua_kernel::intf_fire_event, true> },
4997  { nullptr, nullptr }
4998  };
4999  lua_getglobal(L, "wesnoth");
5000  lua_newtable(L);
5001  luaL_setfuncs(L, event_callbacks, 0);
5002  lua_setfield(L, -2, "game_events");
5003  lua_pop(L, 1);
5004 
5005  // Create the theme_items table.
5006  cmd_log_ << "Adding game_display table...\n";
5007 
5008  luaW_getglobal(L, "wesnoth", "interface");
5009  lua_newtable(L);
5010  lua_createtable(L, 0, 2);
5011  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_theme_items_get>);
5012  lua_setfield(L, -2, "__index");
5013  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_theme_items_set>);
5014  lua_setfield(L, -2, "__newindex");
5015  lua_setmetatable(L, -2);
5016  lua_setfield(L, -2, "game_display");
5017  lua_pop(L, 1);
5018 
5019  // Create the scenario table.
5020  cmd_log_ << "Adding scenario table...\n";
5021 
5022  luaW_getglobal(L, "wesnoth");
5023  lua_newtable(L);
5024  lua_createtable(L, 0, 2);
5025  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_scenario_get>);
5026  lua_setfield(L, -2, "__index");
5027  lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_scenario_set>);
5028  lua_setfield(L, -2, "__newindex");
5029  lua_setmetatable(L, -2);
5030  lua_setfield(L, -2, "scenario");
5031  lua_pop(L, 1);
5032 
5033  lua_settop(L, 0);
5034 
5035  for(const auto& handler : game_events::wml_action::registry())
5036  {
5037  set_wml_action(handler.first, handler.second);
5038  }
5039  luaW_getglobal(L, "wesnoth", "effects");
5040  for(const std::string& effect : unit::builtin_effects) {
5041  lua_pushstring(L, effect.c_str());
5043  lua_rawset(L, -3);
5044  }
5045  lua_settop(L, 0);
5046 
5047  // Set up the registry table for event handlers
5048  lua_newtable(L);
5049  EVENT_TABLE = luaL_ref(L, LUA_REGISTRYINDEX);
5050 }
5051 
5053 {
5054  lua_State *L = mState;
5055  assert(level_lua_.empty());
5056  level_lua_.append_children(level, "lua");
5057 
5058  //Create the races table.
5059  cmd_log_ << "Adding races table...\n";
5060 
5061  lua_settop(L, 0);
5062  lua_getglobal(L, "wesnoth");
5063  luaW_pushracetable(L);
5064  lua_setfield(L, -2, "races");
5065  lua_pop(L, 1);
5066 
5067  // Execute the preload scripts.
5068  cmd_log_ << "Running preload scripts...\n";
5069 
5070  game_config::load_config(game_lua_kernel::preload_config);
5071  for (const config &cfg : game_lua_kernel::preload_scripts) {
5072  run_lua_tag(cfg);
5073  }
5074  for (const config &cfg : level_lua_.child_range("lua")) {
5075  run_lua_tag(cfg);
5076  }
5077 }
5078 
5080  game_display_ = gd;
5081 }
5082 
5083 /**
5084  * These are the child tags of [scenario] (and the like) that are handled
5085  * elsewhere (in the C++ code).
5086  * Any child tags not in this list will be passed to Lua's on_load event.
5087  */
5088 static bool is_handled_file_tag(const std::string& s)
5089 {
5090  // Make sure this is sorted, since we binary_search!
5091  using namespace std::literals::string_view_literals;
5092  static const std::array handled_file_tags {
5093  "color_palette"sv,
5094  "color_range"sv,
5095  "display"sv,
5096  "end_level_data"sv,
5097  "era"sv,
5098  "event"sv,
5099  "generator"sv,
5100  "label"sv,
5101  "lua"sv,
5102  "map"sv,
5103  "menu_item"sv,
5104  "modification"sv,
5105  "modify_unit_type"sv,
5106  "music"sv,
5107  "options"sv,
5108  "side"sv,
5109  "sound_source"sv,
5110  "story"sv,
5111  "terrain_graphics"sv,
5112  "time"sv,
5113  "time_area"sv,
5114  "tunnel"sv,
5115  "undo_stack"sv,
5116  "variables"sv
5117  };
5118 
5119  return std::binary_search(handled_file_tags.begin(), handled_file_tags.end(), s);
5120 }
5121 
5122 /**
5123  * Executes the game_events.on_load function and passes to it all the
5124  * scenario tags not yet handled.
5125  */
5127 {
5128  lua_State *L = mState;
5129 
5130  if (!luaW_getglobal(L, "wesnoth", "game_events", "on_load"))
5131  return;
5132 
5133  lua_newtable(L);
5134  int k = 1;
5135  for (const config::any_child v : level.all_children_range())
5136  {
5137  if (is_handled_file_tag(v.key)) continue;
5138  lua_createtable(L, 2, 0);
5139  lua_pushstring(L, v.key.c_str());
5140  lua_rawseti(L, -2, 1);
5141  luaW_pushconfig(L, v.cfg);
5142  lua_rawseti(L, -2, 2);
5143  lua_rawseti(L, -2, k++);
5144  }
5145 
5146  luaW_pcall(L, 1, 0, true);
5147 }
5148 
5149 /**
5150  * Executes the game_events.on_save function and adds to @a cfg the
5151  * returned tags. Also flushes the [lua] tags.
5152  */
5154 {
5155  lua_State *L = mState;
5156 
5157  if (!luaW_getglobal(L, "wesnoth", "game_events", "on_save"))
5158  return;
5159 
5160  if (!luaW_pcall(L, 0, 1, false))
5161  return;
5162 
5163  config v;
5164  luaW_toconfig(L, -1, v);
5165  lua_pop(L, 1);
5166 
5167  for (;;)
5168  {
5170  if (i == v.ordered_end()) break;
5171  if (is_handled_file_tag(i->key))
5172  {
5173  /*
5174  * It seems the only tags appearing in the config v variable here
5175  * are the core-lua-handled (currently [item] and [objectives])
5176  * and the extra UMC ones.
5177  */
5178  const std::string m = "Tag is already used: [" + i->key + "]";
5179  log_error(m.c_str());
5180  v.erase(i);
5181  continue;
5182  }
5183  cfg.splice_children(v, i->key);
5184  }
5185 }
5186 
5187 /**
5188  * Executes the game_events.on_event function.
5189  * Returns false if there was no lua handler for this event
5190  */
5192 {
5193  lua_State *L = mState;
5194 
5195  if (!luaW_getglobal(L, "wesnoth", "game_events", "on_event"))
5196  return false;
5197 
5198  queued_event_context dummy(&ev, queued_events_);
5199  lua_pushstring(L, ev.name.c_str());
5200  luaW_pcall(L, 1, 0, false);
5201  return true;
5202 }
5203 
5204 void game_lua_kernel::custom_command(const std::string& name, const config& cfg)
5205 {
5206  lua_State *L = mState;
5207 
5208  if (!luaW_getglobal(L, "wesnoth", "custom_synced_commands", name)) {
5209  return;
5210  }
5211  luaW_pushconfig(L, cfg);
5212  luaW_pcall(L, 1, 0, false);
5213 }
5214 
5215 /**
5216  * Applies its upvalue as an effect
5217  * Arg 1: The unit to apply to
5218  * Arg 3: The [effect] tag contents
5219  * Arg 3: If false, only build description
5220  * Return: The description of the effect
5221  */
5223 {
5224  std::string which_effect = lua_tostring(L, lua_upvalueindex(1));
5225  bool need_apply = luaW_toboolean(L, lua_upvalueindex(2));
5226  // Argument 1 is the implicit "self" argument, which isn't needed here
5227  lua_unit u(luaW_checkunit(L, 2));
5228  config cfg = luaW_checkconfig(L, 3);
5229 
5230  // The times= key is supposed to be ignored by the effect function.
5231  // However, just in case someone doesn't realize this, we will set it to 1 here.
5232  cfg["times"] = 1;
5233 
5234  if(need_apply) {
5235  u->apply_builtin_effect(which_effect, cfg);
5236  return 0;
5237  } else {
5238  std::string description = u->describe_builtin_effect(which_effect, cfg);
5239  lua_pushstring(L, description.c_str());
5240  return 1;
5241  }
5242 }
5243 
5244 /**
5245 * Registers a function for use as an effect handler.
5246 */
5248 {
5249  lua_State *L = mState;
5250 
5251  // The effect name is at the top of the stack
5252  int str_i = lua_gettop(L);
5253  lua_newtable(L); // The functor table
5254  lua_newtable(L); // The functor metatable
5255  lua_pushstring(L, "__call");
5256  lua_pushvalue(L, str_i);
5257  lua_pushboolean(L, true);
5258  lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_builtin_effect>, 2);
5259  lua_rawset(L, -3); // Set the call metafunction
5260  lua_pushstring(L, "__descr");
5261  lua_pushvalue(L, str_i);
5262  lua_pushboolean(L, false);
5263  lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_builtin_effect>, 2);
5264  lua_rawset(L, -3); // Set the descr "metafunction"
5265  lua_setmetatable(L, -2); // Apply the metatable to the functor table
5266 }
5267 
5268 
5269 /**
5270  * Executes its upvalue as a wml action.
5271  */
5273 {
5275  (lua_touserdata(L, lua_upvalueindex(1)));
5276 
5277  vconfig vcfg = luaW_checkvconfig(L, 1);
5278  h(get_event_info(), vcfg);
5279  return 0;
5280 }
5281 
5282 /**
5283  * Registers a function for use as an action handler.
5284  */
5286 {
5287  lua_State *L = mState;
5288 
5289  lua_getglobal(L, "wesnoth");
5290  lua_pushstring(L, "wml_actions");
5291  lua_rawget(L, -2);
5292  lua_pushstring(L, cmd.c_str());
5293  lua_pushlightuserdata(L, reinterpret_cast<void *>(h));
5294  lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_wml_action>, 1);
5295  lua_rawset(L, -3);
5296  lua_pop(L, 2);
5297 }
5298 
5299 using wml_conditional_handler = bool(*)(const vconfig&);
5300 
5301 /**
5302  * Executes its upvalue as a wml condition and returns the result.
5303  */
5304 static int cfun_wml_condition(lua_State *L)
5305 {
5307  (lua_touserdata(L, lua_upvalueindex(1)));
5308 
5309  vconfig vcfg = luaW_checkvconfig(L, 1);
5310  lua_pushboolean(L, h(vcfg));
5311  return 1;
5312 }
5313 
5314 /**
5315  * Registers a function for use as a conditional handler.
5316  */
5318 {
5319  lua_State *L = mState;
5320 
5321  lua_getglobal(L, "wesnoth");
5322  lua_pushstring(L, "wml_conditionals");
5323  lua_rawget(L, -2);
5324  lua_pushstring(L, cmd.c_str());
5325  lua_pushlightuserdata(L, reinterpret_cast<void *>(h));
5326  lua_pushcclosure(L, &cfun_wml_condition, 1);
5327  lua_rawset(L, -3);
5328  lua_pop(L, 2);
5329 }
5330 
5331 /**
5332  * Runs a command from an event handler.
5333  * @return true if there is a handler for the command.
5334  * @note @a cfg should be either volatile or long-lived since the Lua
5335  * code may grab it for an arbitrary long time.
5336  */
5337 bool game_lua_kernel::run_wml_action(const std::string& cmd, const vconfig& cfg,
5338  const game_events::queued_event& ev)
5339 {
5340  lua_State *L = mState;
5341 
5342 
5343  if (!luaW_getglobal(L, "wesnoth", "wml_actions", cmd))
5344  return false;
5345 
5346  queued_event_context dummy(&ev, queued_events_);
5347  luaW_pushvconfig(L, cfg);
5348  luaW_pcall(L, 1, 0, true);
5349  return true;
5350 }
5351 
5352 
5353 /**
5354  * Evaluates a WML conidition.
5355  *
5356  * @returns Whether the condition passed.
5357  * @note @a cfg should be either volatile or long-lived since the Lua
5358  * code may grab it for an arbitrarily long time.
5359  */
5360 bool game_lua_kernel::run_wml_conditional(const std::string& cmd, const vconfig& cfg)
5361 {
5362  lua_State* L = mState;
5363 
5364  // If an invalid coniditional tag is used, consider it a pass.
5365  if(!luaW_getglobal(L, "wesnoth", "wml_conditionals", cmd)) {
5366  lg::log_to_chat() << "unknown conditional wml: [" << cmd << "]\n";
5367  ERR_WML << "unknown conditional wml: [" << cmd << "]";
5368  return true;
5369  }
5370 
5371  luaW_pushvconfig(L, cfg);
5372 
5373  // Any runtime error is considered a fail.
5374  if(!luaW_pcall(L, 1, 1, true)) {
5375  return false;
5376  }
5377 
5378  bool b = luaW_toboolean(L, -1);
5379 
5380  lua_pop(L, 1);
5381  return b;
5382 }
5383 
5384 static int intf_run_event_wml(lua_State* L)
5385 {
5386  int argIdx = lua_gettop(L);
5387  if(!luaW_getglobal(L, "wesnoth", "wml_actions", "command")) {
5388  return luaL_error(L, "wesnoth.wml_actions.command is missing");
5389  }
5390  lua_pushvalue(L, argIdx);
5391  lua_call(L, 1, 0);
5392  return 0;
5393 }
5394 
5396 {
5397  lua_State* L = mState;
5398  lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
5399  int evtIdx = lua_gettop(L);
5400  ON_SCOPE_EXIT(L) {
5401  lua_pop(L, 1);
5402  };
5403  lua_pushcfunction(L, intf_run_event_wml);
5404  return luaL_ref(L, evtIdx);
5405 }
5406 
5407 int game_lua_kernel::save_wml_event(const std::string& name, const std::string& id, const std::string& code)
5408 {
5409  lua_State* L = mState;
5410  lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
5411  int evtIdx = lua_gettop(L);
5412  ON_SCOPE_EXIT(L) {
5413  lua_pop(L, 1);
5414  };
5415  std::ostringstream lua_name;
5416  lua_name << "event ";
5417  if(name.empty()) {
5418  lua_name << "<anon>";
5419  } else {
5420  lua_name << name;
5421  }
5422  if(!id.empty()) {
5423  lua_name << "[id=" << id << "]";
5424  }
5425  if(!load_string(code.c_str(), lua_name.str())) {
5426  ERR_LUA << "Failed to register WML event: " << lua_name.str();
5427  return LUA_NOREF;
5428  }
5429  return luaL_ref(L, evtIdx);
5430 }
5431 
5433 {
5434  lua_State* L = mState;
5435  idx = lua_absindex(L, idx);
5436  lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
5437  int evtIdx = lua_gettop(L);
5438  ON_SCOPE_EXIT(L) {
5439  lua_pop(L, 1);
5440  };
5441  lua_pushvalue(L, idx);
5442  return luaL_ref(L, evtIdx);
5443 }
5444 
5446 {
5447  lua_State* L = mState;
5448  lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
5449  luaL_unref(L, -1, ref);
5450  lua_pop(L, 1);
5451 }
5452 
5453 bool game_lua_kernel::run_wml_event(int ref, const vconfig& args, const game_events::queued_event& ev, bool* out)
5454 {
5455  lua_State* L = mState;
5456  lua_geti(L, LUA_REGISTRYINDEX, EVENT_TABLE);
5457  ON_SCOPE_EXIT(L) {
5458  lua_pop(L, 1);
5459  };
5460  lua_geti(L, -1, ref);
5461  if(lua_isnil(L, -1)) return false;
5462  luaW_pushvconfig(L, args);
5463  queued_event_context dummy(&ev, queued_events_);
5464  if(luaW_pcall(L, 1, out ? 1 : 0, true)) {
5465  if(out) {
5466  *out = luaW_toboolean(L, -1);
5467  lua_pop(L, 1);
5468  }
5469  return true;
5470  }
5471  return false;
5472 }
5473 
5474 
5475 /**
5476 * Runs a script from a location filter.
5477 * The script is an already compiled function given by its name.
5478 */
5479 bool game_lua_kernel::run_filter(char const *name, const map_location& l)
5480 {
5481  lua_pushinteger(mState, l.wml_x());
5482  lua_pushinteger(mState, l.wml_y());
5483  return run_filter(name, 2);
5484 }
5485 
5486 /**
5487 * Runs a script from a location filter.
5488 * The script is an already compiled function given by its name.
5489 */
5490 bool game_lua_kernel::run_filter(char const *name, const team& t)
5491 {
5492  //TODO: instead of passing the lua team object we coudl also jsut pass its
5493  // number. then we wouldn't need this const cast.
5494  luaW_pushteam(mState, const_cast<team&>(t));
5495  return run_filter(name, 1);
5496 }
5497 /**
5498 * Runs a script from a unit filter.
5499 * The script is an already compiled function given by its name.
5500 */
5501 bool game_lua_kernel::run_filter(char const *name, const unit& u)
5502 {
5503  lua_State *L = mState;
5504  lua_unit* lu = luaW_pushlocalunit(L, const_cast<unit&>(u));
5505  // stack: unit
5506  // put the unit to the stack twice to prevent gc.
5507  lua_pushvalue(L, -1);
5508  // stack: unit, unit
5509  bool res = run_filter(name, 1);
5510  // stack: unit
5511  lu->clear_ref();
5512  lua_pop(L, 1);
5513  return res;
5514 }
5515 /**
5516 * Runs a script from a filter.
5517 * The script is an already compiled function given by its name.
5518 */
5519 bool game_lua_kernel::run_filter(char const *name, int nArgs)
5520 {
5521  map_locker(this);
5522  lua_State *L = mState;
5523  // Get the user filter by name.
5524  const std::vector<std::string>& path = utils::split(name, '.', utils::STRIP_SPACES);
5525  if (!luaW_getglobal(L, path))
5526  {
5527  std::string message = std::string() + "function " + name + " not found";
5528  log_error(message.c_str(), "Lua SUF Error");
5529  //we pushed nothing and can safeley return.
5530  return false;
5531  }
5532  lua_insert(L, -nArgs - 1);
5533 
5534  if (!luaW_pcall(L, nArgs, 1)) return false;
5535 
5536  bool b = luaW_toboolean(L, -1);
5537  lua_pop(L, 1);
5538  return b;
5539 }
5540 
5541 std::string game_lua_kernel::apply_effect(const std::string& name, unit& u, const config& cfg, bool need_apply)
5542 {
5543  lua_State *L = mState;
5544  int top = lua_gettop(L);
5545  std::string descr;
5546  // Stack: nothing
5547  lua_unit* lu = luaW_pushlocalunit(L, u);
5548  // Stack: unit
5549  // (Note: The unit needs to be on the stack twice to prevent untimely GC.)
5550  luaW_pushconfig(L, cfg);
5551  // Stack: unit, cfg
5552  if(luaW_getglobal(L, "wesnoth", "effects", name)) {
5553  map_locker(this);
5554  // Stack: unit, cfg, effect
5555  if(lua_istable(L, -1)) {
5556  // Effect is implemented by a table with __call and __descr
5557  if(need_apply) {
5558  lua_pushvalue(L, -1);
5559  // Stack: unit, cfg, effect, effect
5560  lua_pushvalue(L, top + 1);
5561  // Stack: unit, cfg, effect, effect, unit
5562  lua_pushvalue(L, top + 2);
5563  // Stack: unit, cfg, effect, effect, unit, cfg
5564  luaW_pcall(L, 2, 0);
5565  // Stack: unit, cfg, effect
5566  }
5567  if(luaL_getmetafield(L, -1, "__descr")) {
5568  // Stack: unit, cfg, effect, __descr
5569  if(lua_isstring(L, -1)) {
5570  // __descr was a static string
5571  descr = lua_tostring(L, -1);
5572  } else {
5573  lua_pushvalue(L, -2);
5574  // Stack: unit, cfg, effect, __descr, effect
5575  lua_pushvalue(L, top + 1);
5576  // Stack: unit, cfg, effect, __descr, effect, unit
5577  lua_pushvalue(L, top + 2);
5578  // Stack: unit, cfg, effect, __descr, effect, unit, cfg
5579  luaW_pcall(L, 3, 1);
5580  if(lua_isstring(L, -1) && !lua_isnumber(L, -1)) {
5581  descr = lua_tostring(L, -1);
5582  } else {
5583  ERR_LUA << "Effect __descr metafunction should have returned a string, but instead returned ";
5584  if(lua_isnone(L, -1)) {
5585  ERR_LUA << "nothing";
5586  } else {
5587  ERR_LUA << lua_typename(L, lua_type(L, -1));
5588  }
5589  }
5590  }
5591  }
5592  } else if(need_apply) {
5593  // Effect is assumed to be a simple function; no description is provided
5594  lua_pushvalue(L, top + 1);
5595  // Stack: unit, cfg, effect, unit
5596  lua_pushvalue(L, top + 2);
5597  // Stack: unit, cfg, effect, unit, cfg
5598  luaW_pcall(L, 2, 0);
5599  // Stack: unit, cfg
5600  }
5601  }
5602  lua_settop(L, top);
5603  lu->clear_ref();
5604  return descr;
5605 }
5606 
5608 {
5609  return ai::lua_ai_context::create(mState,code,engine);
5610 }
5611 
5613 {
5614  return ai::lua_ai_action_handler::create(mState,code,context);
5615 }
5616 
5618 {
5619  lua_State *L = mState;
5620 
5621  if (!luaW_getglobal(L, "wesnoth", "game_events", "on_mouse_move")) {
5622  return;
5623  }
5624  lua_push(L, loc.wml_x());
5625  lua_push(L, loc.wml_y());
5626  luaW_pcall(L, 2, 0, false);
5627  return;
5628 }
5629 
5631 {
5632  lua_State *L = mState;
5633 
5634  if (!luaW_getglobal(L, "wesnoth", "game_events", "on_mouse_action")) {
5635  return;
5636  }
5637  lua_push(L, loc.wml_x());
5638  lua_push(L, loc.wml_y());
5639  luaW_pcall(L, 2, 0, false);
5640  return;
5641 }
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
Definition: attack.hpp:53
void set_wml_y(int v)
Definition: location.hpp:157
std::decay_t< T > lua_check(lua_State *L, int n)
Definition: push_check.hpp:359
bool luaW_checkvariable(lua_State *L, variable_access_create &v, int n)
int dispatch(lua_State *L)
#define modify_bool_attrib(name, accessor)
Definition: lua_common.hpp:404
bool luaW_tableget(lua_State *L, int index, const char *key)
play_controller * controller
Definition: resources.cpp:22
static int intf_transform_unit(lua_State *L)
Changes a unit to the given unit type.
void wait_for_end() const
Definition: animation.cpp:1458
unsigned int end_text_duration
for how long the end-of-campaign text is shown
bool empty() const
Tests for an attribute that either was never set or was set to "".
static int impl_end_level_data_get(lua_State *L)
config get_user_choice(const std::string &name, const user_choice &uch, int side=0)
std::stack< game_events::queued_event const *> queued_events_
void luaW_push_schedule(lua_State *L, int area_index)
Game board class.
Definition: game_board.hpp:52
static synced_state get_synced_state()
static int intf_advance_unit(lua_State *L)
Advances a unit if the unit has enough xp.
static int intf_get_era(lua_State *L)
Gets a table for an era tag.
virtual std::string get_id() const =0
std::decay_t< T > luaW_table_get_def(lua_State *L, int index, std::string_view k, const T &def)
returns t[k] where k is the table at index index and k is k or def if it is not convertible to the co...
Definition: push_check.hpp:383
int map_locked_
A value != 0 means that the shouldn&#39;t remove any units from the map, usually because we are currently...
std::string apply_effect(const std::string &name, unit &u, const config &cfg, bool need_apply)
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
lua_unit * luaW_pushunit(lua_State *L, Args... args)
Definition: lua_unit.hpp:116
bool on_map() const
Definition: lua_unit.hpp:100
bool end_credits
whether to show the standard credits at the end
bool is_castle() const
Definition: terrain.hpp:142
double untouched
Resulting chance we were not hit by this opponent (important if it poisons)
std::string mp_scenario
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
entity_location loc2
Definition: pump.hpp:66
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:97
int intf_allow_end_turn(lua_State *)
Allow undo sets the flag saying whether the event has mutated the game to false.
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:978
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1246
void remove_floating_label(int handle, int fadeout)
removes the floating label given by &#39;handle&#39; from the screen
void start_animations()
Definition: animation.cpp:1396
int intf_find_path(lua_State *L)
Finds a path between two locations.
const terrain_code NONE_TERRAIN
Definition: translation.hpp:58
bool luaW_tovconfig(lua_State *L, int index, vconfig &vcfg)
Gets an optional vconfig from either a table or a userdata.
Definition: lua_common.cpp:937
std::string register_metatables(lua_State *L)
Definition: lua_unit.cpp:650
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
std::string image_mask
The image that is to be laid over all images while this time of day lasts.
Definition: time_of_day.hpp:96
#define return_tstring_attrib(name, accessor)
Definition: lua_common.hpp:236
void reshroud()
Definition: team.hpp:311
const t_string & description() const
Definition: terrain.hpp:50
game_display * game_display_
void luaW_pushconfig(lua_State *L, const config &cfg)
Converts a config object to a Lua table pushed at the top of the stack.
Definition: lua_common.cpp:830
int intf_fire_event(lua_State *L, const bool by_id)
Fires an event.
team & luaW_checkteam(lua_State *L, int idx)
Test if the top stack element is a team, and if not, error.
Definition: lua_team.cpp:348
int intf_get_unit(lua_State *)
Gets the unit at the given location or with the given id.
int village_support
Definition: game_config.cpp:56
bool luaW_pcall(lua_State *L, int nArgs, int nRets, bool allow_wml_error)
Calls a Lua function stored below its nArgs arguments at the top of the stack.
void luaW_pushteam(lua_State *L, team &tm)
Create a full userdata containing a pointer to the team.
Definition: lua_team.cpp:341
std::string plague_type
The plague type used by the attack, if any.
Definition: attack.hpp:85
This class represents a single unit of a specific type.
Definition: unit.hpp:120
static lg::log_domain log_wml("wml")
game_classification * classification
Definition: resources.cpp:35
game_lua_kernel & lk
int intf_get_variable(lua_State *L)
Gets a WML variable.
tod_color color
The color modifications that should be made to the game board to reflect the time of day...
int movement_cost(const t_translation::terrain_code &terrain) const
Get the unit&#39;s movement cost on a particular terrain.
Definition: unit.hpp:1428
int intf_match_location(lua_State *L)
Matches a location against the given filter.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
void advance_to(const unit_type &t, bool use_traits=false)
Advances this unit to another type.
Definition: unit.cpp:881
std::vector< int > get_sides_vector(const vconfig &cfg)
Gets a vector of sides from side= attribute in a given config node.
Various functions implementing vision (through fog of war and shroud).
int luaW_type_error(lua_State *L, int narg, const char *tname)
bool run_filter(char const *name, const unit &u)
Runs a script from a unit filter.
int impl_theme_items_get(lua_State *L)
Creates a field of the theme_items table and returns it (__index metamethod).
int intf_replace_schedule(lua_State *l)
Replacing the current time of day schedule.
bool play_stage()
Play the turn - strategy.
Definition: stage.cpp:57
void set_clip_rect(const SDL_Rect &r)
unit & luaW_checkunit(lua_State *L, int index, bool only_on_map)
Converts a Lua value to a unit pointer.
Definition: lua_unit.cpp:191
static manager & get_singleton()
Definition: manager.hpp:145
int impl_get_terrain_info(lua_State *L)
Gets details about a terrain.
void write(config &res) const
Definition: label.cpp:432
config_array_view traits() const
Definition: types.hpp:398
bool run_wml_event(int ref, const vconfig &args, const game_events::queued_event &ev, bool *out=nullptr)
Run a WML stored in the Lua registry.
int intf_override_shroud(lua_State *L)
Overrides the shroud entirely.
Various functions that implement attacks and attack calculations.
int impl_end_level_data_set(lua_State *)
Variant for storing WML attributes.
int intf_view_locked(lua_State *L)
Gets whether gamemap scrolling is disabled for the user.
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
bool luaW_isunit(lua_State *L, int index)
Test if a Lua value is a unit.
Definition: lua_unit.cpp:113
bool clear_fog(const map_location &loc)
Definition: team.hpp:310
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:314
virtual std::vector< component * > get_children(const std::string &type)
Definition: component.cpp:106
std::vector< team > & teams()
static void luaW_push_tod(lua_State *L, const time_of_day &tod)
static const char animatorKey[]
int intf_log(lua_State *L)
Logs a message Arg 1: (optional) Logger; "wml" for WML errors or deprecations Arg 2: Message Arg 3: W...
logger & info()
Definition: log.cpp:171
#define a
config_array_view child_range(config_key_type key) const
std::string mp_campaign
static map & registry()
using static function variable instead of static member variable to prevent static initialization fia...
int intf_clear_messages(lua_State *)
Removes all messages from the chat window.
int intf_find_vision_range(lua_State *L)
Finds all the locations for which a given unit would remove the fog (if there was fog on the map)...
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:394
void refog()
Definition: team.hpp:312
int intf_play_sound(lua_State *L)
Plays a sound, possibly repeated.
pending_event_handler add_event_handler_from_lua(const std::string &name, const std::string &id, bool repeat=false, bool is_menu_item=false)
Create an empty event handler.
Definition: