1 /*
2 Copyright (C) 2009 - 2018 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 /**
16 * @file
17 * Provides a Lua interpreter, to be embedded in WML.
18 *
19 * @note Naming conventions:
20 * - intf_ functions are exported in the wesnoth domain,
21 * - impl_ functions are hidden inside metatables,
22 * - cfun_ functions are closures,
23 * - luaW_ functions are helpers in Lua style.
24 */
25
26 #include "scripting/game_lua_kernel.hpp"
27
28 #include "actions/attack.hpp" // for battle_context_unit_stats, etc
29 #include "actions/advancement.hpp" // for advance_unit_at, etc
30 #include "actions/move.hpp" // for clear_shroud
31 #include "actions/vision.hpp" // for clear_shroud
32 #include "ai/composite/ai.hpp" // for ai_composite
33 #include "ai/composite/component.hpp" // for component, etc
34 #include "ai/composite/contexts.hpp" // for ai_context
35 #include "ai/lua/engine_lua.hpp" // for engine_lua
36 #include "ai/composite/rca.hpp" // for candidate_action
37 #include "ai/composite/stage.hpp" // for stage
38 #include "ai/configuration.hpp" // for configuration
39 #include "ai/lua/core.hpp" // for lua_ai_context, etc
40 #include "ai/manager.hpp" // for manager, holder
41 #include "attack_prediction.hpp" // for combatant
42 #include "chat_events.hpp" // for chat_handler, etc
43 #include "config.hpp" // for config, etc
44 #include "display_chat_manager.hpp" // for clear_chat_messages
45 #include "formatter.hpp"
46 #include "game_board.hpp" // for game_board
47 #include "game_classification.hpp" // for game_classification, etc
48 #include "game_config.hpp" // for debug, base_income, etc
49 #include "game_config_manager.hpp" // for game_config_manager
50 #include "game_data.hpp" // for game_data, etc
51 #include "game_display.hpp" // for game_display
52 #include "game_errors.hpp" // for game_error
53 #include "game_events/conditional_wml.hpp" // for conditional_passed
54 #include "game_events/entity_location.hpp"
55 #include "game_events/pump.hpp" // for queued_event
56 #include "preferences/game.hpp" // for encountered_units
57 #include "help/help.hpp"
58 #include "log.hpp" // for LOG_STREAM, logger, etc
59 #include "utils/make_enum.hpp" // for operator<<
60 #include "map/map.hpp" // for gamemap
61 #include "map/label.hpp"
62 #include "map/location.hpp" // for map_location
63 #include "mouse_events.hpp" // for mouse_handler
64 #include "mp_game_settings.hpp" // for mp_game_settings
65 #include "pathfind/pathfind.hpp" // for full_cost_map, plain_route, etc
66 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
67 #include "play_controller.hpp" // for play_controller
68 #include "recall_list_manager.hpp" // for recall_list_manager
69 #include "replay.hpp" // for get_user_choice, etc
70 #include "reports.hpp" // for register_generator, etc
71 #include "resources.hpp" // for whiteboard
72 #include "scripting/lua_audio.hpp"
73 #include "scripting/lua_unit.hpp"
74 #include "scripting/lua_unit_attacks.hpp"
75 #include "scripting/lua_common.hpp"
76 #include "scripting/lua_cpp_function.hpp"
77 #include "scripting/lua_gui2.hpp" // for show_gamestate_inspector
78 #include "scripting/lua_pathfind_cost_calculator.hpp"
79 #include "scripting/lua_race.hpp"
80 #include "scripting/lua_team.hpp"
81 #include "scripting/lua_unit_type.hpp"
82 #include "scripting/push_check.hpp"
83 #include "synced_commands.hpp"
84 #include "color.hpp" // for surface
85 #include "sdl/surface.hpp" // for surface
86 #include "side_filter.hpp" // for side_filter
87 #include "sound.hpp" // for commit_music_changes, etc
88 #include "soundsource.hpp"
89 #include "synced_context.hpp" // for synced_context, etc
90 #include "synced_user_choice.hpp"
91 #include "team.hpp" // for team, village_owner
92 #include "terrain/terrain.hpp" // for terrain_type
93 #include "terrain/filter.hpp" // for terrain_filter
94 #include "terrain/translation.hpp" // for read_terrain_code, etc
95 #include "terrain/type_data.hpp"
96 #include "time_of_day.hpp" // for time_of_day
97 #include "tod_manager.hpp" // for tod_manager
98 #include "tstring.hpp" // for t_string, operator+
99 #include "units/unit.hpp" // for unit
100 #include "units/animation_component.hpp" // for unit_animation_component
101 #include "units/udisplay.hpp"
102 #include "units/filter.hpp"
103 #include "units/map.hpp" // for unit_map, etc
104 #include "units/ptr.hpp" // for unit_const_ptr, unit_ptr
105 #include "units/types.hpp" // for unit_type_data, unit_types, etc
106 #include "variable.hpp" // for vconfig, etc
107 #include "variable_info.hpp"
108 #include "whiteboard/manager.hpp" // for whiteboard
109 #include "wml_exception.hpp"
110 #include "deprecation.hpp"
111
112 #include "utils/functional.hpp" // for bind_t, bind
113 #include <boost/range/algorithm/copy.hpp> // boost::copy
114 #include <boost/range/adaptors.hpp> // boost::adaptors::filtered
115 #include <cassert> // for assert
116 #include <cstring> // for strcmp
117 #include <iterator> // for distance, advance
118 #include <map> // for map, map<>::value_type, etc
119 #include <new> // for operator new
120 #include <set> // for set
121 #include <sstream> // for operator<<, basic_ostream, etc
122 #include <utility> // for pair
123 #include <algorithm>
124 #include <vector> // for vector, etc
125 #include <SDL2/SDL_timer.h> // for SDL_GetTicks
126 #include "lua/lauxlib.h" // for luaL_checkinteger, etc
127 #include "lua/lua.h" // for lua_setfield, etc
128
129 class CVideo;
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 std::vector<config> game_lua_kernel::preload_scripts;
141 config game_lua_kernel::preload_config;
142
143 // Template which allows to push member functions to the lua kernel base into lua as C functions, using a shim
144 typedef int (game_lua_kernel::*member_callback)(lua_State *);
145
146 template <member_callback method>
dispatch(lua_State * L)147 int dispatch(lua_State *L) {
148 return ((lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)).*method)(L);
149 }
150
151 // Pass a const bool also...
152 typedef int (game_lua_kernel::*member_callback2)(lua_State *, bool);
153
154 template <member_callback2 method, bool b>
dispatch2(lua_State * L)155 int dispatch2(lua_State *L) {
156 return ((lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)).*method)(L, b);
157 }
158
159 struct map_locker
160 {
map_lockermap_locker161 map_locker(game_lua_kernel* kernel) : kernel_(kernel)
162 {
163 ++kernel_->map_locked_;
164 }
~map_lockermap_locker165 ~map_locker()
166 {
167 --kernel_->map_locked_;
168 }
169 game_lua_kernel* kernel_;
170 };
171
172
extract_preload_scripts(const config & game_config)173 void game_lua_kernel::extract_preload_scripts(const config& game_config)
174 {
175 game_lua_kernel::preload_scripts.clear();
176 for (const config& cfg : game_config.child_range("lua")) {
177 game_lua_kernel::preload_scripts.push_back(cfg);
178 }
179 game_lua_kernel::preload_config = game_config.child("game_config");
180 }
181
log_error(char const * msg,char const * context)182 void game_lua_kernel::log_error(char const * msg, char const * context)
183 {
184 lua_kernel_base::log_error(msg, context);
185 lua_chat(context, msg);
186 }
187
lua_chat(const std::string & caption,const std::string & msg)188 void game_lua_kernel::lua_chat(const std::string& caption, const std::string& msg)
189 {
190 if (game_display_) {
191 game_display_->get_chat_manager().add_chat_message(time(nullptr), caption, 0, msg,
192 events::chat_handler::MESSAGE_PUBLIC, false);
193 }
194 }
195
196 /**
197 * Gets a vector of sides from side= attribute in a given config node.
198 * Promotes consistent behavior.
199 */
get_sides_vector(const vconfig & cfg)200 std::vector<int> game_lua_kernel::get_sides_vector(const vconfig& cfg)
201 {
202 const config::attribute_value sides = cfg["side"];
203 const vconfig &ssf = cfg.child("filter_side");
204
205 if (!ssf.null()) {
206 if(!sides.empty()) { WRN_LUA << "ignoring duplicate side filter information (inline side=)" << std::endl; }
207 side_filter filter(ssf, &game_state_);
208 return filter.get_teams();
209 }
210
211 side_filter filter(sides.str(), &game_state_);
212 return filter.get_teams();
213 }
214
215
216
special_locations_len(lua_State * L)217 static int special_locations_len(lua_State *L)
218 {
219 lua_pushnumber(L, lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).map().special_locations().size());
220 return 1;
221 }
222
special_locations_next(lua_State * L)223 static int special_locations_next(lua_State *L)
224 {
225 const t_translation::starting_positions::left_map& left = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).map().special_locations().left;
226
227 t_translation::starting_positions::left_const_iterator it;
228 if (lua_isnoneornil(L, 2)) {
229 it = left.begin();
230 }
231 else {
232 it = left.find(luaL_checkstring(L, 2));
233 if (it == left.end()) {
234 return 0;
235 }
236 ++it;
237 }
238 if (it == left.end()) {
239 return 0;
240 }
241 lua_pushstring(L, it->first.c_str());
242 luaW_pushlocation(L, it->second);
243 return 2;
244 }
245
special_locations_pairs(lua_State * L)246 static int special_locations_pairs(lua_State *L)
247 {
248 lua_pushcfunction(L, &special_locations_next);
249 lua_pushvalue(L, -2);
250 lua_pushnil(L);
251 return 3;
252 }
253
special_locations_index(lua_State * L)254 static int special_locations_index(lua_State *L)
255 {
256 const t_translation::starting_positions::left_map& left = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).map().special_locations().left;
257 auto it = left.find(luaL_checkstring(L, 2));
258 if (it == left.end()) {
259 return 0;
260 }
261 else {
262 luaW_pushlocation(L, it->second);
263 return 1;
264 }
265 }
266
special_locations_newindex(lua_State * L)267 static int special_locations_newindex(lua_State *L)
268 {
269 lua_pushstring(L, "special locations cannot be modified using wesnoth.special_locations");
270 return lua_error(L);
271 }
272
push_locations_table(lua_State * L)273 static void push_locations_table(lua_State *L)
274 {
275 lua_newtable(L); // The functor table
276 lua_newtable(L); // The functor metatable
277 lua_pushstring(L, "__len");
278 lua_pushcfunction(L, &special_locations_len);
279 lua_rawset(L, -3);
280 lua_pushstring(L, "__index");
281 lua_pushcfunction(L, &special_locations_index);
282 lua_rawset(L, -3);
283 lua_pushstring(L, "__newindex");
284 lua_pushcfunction(L, &special_locations_newindex);
285 lua_rawset(L, -3);
286 lua_pushstring(L, "__pairs");
287 lua_pushcfunction(L, &special_locations_pairs);
288 lua_rawset(L, -3);
289 lua_setmetatable(L, -2); // Apply the metatable to the functor table
290 }
291
292 namespace {
293 /**
294 * Temporary entry to a queued_event stack
295 */
296 struct queued_event_context
297 {
298 typedef game_events::queued_event qe;
299 std::stack<qe const *> & stack_;
300
queued_event_context__anon384f78800111::queued_event_context301 queued_event_context(qe const *new_qe, std::stack<qe const*> & stack)
302 : stack_(stack)
303 {
304 stack_.push(new_qe);
305 }
306
~queued_event_context__anon384f78800111::queued_event_context307 ~queued_event_context()
308 {
309 stack_.pop();
310 }
311 };
312 }//unnamed namespace for queued_event_context
313
314 /**
315 * Gets currently viewing side.
316 * - Ret 1: integer specifying the currently viewing side
317 * - Ret 2: Bool whether the vision is not limited to that team, this can for example be true during replays.
318 */
intf_get_viewing_side(lua_State * L)319 static int intf_get_viewing_side(lua_State *L)
320 {
321 if(const display* disp = display::get_singleton()) {
322 lua_pushinteger(L, disp->viewing_side());
323 lua_pushboolean(L, disp->show_everything());
324 return 2;
325 }
326 else {
327 return 0;
328 }
329 }
330
331 static const char animatorKey[] = "unit animator";
332
impl_animator_collect(lua_State * L)333 static int impl_animator_collect(lua_State* L) {
334 unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
335 anim.~unit_animator();
336 return 0;
337 }
338
impl_add_animation(lua_State * L)339 static int impl_add_animation(lua_State* L)
340 {
341 unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
342 unit& u = luaW_checkunit(L, 2);
343 std::string which = luaL_checkstring(L, 3);
344
345 using hit_type = unit_animation::hit_type;
346 std::string hits_str = luaL_checkstring(L, 4);
347 hit_type hits = hit_type::string_to_enum(hits_str, hit_type::INVALID);
348
349 map_location dest;
350 int v1 = 0, v2 = 0;
351 bool bars = false;
352 t_string text;
353 color_t color{255, 255, 255};
354 const_attack_ptr primary, secondary;
355
356 if(lua_istable(L, 5)) {
357 lua_getfield(L, 5, "target");
358 if(luaW_tolocation(L, -1, dest)) {
359 if(dest == u.get_location()) {
360 return luaL_argerror(L, 5, "target location must be different from animated unit's location");
361 } else if(!tiles_adjacent(dest, u.get_location())) {
362 return luaL_argerror(L, 5, "target location must be adjacent to the animated unit");
363 }
364 } else {
365 // luaW_tolocation may set the location to (0,0) if it fails
366 dest = map_location();
367 if(!lua_isnoneornil(L, -1)) {
368 return luaW_type_error(L, 5, "target", "location table");
369 }
370 }
371 lua_pop(L, 1);
372
373 lua_getfield(L, 5, "value");
374 if(lua_isnumber(L, -1)) {
375 v1 = lua_tonumber(L, -1);
376 } else if(lua_istable(L, -1)) {
377 lua_rawgeti(L, -1, 1);
378 v1 = lua_tonumber(L, -1);
379 lua_pop(L, 1);
380 lua_rawgeti(L, -1, 2);
381 v2 = lua_tonumber(L, -1);
382 lua_pop(L, 1);
383 } else if(!lua_isnoneornil(L, -1)) {
384 return luaW_type_error(L, 5, "value", "number or array of two numbers");
385 }
386 lua_pop(L, 1);
387
388 lua_getfield(L, 5, "with_bars");
389 if(lua_isboolean(L, -1)) {
390 bars = luaW_toboolean(L, -1);
391 } else if(!lua_isnoneornil(L, -1)) {
392 return luaW_type_error(L, 5, "with_bars", lua_typename(L, LUA_TBOOLEAN));
393 }
394 lua_pop(L, 1);
395
396 lua_getfield(L, 5, "text");
397 if(lua_isstring(L, -1)) {
398 text = lua_tostring(L, -1);
399 } else if(luaW_totstring(L, -1, text)) {
400 // Do nothing; luaW_totstring already assigned the value
401 } else if(!lua_isnoneornil(L, -1)) {
402 return luaW_type_error(L, 5, "text", lua_typename(L, LUA_TSTRING));
403 }
404 lua_pop(L, 1);
405
406 lua_getfield(L, 5, "color");
407 if(lua_istable(L, -1) && lua_rawlen(L, -1) == 3) {
408 int idx = lua_absindex(L, -1);
409 lua_rawgeti(L, idx, 1); // red @ -3
410 lua_rawgeti(L, idx, 2); // green @ -2
411 lua_rawgeti(L, idx, 3); // blue @ -1
412 color = color_t(lua_tonumber(L, -3), lua_tonumber(L, -2), lua_tonumber(L, -1));
413 lua_pop(L, 3);
414 } else if(!lua_isnoneornil(L, -1)) {
415 return luaW_type_error(L, 5, "color", "array of three numbers");
416 }
417 lua_pop(L, 1);
418
419 lua_getfield(L, 5, "primary");
420 primary = luaW_toweapon(L, -1);
421 if(!primary && !lua_isnoneornil(L, -1)) {
422 return luaW_type_error(L, 5, "primary", "weapon");
423 }
424 lua_pop(L, 1);
425
426 lua_getfield(L, 5, "secondary");
427 secondary = luaW_toweapon(L, -1);
428 if(!secondary && !lua_isnoneornil(L, -1)) {
429 return luaW_type_error(L, 5, "secondary", "weapon");
430 }
431 lua_pop(L, 1);
432 } else if(!lua_isnoneornil(L, 5)) {
433 return luaW_type_error(L, 5, "table of options");
434 }
435
436 anim.add_animation(&u, which, u.get_location(), dest, v1, bars, text, color, hits, primary, secondary, v2);
437 return 0;
438 }
439
impl_run_animation(lua_State * L)440 int game_lua_kernel::impl_run_animation(lua_State* L)
441 {
442 CVideo& v = CVideo::get_singleton();
443 if(v.update_locked() || v.faked()) {
444 return 0;
445 }
446 events::command_disabler command_disabler;
447 unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
448 play_controller_.play_slice(false);
449 anim.start_animations();
450 anim.wait_for_end();
451 anim.set_all_standing();
452 return 0;
453 }
454
impl_clear_animation(lua_State * L)455 static int impl_clear_animation(lua_State* L)
456 {
457 unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
458 anim.clear();
459 return 0;
460 }
461
impl_animator_get(lua_State * L)462 static int impl_animator_get(lua_State* L)
463 {
464 const char* m = lua_tostring(L, 2);
465 return luaW_getmetafield(L, 1, m);
466 }
467
intf_create_animator(lua_State * L)468 int game_lua_kernel::intf_create_animator(lua_State* L)
469 {
470 new(L) unit_animator;
471 if(luaL_newmetatable(L, animatorKey)) {
472 luaL_Reg metafuncs[] {
473 {"__gc", impl_animator_collect},
474 {"__index", impl_animator_get},
475 {"add", impl_add_animation},
476 {"run", &dispatch<&game_lua_kernel::impl_run_animation>},
477 {"clear", impl_clear_animation},
478 {nullptr, nullptr},
479 };
480 luaL_setfuncs(L, metafuncs, 0);
481 lua_pushstring(L, "__metatable");
482 lua_setfield(L, -2, animatorKey);
483 }
484 lua_setmetatable(L, -2);
485 return 1;
486 }
487
intf_gamestate_inspector(lua_State * L)488 int game_lua_kernel::intf_gamestate_inspector(lua_State *L)
489 {
490 if (game_display_) {
491 return lua_gui2::show_gamestate_inspector(luaW_checkvconfig(L, 1), gamedata(), game_state_);
492 }
493 return 0;
494 }
495
496 /**
497 * Gets the unit at the given location or with the given id.
498 * - Arg 1: location
499 * OR
500 * - Arg 1: string ID
501 * - Ret 1: full userdata with __index pointing to impl_unit_get and
502 * __newindex pointing to impl_unit_set.
503 */
intf_get_unit(lua_State * L)504 int game_lua_kernel::intf_get_unit(lua_State *L)
505 {
506 map_location loc;
507 if(lua_isstring(L, 1) && !lua_isnumber(L, 1)) {
508 std::string id = luaL_checkstring(L, 1);
509 for(const unit& u : units()) {
510 if(u.id() == id) {
511 luaW_pushunit(L, u.underlying_id());
512 return 1;
513 }
514 }
515 return 0;
516 }
517 if(!luaW_tolocation(L, 1, loc)) {
518 return luaL_argerror(L, 1, "expected string or location");
519 }
520 unit_map::const_iterator ui = units().find(loc);
521
522 if (!ui.valid()) return 0;
523
524 luaW_pushunit(L, ui->underlying_id());
525 return 1;
526 }
527
528 /**
529 * Gets the unit displayed in the sidebar.
530 * - Ret 1: full userdata with __index pointing to impl_unit_get and
531 * __newindex pointing to impl_unit_set.
532 */
intf_get_displayed_unit(lua_State * L)533 int game_lua_kernel::intf_get_displayed_unit(lua_State *L)
534 {
535 if (!game_display_) {
536 return 0;
537 }
538
539 unit_map::const_iterator ui = board().find_visible_unit(
540 game_display_->displayed_unit_hex(),
541 teams()[game_display_->viewing_team()],
542 game_display_->show_everything());
543 if (!ui.valid()) return 0;
544
545 luaW_pushunit(L, ui->underlying_id());
546 return 1;
547 }
548
549 /**
550 * Gets all the units matching a given filter.
551 * - Arg 1: optional table containing a filter
552 * - Arg 2: optional location (to find all units that would match on that location)
553 OR unit (to find all units that would match adjacent to that unit)
554 * - Ret 1: table containing full userdata with __index pointing to
555 * impl_unit_get and __newindex pointing to impl_unit_set.
556 */
intf_get_units(lua_State * L)557 int game_lua_kernel::intf_get_units(lua_State *L)
558 {
559 vconfig filter = luaW_checkvconfig(L, 1, true);
560
561 unit_filter filt(filter);
562 std::vector<const unit*> units;
563
564 if(unit* u_adj = luaW_tounit(L, 2)) {
565 if(!u_adj) {
566 return luaL_argerror(L, 2, "unit not found");
567 }
568 units = filt.all_matches_with_unit(*u_adj);
569 } else if(!lua_isnoneornil(L, 2)) {
570 map_location loc;
571 luaW_tolocation(L, 2, loc);
572 if(!loc.valid()) {
573 return luaL_argerror(L, 2, "invalid location");
574 }
575 units = filt.all_matches_at(loc);
576 } else {
577 units = filt.all_matches_on_map();
578 }
579
580 // Go through all the units while keeping the following stack:
581 // 1: return table, 2: userdata
582 lua_settop(L, 0);
583 lua_newtable(L);
584 int i = 1;
585
586 for (const unit * ui : units) {
587 luaW_pushunit(L, ui->underlying_id());
588 lua_rawseti(L, 1, i);
589 ++i;
590 }
591 return 1;
592 }
593
594 /**
595 * Matches a unit against the given filter.
596 * - Arg 1: full userdata.
597 * - Arg 2: table containing a filter
598 * - Arg 3: optional location OR optional "adjacent" unit
599 * - Ret 1: boolean.
600 */
intf_match_unit(lua_State * L)601 int game_lua_kernel::intf_match_unit(lua_State *L)
602 {
603 lua_unit& u = *luaW_checkunit_ref(L, 1);
604
605 vconfig filter = luaW_checkvconfig(L, 2, true);
606
607 if (filter.null()) {
608 lua_pushboolean(L, true);
609 return 1;
610 }
611
612 if(unit* u_adj = luaW_tounit(L, 3)) {
613 if(int side = u.on_recall_list()) {
614 WRN_LUA << "wesnoth.match_unit called with a secondary unit (3rd argument), ";
615 WRN_LUA << "but unit to match was on recall list. ";
616 WRN_LUA << "Thus the 3rd argument is ignored.\n";
617 team &t = board().get_team(side);
618 scoped_recall_unit auto_store("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id()));
619 lua_pushboolean(L, unit_filter(filter).matches(*u, map_location()));
620 return 1;
621 }
622 if (!u_adj) {
623 return luaL_argerror(L, 3, "unit not found");
624 }
625 lua_pushboolean(L, unit_filter(filter).matches(*u, *u_adj));
626 } else if(int side = u.on_recall_list()) {
627 map_location loc;
628 luaW_tolocation(L, 3, loc); // If argument 3 isn't a location, loc is unchanged
629 team &t = board().get_team(side);
630 scoped_recall_unit auto_store("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id()));
631 lua_pushboolean(L, unit_filter(filter).matches(*u, loc));
632 return 1;
633 } else {
634 map_location loc = u->get_location();
635 luaW_tolocation(L, 3, loc); // If argument 3 isn't a location, loc is unchanged
636 lua_pushboolean(L, unit_filter(filter).matches(*u, loc));
637 }
638 return 1;
639 }
640
641 /**
642 * Gets the numeric ids of all the units matching a given filter on the recall lists.
643 * - Arg 1: optional table containing a filter
644 * - Ret 1: table containing full userdata with __index pointing to
645 * impl_unit_get and __newindex pointing to impl_unit_set.
646 */
intf_get_recall_units(lua_State * L)647 int game_lua_kernel::intf_get_recall_units(lua_State *L)
648 {
649 vconfig filter = luaW_checkvconfig(L, 1, true);
650
651 // Go through all the units while keeping the following stack:
652 // 1: return table, 2: userdata
653 lua_settop(L, 0);
654 lua_newtable(L);
655 int i = 1, s = 1;
656 const unit_filter ufilt(filter);
657 for (team &t : teams())
658 {
659 for (unit_ptr & u : t.recall_list())
660 {
661 if (!filter.null()) {
662 scoped_recall_unit auto_store("this_unit",
663 t.save_id_or_number(), t.recall_list().find_index(u->id()));
664 if (!ufilt( *u, map_location() ))
665 continue;
666 }
667 luaW_pushunit(L, s, u->underlying_id());
668 lua_rawseti(L, 1, i);
669 ++i;
670 }
671 ++s;
672 }
673 return 1;
674 }
675
676 /**
677 * Fires an event.
678 * - Arg 1: string containing the event name or id.
679 * - Arg 2: optional first location.
680 * - Arg 3: optional second location.
681 * - Arg 4: optional WML table used as the [weapon] tag.
682 * - Arg 5: optional WML table used as the [second_weapon] tag.
683 * - Ret 1: boolean indicating whether the event was processed or not.
684 */
intf_fire_event(lua_State * L,const bool by_id)685 int game_lua_kernel::intf_fire_event(lua_State *L, const bool by_id)
686 {
687 char const *m = luaL_checkstring(L, 1);
688
689 int pos = 2;
690 map_location l1, l2;
691 config data;
692
693 if (luaW_tolocation(L, 2, l1)) {
694 if (luaW_tolocation(L, 3, l2)) {
695 pos = 4;
696 } else {
697 pos = 3;
698 }
699 }
700
701 if (!lua_isnoneornil(L, pos)) {
702 data.add_child("first", luaW_checkconfig(L, pos));
703 }
704 ++pos;
705 if (!lua_isnoneornil(L, pos)) {
706 data.add_child("second", luaW_checkconfig(L, pos));
707 }
708
709 bool b = false;
710
711 if (by_id) {
712 b = std::get<0>(play_controller_.pump().fire("", m, l1, l2, data));
713 }
714 else {
715 b = std::get<0>(play_controller_.pump().fire(m, l1, l2, data));
716 }
717 lua_pushboolean(L, b);
718 return 1;
719 }
720
721
722 /**
723 * Fires a wml menu item.
724 * - Arg 1: id of the item. it is not possible to fire items that don't have ids with this function.
725 * - Arg 2: optional first location.
726 * - Ret 1: boolean, true indicating that the event was fired successfully
727 *
728 * NOTE: This is not an "official" feature, it may currently cause assertion failures if used with
729 * menu items which have "needs_select". It is not supported right now to use it this way.
730 * The purpose of this function right now is to make it possible to have automated sanity tests for
731 * the wml menu items system.
732 */
intf_fire_wml_menu_item(lua_State * L)733 int game_lua_kernel::intf_fire_wml_menu_item(lua_State *L)
734 {
735 char const *m = luaL_checkstring(L, 1);
736
737 map_location l1 = luaW_checklocation(L, 2);
738
739 bool b = game_state_.get_wml_menu_items().fire_item(m, l1, gamedata(), game_state_, units());
740 lua_pushboolean(L, b);
741 return 1;
742 }
743
744 /**
745 * Gets a WML variable.
746 * - Arg 1: string containing the variable name.
747 * - Arg 2: optional bool indicating if tables for containers should be left empty.
748 * - Ret 1: value of the variable, if any.
749 */
intf_get_variable(lua_State * L)750 int game_lua_kernel::intf_get_variable(lua_State *L)
751 {
752 char const *m = luaL_checkstring(L, 1);
753 variable_access_const v = gamedata().get_variable_access_read(m);
754 return luaW_pushvariable(L, v) ? 1 : 0;
755 }
756
757 /**
758 * Gets a side specific WML variable.
759 * - Arg 1: integer side number.
760 * - Arg 2: string containing the variable name.
761 * - Ret 1: value of the variable, if any.
762 */
intf_get_side_variable(lua_State * L)763 int game_lua_kernel::intf_get_side_variable(lua_State *L)
764 {
765
766 unsigned side_index = luaL_checkinteger(L, 1) - 1;
767 if(side_index >= teams().size()) {
768 return luaL_argerror(L, 1, "invalid side number");
769 }
770 char const *m = luaL_checkstring(L, 2);
771 variable_access_const v(m, teams()[side_index].variables());
772 return luaW_pushvariable(L, v) ? 1 : 0;
773 }
774
775 /**
776 * Gets a side specific WML variable.
777 * - Arg 1: integer side number.
778 * - Arg 2: string containing the variable name.
779 * - Arg 3: boolean/integer/string/table containing the value.
780 */
intf_set_side_variable(lua_State * L)781 int game_lua_kernel::intf_set_side_variable(lua_State *L)
782 {
783 unsigned side = luaL_checkinteger(L, 1);
784 if(side > teams().size() || side == 0) {
785 return luaL_argerror(L, 1, "invalid side number");
786 }
787 char const *m = luaL_checkstring(L, 2);
788 config& vars = game_state_.board_.get_team(side).variables();
789 if(lua_isnoneornil(L, 3)) {
790 try {
791 variable_access_throw(m, vars).clear(false);
792 } catch(const invalid_variablename_exception&) {
793 }
794 return 0;
795 }
796 variable_access_create v(m, vars);
797 luaW_checkvariable(L, v, 3);
798 return 0;
799 }
800
801 /**
802 * Sets a WML variable.
803 * - Arg 1: string containing the variable name.
804 * - Arg 2: boolean/integer/string/table containing the value.
805 */
intf_set_variable(lua_State * L)806 int game_lua_kernel::intf_set_variable(lua_State *L)
807 {
808 const std::string m = luaL_checkstring(L, 1);
809 if(m.empty()) return luaL_argerror(L, 1, "empty variable name");
810 if (lua_isnoneornil(L, 2)) {
811 gamedata().clear_variable(m);
812 return 0;
813 }
814 variable_access_create v = gamedata().get_variable_access_write(m);
815 luaW_checkvariable(L, v, 2);
816 return 0;
817 }
818
intf_set_menu_item(lua_State * L)819 int game_lua_kernel::intf_set_menu_item(lua_State *L)
820 {
821 game_state_.get_wml_menu_items().set_item(luaL_checkstring(L, 1), luaW_checkvconfig(L,2));
822 return 0;
823 }
824
intf_clear_menu_item(lua_State * L)825 int game_lua_kernel::intf_clear_menu_item(lua_State *L)
826 {
827 std::string ids(luaL_checkstring(L, 1));
828 for(const std::string& id : utils::split(ids, ',', utils::STRIP_SPACES)) {
829 if(id.empty()) {
830 WRN_LUA << "[clear_menu_item] has been given an empty id=, ignoring" << std::endl;
831 continue;
832 }
833 game_state_.get_wml_menu_items().erase(id);
834 }
835 return 0;
836 }
837
intf_set_end_campaign_credits(lua_State * L)838 int game_lua_kernel::intf_set_end_campaign_credits(lua_State *L)
839 {
840 game_classification &classification = play_controller_.get_classification();
841 classification.end_credits = luaW_toboolean(L, 1);
842 return 0;
843 }
844
intf_set_end_campaign_text(lua_State * L)845 int game_lua_kernel::intf_set_end_campaign_text(lua_State *L)
846 {
847 game_classification &classification = play_controller_.get_classification();
848 classification.end_text = luaW_checktstring(L, 1);
849 if (lua_isnumber(L, 2)) {
850 classification.end_text_duration = static_cast<int> (lua_tonumber(L, 2));
851 }
852
853 return 0;
854 }
855
intf_set_next_scenario(lua_State * L)856 int game_lua_kernel::intf_set_next_scenario(lua_State *L)
857 {
858 deprecated_message("wesnoth.set_next_scenario", DEP_LEVEL::INDEFINITE, "");
859 gamedata().set_next_scenario(luaL_checkstring(L, 1));
860 return 0;
861 }
862
intf_shroud_op(lua_State * L,bool place_shroud)863 int game_lua_kernel::intf_shroud_op(lua_State *L, bool place_shroud)
864 {
865
866 int side_num = luaL_checkinteger(L, 1);
867
868 if(lua_isstring(L, 2)) {
869 std::string data = lua_tostring(L, 2);
870 // Special case - using a shroud_data string, or "all"
871 team& side = board().get_team(side_num);
872 if(place_shroud) {
873 side.reshroud();
874 }
875 if(data != "all") {
876 side.merge_shroud_map_data(data);
877 } else if(!place_shroud) {
878 bool was_shrouded = side.uses_shroud();
879 side.set_shroud(false);
880 actions::clear_shroud(side.side());
881 side.set_shroud(was_shrouded);
882 }
883 return 0;
884 } else if(lua_istable(L, 2)) {
885 std::vector<map_location> locs_v = lua_check<std::vector<map_location>>(L, 2);
886 std::set<map_location> locs(locs_v.begin(), locs_v.end());
887 team &t = board().get_team(side_num);
888
889 for (const map_location& loc : locs)
890 {
891 if (place_shroud) {
892 t.place_shroud(loc);
893 } else {
894 t.clear_shroud(loc);
895 }
896 }
897 } else {
898 return luaL_argerror(L, 2, "expected list of locations or shroud data string");
899 }
900
901 game_display_->labels().recalculate_shroud();
902 game_display_->recalculate_minimap();
903 game_display_->invalidate_all();
904
905 return 0;
906 }
907
908
909 /**
910 * Highlights the given location on the map.
911 * - Arg 1: location.
912 */
intf_highlight_hex(lua_State * L)913 int game_lua_kernel::intf_highlight_hex(lua_State *L)
914 {
915 if (!game_display_) {
916 return 0;
917 }
918
919 const map_location loc = luaW_checklocation(L, 1);
920 if(!map().on_board(loc)) return luaL_argerror(L, 1, "not on board");
921 game_display_->highlight_hex(loc);
922 game_display_->display_unit_hex(loc);
923
924 return 0;
925 }
926
927 /**
928 * Returns whether the first side is an enemy of the second one.
929 * - Args 1,2: side numbers.
930 * - Ret 1: boolean.
931 */
intf_is_enemy(lua_State * L)932 int game_lua_kernel::intf_is_enemy(lua_State *L)
933 {
934 unsigned side_1 = luaL_checkinteger(L, 1) - 1;
935 unsigned side_2 = luaL_checkinteger(L, 2) - 1;
936 if (side_1 >= teams().size() || side_2 >= teams().size()) return 0;
937 lua_pushboolean(L, teams()[side_1].is_enemy(side_2 + 1));
938 return 1;
939 }
940
941 /**
942 * Gets whether gamemap scrolling is disabled for the user.
943 * - Ret 1: boolean.
944 */
intf_view_locked(lua_State * L)945 int game_lua_kernel::intf_view_locked(lua_State *L)
946 {
947 if (!game_display_) {
948 return 0;
949 }
950
951 lua_pushboolean(L, game_display_->view_locked());
952 return 1;
953 }
954
955 /**
956 * Sets whether gamemap scrolling is disabled for the user.
957 * - Arg 1: boolean, specifying the new locked/unlocked status.
958 */
intf_lock_view(lua_State * L)959 int game_lua_kernel::intf_lock_view(lua_State *L)
960 {
961 bool lock = luaW_toboolean(L, 1);
962 if (game_display_) {
963 game_display_->set_view_locked(lock);
964 }
965 return 0;
966 }
967
968 /**
969 * Gets a terrain code.
970 * - Arg 1: map location.
971 * - Ret 1: string.
972 */
intf_get_terrain(lua_State * L)973 int game_lua_kernel::intf_get_terrain(lua_State *L)
974 {
975 map_location loc = luaW_checklocation(L, 1);
976
977 const t_translation::terrain_code& t = board().map().
978 get_terrain(loc);
979 lua_pushstring(L, t_translation::write_terrain_code(t).c_str());
980 return 1;
981 }
982
983 /**
984 * Sets a terrain code.
985 * - Arg 1: map location.
986 * - Arg 2: terrain code string.
987 * - Arg 3: layer: (overlay|base|both, default=both)
988 * - Arg 4: replace_if_failed, default = no
989 */
intf_set_terrain(lua_State * L)990 int game_lua_kernel::intf_set_terrain(lua_State *L)
991 {
992 map_location loc = luaW_checklocation(L, 1);
993 std::string t_str(luaL_checkstring(L, 2));
994
995 std::string mode_str = "both";
996 bool replace_if_failed = false;
997 if (!lua_isnone(L, 3)) {
998 if (!lua_isnil(L, 3)) {
999 mode_str = luaL_checkstring(L, 3);
1000 }
1001
1002 if(!lua_isnoneornil(L, 4)) {
1003 replace_if_failed = luaW_toboolean(L, 4);
1004 }
1005 }
1006
1007 bool result = board().change_terrain(loc, t_str, mode_str, replace_if_failed);
1008
1009 if (game_display_) {
1010 game_display_->needs_rebuild(result);
1011 }
1012
1013 return 0;
1014 }
1015
1016 /**
1017 * Gets details about a terrain.
1018 * - Arg 1: terrain code string.
1019 * - Ret 1: table.
1020 */
intf_get_terrain_info(lua_State * L)1021 int game_lua_kernel::intf_get_terrain_info(lua_State *L)
1022 {
1023 char const *m = luaL_checkstring(L, 1);
1024 t_translation::terrain_code t = t_translation::read_terrain_code(m);
1025 if (t == t_translation::NONE_TERRAIN) return 0;
1026 const terrain_type& info = board().map().tdata()->get_terrain_info(t);
1027
1028 lua_newtable(L);
1029 lua_pushstring(L, info.id().c_str());
1030 lua_setfield(L, -2, "id");
1031 luaW_pushtstring(L, info.name());
1032 lua_setfield(L, -2, "name");
1033 luaW_pushtstring(L, info.editor_name());
1034 lua_setfield(L, -2, "editor_name");
1035 luaW_pushtstring(L, info.description());
1036 lua_setfield(L, -2, "description");
1037 lua_push(L, info.icon_image());
1038 lua_setfield(L, -2, "icon");
1039 lua_push(L, info.editor_image());
1040 lua_setfield(L, -2, "editor_image");
1041 lua_pushinteger(L, info.light_bonus(0));
1042 lua_setfield(L, -2, "light");
1043 lua_pushboolean(L, info.is_village());
1044 lua_setfield(L, -2, "village");
1045 lua_pushboolean(L, info.is_castle());
1046 lua_setfield(L, -2, "castle");
1047 lua_pushboolean(L, info.is_keep());
1048 lua_setfield(L, -2, "keep");
1049 lua_pushinteger(L, info.gives_healing());
1050 lua_setfield(L, -2, "healing");
1051
1052 return 1;
1053 }
1054
1055 /**
1056 * Gets time of day information.
1057 * - Arg 1: optional turn number
1058 * - Arg 2: optional location
1059 * - Arg 3: optional boolean (consider_illuminates)
1060 * - Ret 1: table.
1061 */
intf_get_time_of_day(lua_State * L)1062 int game_lua_kernel::intf_get_time_of_day(lua_State *L)
1063 {
1064 unsigned arg = 1;
1065
1066 int for_turn = tod_man().turn();
1067 map_location loc = map_location();
1068 bool consider_illuminates = false;
1069
1070 if(lua_isnumber(L, arg)) {
1071 ++arg;
1072 for_turn = luaL_checkinteger(L, 1);
1073 int number_of_turns = tod_man().number_of_turns();
1074 if(for_turn < 1 || (number_of_turns != -1 && for_turn > number_of_turns)) {
1075 return luaL_argerror(L, 1, "turn number out of range");
1076 }
1077 }
1078 else if(lua_isnil(L, arg)) ++arg;
1079
1080 if(luaW_tolocation(L, arg, loc)) {
1081 if(!board().map().on_board(loc)) return luaL_argerror(L, arg, "coordinates are not on board");
1082
1083 if(lua_istable(L, arg)) {
1084 lua_rawgeti(L, arg, 3);
1085 consider_illuminates = luaW_toboolean(L, -1);
1086 lua_pop(L, 1);
1087 } else if(lua_isboolean(L, arg + 1)) {
1088 consider_illuminates = luaW_toboolean(L, arg + 1);
1089 }
1090 }
1091
1092 const time_of_day& tod = consider_illuminates ?
1093 tod_man().get_illuminated_time_of_day(board().units(), board().map(), loc, for_turn) :
1094 tod_man().get_time_of_day(loc, for_turn);
1095
1096 lua_newtable(L);
1097 lua_pushstring(L, tod.id.c_str());
1098 lua_setfield(L, -2, "id");
1099 lua_pushinteger(L, tod.lawful_bonus);
1100 lua_setfield(L, -2, "lawful_bonus");
1101 lua_pushinteger(L, tod.bonus_modified);
1102 lua_setfield(L, -2, "bonus_modified");
1103 lua_pushstring(L, tod.image.c_str());
1104 lua_setfield(L, -2, "image");
1105 luaW_pushtstring(L, tod.name);
1106 lua_setfield(L, -2, "name");
1107
1108 lua_pushinteger(L, tod.color.r);
1109 lua_setfield(L, -2, "red");
1110 lua_pushinteger(L, tod.color.g);
1111 lua_setfield(L, -2, "green");
1112 lua_pushinteger(L, tod.color.b);
1113 lua_setfield(L, -2, "blue");
1114
1115 return 1;
1116 }
1117
1118 /**
1119 * Gets the side of a village owner.
1120 * - Arg 1: map location.
1121 * - Ret 1: integer.
1122 */
intf_get_village_owner(lua_State * L)1123 int game_lua_kernel::intf_get_village_owner(lua_State *L)
1124 {
1125 map_location loc = luaW_checklocation(L, 1);
1126 if (!board().map().is_village(loc))
1127 return 0;
1128
1129 int side = board().village_owner(loc) + 1;
1130 if (!side) return 0;
1131 lua_pushinteger(L, side);
1132 return 1;
1133 }
1134
1135 /**
1136 * Sets the owner of a village.
1137 * - Arg 1: map location.
1138 * - Arg 2: integer for the side or empty to remove ownership.
1139 */
intf_set_village_owner(lua_State * L)1140 int game_lua_kernel::intf_set_village_owner(lua_State *L)
1141 {
1142 map_location loc = luaW_checklocation(L, 1);
1143 if(!board().map().is_village(loc)) {
1144 return 0;
1145 }
1146
1147 const int old_side_num = board().village_owner(loc) + 1;
1148 const int new_side_num = lua_isnoneornil(L, 2) ? 0 : luaL_checkinteger(L, 2);
1149
1150 team* old_side = nullptr;
1151 team* new_side = nullptr;
1152
1153 if(old_side_num == new_side_num) {
1154 return 0;
1155 }
1156
1157 try {
1158 old_side = &board().get_team(old_side_num);
1159 } catch(const std::out_of_range&) {
1160 // old_side_num is invalid, most likely because the village wasn't captured.
1161 old_side = nullptr;
1162 }
1163
1164 try {
1165 new_side = &board().get_team(new_side_num);
1166 } catch(const std::out_of_range&) {
1167 // new_side_num is invalid.
1168 new_side = nullptr;
1169 }
1170
1171 // The new side was valid, but already defeated. Do nothing.
1172 if(new_side && board().team_is_defeated(*new_side)) {
1173 return 0;
1174 }
1175
1176 // Even if the new side is not valid, we still want to remove the village from the old side.
1177 // This covers the case where new_side_num equals 0. The behavior in that case is to simply
1178 // un-assign the village from the old side, which of course we also want to happen if the new
1179 // side IS valid. If the village in question hadn't been captured, this won't fire (old_side
1180 // will be a nullptr).
1181 if(old_side) {
1182 old_side->lose_village(loc);
1183 }
1184
1185 // If the new side was valid, re-assign the village.
1186 if(new_side) {
1187 new_side->get_village(loc, old_side_num, (luaW_toboolean(L, 3) ? &gamedata() : nullptr));
1188 }
1189
1190 return 0;
1191 }
1192
1193
1194 /**
1195 * Returns the map size.
1196 * - Ret 1: width.
1197 * - Ret 2: height.
1198 * - Ret 3: border size.
1199 */
intf_get_map_size(lua_State * L)1200 int game_lua_kernel::intf_get_map_size(lua_State *L)
1201 {
1202 const gamemap &map = board().map();
1203 lua_pushinteger(L, map.w());
1204 lua_pushinteger(L, map.h());
1205 lua_pushinteger(L, map.border_size());
1206 return 3;
1207 }
1208
1209 /**
1210 * Returns the currently overed tile.
1211 * - Ret 1: x.
1212 * - Ret 2: y.
1213 */
intf_get_mouseover_tile(lua_State * L)1214 int game_lua_kernel::intf_get_mouseover_tile(lua_State *L)
1215 {
1216 if (!game_display_) {
1217 return 0;
1218 }
1219
1220 const map_location &loc = game_display_->mouseover_hex();
1221 if (!board().map().on_board(loc)) return 0;
1222 lua_pushinteger(L, loc.wml_x());
1223 lua_pushinteger(L, loc.wml_y());
1224 return 2;
1225 }
1226
1227 /**
1228 * Returns the currently selected tile.
1229 * - Ret 1: x.
1230 * - Ret 2: y.
1231 */
intf_get_selected_tile(lua_State * L)1232 int game_lua_kernel::intf_get_selected_tile(lua_State *L)
1233 {
1234 if (!game_display_) {
1235 return 0;
1236 }
1237
1238 const map_location &loc = game_display_->selected_hex();
1239 if (!board().map().on_board(loc)) return 0;
1240 lua_pushinteger(L, loc.wml_x());
1241 lua_pushinteger(L, loc.wml_y());
1242 return 2;
1243 }
1244
1245 /**
1246 * Returns the starting position of a side.
1247 * Arg 1: side number
1248 * Ret 1: table with unnamed indices holding wml coordinates x and y
1249 */
intf_get_starting_location(lua_State * L)1250 int game_lua_kernel::intf_get_starting_location(lua_State* L)
1251 {
1252 const int side = luaL_checkinteger(L, 1);
1253 if(side < 1 || static_cast<int>(teams().size()) < side)
1254 return luaL_argerror(L, 1, "out of bounds");
1255 const map_location& starting_pos = board().map().starting_position(side);
1256 if(!board().map().on_board(starting_pos)) return 0;
1257
1258 luaW_pushlocation(L, starting_pos);
1259 return 1;
1260 }
1261
1262 /**
1263 * Gets a table for an era tag.
1264 * - Arg 1: userdata (ignored).
1265 * - Arg 2: string containing id of the desired era
1266 * - Ret 1: config for the era
1267 */
intf_get_era(lua_State * L)1268 static int intf_get_era(lua_State *L)
1269 {
1270 char const *m = luaL_checkstring(L, 1);
1271 luaW_pushconfig(L, game_config_manager::get()->game_config().find_child("era","id",m));
1272 return 1;
1273 }
1274
1275 /**
1276 * Gets some game_config data (__index metamethod).
1277 * - Arg 1: userdata (ignored).
1278 * - Arg 2: string containing the name of the property.
1279 * - Ret 1: something containing the attribute.
1280 */
impl_game_config_get(lua_State * L)1281 int game_lua_kernel::impl_game_config_get(lua_State *L)
1282 {
1283 LOG_LUA << "impl_game_config_get\n";
1284 char const *m = luaL_checkstring(L, 2);
1285
1286 // Find the corresponding attribute.
1287 return_int_attrib("last_turn", tod_man().number_of_turns());
1288 return_string_attrib("next_scenario", gamedata().next_scenario());
1289 return_string_attrib("theme", gamedata().get_theme());
1290 return_string_attrib("scenario_id", gamedata().get_id());
1291 return_vector_string_attrib("defeat_music", gamedata().get_defeat_music());
1292 return_vector_string_attrib("victory_music", gamedata().get_victory_music());
1293
1294 const mp_game_settings& mp_settings = play_controller_.get_mp_settings();
1295 const game_classification & classification = play_controller_.get_classification();
1296
1297 return_string_attrib("campaign_type", classification.campaign_type.to_string());
1298 if(classification.campaign_type==game_classification::CAMPAIGN_TYPE::MULTIPLAYER) {
1299 return_cfgref_attrib("mp_settings", mp_settings.to_config());
1300 return_cfgref_attrib("era", game_config_manager::get()->game_config().find_child("era","id",mp_settings.mp_era));
1301 //^ finds the era with name matching mp_era, and creates a lua reference from the config of that era.
1302
1303 //This code for SigurdFD, not the cleanest implementation but seems to work just fine.
1304 config::const_child_itors its = game_config_manager::get()->game_config().child_range("era");
1305 std::string eras_list(its.front()["id"]);
1306 its.pop_front();
1307 for(const auto& cfg : its) {
1308 eras_list = eras_list + "," + cfg["id"];
1309 }
1310 return_string_attrib("eras", eras_list);
1311 }
1312 return lua_kernel_base::impl_game_config_get(L);
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 */
impl_game_config_set(lua_State * L)1321 int game_lua_kernel::impl_game_config_set(lua_State *L)
1322 {
1323 LOG_LUA << "impl_game_config_set\n";
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("last_turn", tod_man().set_number_of_turns_by_wml(value));
1335 modify_string_attrib("next_scenario", gamedata().set_next_scenario(value));
1336 modify_string_attrib("theme",
1337 gamedata().set_theme(value);
1338 const config& game_config = game_config_manager::get()->game_config();
1339 game_display_->set_theme(play_controller_.get_theme(game_config, value));
1340 );
1341 modify_vector_string_attrib("defeat_music", gamedata().set_defeat_music(std::move(value)));
1342 modify_vector_string_attrib("victory_music", gamedata().set_victory_music(std::move(value)));
1343 return lua_kernel_base::impl_game_config_set(L);
1344 }
1345
1346 /**
1347 converts synced_context::get_synced_state() to a string.
1348 */
synced_state()1349 std::string game_lua_kernel::synced_state()
1350 {
1351 //maybe return "initial" for game_data::INITIAL?
1352 if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::INITIAL)
1353 {
1354 return "preload";
1355 }
1356 switch(synced_context::get_synced_state())
1357 {
1358 case synced_context::LOCAL_CHOICE:
1359 return "local_choice";
1360 case synced_context::SYNCED:
1361 return "synced";
1362 case synced_context::UNSYNCED:
1363 return "unsynced";
1364 default:
1365 throw game::game_error("Found corrupt synced_context::synced_state");
1366 }
1367 }
1368
1369
1370 /**
1371 * Gets some data about current point of game (__index metamethod).
1372 * - Arg 1: userdata (ignored).
1373 * - Arg 2: string containing the name of the property.
1374 * - Ret 1: something containing the attribute.
1375 */
impl_current_get(lua_State * L)1376 int game_lua_kernel::impl_current_get(lua_State *L)
1377 {
1378 char const *m = luaL_checkstring(L, 2);
1379
1380 // Find the corresponding attribute.
1381 return_int_attrib("side", play_controller_.current_side());
1382 return_int_attrib("turn", play_controller_.turn());
1383 return_string_attrib("synced_state", synced_state());
1384 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);
1385
1386 if (strcmp(m, "event_context") == 0)
1387 {
1388 const game_events::queued_event &ev = get_event_info();
1389 config cfg;
1390 cfg["name"] = ev.name;
1391 cfg["id"] = ev.id;
1392 if (const config &weapon = ev.data.child("first")) {
1393 cfg.add_child("weapon", weapon);
1394 }
1395 if (const config &weapon = ev.data.child("second")) {
1396 cfg.add_child("second_weapon", weapon);
1397 }
1398
1399 const config::attribute_value di = ev.data["damage_inflicted"];
1400 if(!di.empty()) {
1401 cfg["damage_inflicted"] = di;
1402 }
1403
1404 if (ev.loc1.valid()) {
1405 cfg["x1"] = ev.loc1.filter_loc().wml_x();
1406 cfg["y1"] = ev.loc1.filter_loc().wml_y();
1407 // 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
1408 cfg["unit_x"] = ev.loc1.wml_x();
1409 cfg["unit_y"] = ev.loc1.wml_y();
1410 }
1411 if (ev.loc2.valid()) {
1412 cfg["x2"] = ev.loc2.filter_loc().wml_x();
1413 cfg["y2"] = ev.loc2.filter_loc().wml_y();
1414 }
1415 luaW_pushconfig(L, cfg);
1416 return 1;
1417 }
1418
1419 return 0;
1420 }
1421
1422 /**
1423 * Displays a message in the chat window and in the logs.
1424 * - Arg 1: optional message header.
1425 * - Arg 2 (or 1): message.
1426 */
intf_message(lua_State * L)1427 int game_lua_kernel::intf_message(lua_State *L)
1428 {
1429 t_string m = luaW_checktstring(L, 1);
1430 t_string h = m;
1431 if (lua_isnone(L, 2)) {
1432 h = "Lua";
1433 } else {
1434 m = luaW_checktstring(L, 2);
1435 }
1436 lua_chat(h, m);
1437 LOG_LUA << "Script says: \"" << m << "\"\n";
1438 return 0;
1439 }
1440
intf_open_help(lua_State * L)1441 int game_lua_kernel::intf_open_help(lua_State *L)
1442 {
1443 if (game_display_) {
1444 help::show_help(luaL_checkstring(L, 1));
1445 }
1446 return 0;
1447 }
1448
intf_zoom(lua_State * L)1449 int game_lua_kernel::intf_zoom(lua_State* L)
1450 {
1451 if(!game_display_) {
1452 return 0;
1453 }
1454 double factor = luaL_checknumber(L, 1);
1455 bool relative = luaW_toboolean(L, 2);
1456 if(relative) {
1457 factor *= game_display_->get_zoom_factor();
1458 }
1459 // Passing true explicitly to avoid casting to int.
1460 // Without doing one of the two, the call is ambiguous.
1461 game_display_->set_zoom(factor * game_config::tile_size, true);
1462 lua_pushnumber(L, game_display_->get_zoom_factor());
1463 return 1;
1464 }
1465
1466 /**
1467 * Removes all messages from the chat window.
1468 */
intf_clear_messages(lua_State *)1469 int game_lua_kernel::intf_clear_messages(lua_State*)
1470 {
1471 if (game_display_) {
1472 game_display_->get_chat_manager().clear_chat_messages();
1473 }
1474 return 0;
1475 }
1476
impl_end_level_data_get(lua_State * L)1477 static int impl_end_level_data_get(lua_State* L)
1478 {
1479 const end_level_data& data = *static_cast<end_level_data*>(lua_touserdata(L, 1));
1480 const char* m = luaL_checkstring(L, 2);
1481
1482 return_bool_attrib("linger_mode", data.transient.linger_mode);
1483 return_bool_attrib("reveal_map", data.transient.reveal_map);
1484 return_bool_attrib("carryover_report", data.transient.carryover_report);
1485 return_bool_attrib("prescenario_save", data.prescenario_save);
1486 return_bool_attrib("replay_save", data.replay_save);
1487 return_bool_attrib("proceed_to_next_level", data.proceed_to_next_level);
1488 return_bool_attrib("is_victory", data.is_victory);
1489 return_bool_attrib("is_loss", !data.is_victory);
1490 return_cstring_attrib("result", data.is_victory ? "victory" : "loss"); // to match wesnoth.end_level()
1491 return_cfg_attrib("__cfg", data.to_config_full());
1492
1493 return 0;
1494 }
1495
1496 namespace {
1497 struct end_level_committer {
end_level_committer__anon384f78800211::end_level_committer1498 end_level_committer(end_level_data& data, play_controller& pc) : data_(data), pc_(pc) {}
~end_level_committer__anon384f78800211::end_level_committer1499 ~end_level_committer() {
1500 pc_.set_end_level_data(data_);
1501 }
1502 private:
1503 end_level_data& data_;
1504 play_controller& pc_;
1505 };
1506 }
1507
impl_end_level_data_set(lua_State * L)1508 int game_lua_kernel::impl_end_level_data_set(lua_State* L)
1509 {
1510 end_level_data& data = *static_cast<end_level_data*>(lua_touserdata(L, 1));
1511 const char* m = luaL_checkstring(L, 2);
1512 end_level_committer commit(data, play_controller_);
1513
1514 modify_bool_attrib("linger_mode", data.transient.linger_mode = value);
1515 modify_bool_attrib("reveal_map", data.transient.reveal_map = value);
1516 modify_bool_attrib("carryover_report", data.transient.carryover_report = value);
1517 modify_bool_attrib("prescenario_save", data.prescenario_save = value);
1518 modify_bool_attrib("replay_save", data.replay_save = value);
1519
1520 return 0;
1521 }
1522
impl_end_level_data_collect(lua_State * L)1523 static int impl_end_level_data_collect(lua_State* L)
1524 {
1525 end_level_data* data = static_cast<end_level_data*>(lua_touserdata(L, 1));
1526 UNUSED(data); // Suppress an erroneous MSVC warning (a destructor call doesn't count as a reference)
1527 data->~end_level_data();
1528 return 0;
1529 }
1530
intf_get_end_level_data(lua_State * L)1531 int game_lua_kernel::intf_get_end_level_data(lua_State* L)
1532 {
1533 if (!play_controller_.is_regular_game_end()) {
1534 return 0;
1535 }
1536 auto data = play_controller_.get_end_level_data_const();
1537 new(L) end_level_data(data);
1538 if(luaL_newmetatable(L, "end level data")) {
1539 static luaL_Reg const callbacks[] {
1540 { "__index", &impl_end_level_data_get},
1541 { "__newindex", &dispatch<&game_lua_kernel::impl_end_level_data_set>},
1542 { "__gc", &impl_end_level_data_collect},
1543 { nullptr, nullptr }
1544 };
1545 luaL_setfuncs(L, callbacks, 0);
1546 }
1547 lua_setmetatable(L, -2);
1548 return 1;
1549 }
1550
intf_end_level(lua_State * L)1551 int game_lua_kernel::intf_end_level(lua_State *L)
1552 {
1553 vconfig cfg(luaW_checkvconfig(L, 1));
1554 end_level_data data;
1555
1556 data.proceed_to_next_level = cfg["proceed_to_next_level"].to_bool(true);
1557 data.transient.carryover_report = cfg["carryover_report"].to_bool(true);
1558 data.prescenario_save = cfg["save"].to_bool(true);
1559 data.replay_save = cfg["replay_save"].to_bool(true);
1560 data.transient.linger_mode = cfg["linger_mode"].to_bool(true) && !teams().empty();
1561 data.transient.reveal_map = cfg["reveal_map"].to_bool(true);
1562 data.is_victory = cfg["result"] == "victory";
1563 play_controller_.set_end_level_data(data);
1564 return 0;
1565 }
1566
intf_end_turn(lua_State * L)1567 int game_lua_kernel::intf_end_turn(lua_State* L)
1568 {
1569 //note that next_player_number = 1, next_player_number = nteams+1 both set the next team to be the first team
1570 //but the later will make the turn counter change aswell fire turn end events accoringly etc.
1571 if (!lua_isnoneornil(L, 1)) {
1572 int npn = luaL_checknumber(L, 1);
1573 if (npn <= 0 /*TODO: || npn > 2*nteams*/) {
1574 return luaL_argerror(L, 1, "side number out of range");
1575 }
1576 resources::controller->gamestate().next_player_number_ = npn;
1577 }
1578 play_controller_.force_end_turn();
1579 return 0;
1580 }
1581
1582 /**
1583 * Evaluates a boolean WML conditional.
1584 * - Arg 1: WML table.
1585 * - Ret 1: boolean.
1586 */
intf_eval_conditional(lua_State * L)1587 static int intf_eval_conditional(lua_State *L)
1588 {
1589 vconfig cond = luaW_checkvconfig(L, 1);
1590 bool b = game_events::conditional_passed(cond);
1591 lua_pushboolean(L, b);
1592 return 1;
1593 }
1594
1595
1596 /**
1597 * Finds a path between two locations.
1598 * - Arg 1: source location. (Or Arg 1: unit.)
1599 * - Arg 2: destination.
1600 * - Arg 3: optional cost function or
1601 * table (optional fields: ignore_units, ignore_teleport, max_cost, viewing_side).
1602 * - Ret 1: array of pairs containing path steps.
1603 * - Ret 2: path cost.
1604 */
intf_find_path(lua_State * L)1605 int game_lua_kernel::intf_find_path(lua_State *L)
1606 {
1607 int arg = 1;
1608 map_location src, dst;
1609 const unit* u = nullptr;
1610
1611 if (lua_isuserdata(L, arg))
1612 {
1613 u = &luaW_checkunit(L, arg);
1614 src = u->get_location();
1615 ++arg;
1616 }
1617 else
1618 {
1619 src = luaW_checklocation(L, arg);
1620 unit_map::const_unit_iterator ui = units().find(src);
1621 if (ui.valid()) {
1622 u = ui.get_shared_ptr().get();
1623 }
1624 ++arg;
1625 }
1626
1627 dst = luaW_checklocation(L, arg);
1628 ++arg;
1629
1630 if (!board().map().on_board(src))
1631 return luaL_argerror(L, 1, "invalid location");
1632 if (!board().map().on_board(dst))
1633 return luaL_argerror(L, arg - 2, "invalid location");
1634
1635 const gamemap &map = board().map();
1636 int viewing_side = 0;
1637 bool ignore_units = false, see_all = false, ignore_teleport = false;
1638 double stop_at = 10000;
1639 std::unique_ptr<pathfind::cost_calculator> calc;
1640
1641 if (lua_istable(L, arg))
1642 {
1643 lua_pushstring(L, "ignore_units");
1644 lua_rawget(L, arg);
1645 ignore_units = luaW_toboolean(L, -1);
1646 lua_pop(L, 1);
1647
1648 lua_pushstring(L, "ignore_teleport");
1649 lua_rawget(L, arg);
1650 ignore_teleport = luaW_toboolean(L, -1);
1651 lua_pop(L, 1);
1652
1653 lua_pushstring(L, "max_cost");
1654 lua_rawget(L, arg);
1655 if (!lua_isnil(L, -1))
1656 stop_at = luaL_checknumber(L, -1);
1657 lua_pop(L, 1);
1658
1659 lua_pushstring(L, "viewing_side");
1660 lua_rawget(L, arg);
1661 if (!lua_isnil(L, -1)) {
1662 int i = luaL_checkinteger(L, -1);
1663 if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
1664 else see_all = true;
1665 }
1666 lua_pop(L, 1);
1667 }
1668 else if (lua_isfunction(L, arg))
1669 {
1670 calc.reset(new lua_pathfind_cost_calculator(L, arg));
1671 }
1672
1673 pathfind::teleport_map teleport_locations;
1674
1675 if (!calc) {
1676 if (!u) return luaL_argerror(L, 1, "unit not found");
1677
1678 const team& viewing_team = viewing_side
1679 ? board().get_team(viewing_side)
1680 : board().get_team(u->side());
1681
1682 if (!ignore_teleport) {
1683 teleport_locations = pathfind::get_teleport_locations(
1684 *u, viewing_team, see_all, ignore_units);
1685 }
1686 calc.reset(new pathfind::shortest_path_calculator(*u, viewing_team,
1687 teams(), map, ignore_units, false, see_all));
1688 }
1689
1690 pathfind::plain_route res = pathfind::a_star_search(src, dst, stop_at, *calc, map.w(), map.h(),
1691 &teleport_locations);
1692
1693 int nb = res.steps.size();
1694 lua_createtable(L, nb, 0);
1695 for (int i = 0; i < nb; ++i)
1696 {
1697 lua_createtable(L, 2, 0);
1698 lua_pushinteger(L, res.steps[i].wml_x());
1699 lua_rawseti(L, -2, 1);
1700 lua_pushinteger(L, res.steps[i].wml_y());
1701 lua_rawseti(L, -2, 2);
1702 lua_rawseti(L, -2, i + 1);
1703 }
1704 lua_pushinteger(L, res.move_cost);
1705
1706 return 2;
1707 }
1708
1709 /**
1710 * Finds all the locations reachable by a unit.
1711 * - Arg 1: source location OR unit.
1712 * - Arg 2: optional table (optional fields: ignore_units, ignore_teleport, additional_turns, viewing_side).
1713 * - Ret 1: array of triples (coordinates + remaining movement).
1714 */
intf_find_reach(lua_State * L)1715 int game_lua_kernel::intf_find_reach(lua_State *L)
1716 {
1717 int arg = 1;
1718 const unit* u = nullptr;
1719
1720 if (lua_isuserdata(L, arg))
1721 {
1722 u = &luaW_checkunit(L, arg);
1723 ++arg;
1724 }
1725 else
1726 {
1727 map_location src = luaW_checklocation(L, arg);
1728 unit_map::const_unit_iterator ui = units().find(src);
1729 if (!ui.valid())
1730 return luaL_argerror(L, 1, "unit not found");
1731 u = ui.get_shared_ptr().get();
1732 ++arg;
1733 }
1734
1735 int viewing_side = 0;
1736 bool ignore_units = false, see_all = false, ignore_teleport = false;
1737 int additional_turns = 0;
1738
1739 if (lua_istable(L, arg))
1740 {
1741 lua_pushstring(L, "ignore_units");
1742 lua_rawget(L, arg);
1743 ignore_units = luaW_toboolean(L, -1);
1744 lua_pop(L, 1);
1745
1746 lua_pushstring(L, "ignore_teleport");
1747 lua_rawget(L, arg);
1748 ignore_teleport = luaW_toboolean(L, -1);
1749 lua_pop(L, 1);
1750
1751 lua_pushstring(L, "additional_turns");
1752 lua_rawget(L, arg);
1753 additional_turns = lua_tointeger(L, -1);
1754 lua_pop(L, 1);
1755
1756 lua_pushstring(L, "viewing_side");
1757 lua_rawget(L, arg);
1758 if (!lua_isnil(L, -1)) {
1759 int i = luaL_checkinteger(L, -1);
1760 if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
1761 else see_all = true;
1762 }
1763 lua_pop(L, 1);
1764 }
1765
1766 const team& viewing_team = viewing_side
1767 ? board().get_team(viewing_side)
1768 : board().get_team(u->side());
1769
1770 pathfind::paths res(*u, ignore_units, !ignore_teleport,
1771 viewing_team, additional_turns, see_all, ignore_units);
1772
1773 int nb = res.destinations.size();
1774 lua_createtable(L, nb, 0);
1775 for (int i = 0; i < nb; ++i)
1776 {
1777 pathfind::paths::step &s = res.destinations[i];
1778 lua_createtable(L, 2, 0);
1779 lua_pushinteger(L, s.curr.wml_x());
1780 lua_rawseti(L, -2, 1);
1781 lua_pushinteger(L, s.curr.wml_y());
1782 lua_rawseti(L, -2, 2);
1783 lua_pushinteger(L, s.move_left);
1784 lua_rawseti(L, -2, 3);
1785 lua_rawseti(L, -2, i + 1);
1786 }
1787
1788 return 1;
1789 }
1790
intf_find_cost_map_helper(const unit * ptr)1791 static bool intf_find_cost_map_helper(const unit * ptr) {
1792 return ptr->get_location().valid();
1793 }
1794
1795 template<typename T> // This is only a template so I can avoid typing out the long typename. >_>
load_fake_units(lua_State * L,int arg,T & fake_units)1796 static int load_fake_units(lua_State* L, int arg, T& fake_units)
1797 {
1798 for (int i = 1, i_end = lua_rawlen(L, arg); i <= i_end; ++i)
1799 {
1800 map_location src;
1801 lua_rawgeti(L, arg, i);
1802 int entry = lua_gettop(L);
1803 if (!lua_istable(L, entry)) {
1804 goto error;
1805 }
1806
1807 if (!luaW_tolocation(L, entry, src)) {
1808 goto error;
1809 }
1810
1811 lua_rawgeti(L, entry, 3);
1812 if (!lua_isnumber(L, -1)) {
1813 lua_getfield(L, entry, "side");
1814 if (!lua_isnumber(L, -1)) {
1815 goto error;
1816 }
1817 }
1818 int side = lua_tointeger(L, -1);
1819
1820 lua_rawgeti(L, entry, 4);
1821 if (!lua_isstring(L, -1)) {
1822 lua_getfield(L, entry, "type");
1823 if (!lua_isstring(L, -1)) {
1824 goto error;
1825 }
1826 }
1827 std::string unit_type = lua_tostring(L, -1);
1828
1829 fake_units.emplace_back(src, side, unit_type);
1830
1831 lua_settop(L, entry - 1);
1832 }
1833 return 0;
1834 error:
1835 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");
1836 }
1837
1838 /**
1839 * Is called with one or more units and builds a cost map.
1840 * - Arg 1: source location. (Or Arg 1: unit. Or Arg 1: table containing a filter)
1841 * - Arg 2: optional array of tables with 4 elements (coordinates + side + unit type string)
1842 * - Arg 3: optional table (optional fields: ignore_units, ignore_teleport, viewing_side, debug).
1843 * - Arg 4: optional table: standard location filter.
1844 * - Ret 1: array of triples (coordinates + array of tuples(summed cost + reach counter)).
1845 */
intf_find_cost_map(lua_State * L)1846 int game_lua_kernel::intf_find_cost_map(lua_State *L)
1847 {
1848 int arg = 1;
1849 unit* unit = luaW_tounit(L, arg, true);
1850 vconfig filter = vconfig::unconstructed_vconfig();
1851 luaW_tovconfig(L, arg, filter);
1852
1853 std::vector<const ::unit*> real_units;
1854 typedef std::vector<std::tuple<map_location, int, std::string>> unit_type_vector;
1855 unit_type_vector fake_units;
1856
1857
1858 if (unit) // 1. arg - unit
1859 {
1860 real_units.push_back(unit);
1861 }
1862 else if (!filter.null()) // 1. arg - filter
1863 {
1864 boost::copy(unit_filter(filter).all_matches_on_map() | boost::adaptors::filtered(&intf_find_cost_map_helper), std::back_inserter(real_units));
1865 }
1866 else // 1. arg - coordinates
1867 {
1868 map_location src = luaW_checklocation(L, arg);
1869 unit_map::const_unit_iterator ui = units().find(src);
1870 if (ui.valid())
1871 {
1872 real_units.push_back(&(*ui));
1873 }
1874 }
1875 ++arg;
1876
1877 if (lua_istable(L, arg)) // 2. arg - optional types
1878 {
1879 load_fake_units(L, arg, fake_units);
1880 ++arg;
1881 }
1882
1883 if(real_units.empty() && fake_units.empty())
1884 {
1885 return luaL_argerror(L, 1, "unit(s) not found");
1886 }
1887
1888 int viewing_side = 0;
1889 bool ignore_units = true, see_all = true, ignore_teleport = false, debug = false, use_max_moves = false;
1890
1891 if (lua_istable(L, arg)) // 4. arg - options
1892 {
1893 lua_pushstring(L, "ignore_units");
1894 lua_rawget(L, arg);
1895 if (!lua_isnil(L, -1))
1896 {
1897 ignore_units = luaW_toboolean(L, -1);
1898 }
1899 lua_pop(L, 1);
1900
1901 lua_pushstring(L, "ignore_teleport");
1902 lua_rawget(L, arg);
1903 if (!lua_isnil(L, -1))
1904 {
1905 ignore_teleport = luaW_toboolean(L, -1);
1906 }
1907 lua_pop(L, 1);
1908
1909 lua_pushstring(L, "viewing_side");
1910 lua_rawget(L, arg);
1911 if (!lua_isnil(L, -1))
1912 {
1913 int i = luaL_checkinteger(L, -1);
1914 if (i >= 1 && i <= static_cast<int>(teams().size()))
1915 {
1916 viewing_side = i;
1917 see_all = false;
1918 }
1919 }
1920
1921 lua_pushstring(L, "debug");
1922 lua_rawget(L, arg);
1923 if (!lua_isnil(L, -1))
1924 {
1925 debug = luaW_toboolean(L, -1);
1926 }
1927 lua_pop(L, 1);
1928
1929 lua_pushstring(L, "use_max_moves");
1930 lua_rawget(L, arg);
1931 if (!lua_isnil(L, -1))
1932 {
1933 use_max_moves = luaW_toboolean(L, -1);
1934 }
1935 lua_pop(L, 1);
1936 ++arg;
1937 }
1938
1939 // 5. arg - location filter
1940 filter = vconfig::unconstructed_vconfig();
1941 std::set<map_location> location_set;
1942 luaW_tovconfig(L, arg, filter);
1943 if (filter.null())
1944 {
1945 filter = vconfig(config(), true);
1946 }
1947 filter_context & fc = game_state_;
1948 const terrain_filter t_filter(filter, &fc);
1949 t_filter.get_locations(location_set, true);
1950 ++arg;
1951
1952 // build cost_map
1953 const team& viewing_team = viewing_side
1954 ? board().get_team(viewing_side)
1955 : board().teams()[0];
1956
1957 pathfind::full_cost_map cost_map(
1958 ignore_units, !ignore_teleport, viewing_team, see_all, ignore_units);
1959
1960 for (const ::unit* const u : real_units)
1961 {
1962 cost_map.add_unit(*u, use_max_moves);
1963 }
1964 for (const unit_type_vector::value_type& fu : fake_units)
1965 {
1966 const unit_type* ut = unit_types.find(std::get<2>(fu));
1967 cost_map.add_unit(std::get<0>(fu), ut, std::get<1>(fu));
1968 }
1969
1970 if (debug)
1971 {
1972 if (game_display_) {
1973 game_display_->labels().clear_all();
1974 for (const map_location& loc : location_set)
1975 {
1976 std::stringstream s;
1977 s << cost_map.get_pair_at(loc.x, loc.y).first;
1978 s << " / ";
1979 s << cost_map.get_pair_at(loc.x, loc.y).second;
1980 game_display_->labels().set_label(loc, s.str());
1981 }
1982 }
1983 }
1984
1985 // create return value
1986 lua_createtable(L, location_set.size(), 0);
1987 int counter = 1;
1988 for (const map_location& loc : location_set)
1989 {
1990 lua_createtable(L, 4, 0);
1991
1992 lua_pushinteger(L, loc.wml_x());
1993 lua_rawseti(L, -2, 1);
1994
1995 lua_pushinteger(L, loc.wml_y());
1996 lua_rawseti(L, -2, 2);
1997
1998 lua_pushinteger(L, cost_map.get_pair_at(loc.x, loc.y).first);
1999 lua_rawseti(L, -2, 3);
2000
2001 lua_pushinteger(L, cost_map.get_pair_at(loc.x, loc.y).second);
2002 lua_rawseti(L, -2, 4);
2003
2004 lua_rawseti(L, -2, counter);
2005 ++counter;
2006 }
2007 return 1;
2008 }
2009
intf_print(lua_State * L)2010 int game_lua_kernel::intf_print(lua_State *L) {
2011 vconfig cfg(luaW_checkvconfig(L, 1));
2012
2013 // Remove any old message.
2014 static int floating_label = 0;
2015 if (floating_label)
2016 font::remove_floating_label(floating_label);
2017
2018 // Display a message on-screen
2019 std::string text = cfg["text"];
2020 if(text.empty() || !game_display_)
2021 return 0;
2022
2023 int size = cfg["size"].to_int(font::SIZE_SMALL);
2024 int lifetime = cfg["duration"].to_int(50);
2025
2026 color_t color = font::LABEL_COLOR;
2027
2028 if(!cfg["color"].empty()) {
2029 color = color_t::from_rgb_string(cfg["color"]);
2030 } else if(cfg.has_attribute("red") || cfg.has_attribute("green") || cfg.has_attribute("blue")) {
2031 color = color_t(cfg["red"], cfg["green"], cfg["blue"]);
2032 }
2033
2034 const SDL_Rect& rect = game_display_->map_outside_area();
2035
2036 font::floating_label flabel(text);
2037 flabel.set_font_size(size);
2038 flabel.set_color(color);
2039 flabel.set_position(rect.x + rect.w/2, rect.y + rect.h/2);
2040 flabel.set_lifetime(lifetime);
2041 flabel.set_clip_rect(rect);
2042
2043 floating_label = font::add_floating_label(flabel);
2044
2045 return 0;
2046 }
2047
put_unit_helper(const map_location & loc)2048 void game_lua_kernel::put_unit_helper(const map_location& loc)
2049 {
2050 if(game_display_) {
2051 game_display_->invalidate(loc);
2052 }
2053
2054 units().erase(loc);
2055 resources::whiteboard->on_kill_unit();
2056 }
2057
2058 /**
2059 * Places a unit on the map.
2060 * - Arg 1: (optional) location.
2061 * - Arg 2: Unit (WML table or proxy), or nothing/nil to delete.
2062 * OR
2063 * - Arg 1: Unit (WML table or proxy)
2064 * - Arg 2: (optional) location
2065 * - Arg 3: (optional) boolean
2066 */
intf_put_unit(lua_State * L)2067 int game_lua_kernel::intf_put_unit(lua_State *L)
2068 {
2069 if(map_locked_) {
2070 return luaL_error(L, "Attempted to move a unit while the map is locked");
2071 }
2072 int unit_arg = 1;
2073
2074 map_location loc;
2075 if (lua_isnumber(L, 1)) {
2076 // Since this form is deprecated, I didn't bother updating it to luaW_tolocation.
2077 unit_arg = 3;
2078 loc.set_wml_x(lua_tointeger(L, 1));
2079 loc.set_wml_y(luaL_checkinteger(L, 2));
2080 if (!map().on_board(loc)) {
2081 return luaL_argerror(L, 1, "invalid location");
2082 }
2083 } else if (luaW_tolocation(L, 2, loc)) {
2084 if (!map().on_board(loc)) {
2085 return luaL_argerror(L, 2, "invalid location");
2086 }
2087 }
2088
2089 if((luaW_isunit(L, unit_arg))) {
2090 lua_unit& u = *luaW_checkunit_ref(L, unit_arg);
2091 if(u.on_map() && u->get_location() == loc) {
2092 return 0;
2093 }
2094 if (!loc.valid()) {
2095 loc = u->get_location();
2096 if (!map().on_board(loc))
2097 return luaL_argerror(L, 1, "invalid location");
2098 } else if (unit_arg != 1) {
2099 deprecated_message("wesnoth.put_unit(x, y, unit)", DEP_LEVEL::FOR_REMOVAL, {1, 15, 0}, "Use wesnoth.put_unit(unit, x, y) or unit:to_map(x, y) instead.");
2100 }
2101 put_unit_helper(loc);
2102 u.put_map(loc);
2103 u.get_shared()->anim_comp().set_standing();
2104 } else if(!lua_isnoneornil(L, unit_arg)) {
2105 const vconfig* vcfg = nullptr;
2106 config cfg = luaW_checkconfig(L, unit_arg, vcfg);
2107 if (unit_arg == 1 && !map().on_board(loc)) {
2108 loc.set_wml_x(cfg["x"]);
2109 loc.set_wml_y(cfg["y"]);
2110 if (!map().on_board(loc))
2111 return luaL_argerror(L, 2, "invalid location");
2112 } else if (unit_arg != 1) {
2113 deprecated_message("wesnoth.put_unit(x, y, unit)", DEP_LEVEL::FOR_REMOVAL, {1, 15, 0}, "Use wesnoth.put_unit(unit, x, y) or unit:to_map(x, y) instead.");
2114 }
2115 unit_ptr u = unit::create(cfg, true, vcfg);
2116 put_unit_helper(loc);
2117 u->set_location(loc);
2118 units().insert(u);
2119 } else {
2120 deprecated_message("wesnoth.put_unit(x, y)", DEP_LEVEL::FOR_REMOVAL, {1, 15, 0}, "Use wesnoth.erase_unit(x, y) or unit:erase() instead.");
2121 put_unit_helper(loc);
2122 return 0; // Don't fire event when unit is only erase
2123 }
2124
2125 // Fire event if using the deprecated version or if the final argument is not false
2126 // If the final boolean argument is omitted, the actual final argument (the unit or location) will always yield true.
2127 if(unit_arg != 1 || luaW_toboolean(L, -1)) {
2128 play_controller_.pump().fire("unit_placed", loc);
2129 }
2130 return 0;
2131 }
2132
2133 /**
2134 * Erases a unit from the map
2135 * - Arg 1: Unit to erase OR Location to erase unit
2136 */
intf_erase_unit(lua_State * L)2137 int game_lua_kernel::intf_erase_unit(lua_State *L)
2138 {
2139 if(map_locked_) {
2140 return luaL_error(L, "Attempted to remove a unit while the map is locked");
2141 }
2142 map_location loc;
2143
2144 if(luaW_isunit(L, 1)) {
2145 lua_unit& u = *luaW_checkunit_ref(L, 1);
2146 if (u.on_map()) {
2147 loc = u->get_location();
2148 if (!map().on_board(loc)) {
2149 return luaL_argerror(L, 1, "invalid location");
2150 }
2151 } else if (int side = u.on_recall_list()) {
2152 team &t = board().get_team(side);
2153 // Should it use underlying ID instead?
2154 t.recall_list().erase_if_matches_id(u->id());
2155 } else {
2156 return luaL_argerror(L, 1, "can't erase private units");
2157 }
2158 } else if (luaW_tolocation(L, 1, loc)) {
2159 if (!map().on_board(loc)) {
2160 return luaL_argerror(L, 1, "invalid location");
2161 }
2162 } else {
2163 return luaL_argerror(L, 1, "expected unit or location");
2164 }
2165
2166 units().erase(loc);
2167 resources::whiteboard->on_kill_unit();
2168 return 0;
2169 }
2170
2171 /**
2172 * Puts a unit on a recall list.
2173 * - Arg 1: WML table or unit.
2174 * - Arg 2: (optional) side.
2175 */
intf_put_recall_unit(lua_State * L)2176 int game_lua_kernel::intf_put_recall_unit(lua_State *L)
2177 {
2178 if(map_locked_) {
2179 return luaL_error(L, "Attempted to move a unit while the map is locked");
2180 }
2181 lua_unit *lu = nullptr;
2182 unit_ptr u = unit_ptr();
2183 int side = lua_tointeger(L, 2);
2184 if (static_cast<unsigned>(side) > teams().size()) side = 0;
2185
2186 if(luaW_isunit(L, 1)) {
2187 lu = luaW_checkunit_ref(L, 1);
2188 u = lu->get_shared();
2189 if(lu->on_recall_list() && lu->on_recall_list() == side) {
2190 return luaL_argerror(L, 1, "unit already on recall list");
2191 }
2192 } else {
2193 const vconfig* vcfg = nullptr;
2194 config cfg = luaW_checkconfig(L, 1, vcfg);
2195 u = unit::create(cfg, true, vcfg);
2196 }
2197
2198 if (!side) {
2199 side = u->side();
2200 } else {
2201 u->set_side(side);
2202 }
2203 team &t = board().get_team(side);
2204 // Avoid duplicates in the recall list.
2205 size_t uid = u->underlying_id();
2206 t.recall_list().erase_by_underlying_id(uid);
2207 t.recall_list().add(u);
2208 if (lu) {
2209 if (lu->on_map()) {
2210 units().erase(u->get_location());
2211 resources::whiteboard->on_kill_unit();
2212 u->anim_comp().clear_haloes();
2213 }
2214 lu->lua_unit::~lua_unit();
2215 new(lu) lua_unit(side, uid);
2216 }
2217
2218 return 0;
2219 }
2220
2221 /**
2222 * Extracts a unit from the map or a recall list and gives it to Lua.
2223 * - Arg 1: unit userdata.
2224 */
intf_extract_unit(lua_State * L)2225 int game_lua_kernel::intf_extract_unit(lua_State *L)
2226 {
2227 if(map_locked_) {
2228 return luaL_error(L, "Attempted to remove a unit while the map is locked");
2229 }
2230 lua_unit* lu = luaW_checkunit_ref(L, 1);
2231 unit_ptr u = lu->get_shared();
2232
2233 if (lu->on_map()) {
2234 u = units().extract(u->get_location());
2235 assert(u);
2236 u->anim_comp().clear_haloes();
2237 } else if (int side = lu->on_recall_list()) {
2238 team &t = board().get_team(side);
2239 unit_ptr v = u->clone();
2240 t.recall_list().erase_if_matches_id(u->id());
2241 u = v;
2242 } else {
2243 return 0;
2244 }
2245
2246 lu->lua_unit::~lua_unit();
2247 new(lu) lua_unit(u);
2248 return 0;
2249 }
2250
2251 /**
2252 * Finds a vacant tile.
2253 * - Arg 1: location.
2254 * - Arg 2: optional unit for checking movement type.
2255 * - Rets 1,2: location.
2256 */
intf_find_vacant_tile(lua_State * L)2257 int game_lua_kernel::intf_find_vacant_tile(lua_State *L)
2258 {
2259 map_location loc = luaW_checklocation(L, 1);
2260
2261 unit_ptr u;
2262 if (!lua_isnoneornil(L, 2)) {
2263 if(luaW_isunit(L, 2)) {
2264 u = luaW_checkunit_ptr(L, 2, false);
2265 } else {
2266 const vconfig* vcfg = nullptr;
2267 config cfg = luaW_checkconfig(L, 2, vcfg);
2268 u = unit::create(cfg, false, vcfg);
2269 }
2270 }
2271
2272 map_location res = find_vacant_tile(loc, pathfind::VACANT_ANY, u.get());
2273
2274 if (!res.valid()) return 0;
2275 lua_pushinteger(L, res.wml_x());
2276 lua_pushinteger(L, res.wml_y());
2277 return 2;
2278 }
2279
2280 /**
2281 * Floats some text on the map.
2282 * - Arg 1: location.
2283 * - Arg 2: string.
2284 * - Arg 3: color.
2285 */
intf_float_label(lua_State * L)2286 int game_lua_kernel::intf_float_label(lua_State *L)
2287 {
2288 map_location loc = luaW_checklocation(L, 1);
2289 color_t color = font::LABEL_COLOR;
2290
2291 t_string text = luaW_checktstring(L, 2);
2292 if (!lua_isnoneornil(L, 3)) {
2293 color = color_t::from_rgb_string(luaL_checkstring(L, 3));
2294 }
2295
2296 if (game_display_) {
2297 game_display_->float_label(loc, text, color);
2298 }
2299 return 0;
2300 }
2301
2302 /**
2303 * Creates a unit from its WML description.
2304 * - Arg 1: WML table.
2305 * - Ret 1: unit userdata.
2306 */
intf_create_unit(lua_State * L)2307 static int intf_create_unit(lua_State *L)
2308 {
2309 const vconfig* vcfg = nullptr;
2310 config cfg = luaW_checkconfig(L, 1, vcfg);
2311 unit_ptr u = unit::create(cfg, true, vcfg);
2312 luaW_pushunit(L, u);
2313 return 1;
2314 }
2315
2316 /**
2317 * Copies a unit.
2318 * - Arg 1: unit userdata.
2319 * - Ret 1: unit userdata.
2320 */
intf_copy_unit(lua_State * L)2321 static int intf_copy_unit(lua_State *L)
2322 {
2323 unit& u = luaW_checkunit(L, 1);
2324 luaW_pushunit(L, u.clone());
2325 return 1;
2326 }
2327
2328 /**
2329 * Returns unit resistance against a given attack type.
2330 * - Arg 1: unit userdata.
2331 * - Arg 2: string containing the attack type.
2332 * - Arg 3: boolean indicating if attacker.
2333 * - Arg 4: optional location.
2334 * - Ret 1: integer.
2335 */
intf_unit_resistance(lua_State * L)2336 static int intf_unit_resistance(lua_State *L)
2337 {
2338 const unit& u = luaW_checkunit(L, 1);
2339 char const *m = luaL_checkstring(L, 2);
2340 bool a = luaW_toboolean(L, 3);
2341
2342 map_location loc = u.get_location();
2343 if (!lua_isnoneornil(L, 4)) {
2344 loc = luaW_checklocation(L, 4);
2345 }
2346
2347 lua_pushinteger(L, u.resistance_against(m, a, loc));
2348 return 1;
2349 }
2350
2351 /**
2352 * Returns unit movement cost on a given terrain.
2353 * - Arg 1: unit userdata.
2354 * - Arg 2: string containing the terrain type.
2355 * - Ret 1: integer.
2356 */
intf_unit_movement_cost(lua_State * L)2357 static int intf_unit_movement_cost(lua_State *L)
2358 {
2359 const unit& u = luaW_checkunit(L, 1);
2360 char const *m = luaL_checkstring(L, 2);
2361 t_translation::terrain_code t = t_translation::read_terrain_code(m);
2362 lua_pushinteger(L, u.movement_cost(t));
2363 return 1;
2364 }
2365
2366 /**
2367 * Returns unit vision cost on a given terrain.
2368 * - Arg 1: unit userdata.
2369 * - Arg 2: string containing the terrain type.
2370 * - Ret 1: integer.
2371 */
intf_unit_vision_cost(lua_State * L)2372 static int intf_unit_vision_cost(lua_State *L)
2373 {
2374 const unit& u = luaW_checkunit(L, 1);
2375 char const *m = luaL_checkstring(L, 2);
2376 t_translation::terrain_code t = t_translation::read_terrain_code(m);
2377 lua_pushinteger(L, u.vision_cost(t));
2378 return 1;
2379 }
2380
2381 /**
2382 * Returns unit jamming cost on a given terrain.
2383 * - Arg 1: unit userdata.
2384 * - Arg 2: string containing the terrain type.
2385 * - Ret 1: integer.
2386 */
intf_unit_jamming_cost(lua_State * L)2387 static int intf_unit_jamming_cost(lua_State *L)
2388 {
2389 const unit& u = luaW_checkunit(L, 1);
2390 char const *m = luaL_checkstring(L, 2);
2391 t_translation::terrain_code t = t_translation::read_terrain_code(m);
2392 lua_pushinteger(L, u.jamming_cost(t));
2393 return 1;
2394 }
2395
2396 /**
2397 * Returns unit defense on a given terrain.
2398 * - Arg 1: unit userdata.
2399 * - Arg 2: string containing the terrain type.
2400 * - Ret 1: integer.
2401 */
intf_unit_defense(lua_State * L)2402 static int intf_unit_defense(lua_State *L)
2403 {
2404 const unit& u = luaW_checkunit(L, 1);
2405 char const *m = luaL_checkstring(L, 2);
2406 t_translation::terrain_code t = t_translation::read_terrain_code(m);
2407 lua_pushinteger(L, u.defense_modifier(t));
2408 return 1;
2409 }
2410
2411 /**
2412 * Returns true if the unit has the given ability enabled.
2413 * - Arg 1: unit userdata.
2414 * - Arg 2: string.
2415 * - Ret 1: boolean.
2416 */
intf_unit_ability(lua_State * L)2417 int game_lua_kernel::intf_unit_ability(lua_State *L)
2418 {
2419 const unit& u = luaW_checkunit(L, 1);
2420 char const *m = luaL_checkstring(L, 2);
2421 lua_pushboolean(L, u.get_ability_bool(m, board()));
2422 return 1;
2423 }
2424
2425 /**
2426 * Changes a unit to the given unit type.
2427 * - Arg 1: unit userdata.
2428 * - Arg 2: string.
2429 */
intf_transform_unit(lua_State * L)2430 static int intf_transform_unit(lua_State *L)
2431 {
2432 unit& u = luaW_checkunit(L, 1);
2433 char const *m = luaL_checkstring(L, 2);
2434 const unit_type *utp = unit_types.find(m);
2435 if (!utp) return luaL_argerror(L, 2, "unknown unit type");
2436 u.advance_to(*utp);
2437
2438 return 0;
2439 }
2440
2441 /**
2442 * Puts a table at the top of the stack with some combat result.
2443 */
luaW_pushsimdata(lua_State * L,const combatant & cmb)2444 static void luaW_pushsimdata(lua_State *L, const combatant &cmb)
2445 {
2446 int n = cmb.hp_dist.size();
2447 lua_createtable(L, 0, 4);
2448 lua_pushnumber(L, cmb.poisoned);
2449 lua_setfield(L, -2, "poisoned");
2450 lua_pushnumber(L, cmb.slowed);
2451 lua_setfield(L, -2, "slowed");
2452 lua_pushnumber(L, cmb.untouched);
2453 lua_setfield(L, -2, "untouched");
2454 lua_pushnumber(L, cmb.average_hp());
2455 lua_setfield(L, -2, "average_hp");
2456 lua_createtable(L, n, 0);
2457 for (int i = 0; i < n; ++i) {
2458 lua_pushnumber(L, cmb.hp_dist[i]);
2459 lua_rawseti(L, -2, i);
2460 }
2461 lua_setfield(L, -2, "hp_chance");
2462 }
2463
2464 /**
2465 * Puts a table at the top of the stack with information about the combatants' weapons.
2466 */
luaW_pushsimweapon(lua_State * L,const battle_context_unit_stats & bcustats)2467 static void luaW_pushsimweapon(lua_State *L, const battle_context_unit_stats &bcustats)
2468 {
2469
2470 lua_createtable(L, 0, 16);
2471
2472 lua_pushnumber(L, bcustats.num_blows);
2473 lua_setfield(L, -2, "num_blows");
2474 lua_pushnumber(L, bcustats.damage);
2475 lua_setfield(L, -2, "damage");
2476 lua_pushnumber(L, bcustats.chance_to_hit);
2477 lua_setfield(L, -2, "chance_to_hit");
2478 lua_pushboolean(L, bcustats.poisons);
2479 lua_setfield(L, -2, "poisons");
2480 lua_pushboolean(L, bcustats.slows);
2481 lua_setfield(L, -2, "slows");
2482 lua_pushboolean(L, bcustats.petrifies);
2483 lua_setfield(L, -2, "petrifies");
2484 lua_pushboolean(L, bcustats.plagues);
2485 lua_setfield(L, -2, "plagues");
2486 lua_pushstring(L, bcustats.plague_type.c_str());
2487 lua_setfield(L, -2, "plague_type");
2488 lua_pushboolean(L, bcustats.backstab_pos);
2489 lua_setfield(L, -2, "backstabs");
2490 lua_pushnumber(L, bcustats.rounds);
2491 lua_setfield(L, -2, "rounds");
2492 lua_pushboolean(L, bcustats.firststrike);
2493 lua_setfield(L, -2, "firststrike");
2494 lua_pushboolean(L, bcustats.drains);
2495 lua_setfield(L, -2, "drains");
2496 lua_pushnumber(L, bcustats.drain_constant);
2497 lua_setfield(L, -2, "drain_constant");
2498 lua_pushnumber(L, bcustats.drain_percent);
2499 lua_setfield(L, -2, "drain_percent");
2500
2501
2502 //if we called simulate_combat without giving an explicit weapon this can be useful.
2503 lua_pushnumber(L, bcustats.attack_num);
2504 lua_setfield(L, -2, "attack_num"); // DEPRECATED
2505 lua_pushnumber(L, bcustats.attack_num + 1);
2506 lua_setfield(L, -2, "number");
2507 //this is nullptr when there is no counter weapon
2508 if(bcustats.weapon != nullptr)
2509 {
2510 lua_pushstring(L, bcustats.weapon->id().c_str());
2511 lua_setfield(L, -2, "name");
2512 luaW_pushweapon(L, bcustats.weapon);
2513 lua_setfield(L, -2, "weapon");
2514 }
2515
2516 }
2517
2518 /**
2519 * Simulates a combat between two units.
2520 * - Arg 1: attacker userdata.
2521 * - Arg 2: optional weapon index.
2522 * - Arg 3: defender userdata.
2523 * - Arg 4: optional weapon index.
2524 * - Ret 1: attacker results.
2525 * - Ret 2: defender results.
2526 * - Ret 3: info about the attacker weapon.
2527 * - Ret 4: info about the defender weapon.
2528 */
intf_simulate_combat(lua_State * L)2529 int game_lua_kernel::intf_simulate_combat(lua_State *L)
2530 {
2531 int arg_num = 1, att_w = -1, def_w = -1;
2532
2533 const unit& att = luaW_checkunit(L, arg_num);
2534 ++arg_num;
2535 if (lua_isnumber(L, arg_num)) {
2536 att_w = lua_tointeger(L, arg_num) - 1;
2537 if (att_w < 0 || att_w >= static_cast<int>(att.attacks().size()))
2538 return luaL_argerror(L, arg_num, "weapon index out of bounds");
2539 ++arg_num;
2540 }
2541
2542 const unit& def = luaW_checkunit(L, arg_num, true);
2543 ++arg_num;
2544 if (lua_isnumber(L, arg_num)) {
2545 def_w = lua_tointeger(L, arg_num) - 1;
2546 if (def_w < 0 || def_w >= static_cast<int>(def.attacks().size()))
2547 return luaL_argerror(L, arg_num, "weapon index out of bounds");
2548 ++arg_num;
2549 }
2550
2551 battle_context context(units(), att.get_location(),
2552 def.get_location(), att_w, def_w, 0.0, nullptr, &att);
2553
2554 luaW_pushsimdata(L, context.get_attacker_combatant());
2555 luaW_pushsimdata(L, context.get_defender_combatant());
2556 luaW_pushsimweapon(L, context.get_attacker_stats());
2557 luaW_pushsimweapon(L, context.get_defender_stats());
2558 return 4;
2559 }
2560
2561 /**
2562 * Modifies the music playlist.
2563 * - Arg 1: WML table, or nil to force changes.
2564 */
intf_set_music(lua_State * L)2565 static int intf_set_music(lua_State *L)
2566 {
2567 deprecated_message("wesnoth.set_music", DEP_LEVEL::INDEFINITE, "", "Use the wesnoth.playlist table instead!");
2568 if (lua_isnoneornil(L, 1)) {
2569 sound::commit_music_changes();
2570 return 0;
2571 }
2572
2573 config cfg = luaW_checkconfig(L, 1);
2574 sound::play_music_config(cfg);
2575 return 0;
2576 }
2577
2578 /**
2579 * Plays a sound, possibly repeated.
2580 * - Arg 1: string.
2581 * - Arg 2: optional integer.
2582 */
intf_play_sound(lua_State * L)2583 int game_lua_kernel::intf_play_sound(lua_State *L)
2584 {
2585 char const *m = luaL_checkstring(L, 1);
2586 if (play_controller_.is_skipping_replay()) return 0;
2587 int repeats = lua_tointeger(L, 2);
2588 sound::play_sound(m, sound::SOUND_FX, repeats);
2589 return 0;
2590 }
2591
2592 /**
2593 * Gets/sets the current sound volume
2594 * - Arg 1: (optional) New volume to set
2595 * - Return: Original volume
2596 */
intf_sound_volume(lua_State * L)2597 static int intf_sound_volume(lua_State* L)
2598 {
2599 int vol = preferences::sound_volume();
2600 lua_pushnumber(L, sound::get_sound_volume() * 100.0 / vol);
2601 if(lua_isnumber(L, 1)) {
2602 float rel = lua_tonumber(L, 1);
2603 if(rel < 0.0f || rel > 100.0f) {
2604 return luaL_argerror(L, 1, "volume must be in range 0..100");
2605 }
2606 vol = static_cast<int>(rel*vol / 100.0f);
2607 sound::set_sound_volume(vol);
2608 }
2609 return 1;
2610 }
2611
2612 /**
2613 * Scrolls to given tile.
2614 * - Arg 1: location.
2615 * - Arg 2: boolean preventing scroll to fog.
2616 * - Arg 3: boolean specifying whether to warp instantly.
2617 * - Arg 4: boolean specifying whether to skip if already onscreen
2618 */
intf_scroll_to_tile(lua_State * L)2619 int game_lua_kernel::intf_scroll_to_tile(lua_State *L)
2620 {
2621 map_location loc = luaW_checklocation(L, 1);
2622 bool check_fogged = luaW_toboolean(L, 2);
2623 game_display::SCROLL_TYPE scroll = luaW_toboolean(L, 4)
2624 ? luaW_toboolean(L, 3)
2625 ? game_display::ONSCREEN_WARP
2626 : game_display::ONSCREEN
2627 : luaW_toboolean(L, 3)
2628 ? game_display::WARP
2629 : game_display::SCROLL
2630 ;
2631 if (game_display_) {
2632 game_display_->scroll_to_tile(loc, scroll, check_fogged);
2633 }
2634 return 0;
2635 }
2636
intf_select_hex(lua_State * L)2637 int game_lua_kernel::intf_select_hex(lua_State *L)
2638 {
2639 events::command_disabler command_disabler;
2640 deprecated_message("wesnoth.select_hex", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use wesnoth.select_unit and/or wesnoth.highlight_hex instead.");
2641
2642 // Need this because check_location may change the stack
2643 // By doing this now, we ensure that it won't do so when
2644 // intf_select_unit and intf_highlight_hex call it.
2645 const map_location loc = luaW_checklocation(L, 1);
2646 luaW_pushlocation(L, loc);
2647 lua_replace(L, 1);
2648
2649 intf_select_unit(L);
2650 if(!lua_isnoneornil(L, 2) && luaW_toboolean(L,2)) {
2651 intf_highlight_hex(L);
2652 }
2653 return 0;
2654 }
2655
2656 /**
2657 * Selects and highlights the given location on the map.
2658 * - Arg 1: location.
2659 * - Args 2,3: booleans
2660 */
intf_select_unit(lua_State * L)2661 int game_lua_kernel::intf_select_unit(lua_State *L)
2662 {
2663 events::command_disabler command_disabler;
2664 if(lua_isnoneornil(L, 1)) {
2665 play_controller_.get_mouse_handler_base().select_hex(map_location::null_location(), false, false, false);
2666 return 0;
2667 }
2668 const map_location loc = luaW_checklocation(L, 1);
2669 if(!map().on_board(loc)) return luaL_argerror(L, 1, "not on board");
2670 bool highlight = true;
2671 if(!lua_isnoneornil(L, 2))
2672 highlight = luaW_toboolean(L, 2);
2673 const bool fire_event = luaW_toboolean(L, 3);
2674 play_controller_.get_mouse_handler_base().select_hex(
2675 loc, false, highlight, fire_event);
2676 return 0;
2677 }
2678
2679 /**
2680 * Deselects any highlighted hex on the map.
2681 * No arguments or return values
2682 */
intf_deselect_hex(lua_State *)2683 int game_lua_kernel::intf_deselect_hex(lua_State*)
2684 {
2685 if(game_display_) {
2686 game_display_->highlight_hex(map_location::null_location());
2687 }
2688
2689 return 0;
2690 }
2691
2692 /**
2693 * Return true if a replay is in progress but the player has chosen to skip it
2694 */
intf_is_skipping_messages(lua_State * L)2695 int game_lua_kernel::intf_is_skipping_messages(lua_State *L)
2696 {
2697 bool skipping = play_controller_.is_skipping_replay() || play_controller_.is_skipping_story();
2698 if (!skipping) {
2699 skipping = game_state_.events_manager_->pump().context_skip_messages();
2700 }
2701 lua_pushboolean(L, skipping);
2702 return 1;
2703 }
2704
2705 /**
2706 * Set whether to skip messages
2707 * Arg 1 (optional) - boolean
2708 */
intf_skip_messages(lua_State * L)2709 int game_lua_kernel::intf_skip_messages(lua_State *L)
2710 {
2711 bool skip = true;
2712 if (!lua_isnone(L, 1)) {
2713 skip = luaW_toboolean(L, 1);
2714 }
2715 game_state_.events_manager_->pump().context_skip_messages(skip);
2716 return 0;
2717 }
2718
2719 namespace
2720 {
2721 struct lua_synchronize : mp_sync::user_choice
2722 {
2723 lua_State *L;
2724 int user_choice_index;
2725 int random_choice_index;
2726 int ai_choice_index;
2727 std::string desc;
lua_synchronize__anon384f78800311::lua_synchronize2728 lua_synchronize(lua_State *l, const std::string& descr, int user_index, int random_index = 0, int ai_index = 0)
2729 : L(l)
2730 , user_choice_index(user_index)
2731 , random_choice_index(random_index)
2732 , ai_choice_index(ai_index != 0 ? ai_index : user_index)
2733 , desc(descr)
2734 {}
2735
query_user__anon384f78800311::lua_synchronize2736 virtual config query_user(int side) const override
2737 {
2738 bool is_local_ai = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).board().get_team(side).is_local_ai();
2739 config cfg;
2740 query_lua(side, is_local_ai ? ai_choice_index : user_choice_index, cfg);
2741 return cfg;
2742 }
2743
random_choice__anon384f78800311::lua_synchronize2744 virtual config random_choice(int side) const override
2745 {
2746 config cfg;
2747 if(random_choice_index != 0 && lua_isfunction(L, random_choice_index)) {
2748 query_lua(side, random_choice_index, cfg);
2749 }
2750 return cfg;
2751 }
2752
description__anon384f78800311::lua_synchronize2753 virtual std::string description() const override
2754 {
2755 return desc;
2756 }
2757
query_lua__anon384f78800311::lua_synchronize2758 void query_lua(int side, int function_index, config& cfg) const
2759 {
2760 assert(cfg.empty());
2761 lua_pushvalue(L, function_index);
2762 lua_pushnumber(L, side);
2763 if (luaW_pcall(L, 1, 1, false)) {
2764 if(!luaW_toconfig(L, -1, cfg)) {
2765 lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).log_error("function returned to wesnoth.synchronize_choice a table which was partially invalid");
2766 }
2767 }
2768 }
2769 //Although lua's sync_choice can show a dialog, (and will in most cases)
2770 //we return false to enable other possible things that do not contain UI things.
2771 //it's in the responsibility of the umc dev to not show dialogs during prestart events.
is_visible__anon384f78800311::lua_synchronize2772 virtual bool is_visible() const override { return false; }
2773 };
2774 }//unnamed namespace for lua_synchronize
2775
2776 /**
2777 * Ensures a value is synchronized among all the clients.
2778 * - Arg 1: optional string specifying the type id of the choice.
2779 * - Arg 2: function to compute the value, called if the client is the master.
2780 * - Arg 3: optional function, called instead of the first function if the user is not human.
2781 * - Arg 4: optional integer specifying, on which side the function should be evaluated.
2782 * - Ret 1: WML table returned by the function.
2783 */
intf_synchronize_choice(lua_State * L)2784 static int intf_synchronize_choice(lua_State *L)
2785 {
2786 std::string tagname = "input";
2787 t_string desc = _("input");
2788 int human_func = 0;
2789 int ai_func = 0;
2790 int side_for;
2791
2792 int nextarg = 1;
2793 if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) {
2794 ++nextarg;
2795 }
2796 if(lua_isfunction(L, nextarg)) {
2797 human_func = nextarg++;
2798 }
2799 else {
2800 return luaL_argerror(L, nextarg, "expected a function");
2801 }
2802 if(lua_isfunction(L, nextarg)) {
2803 ai_func = nextarg++;
2804 }
2805 side_for = lua_tointeger(L, nextarg);
2806
2807 config cfg = mp_sync::get_user_choice(tagname, lua_synchronize(L, desc, human_func, 0, ai_func), side_for);
2808 luaW_pushconfig(L, cfg);
2809 return 1;
2810 }
2811 /**
2812 * Ensures a value is synchronized among all the clients.
2813 * - Arg 1: optional string the id of this type of user input, may only contain characters a-z and '_'
2814 * - Arg 2: function to compute the value, called if the client is the master.
2815 * - Arg 3: an optional function to compute the value, if the side was null/empty controlled.
2816 * - Arg 4: an array of integers specifying, on which side the function should be evaluated.
2817 * - Ret 1: a map int -> WML tabls.
2818 */
intf_synchronize_choices(lua_State * L)2819 static int intf_synchronize_choices(lua_State *L)
2820 {
2821 std::string tagname = "input";
2822 t_string desc = _("input");
2823 int human_func = 0;
2824 int null_func = 0;
2825 std::vector<int> sides_for;
2826
2827 int nextarg = 1;
2828 if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) {
2829 ++nextarg;
2830 }
2831 if(lua_isfunction(L, nextarg)) {
2832 human_func = nextarg++;
2833 }
2834 else {
2835 return luaL_argerror(L, nextarg, "expected a function");
2836 }
2837 if(lua_isfunction(L, nextarg)) {
2838 null_func = nextarg++;
2839 };
2840 sides_for = lua_check<std::vector<int>>(L, nextarg++);
2841
2842 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())));
2843 return 1;
2844 }
2845
2846
2847 /**
2848 * Calls a function in an unsynced context (this specially means that all random calls used by that function will be unsynced).
2849 * This is usually used together with an unsynced if like 'if controller != network'
2850 * - Arg 1: function that will be called during the unsynced context.
2851 */
intf_do_unsynced(lua_State * L)2852 static int intf_do_unsynced(lua_State *L)
2853 {
2854 set_scontext_unsynced sync;
2855 lua_pushvalue(L, 1);
2856 luaW_pcall(L, 0, 0, false);
2857 return 0;
2858 }
2859
2860 /**
2861 * Gets all the locations matching a given filter.
2862 * - Arg 1: WML table.
2863 * - Arg 2: Optional reference unit (teleport_unit)
2864 * - Ret 1: array of integer pairs.
2865 */
intf_get_locations(lua_State * L)2866 int game_lua_kernel::intf_get_locations(lua_State *L)
2867 {
2868 vconfig filter = luaW_checkvconfig(L, 1);
2869
2870 std::set<map_location> res;
2871 filter_context & fc = game_state_;
2872 const terrain_filter t_filter(filter, &fc);
2873 if(luaW_isunit(L, 2)) {
2874 t_filter.get_locations(res, *luaW_tounit(L, 2), true);
2875 } else {
2876 t_filter.get_locations(res, true);
2877 }
2878
2879 lua_createtable(L, res.size(), 0);
2880 int i = 1;
2881 for (const map_location& loc : res)
2882 {
2883 lua_createtable(L, 2, 0);
2884 lua_pushinteger(L, loc.wml_x());
2885 lua_rawseti(L, -2, 1);
2886 lua_pushinteger(L, loc.wml_y());
2887 lua_rawseti(L, -2, 2);
2888 lua_rawseti(L, -2, i);
2889 ++i;
2890 }
2891 return 1;
2892 }
2893
2894 /**
2895 * Gets all the villages matching a given filter, or all the villages on the map if no filter is given.
2896 * - Arg 1: WML table (optional).
2897 * - Ret 1: array of integer pairs.
2898 */
intf_get_villages(lua_State * L)2899 int game_lua_kernel::intf_get_villages(lua_State *L)
2900 {
2901 std::vector<map_location> locs = map().villages();
2902 lua_newtable(L);
2903 int i = 1;
2904
2905 vconfig filter = luaW_checkvconfig(L, 1);
2906
2907 filter_context & fc = game_state_;
2908 for(std::vector<map_location>::const_iterator it = locs.begin(); it != locs.end(); ++it) {
2909 bool matches = terrain_filter(filter, &fc).match(*it);
2910 if (matches) {
2911 lua_createtable(L, 2, 0);
2912 lua_pushinteger(L, it->wml_x());
2913 lua_rawseti(L, -2, 1);
2914 lua_pushinteger(L, it->wml_y());
2915 lua_rawseti(L, -2, 2);
2916 lua_rawseti(L, -2, i);
2917 ++i;
2918 }
2919 }
2920 return 1;
2921 }
2922
2923 /**
2924 * Matches a location against the given filter.
2925 * - Arg 1: location.
2926 * - Arg 2: WML table.
2927 * - Arg 3: Optional reference unit (teleport_unit)
2928 * - Ret 1: boolean.
2929 */
intf_match_location(lua_State * L)2930 int game_lua_kernel::intf_match_location(lua_State *L)
2931 {
2932 map_location loc = luaW_checklocation(L, 1);
2933 vconfig filter = luaW_checkvconfig(L, 2, true);
2934
2935 if (filter.null()) {
2936 lua_pushboolean(L, true);
2937 return 1;
2938 }
2939
2940 filter_context & fc = game_state_;
2941 const terrain_filter t_filter(filter, &fc);
2942 if(luaW_isunit(L, 3)) {
2943 lua_pushboolean(L, t_filter.match(loc, *luaW_tounit(L, 3)));
2944 } else {
2945 lua_pushboolean(L, t_filter.match(loc));
2946 }
2947 return 1;
2948 }
2949
2950
2951
2952 /**
2953 * Matches a side against the given filter.
2954 * - Args 1: side number.
2955 * - Arg 2: WML table.
2956 * - Ret 1: boolean.
2957 */
intf_match_side(lua_State * L)2958 int game_lua_kernel::intf_match_side(lua_State *L)
2959 {
2960 vconfig filter = luaW_checkvconfig(L, 2, true);
2961
2962 if (filter.null()) {
2963 lua_pushboolean(L, true);
2964 return 1;
2965 }
2966
2967 filter_context & fc = game_state_;
2968 side_filter s_filter(filter, &fc);
2969
2970 if(team* t = luaW_toteam(L, 1)) {
2971 lua_pushboolean(L, s_filter.match(*t));
2972 } else {
2973 unsigned side = luaL_checkinteger(L, 1) - 1;
2974 if (side >= teams().size()) return 0;
2975 lua_pushboolean(L, s_filter.match(side + 1));
2976 }
2977 return 1;
2978 }
2979
intf_set_side_id(lua_State * L)2980 int game_lua_kernel::intf_set_side_id(lua_State *L)
2981 {
2982 int team_i = luaL_checkinteger(L, 1) - 1;
2983 std::string flag = luaL_optlstring(L, 2, "", nullptr);
2984 std::string color = luaL_optlstring(L, 3, "", nullptr);
2985
2986 if(flag.empty() && color.empty()) {
2987 return 0;
2988 }
2989 if(team_i < 0 || static_cast<size_t>(team_i) >= teams().size()) {
2990 return luaL_error(L, "set_side_id: side number %d out of range", team_i);
2991 }
2992 team& side = teams()[team_i];
2993
2994 if(!color.empty()) {
2995 side.set_color(color);
2996 }
2997 if(!flag.empty()) {
2998 side.set_flag(flag);
2999 }
3000
3001 game_display_->reinit_flags_for_side(team_i);
3002 return 0;
3003 }
3004
intf_modify_ai(lua_State * L,const char * action)3005 static int intf_modify_ai(lua_State *L, const char* action)
3006 {
3007 int side_num = luaL_checkinteger(L, 1);
3008 std::string path = luaL_checkstring(L, 2);
3009 config cfg {
3010 "action", action,
3011 "path", path
3012 };
3013 if(strcmp(action, "delete") == 0) {
3014 ai::manager::get_singleton().modify_active_ai_for_side(side_num, cfg);
3015 return 0;
3016 }
3017 config component = luaW_checkconfig(L, 3);
3018 size_t len = std::string::npos, open_brak = path.find_last_of('[');
3019 size_t dot = path.find_last_of('.');
3020 if(open_brak != len) {
3021 len = open_brak - dot - 1;
3022 }
3023 cfg.add_child(path.substr(dot + 1, len), component);
3024 ai::manager::get_singleton().modify_active_ai_for_side(side_num, cfg);
3025 return 0;
3026 }
3027
intf_switch_ai(lua_State * L)3028 static int intf_switch_ai(lua_State *L)
3029 {
3030 int side_num = luaL_checkinteger(L, 1);
3031 if(lua_isstring(L, 2)) {
3032 std::string file = luaL_checkstring(L, 2);
3033 if(!ai::manager::get_singleton().add_ai_for_side_from_file(side_num, file)) {
3034 std::string err = formatter() << "Could not load AI for side " << side_num << " from file " << file;
3035 lua_pushlstring(L, err.c_str(), err.length());
3036 return lua_error(L);
3037 }
3038 } else {
3039 ai::manager::get_singleton().add_ai_for_side_from_config(side_num, luaW_checkconfig(L, 2));
3040 }
3041 return 0;
3042 }
3043
intf_append_ai(lua_State * L)3044 static int intf_append_ai(lua_State *L)
3045 {
3046 int side_num = luaL_checkinteger(L, 1);
3047 config cfg = luaW_checkconfig(L, 2);
3048 if(!cfg.has_child("ai")) {
3049 cfg = config {"ai", cfg};
3050 }
3051 bool added_dummy_stage = false;
3052 if(!cfg.child("ai").has_child("stage")) {
3053 added_dummy_stage = true;
3054 cfg.child("ai").add_child("stage", config {"name", "empty"});
3055 }
3056 ai::configuration::expand_simplified_aspects(side_num, cfg);
3057 if(added_dummy_stage) {
3058 for(auto iter = cfg.ordered_begin(); iter != cfg.ordered_end(); iter++) {
3059 if(iter->key == "stage" && iter->cfg["name"] == "empty") {
3060 iter = cfg.erase(iter);
3061 }
3062 }
3063 }
3064 ai::manager::get_singleton().append_active_ai_for_side(side_num, cfg.child("ai"));
3065 return 0;
3066 }
3067
3068 /**
3069 * Returns a proxy table array for all sides matching the given SSF.
3070 * - Arg 1: SSF
3071 * - Ret 1: proxy table array
3072 */
intf_get_sides(lua_State * L)3073 int game_lua_kernel::intf_get_sides(lua_State* L)
3074 {
3075 LOG_LUA << "intf_get_sides called: this = " << std::hex << this << std::dec << " myname = " << my_name() << std::endl;
3076 std::vector<int> sides;
3077 const vconfig ssf = luaW_checkvconfig(L, 1, true);
3078 if(ssf.null()) {
3079 for (unsigned side_number = 1; side_number <= teams().size(); ++side_number) {
3080 sides.push_back(side_number);
3081 }
3082 } else {
3083 filter_context & fc = game_state_;
3084
3085 side_filter filter(ssf, &fc);
3086 sides = filter.get_teams();
3087 }
3088
3089 lua_settop(L, 0);
3090 lua_createtable(L, sides.size(), 0);
3091 unsigned index = 1;
3092 for(int side : sides) {
3093 luaW_pushteam(L, board().get_team(side));
3094 lua_rawseti(L, -2, index);
3095 ++index;
3096 }
3097
3098 return 1;
3099 }
3100
3101 /**
3102 * .Returns information about the global traits known to the engine.
3103 * - Ret 1: Table with named fields holding wml tables describing the traits.
3104 */
intf_get_traits(lua_State * L)3105 static int intf_get_traits(lua_State* L)
3106 {
3107 lua_newtable(L);
3108 for(const config& trait : unit_types.traits()) {
3109 const std::string& id = trait["id"];
3110 //It seems the engine does nowhere check the id field for emptyness or duplicates
3111 //(also not later on).
3112 //However, the worst thing to happen is that the trait read later overwrites the older one,
3113 //and this is not the right place for such checks.
3114 lua_pushstring(L, id.c_str());
3115 luaW_pushconfig(L, trait);
3116 lua_rawset(L, -3);
3117 }
3118 return 1;
3119 }
3120
3121 /**
3122 * Adds a modification to a unit.
3123 * - Arg 1: unit.
3124 * - Arg 2: string.
3125 * - Arg 3: WML table.
3126 * - Arg 4: (optional) Whether to add to [modifications] - default true
3127 */
intf_add_modification(lua_State * L)3128 static int intf_add_modification(lua_State *L)
3129 {
3130 unit& u = luaW_checkunit(L, 1);
3131 char const *m = luaL_checkstring(L, 2);
3132 std::string sm = m;
3133 if (sm == "advance") { // Maintain backwards compatibility
3134 sm = "advancement";
3135 deprecated_message("\"advance\" modification type", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use \"advancement\" instead.");
3136 }
3137 if (sm != "advancement" && sm != "object" && sm != "trait") {
3138 return luaL_argerror(L, 2, "unknown modification type");
3139 }
3140 bool write_to_mods = true;
3141 if (!lua_isnone(L, 4)) {
3142 write_to_mods = luaW_toboolean(L, 4);
3143 }
3144 if(sm.empty()) {
3145 write_to_mods = false;
3146 }
3147
3148 config cfg = luaW_checkconfig(L, 3);
3149 u.add_modification(sm, cfg, !write_to_mods);
3150 return 0;
3151 }
3152
3153 /**
3154 * Removes modifications from a unit
3155 * - Arg 1: unit
3156 * - Arg 2: table (filter as [filter_wml])
3157 * - Arg 3: type of modification (default "object")
3158 */
intf_remove_modifications(lua_State * L)3159 static int intf_remove_modifications(lua_State *L)
3160 {
3161 unit& u = luaW_checkunit(L, 1);
3162 config filter = luaW_checkconfig(L, 2);
3163 std::string tag = luaL_optstring(L, 3, "object");
3164 //TODO
3165 if(filter.attribute_count() == 1 && filter.all_children_count() == 0 && filter.attribute_range().front().first == "duration") {
3166 u.expire_modifications(filter["duration"]);
3167 } else {
3168 for(config& obj : u.get_modifications().child_range(tag)) {
3169 if(obj.matches(filter)) {
3170 obj["duration"] = "now";
3171 }
3172 }
3173 u.expire_modifications("now");
3174 }
3175 return 0;
3176 }
3177
3178 /**
3179 * Advances a unit if the unit has enough xp.
3180 * - Arg 1: unit.
3181 * - Arg 2: optional boolean whether to animate the advancement.
3182 * - Arg 3: optional boolean whether to fire advancement events.
3183 */
intf_advance_unit(lua_State * L)3184 static int intf_advance_unit(lua_State *L)
3185 {
3186 events::command_disabler command_disabler;
3187 //TODO: check whether the unit is on the map.
3188 unit& u = luaW_checkunit(L, 1, true);
3189 advance_unit_params par(u.get_location());
3190 if(lua_isboolean(L, 2)) {
3191 par.animate(luaW_toboolean(L, 2));
3192 }
3193 if(lua_isboolean(L, 3)) {
3194 par.fire_events(luaW_toboolean(L, 3));
3195 }
3196 advance_unit_at(par);
3197 return 0;
3198 }
3199
3200
3201 /**
3202 * Adds a new known unit type to the help system.
3203 * - Arg 1: string.
3204 */
intf_add_known_unit(lua_State * L)3205 static int intf_add_known_unit(lua_State *L)
3206 {
3207 char const *ty = luaL_checkstring(L, 1);
3208 if(!unit_types.find(ty))
3209 {
3210 std::stringstream ss;
3211 ss << "unknown unit type: '" << ty << "'";
3212 return luaL_argerror(L, 1, ss.str().c_str());
3213 }
3214 preferences::encountered_units().insert(ty);
3215 return 0;
3216 }
3217
3218 /**
3219 * Adds an overlay on a tile.
3220 * - Arg 1: location.
3221 * - Arg 2: WML table.
3222 */
intf_add_tile_overlay(lua_State * L)3223 int game_lua_kernel::intf_add_tile_overlay(lua_State *L)
3224 {
3225 map_location loc = luaW_checklocation(L, 1);
3226 config cfg = luaW_checkconfig(L, 2);
3227
3228 if (game_display_) {
3229 game_display_->add_overlay(loc, cfg["image"], cfg["halo"],
3230 cfg["team_name"], cfg["name"], cfg["visible_in_fog"].to_bool(true));
3231 }
3232 return 0;
3233 }
3234
3235 /**
3236 * Removes an overlay from a tile.
3237 * - Arg 1: location.
3238 * - Arg 2: optional string.
3239 */
intf_remove_tile_overlay(lua_State * L)3240 int game_lua_kernel::intf_remove_tile_overlay(lua_State *L)
3241 {
3242 map_location loc = luaW_checklocation(L, 1);
3243 char const *m = lua_tostring(L, 2);
3244
3245 if (m) {
3246 if (game_display_) {
3247 game_display_->remove_single_overlay(loc, m);
3248 }
3249 } else {
3250 if (game_display_) {
3251 game_display_->remove_overlay(loc);
3252 }
3253 }
3254 return 0;
3255 }
3256
intf_log_replay(lua_State * L)3257 int game_lua_kernel::intf_log_replay(lua_State* L)
3258 {
3259 replay& recorder = play_controller_.get_replay();
3260 const int nargs = lua_gettop(L);
3261 if(nargs < 2 || nargs > 3) {
3262 return luaL_error(L, "Wrong number of arguments to ai.log_replay() - should be 2 or 3 arguments.");
3263 }
3264 const std::string key = nargs == 2 ? luaL_checkstring(L, 1) : luaL_checkstring(L, 2);
3265 config cfg;
3266 if(nargs == 2) {
3267 recorder.add_log_data(key, luaL_checkstring(L, 2));
3268 } else if(luaW_toconfig(L, 3, cfg)) {
3269 recorder.add_log_data(luaL_checkstring(L, 1), key, cfg);
3270 } else if(!lua_isstring(L, 3)) {
3271 return luaL_argerror(L, 3, "accepts only string or config");
3272 } else {
3273 recorder.add_log_data(luaL_checkstring(L, 1), key, luaL_checkstring(L, 3));
3274 }
3275 return 0;
3276 }
3277
3278 /// Adding new events
intf_add_event(lua_State * L)3279 int game_lua_kernel::intf_add_event(lua_State *L)
3280 {
3281 vconfig cfg(luaW_checkvconfig(L, 1));
3282 game_events::manager & man = *game_state_.events_manager_;
3283
3284 if (!cfg["delayed_variable_substitution"].to_bool(true)) {
3285 man.add_event_handler(cfg.get_parsed_config());
3286 } else {
3287 man.add_event_handler(cfg.get_config());
3288 }
3289 return 0;
3290 }
3291
intf_remove_event(lua_State * L)3292 int game_lua_kernel::intf_remove_event(lua_State *L)
3293 {
3294 game_state_.events_manager_->remove_event_handler(luaL_checkstring(L, 1));
3295 return 0;
3296 }
3297
intf_color_adjust(lua_State * L)3298 int game_lua_kernel::intf_color_adjust(lua_State *L)
3299 {
3300 if (game_display_) {
3301 vconfig cfg(luaW_checkvconfig(L, 1));
3302
3303 game_display_->adjust_color_overlay(cfg["red"], cfg["green"], cfg["blue"]);
3304 game_display_->invalidate_all();
3305 game_display_->draw(true,true);
3306 }
3307 return 0;
3308 }
3309
3310 /**
3311 * Delays engine for a while.
3312 * - Arg 1: integer.
3313 * - Arg 2: boolean (optional).
3314 */
intf_delay(lua_State * L)3315 int game_lua_kernel::intf_delay(lua_State *L)
3316 {
3317 if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::PRESTART || gamedata().phase() == game_data::INITIAL) {
3318 //don't call play_slice if the game ui is not active yet.
3319 return 0;
3320 }
3321 events::command_disabler command_disabler;
3322 lua_Integer delay = luaL_checkinteger(L, 1);
3323 if(delay == 0) {
3324 play_controller_.play_slice(false);
3325 return 0;
3326 }
3327 if(luaW_toboolean(L, 2) && game_display_ && game_display_->turbo_speed() > 0) {
3328 delay /= game_display_->turbo_speed();
3329 }
3330 const unsigned final = SDL_GetTicks() + delay;
3331 do {
3332 play_controller_.play_slice(false);
3333 CVideo::delay(10);
3334 } while (static_cast<int>(final - SDL_GetTicks()) > 0);
3335 return 0;
3336 }
3337
intf_label(lua_State * L)3338 int game_lua_kernel::intf_label(lua_State *L)
3339 {
3340 if (game_display_) {
3341 vconfig cfg(luaW_checkvconfig(L, 1));
3342
3343 game_display &screen = *game_display_;
3344
3345 terrain_label label(screen.labels(), cfg.get_config());
3346
3347 screen.labels().set_label(label.location(), label.text(), label.creator(), label.team_name(), label.color(),
3348 label.visible_in_fog(), label.visible_in_shroud(), label.immutable(), label.category(), label.tooltip());
3349 }
3350 return 0;
3351 }
3352
intf_redraw(lua_State * L)3353 int game_lua_kernel::intf_redraw(lua_State *L)
3354 {
3355 if (game_display_) {
3356 game_display & screen = *game_display_;
3357
3358 vconfig cfg(luaW_checkvconfig(L, 1));
3359 bool clear_shroud(luaW_toboolean(L, 2));
3360
3361 // We do this twice so any applicable redraws happen both before and after
3362 // any events caused by redrawing shroud are fired
3363 bool result = screen.maybe_rebuild();
3364 if (!result) {
3365 screen.invalidate_all();
3366 }
3367
3368 if (clear_shroud) {
3369 side_filter filter(cfg, &game_state_);
3370 for (const int side : filter.get_teams()){
3371 actions::clear_shroud(side);
3372 }
3373 screen.recalculate_minimap();
3374 }
3375
3376 result = screen.maybe_rebuild();
3377 if (!result) {
3378 screen.invalidate_all();
3379 }
3380
3381 screen.draw(true,true);
3382 }
3383 return 0;
3384 }
3385
3386 /**
3387 * Lua frontend to the modify_ai functionality
3388 * - Arg 1: config.
3389 */
intf_modify_ai_old(lua_State * L)3390 static int intf_modify_ai_old(lua_State *L)
3391 {
3392 config cfg;
3393 luaW_toconfig(L, 1, cfg);
3394 int side = cfg["side"];
3395 deprecated_message("wesnoth.modify_ai", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use wesnoth.add_ai_component, wesnoth.delete_ai_component, or wesnoth.change_ai_component.");
3396 ai::manager::get_singleton().modify_active_ai_for_side(side, cfg);
3397 return 0;
3398 }
3399
cfun_exec_candidate_action(lua_State * L)3400 static int cfun_exec_candidate_action(lua_State *L)
3401 {
3402 bool exec = luaW_toboolean(L, -1);
3403 lua_pop(L, 1);
3404
3405 lua_getfield(L, -1, "ca_ptr");
3406
3407 ai::candidate_action *ca = static_cast<ai::candidate_action*>(lua_touserdata(L, -1));
3408 lua_pop(L, 2);
3409 if (exec) {
3410 ca->execute();
3411 return 0;
3412 }
3413 lua_pushinteger(L, ca->evaluate());
3414 return 1;
3415 }
3416
cfun_exec_stage(lua_State * L)3417 static int cfun_exec_stage(lua_State *L)
3418 {
3419 lua_getfield(L, -1, "stg_ptr");
3420 ai::stage *stg = static_cast<ai::stage*>(lua_touserdata(L, -1));
3421 lua_pop(L, 2);
3422 stg->play_stage();
3423 return 0;
3424 }
3425
push_component(lua_State * L,ai::component * c,const std::string & ct="")3426 static void push_component(lua_State *L, ai::component* c, const std::string &ct = "")
3427 {
3428 lua_createtable(L, 0, 0); // Table for a component
3429
3430 lua_pushstring(L, "name");
3431 lua_pushstring(L, c->get_name().c_str());
3432 lua_rawset(L, -3);
3433
3434 lua_pushstring(L, "engine");
3435 lua_pushstring(L, c->get_engine().c_str());
3436 lua_rawset(L, -3);
3437
3438 lua_pushstring(L, "id");
3439 lua_pushstring(L, c->get_id().c_str());
3440 lua_rawset(L, -3);
3441
3442 if (ct == "candidate_action") {
3443 lua_pushstring(L, "ca_ptr");
3444 lua_pushlightuserdata(L, c);
3445 lua_rawset(L, -3);
3446
3447 lua_pushstring(L, "exec");
3448 lua_pushcclosure(L, &cfun_exec_candidate_action, 0);
3449 lua_rawset(L, -3);
3450 }
3451
3452 if (ct == "stage") {
3453 lua_pushstring(L, "stg_ptr");
3454 lua_pushlightuserdata(L, c);
3455 lua_rawset(L, -3);
3456
3457 lua_pushstring(L, "exec");
3458 lua_pushcclosure(L, &cfun_exec_stage, 0);
3459 lua_rawset(L, -3);
3460 }
3461
3462
3463 std::vector<std::string> c_types = c->get_children_types();
3464
3465 for (std::vector<std::string>::const_iterator t = c_types.begin(); t != c_types.end(); ++t)
3466 {
3467 std::vector<ai::component*> children = c->get_children(*t);
3468 std::string type = *t;
3469 if (type == "aspect" || type == "goal" || type == "engine")
3470 {
3471 continue;
3472 }
3473
3474 lua_pushstring(L, type.c_str());
3475 lua_createtable(L, 0, 0); // this table will be on top of the stack during recursive calls
3476
3477 for (std::vector<ai::component*>::const_iterator i = children.begin(); i != children.end(); ++i)
3478 {
3479 lua_pushstring(L, (*i)->get_name().c_str());
3480 push_component(L, *i, type);
3481 lua_rawset(L, -3);
3482
3483 //if (type == "candidate_action")
3484 //{
3485 // ai::candidate_action *ca = dynamic_cast<ai::candidate_action*>(*i);
3486 // ca->execute();
3487 //}
3488 }
3489
3490 lua_rawset(L, -3); // setting the child table
3491 }
3492
3493
3494 }
3495
3496 /**
3497 * Debug access to the ai tables
3498 * - Arg 1: int
3499 * - Ret 1: ai table
3500 */
intf_debug_ai(lua_State * L)3501 static int intf_debug_ai(lua_State *L)
3502 {
3503 if (!game_config::debug) { // This function works in debug mode only
3504 return 0;
3505 }
3506 int side = lua_tointeger(L, 1);
3507 lua_pop(L, 1);
3508
3509 ai::component* c = ai::manager::get_singleton().get_active_ai_holder_for_side_dbg(side).get_component(nullptr, "");
3510
3511 // Bad, but works
3512 std::vector<ai::component*> engines = c->get_children("engine");
3513 ai::engine_lua* lua_engine = nullptr;
3514 for (std::vector<ai::component*>::const_iterator i = engines.begin(); i != engines.end(); ++i)
3515 {
3516 if ((*i)->get_name() == "lua")
3517 {
3518 lua_engine = dynamic_cast<ai::engine_lua *>(*i);
3519 }
3520 }
3521
3522 // Better way, but doesn't work
3523 //ai::component* e = ai::manager::get_singleton().get_active_ai_holder_for_side_dbg(side).get_component(c, "engine[lua]");
3524 //ai::engine_lua* lua_engine = dynamic_cast<ai::engine_lua *>(e);
3525
3526 if (lua_engine == nullptr)
3527 {
3528 //no lua engine is defined for this side.
3529 //so set up a dummy engine
3530
3531 ai::ai_composite * ai_ptr = dynamic_cast<ai::ai_composite *>(c);
3532
3533 assert(ai_ptr);
3534
3535 ai::ai_context& ai_context = ai_ptr->get_ai_context();
3536 config cfg = ai::configuration::get_default_ai_parameters();
3537
3538 lua_engine = new ai::engine_lua(ai_context, cfg);
3539 LOG_LUA << "Created new dummy lua-engine for debug_ai(). \n";
3540
3541 //and add the dummy engine as a component
3542 //to the manager, so we could use it later
3543 cfg.add_child("engine", lua_engine->to_config());
3544 ai::component_manager::add_component(c, "engine[]", cfg);
3545 }
3546
3547 lua_engine->push_ai_table(); // stack: [-1: ai_context]
3548
3549 lua_pushstring(L, "components");
3550 push_component(L, c); // stack: [-1: component tree; -2: ai context]
3551 lua_rawset(L, -3);
3552
3553 return 1;
3554 }
3555
3556 /// Allow undo sets the flag saying whether the event has mutated the game to false.
intf_allow_end_turn(lua_State * L)3557 int game_lua_kernel::intf_allow_end_turn(lua_State * L)
3558 {
3559 gamedata().set_allow_end_turn(luaW_toboolean(L, 1));
3560 return 0;
3561 }
3562
3563 /// Allow undo sets the flag saying whether the event has mutated the game to false.
intf_allow_undo(lua_State * L)3564 int game_lua_kernel::intf_allow_undo(lua_State * L)
3565 {
3566 if(lua_isboolean(L, 1)) {
3567 play_controller_.pump().set_undo_disabled(!luaW_toboolean(L, 1));
3568 }
3569 else {
3570 play_controller_.pump().set_undo_disabled(false);
3571 }
3572 return 0;
3573 }
3574
intf_cancel_action(lua_State *)3575 int game_lua_kernel::intf_cancel_action(lua_State*)
3576 {
3577 play_controller_.pump().set_action_canceled();
3578 return 0;
3579 }
3580
3581 /// Adding new time_areas dynamically with Standard Location Filters.
intf_add_time_area(lua_State * L)3582 int game_lua_kernel::intf_add_time_area(lua_State * L)
3583 {
3584 log_scope("time_area");
3585
3586 vconfig cfg(luaW_checkvconfig(L, 1));
3587 const std::string id = cfg["id"];
3588
3589 std::set<map_location> locs;
3590 const terrain_filter filter(cfg, &game_state_);
3591 filter.get_locations(locs, true);
3592 config parsed_cfg = cfg.get_parsed_config();
3593 tod_man().add_time_area(id, locs, parsed_cfg);
3594 LOG_LUA << "Lua inserted time_area '" << id << "'\n";
3595 return 0;
3596 }
3597
3598 /// Removing new time_areas dynamically with Standard Location Filters.
intf_remove_time_area(lua_State * L)3599 int game_lua_kernel::intf_remove_time_area(lua_State * L)
3600 {
3601 log_scope("remove_time_area");
3602
3603 const char * id = luaL_checkstring(L, 1);
3604 tod_man().remove_time_area(id);
3605 LOG_LUA << "Lua removed time_area '" << id << "'\n";
3606
3607 return 0;
3608 }
3609
3610 /// Replacing the current time of day schedule.
intf_replace_schedule(lua_State * L)3611 int game_lua_kernel::intf_replace_schedule(lua_State * L)
3612 {
3613 vconfig cfg = luaW_checkvconfig(L, 1);
3614
3615 if(cfg.get_children("time").empty()) {
3616 ERR_LUA << "attempted to to replace ToD schedule with empty schedule" << std::endl;
3617 } else {
3618 tod_man().replace_schedule(cfg.get_parsed_config());
3619 if (game_display_) {
3620 game_display_->new_turn();
3621 }
3622 LOG_LUA << "replaced ToD schedule\n";
3623 }
3624 return 0;
3625 }
3626
intf_set_time_of_day(lua_State * L)3627 int game_lua_kernel::intf_set_time_of_day(lua_State * L)
3628 {
3629 if(!game_display_) {
3630 return 0;
3631 }
3632 std::string area_id;
3633 size_t area_i = 0;
3634 if (lua_isstring(L, 2)) {
3635 area_id = lua_tostring(L, 1);
3636 std::vector<std::string> area_ids = tod_man().get_area_ids();
3637 area_i = std::distance(area_ids.begin(), std::find(area_ids.begin(), area_ids.end(), area_id));
3638 if(area_i >= area_ids.size()) {
3639 return luaL_argerror(L, 1, "invalid time area ID");
3640 }
3641 }
3642 int is_num = false;
3643 int new_time = lua_tonumberx(L, 1, &is_num) - 1;
3644 const std::vector<time_of_day>& times = area_id.empty()
3645 ? tod_man().times()
3646 : tod_man().times(area_i);
3647 int num_times = times.size();
3648 if(!is_num) {
3649 std::string time_id = luaL_checkstring(L, 1);
3650 new_time = 0;
3651 for(const time_of_day& time : times) {
3652 if(time_id == time.id) {
3653 break;
3654 }
3655 new_time++;
3656 }
3657 if(new_time >= num_times) {
3658 return luaL_argerror(L, 1, "invalid time of day ID");
3659 }
3660 }
3661 if(new_time < 0 || new_time >= num_times) {
3662 return luaL_argerror(L, 1, "invalid time of day index");
3663 }
3664
3665 if(area_id.empty()) {
3666 tod_man().set_current_time(new_time);
3667 } else {
3668 tod_man().set_current_time(new_time, area_i);
3669 }
3670 return 0;
3671 }
3672
intf_scroll(lua_State * L)3673 int game_lua_kernel::intf_scroll(lua_State * L)
3674 {
3675 int x = luaL_checkinteger(L, 1), y = luaL_checkinteger(L, 2);
3676
3677 if (game_display_) {
3678 game_display_->scroll(x, y, true);
3679 game_display_->draw(true, true);
3680 }
3681
3682 return 0;
3683 }
3684
3685 namespace {
3686 struct lua_report_generator : reports::generator
3687 {
3688 lua_State *mState;
3689 std::string name;
lua_report_generator__anon384f78800411::lua_report_generator3690 lua_report_generator(lua_State *L, const std::string &n)
3691 : mState(L), name(n) {}
3692 virtual config generate(reports::context & rc);
3693 };
3694
generate(reports::context &)3695 config lua_report_generator::generate(reports::context & /*rc*/)
3696 {
3697 lua_State *L = mState;
3698 config cfg;
3699 if (!luaW_getglobal(L, "wesnoth", "theme_items", name))
3700 return cfg;
3701 if (!luaW_pcall(L, 0, 1)) return cfg;
3702 luaW_toconfig(L, -1, cfg);
3703 lua_pop(L, 1);
3704 return cfg;
3705 }
3706 }//unnamed namespace for lua_report_generator
3707
3708 /**
3709 * Executes its upvalue as a theme item generator.
3710 */
impl_theme_item(lua_State * L,std::string m)3711 int game_lua_kernel::impl_theme_item(lua_State *L, std::string m)
3712 {
3713 reports::context temp_context = reports::context(board(), *game_display_, tod_man(), play_controller_.get_whiteboard(), play_controller_.get_mouse_handler_base());
3714 luaW_pushconfig(L, reports_.generate_report(m.c_str(), temp_context , true));
3715 return 1;
3716 }
3717
3718 /**
3719 * Creates a field of the theme_items table and returns it (__index metamethod).
3720 */
impl_theme_items_get(lua_State * L)3721 int game_lua_kernel::impl_theme_items_get(lua_State *L)
3722 {
3723 char const *m = luaL_checkstring(L, 2);
3724 lua_cpp::push_closure(L, std::bind(&game_lua_kernel::impl_theme_item, this, _1, std::string(m)), 0);
3725 lua_pushvalue(L, 2);
3726 lua_pushvalue(L, -2);
3727 lua_rawset(L, 1);
3728 reports_.register_generator(m, new lua_report_generator(L, m));
3729 return 1;
3730 }
3731
3732 /**
3733 * Sets a field of the theme_items table (__newindex metamethod).
3734 */
impl_theme_items_set(lua_State * L)3735 int game_lua_kernel::impl_theme_items_set(lua_State *L)
3736 {
3737 char const *m = luaL_checkstring(L, 2);
3738 lua_pushvalue(L, 2);
3739 lua_pushvalue(L, 3);
3740 lua_rawset(L, 1);
3741 reports_.register_generator(m, new lua_report_generator(L, m));
3742 return 0;
3743 }
3744
3745 /**
3746 * Gets all the WML variables currently set.
3747 * - Ret 1: WML table
3748 */
intf_get_all_vars(lua_State * L)3749 int game_lua_kernel::intf_get_all_vars(lua_State *L) {
3750 luaW_pushconfig(L, gamedata().get_variables());
3751 return 1;
3752 }
3753
3754 /**
3755 * Teeleports a unit to a location.
3756 * Arg 1: unit
3757 * Arg 2: target location
3758 * Arg 3: bool (ignore_passability)
3759 * Arg 4: bool (clear_shroud)
3760 * Arg 5: bool (animate)
3761 */
intf_teleport(lua_State * L)3762 int game_lua_kernel::intf_teleport(lua_State *L)
3763 {
3764 events::command_disabler command_disabler;
3765 unit_ptr u = luaW_checkunit_ptr(L, 1, true);
3766 map_location dst = luaW_checklocation(L, 2);
3767 bool check_passability = !luaW_toboolean(L, 3);
3768 bool clear_shroud = luaW_toboolean(L, 4);
3769 bool animate = luaW_toboolean(L, 5);
3770
3771 if (dst == u->get_location() || !map().on_board(dst)) {
3772 return 0;
3773 }
3774 const map_location vacant_dst = find_vacant_tile(dst, pathfind::VACANT_ANY, check_passability ? u.get() : nullptr);
3775 if (!map().on_board(vacant_dst)) {
3776 return 0;
3777 }
3778 // Clear the destination hex before the move (so the animation can be seen).
3779 actions::shroud_clearer clearer;
3780 if ( clear_shroud ) {
3781 clearer.clear_dest(vacant_dst, *u);
3782 }
3783
3784 map_location src_loc = u->get_location();
3785
3786 std::vector<map_location> teleport_path;
3787 teleport_path.push_back(src_loc);
3788 teleport_path.push_back(vacant_dst);
3789 unit_display::move_unit(teleport_path, u, animate);
3790
3791 units().move(src_loc, vacant_dst);
3792 unit::clear_status_caches();
3793
3794 u = &*units().find(vacant_dst);
3795 u->anim_comp().set_standing();
3796
3797 if ( clear_shroud ) {
3798 // Now that the unit is visibly in position, clear the shroud.
3799 clearer.clear_unit(vacant_dst, *u);
3800 }
3801
3802 if (map().is_village(vacant_dst)) {
3803 actions::get_village(vacant_dst, u->side());
3804 }
3805
3806 game_display_->invalidate_unit_after_move(src_loc, vacant_dst);
3807 game_display_->draw();
3808
3809 // Sighted events.
3810 clearer.fire_events();
3811 return 0;
3812 }
3813
3814 /**
3815 * Removes a sound source by its ID
3816 * Arg 1: sound source ID
3817 */
intf_remove_sound_source(lua_State * L)3818 int game_lua_kernel::intf_remove_sound_source(lua_State *L)
3819 {
3820 soundsource::manager* man = play_controller_.get_soundsource_man();
3821 std::string id = luaL_checkstring(L, 1);
3822 man->remove(id);
3823 return 0;
3824 }
3825
3826 /**
3827 * Add a new sound source
3828 * Arg 1: Table containing keyword arguments
3829 */
intf_add_sound_source(lua_State * L)3830 int game_lua_kernel::intf_add_sound_source(lua_State *L)
3831 {
3832 soundsource::manager* man = play_controller_.get_soundsource_man();
3833 config cfg = luaW_checkconfig(L, 1);
3834 try {
3835 soundsource::sourcespec spec(cfg);
3836 man->add(spec);
3837 man->update();
3838 } catch (const bad_lexical_cast &) {
3839 ERR_LUA << "Error when parsing sound_source config: invalid parameter." << std::endl;
3840 ERR_LUA << "sound_source config was: " << cfg.debug() << std::endl;
3841 ERR_LUA << "Skipping this sound source..." << std::endl;
3842 }
3843 return 0;
3844 }
3845
3846 /**
3847 * Get an existing sound source
3848 * Arg 1: The sound source ID
3849 * Return: Config of sound source info, or nil if it didn't exist
3850 * This is a copy of the sound source info, so you need to call
3851 * add_sound_source again after changing it.
3852 */
intf_get_sound_source(lua_State * L)3853 int game_lua_kernel::intf_get_sound_source(lua_State *L)
3854 {
3855 soundsource::manager* man = play_controller_.get_soundsource_man();
3856 std::string id = luaL_checkstring(L, 1);
3857 config cfg = man->get(id);
3858 if(cfg.empty()) {
3859 return 0;
3860 }
3861 // Sound sources do not know their own string ID
3862 // Thus, we need to add this manually
3863 cfg["id"] = id;
3864 luaW_pushconfig(L, cfg);
3865 return 1;
3866 }
3867
3868 /**
3869 * Logs a message
3870 * Arg 1: (optional) Logger; "wml" for WML errors or deprecations
3871 * Arg 2: Message
3872 * Arg 3: Whether to print to chat (always true if arg 1 is "wml")
3873 */
intf_log(lua_State * L)3874 int game_lua_kernel::intf_log(lua_State *L)
3875 {
3876 const std::string& logger = lua_isstring(L, 2) ? luaL_checkstring(L, 1) : "";
3877 const std::string& msg = lua_isstring(L, 2) ? luaL_checkstring(L, 2) : luaL_checkstring(L, 1);
3878
3879 if(logger == "wml" || logger == "WML") {
3880 lg::wml_error() << msg << '\n';
3881 } else {
3882 bool in_chat = luaW_toboolean(L, -1);
3883 game_state_.events_manager_->pump().put_wml_message(logger,msg,in_chat);
3884 }
3885 return 0;
3886 }
3887
intf_get_fog_or_shroud(lua_State * L,bool fog)3888 int game_lua_kernel::intf_get_fog_or_shroud(lua_State *L, bool fog)
3889 {
3890 int side = luaL_checknumber(L, 1);
3891 map_location loc = luaW_checklocation(L, 2);
3892 if(side < 1 || static_cast<size_t>(side) > teams().size()) {
3893 std::string error = "side " + std::to_string(side) + " does not exist";
3894 return luaL_argerror(L, 1, error.c_str());
3895 }
3896
3897 team& t = board().get_team(side);
3898 lua_pushboolean(L, fog ? t.fogged(loc) : t.shrouded(loc));
3899 return 1;
3900 }
3901
3902 /**
3903 * Implements the lifting and resetting of fog via WML.
3904 * Keeping affect_normal_fog as false causes only the fog override to be affected.
3905 * Otherwise, fog lifting will be implemented similar to normal sight (cannot be
3906 * individually reset and ends at the end of the turn), and fog resetting will, in
3907 * addition to removing overrides, extend the specified teams' normal fog to all
3908 * hexes.
3909 *
3910 * Arg 1: (optional) Side number, or list of side numbers
3911 * Arg 2: List of locations; each is a two-element array or a table with x and y keys
3912 * Arg 3: (optional) boolean
3913 */
intf_toggle_fog(lua_State * L,const bool clear)3914 int game_lua_kernel::intf_toggle_fog(lua_State *L, const bool clear)
3915 {
3916 bool affect_normal_fog = false;
3917 if(lua_isboolean(L, -1)) {
3918 affect_normal_fog = luaW_toboolean(L, -1);
3919 }
3920 std::set<int> sides;
3921 if(lua_isnumber(L, 1)) {
3922 sides.insert(lua_tonumber(L, 1));
3923 } else if(lua_istable(L, 1) && lua_istable(L, 2)) {
3924 const auto& v = lua_check<std::vector<int>>(L, 1);
3925 sides.insert(v.begin(), v.end());
3926 } else {
3927 for(const team& t : teams()) {
3928 sides.insert(t.side()+1);
3929 }
3930 }
3931 const auto& v_locs = lua_check<std::vector<map_location>>(L, lua_istable(L, 2) ? 2 : 1);
3932 std::set<map_location> locs(v_locs.begin(), v_locs.end());
3933
3934 for(const int &side_num : sides) {
3935 if(side_num < 1 || static_cast<size_t>(side_num) > teams().size()) {
3936 continue;
3937 }
3938 team &t = board().get_team(side_num);
3939 if(!clear) {
3940 // Extend fog.
3941 t.remove_fog_override(locs);
3942 if(affect_normal_fog) {
3943 t.refog();
3944 }
3945 } else if(!affect_normal_fog) {
3946 // Force the locations clear of fog.
3947 t.add_fog_override(locs);
3948 } else {
3949 // Simply clear fog from the locations.
3950 for(const map_location &hex : locs) {
3951 t.clear_fog(hex);
3952 }
3953 }
3954 }
3955
3956 // Flag a screen update.
3957 game_display_->recalculate_minimap();
3958 game_display_->invalidate_all();
3959 return 0;
3960 }
3961
3962 // Invokes a synced command
intf_invoke_synced_command(lua_State * L)3963 static int intf_invoke_synced_command(lua_State* L)
3964 {
3965 const std::string name = luaL_checkstring(L, 1);
3966 auto it = synced_command::registry().find(name);
3967 config cmd;
3968 if(it == synced_command::registry().end()) {
3969 // Custom command
3970 if(!luaW_getglobal(L, "wesnoth", "custom_synced_commands", name)) {
3971 return luaL_argerror(L, 1, "Unknown synced command");
3972 }
3973 config& cmd_tag = cmd.child_or_add("custom_command");
3974 cmd_tag["name"] = name;
3975 if(!lua_isnoneornil(L, 2)) {
3976 cmd_tag.add_child("data", luaW_checkconfig(L, 2));
3977 }
3978 } else {
3979 // Built-in command
3980 cmd.add_child(name, luaW_checkconfig(L, 2));
3981 }
3982 // Now just forward to the WML action.
3983 luaW_getglobal(L, "wesnoth", "wml_actions", "do_command");
3984 luaW_pushconfig(L, cmd);
3985 luaW_pcall(L, 1, 0);
3986 return 0;
3987 }
3988
3989 // END CALLBACK IMPLEMENTATION
3990
board()3991 game_board & game_lua_kernel::board() {
3992 return game_state_.board_;
3993 }
3994
units()3995 unit_map & game_lua_kernel::units() {
3996 return game_state_.board_.units_;
3997 }
3998
teams()3999 std::vector<team> & game_lua_kernel::teams() {
4000 return game_state_.board_.teams_;
4001 }
4002
map() const4003 const gamemap & game_lua_kernel::map() const {
4004 return game_state_.board_.map();
4005 }
4006
gamedata()4007 game_data & game_lua_kernel::gamedata() {
4008 return game_state_.gamedata_;
4009 }
4010
tod_man()4011 tod_manager & game_lua_kernel::tod_man() {
4012 return game_state_.tod_manager_;
4013 }
4014
get_event_info()4015 const game_events::queued_event & game_lua_kernel::get_event_info() {
4016 return *queued_events_.top();
4017 }
4018
4019
game_lua_kernel(game_state & gs,play_controller & pc,reports & reports_object)4020 game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports & reports_object)
4021 : lua_kernel_base()
4022 , game_display_(nullptr)
4023 , game_state_(gs)
4024 , play_controller_(pc)
4025 , reports_(reports_object)
4026 , level_lua_()
4027 , queued_events_()
4028 , map_locked_(0)
4029 {
4030 static game_events::queued_event default_queued_event("_from_lua", "", map_location(), map_location(), config());
4031 queued_events_.push(&default_queued_event);
4032
4033 lua_State *L = mState;
4034
4035 cmd_log_ << "Registering game-specific wesnoth lib functions...\n";
4036
4037 // Put some callback functions in the scripting environment.
4038 static luaL_Reg const callbacks[] {
4039 { "add_known_unit", &intf_add_known_unit },
4040 { "add_modification", &intf_add_modification },
4041 { "advance_unit", &intf_advance_unit },
4042 { "copy_unit", &intf_copy_unit },
4043 { "create_animator", &dispatch<&game_lua_kernel::intf_create_animator> },
4044 { "create_unit", &intf_create_unit },
4045 { "debug_ai", &intf_debug_ai },
4046 { "eval_conditional", &intf_eval_conditional },
4047 { "get_era", &intf_get_era },
4048 { "get_traits", &intf_get_traits },
4049 { "get_viewing_side", &intf_get_viewing_side },
4050 { "invoke_synced_command", &intf_invoke_synced_command },
4051 { "modify_ai", &intf_modify_ai_old },
4052 { "remove_modifications", &intf_remove_modifications },
4053 { "set_music", &intf_set_music },
4054 { "sound_volume", &intf_sound_volume },
4055 { "transform_unit", &intf_transform_unit },
4056 { "unit_defense", &intf_unit_defense },
4057 { "unit_movement_cost", &intf_unit_movement_cost },
4058 { "unit_vision_cost", &intf_unit_vision_cost },
4059 { "unit_jamming_cost", &intf_unit_jamming_cost },
4060 { "unit_resistance", &intf_unit_resistance },
4061 { "unsynced", &intf_do_unsynced },
4062 { "add_event_handler", &dispatch<&game_lua_kernel::intf_add_event > },
4063 { "add_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, false > },
4064 { "add_tile_overlay", &dispatch<&game_lua_kernel::intf_add_tile_overlay > },
4065 { "add_time_area", &dispatch<&game_lua_kernel::intf_add_time_area > },
4066 { "add_sound_source", &dispatch<&game_lua_kernel::intf_add_sound_source > },
4067 { "allow_end_turn", &dispatch<&game_lua_kernel::intf_allow_end_turn > },
4068 { "allow_undo", &dispatch<&game_lua_kernel::intf_allow_undo > },
4069 { "append_ai", &intf_append_ai },
4070 { "cancel_action", &dispatch<&game_lua_kernel::intf_cancel_action > },
4071 { "clear_menu_item", &dispatch<&game_lua_kernel::intf_clear_menu_item > },
4072 { "clear_messages", &dispatch<&game_lua_kernel::intf_clear_messages > },
4073 { "color_adjust", &dispatch<&game_lua_kernel::intf_color_adjust > },
4074 { "delay", &dispatch<&game_lua_kernel::intf_delay > },
4075 { "end_turn", &dispatch<&game_lua_kernel::intf_end_turn > },
4076 { "end_level", &dispatch<&game_lua_kernel::intf_end_level > },
4077 { "erase_unit", &dispatch<&game_lua_kernel::intf_erase_unit > },
4078 { "extract_unit", &dispatch<&game_lua_kernel::intf_extract_unit > },
4079 { "find_cost_map", &dispatch<&game_lua_kernel::intf_find_cost_map > },
4080 { "find_path", &dispatch<&game_lua_kernel::intf_find_path > },
4081 { "find_reach", &dispatch<&game_lua_kernel::intf_find_reach > },
4082 { "find_vacant_tile", &dispatch<&game_lua_kernel::intf_find_vacant_tile > },
4083 { "fire_event", &dispatch2<&game_lua_kernel::intf_fire_event, false > },
4084 { "fire_event_by_id", &dispatch2<&game_lua_kernel::intf_fire_event, true > },
4085 { "float_label", &dispatch<&game_lua_kernel::intf_float_label > },
4086 { "gamestate_inspector", &dispatch<&game_lua_kernel::intf_gamestate_inspector > },
4087 { "get_all_vars", &dispatch<&game_lua_kernel::intf_get_all_vars > },
4088 { "get_end_level_data", &dispatch<&game_lua_kernel::intf_get_end_level_data > },
4089 { "get_locations", &dispatch<&game_lua_kernel::intf_get_locations > },
4090 { "get_map_size", &dispatch<&game_lua_kernel::intf_get_map_size > },
4091 { "get_mouseover_tile", &dispatch<&game_lua_kernel::intf_get_mouseover_tile > },
4092 { "get_recall_units", &dispatch<&game_lua_kernel::intf_get_recall_units > },
4093 { "get_selected_tile", &dispatch<&game_lua_kernel::intf_get_selected_tile > },
4094 { "get_sides", &dispatch<&game_lua_kernel::intf_get_sides > },
4095 { "get_sound_source", &dispatch<&game_lua_kernel::intf_get_sound_source > },
4096 { "get_starting_location", &dispatch<&game_lua_kernel::intf_get_starting_location > },
4097 { "get_terrain", &dispatch<&game_lua_kernel::intf_get_terrain > },
4098 { "get_terrain_info", &dispatch<&game_lua_kernel::intf_get_terrain_info > },
4099 { "get_time_of_day", &dispatch<&game_lua_kernel::intf_get_time_of_day > },
4100 { "get_unit", &dispatch<&game_lua_kernel::intf_get_unit > },
4101 { "get_units", &dispatch<&game_lua_kernel::intf_get_units > },
4102 { "get_variable", &dispatch<&game_lua_kernel::intf_get_variable > },
4103 { "get_side_variable", &dispatch<&game_lua_kernel::intf_get_side_variable > },
4104 { "get_villages", &dispatch<&game_lua_kernel::intf_get_villages > },
4105 { "get_village_owner", &dispatch<&game_lua_kernel::intf_get_village_owner > },
4106 { "get_displayed_unit", &dispatch<&game_lua_kernel::intf_get_displayed_unit > },
4107 { "highlight_hex", &dispatch<&game_lua_kernel::intf_highlight_hex > },
4108 { "is_enemy", &dispatch<&game_lua_kernel::intf_is_enemy > },
4109 { "label", &dispatch<&game_lua_kernel::intf_label > },
4110 { "lock_view", &dispatch<&game_lua_kernel::intf_lock_view > },
4111 { "log_replay", &dispatch<&game_lua_kernel::intf_log_replay > },
4112 { "log", &dispatch<&game_lua_kernel::intf_log > },
4113 { "match_location", &dispatch<&game_lua_kernel::intf_match_location > },
4114 { "match_side", &dispatch<&game_lua_kernel::intf_match_side > },
4115 { "match_unit", &dispatch<&game_lua_kernel::intf_match_unit > },
4116 { "message", &dispatch<&game_lua_kernel::intf_message > },
4117 { "open_help", &dispatch<&game_lua_kernel::intf_open_help > },
4118 { "play_sound", &dispatch<&game_lua_kernel::intf_play_sound > },
4119 { "print", &dispatch<&game_lua_kernel::intf_print > },
4120 { "put_recall_unit", &dispatch<&game_lua_kernel::intf_put_recall_unit > },
4121 { "put_unit", &dispatch<&game_lua_kernel::intf_put_unit > },
4122 { "redraw", &dispatch<&game_lua_kernel::intf_redraw > },
4123 { "remove_event_handler", &dispatch<&game_lua_kernel::intf_remove_event > },
4124 { "remove_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, true > },
4125 { "remove_tile_overlay", &dispatch<&game_lua_kernel::intf_remove_tile_overlay > },
4126 { "remove_time_area", &dispatch<&game_lua_kernel::intf_remove_time_area > },
4127 { "remove_sound_source", &dispatch<&game_lua_kernel::intf_remove_sound_source > },
4128 { "replace_schedule", &dispatch<&game_lua_kernel::intf_replace_schedule > },
4129 { "scroll", &dispatch<&game_lua_kernel::intf_scroll > },
4130 { "scroll_to_tile", &dispatch<&game_lua_kernel::intf_scroll_to_tile > },
4131 { "select_hex", &dispatch<&game_lua_kernel::intf_select_hex > },
4132 { "set_time_of_day", &dispatch<&game_lua_kernel::intf_set_time_of_day > },
4133 { "deselect_hex", &dispatch<&game_lua_kernel::intf_deselect_hex > },
4134 { "select_unit", &dispatch<&game_lua_kernel::intf_select_unit > },
4135 { "skip_messages", &dispatch<&game_lua_kernel::intf_skip_messages > },
4136 { "is_fogged", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, true > },
4137 { "is_shrouded", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, false > },
4138 { "is_skipping_messages", &dispatch<&game_lua_kernel::intf_is_skipping_messages > },
4139 { "set_end_campaign_credits", &dispatch<&game_lua_kernel::intf_set_end_campaign_credits > },
4140 { "set_end_campaign_text", &dispatch<&game_lua_kernel::intf_set_end_campaign_text > },
4141 { "set_menu_item", &dispatch<&game_lua_kernel::intf_set_menu_item > },
4142 { "set_next_scenario", &dispatch<&game_lua_kernel::intf_set_next_scenario > },
4143 { "set_side_id", &dispatch<&game_lua_kernel::intf_set_side_id > },
4144 { "set_terrain", &dispatch<&game_lua_kernel::intf_set_terrain > },
4145 { "set_variable", &dispatch<&game_lua_kernel::intf_set_variable > },
4146 { "set_side_variable", &dispatch<&game_lua_kernel::intf_set_side_variable > },
4147 { "set_village_owner", &dispatch<&game_lua_kernel::intf_set_village_owner > },
4148 { "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > },
4149 { "switch_ai", &intf_switch_ai },
4150 { "synchronize_choice", &intf_synchronize_choice },
4151 { "synchronize_choices", &intf_synchronize_choices },
4152 { "zoom", &dispatch<&game_lua_kernel::intf_zoom > },
4153 { "teleport", &dispatch<&game_lua_kernel::intf_teleport > },
4154 { "unit_ability", &dispatch<&game_lua_kernel::intf_unit_ability > },
4155 { "view_locked", &dispatch<&game_lua_kernel::intf_view_locked > },
4156 { "place_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, true > },
4157 { "remove_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, false > },
4158 { nullptr, nullptr }
4159 };
4160 std::vector<lua_cpp::Reg> const cpp_callbacks {
4161 {"add_ai_component", std::bind(intf_modify_ai, _1, "add")},
4162 {"delete_ai_component", std::bind(intf_modify_ai, _1, "delete")},
4163 {"change_ai_component", std::bind(intf_modify_ai, _1, "change")},
4164 {nullptr, nullptr}
4165 };
4166 lua_getglobal(L, "wesnoth");
4167 if (!lua_istable(L,-1)) {
4168 lua_newtable(L);
4169 }
4170 luaL_setfuncs(L, callbacks, 0);
4171 lua_cpp::set_functions(L, cpp_callbacks);
4172
4173 if(play_controller_.get_classification().campaign_type == game_classification::CAMPAIGN_TYPE::TEST) {
4174 static luaL_Reg const test_callbacks[] {
4175 { "fire_wml_menu_item", &dispatch<&game_lua_kernel::intf_fire_wml_menu_item > },
4176 { nullptr, nullptr }
4177 };
4178 luaL_setfuncs(L, test_callbacks , 0);
4179 }
4180
4181 lua_setglobal(L, "wesnoth");
4182
4183 // Create the getside metatable.
4184 cmd_log_ << lua_team::register_metatable(L);
4185
4186 // Create the gettype metatable.
4187 cmd_log_ << lua_unit_type::register_metatable(L);
4188
4189 //Create the getrace metatable
4190 cmd_log_ << lua_race::register_metatable(L);
4191
4192 //Create the unit metatables
4193 cmd_log_ << lua_units::register_metatables(L);
4194 cmd_log_ << lua_units::register_attacks_metatables(L);
4195
4196 // Create the vconfig metatable.
4197 cmd_log_ << lua_common::register_vconfig_metatable(L);
4198
4199 // Create the unit_types table
4200 cmd_log_ << lua_unit_type::register_table(L);
4201
4202 // Create the ai elements table.
4203 cmd_log_ << "Adding ai elements table...\n";
4204
4205 ai::lua_ai_context::init(L);
4206
4207 // Create the current variable with its metatable.
4208 cmd_log_ << "Adding wesnoth current table...\n";
4209
4210 lua_getglobal(L, "wesnoth");
4211 lua_newuserdata(L, 0);
4212 lua_createtable(L, 0, 2);
4213 lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_current_get>);
4214 lua_setfield(L, -2, "__index");
4215 lua_pushstring(L, "current config");
4216 lua_setfield(L, -2, "__metatable");
4217 lua_setmetatable(L, -2);
4218 lua_setfield(L, -2, "current");
4219 lua_pop(L, 1);
4220
4221 // Create the playlist table with its metatable
4222 cmd_log_ << lua_audio::register_table(L);
4223
4224 // Create the wml_actions table.
4225 cmd_log_ << "Adding wml_actions table...\n";
4226
4227 lua_getglobal(L, "wesnoth");
4228 lua_newtable(L);
4229 lua_setfield(L, -2, "wml_actions");
4230 lua_pop(L, 1);
4231
4232 // Create the wml_conditionals table.
4233 cmd_log_ << "Adding wml_conditionals table...\n";
4234
4235 lua_getglobal(L, "wesnoth");
4236 lua_newtable(L);
4237 lua_setfield(L, -2, "wml_conditionals");
4238 lua_pop(L, 1);
4239 set_wml_condition("have_unit", &game_events::builtin_conditions::have_unit);
4240 set_wml_condition("have_location", &game_events::builtin_conditions::have_location);
4241 set_wml_condition("variable", &game_events::builtin_conditions::variable_matches);
4242
4243 // Create the effects table.
4244 cmd_log_ << "Adding effects table...\n";
4245
4246 lua_getglobal(L, "wesnoth");
4247 lua_newtable(L);
4248 lua_setfield(L, -2, "effects");
4249 lua_pop(L, 1);
4250
4251 // Create the custom_synced_commands table.
4252 cmd_log_ << "Adding custom_synced_commands table...\n";
4253
4254 lua_getglobal(L, "wesnoth");
4255 lua_newtable(L);
4256 lua_setfield(L, -2, "custom_synced_commands");
4257 lua_pop(L, 1);
4258
4259 // Create the game_events table.
4260 cmd_log_ << "Adding game_events table...\n";
4261
4262 lua_getglobal(L, "wesnoth");
4263 lua_newtable(L);
4264 lua_setfield(L, -2, "game_events");
4265 push_locations_table(L);
4266 lua_setfield(L, -2, "special_locations");
4267 lua_pop(L, 1);
4268
4269 // Create the theme_items table.
4270 cmd_log_ << "Adding theme_items table...\n";
4271
4272 lua_getglobal(L, "wesnoth");
4273 lua_newtable(L);
4274 lua_createtable(L, 0, 2);
4275 lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_theme_items_get>);
4276 lua_setfield(L, -2, "__index");
4277 lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_theme_items_set>);
4278 lua_setfield(L, -2, "__newindex");
4279 lua_setmetatable(L, -2);
4280 lua_setfield(L, -2, "theme_items");
4281 lua_pop(L, 1);
4282
4283 lua_settop(L, 0);
4284
4285 for(const auto& handler : game_events::wml_action::registry())
4286 {
4287 set_wml_action(handler.first, handler.second);
4288 }
4289 luaW_getglobal(L, "wesnoth", "effects");
4290 for(const std::string& effect : unit::builtin_effects) {
4291 lua_pushstring(L, effect.c_str());
4292 push_builtin_effect();
4293 lua_rawset(L, -3);
4294 }
4295 lua_settop(L, 0);
4296 }
4297
initialize(const config & level)4298 void game_lua_kernel::initialize(const config& level)
4299 {
4300 lua_State *L = mState;
4301 assert(level_lua_.empty());
4302 level_lua_.append_children(level, "lua");
4303 // Create the sides table.
4304 // note:
4305 // This table is redundant to the return value of wesnoth.get_sides({}).
4306 // Still needed for backwards compatibility.
4307 lua_settop(L, 0);
4308 lua_getglobal(L, "wesnoth");
4309
4310 lua_pushstring(L, "get_sides");
4311 lua_rawget(L, -2);
4312 lua_createtable(L, 0, 0);
4313
4314 if (!protected_call(1, 1, std::bind(&lua_kernel_base::log_error, this, _1, _2))) {
4315 cmd_log_ << "Failed to compute wesnoth.sides\n";
4316 } else {
4317 lua_setfield(L, -2, "sides");
4318 cmd_log_ << "Added wesnoth.sides\n";
4319 }
4320
4321 //Create the races table.
4322 cmd_log_ << "Adding races table...\n";
4323
4324 lua_settop(L, 0);
4325 lua_getglobal(L, "wesnoth");
4326 luaW_pushracetable(L);
4327 lua_setfield(L, -2, "races");
4328 lua_pop(L, 1);
4329
4330 // Execute the preload scripts.
4331 cmd_log_ << "Running preload scripts...\n";
4332
4333 game_config::load_config(game_lua_kernel::preload_config);
4334 for (const config &cfg : game_lua_kernel::preload_scripts) {
4335 run_lua_tag(cfg);
4336 }
4337 for (const config &cfg : level_lua_.child_range("lua")) {
4338 run_lua_tag(cfg);
4339 }
4340 }
4341
set_game_display(game_display * gd)4342 void game_lua_kernel::set_game_display(game_display * gd) {
4343 game_display_ = gd;
4344 }
4345
4346 /// These are the child tags of [scenario] (and the like) that are handled
4347 /// elsewhere (in the C++ code).
4348 /// Any child tags not in this list will be passed to Lua's on_load event.
4349 static char const *handled_file_tags[] {
4350 "color_palette", "color_range", "display", "end_level_data", "era",
4351 "event", "generator", "label", "lua", "map", "menu_item",
4352 "modification", "music", "options", "side", "sound_source",
4353 "story", "terrain_graphics", "time", "time_area", "tunnel",
4354 "undo_stack", "variables"
4355 };
4356
is_handled_file_tag(const std::string & s)4357 static bool is_handled_file_tag(const std::string &s)
4358 {
4359 for (char const *t : handled_file_tags) {
4360 if (s == t) return true;
4361 }
4362 return false;
4363 }
4364
4365 /**
4366 * Executes the game_events.on_load function and passes to it all the
4367 * scenario tags not yet handled.
4368 */
load_game(const config & level)4369 void game_lua_kernel::load_game(const config& level)
4370 {
4371 lua_State *L = mState;
4372
4373 if (!luaW_getglobal(L, "wesnoth", "game_events", "on_load"))
4374 return;
4375
4376 lua_newtable(L);
4377 int k = 1;
4378 for (const config::any_child &v : level.all_children_range())
4379 {
4380 if (is_handled_file_tag(v.key)) continue;
4381 lua_createtable(L, 2, 0);
4382 lua_pushstring(L, v.key.c_str());
4383 lua_rawseti(L, -2, 1);
4384 luaW_pushconfig(L, v.cfg);
4385 lua_rawseti(L, -2, 2);
4386 lua_rawseti(L, -2, k++);
4387 }
4388
4389 luaW_pcall(L, 1, 0, true);
4390 }
4391
4392 /**
4393 * Executes the game_events.on_save function and adds to @a cfg the
4394 * returned tags. Also flushes the [lua] tags.
4395 */
save_game(config & cfg)4396 void game_lua_kernel::save_game(config &cfg)
4397 {
4398 lua_State *L = mState;
4399
4400 if (!luaW_getglobal(L, "wesnoth", "game_events", "on_save"))
4401 return;
4402
4403 if (!luaW_pcall(L, 0, 1, false))
4404 return;
4405
4406 config v;
4407 luaW_toconfig(L, -1, v);
4408 lua_pop(L, 1);
4409
4410 for (;;)
4411 {
4412 config::all_children_iterator i = v.ordered_begin();
4413 if (i == v.ordered_end()) break;
4414 if (is_handled_file_tag(i->key))
4415 {
4416 /*
4417 * It seems the only tags appearing in the config v variable here
4418 * are the core-lua-handled (currently [item] and [objectives])
4419 * and the extra UMC ones.
4420 */
4421 const std::string m = "Tag is already used: [" + i->key + "]";
4422 log_error(m.c_str());
4423 v.erase(i);
4424 continue;
4425 }
4426 cfg.splice_children(v, i->key);
4427 }
4428 }
4429
4430 /**
4431 * Executes the game_events.on_event function.
4432 * Returns false if there was no lua handler for this event
4433 */
run_event(const game_events::queued_event & ev)4434 bool game_lua_kernel::run_event(const game_events::queued_event& ev)
4435 {
4436 lua_State *L = mState;
4437
4438 if (!luaW_getglobal(L, "wesnoth", "game_events", "on_event"))
4439 return false;
4440
4441 queued_event_context dummy(&ev, queued_events_);
4442 lua_pushstring(L, ev.name.c_str());
4443 luaW_pcall(L, 1, 0, false);
4444 return true;
4445 }
4446
custom_command(const std::string & name,const config & cfg)4447 void game_lua_kernel::custom_command(const std::string& name, const config& cfg)
4448 {
4449 lua_State *L = mState;
4450
4451 if (!luaW_getglobal(L, "wesnoth", "custom_synced_commands", name)) {
4452 return;
4453 }
4454 luaW_pushconfig(L, cfg);
4455 luaW_pcall(L, 1, 0, false);
4456 }
4457
4458 /**
4459 * Applies its upvalue as an effect
4460 * Arg 1: The unit to apply to
4461 * Arg 3: The [effect] tag contents
4462 * Arg 3: If false, only build description
4463 * Return: The description of the effect
4464 */
cfun_builtin_effect(lua_State * L)4465 int game_lua_kernel::cfun_builtin_effect(lua_State *L)
4466 {
4467 std::string which_effect = lua_tostring(L, lua_upvalueindex(1));
4468 bool need_apply = luaW_toboolean(L, lua_upvalueindex(2));
4469 // Argument 1 is the implicit "self" argument, which isn't needed here
4470 lua_unit u(luaW_checkunit(L, 2));
4471 config cfg = luaW_checkconfig(L, 3);
4472
4473 // The times= key is supposed to be ignored by the effect function.
4474 // However, just in case someone doesn't realize this, we will set it to 1 here.
4475 cfg["times"] = 1;
4476
4477 if(need_apply) {
4478 u->apply_builtin_effect(which_effect, cfg);
4479 return 0;
4480 } else {
4481 std::string description = u->describe_builtin_effect(which_effect, cfg);
4482 lua_pushstring(L, description.c_str());
4483 return 1;
4484 }
4485 }
4486
4487 /**
4488 * Registers a function for use as an effect handler.
4489 */
push_builtin_effect()4490 void game_lua_kernel::push_builtin_effect()
4491 {
4492 lua_State *L = mState;
4493
4494 // The effect name is at the top of the stack
4495 int str_i = lua_gettop(L);
4496 lua_newtable(L); // The functor table
4497 lua_newtable(L); // The functor metatable
4498 lua_pushstring(L, "__call");
4499 lua_pushvalue(L, str_i);
4500 lua_pushboolean(L, true);
4501 lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_builtin_effect>, 2);
4502 lua_rawset(L, -3); // Set the call metafunction
4503 lua_pushstring(L, "__descr");
4504 lua_pushvalue(L, str_i);
4505 lua_pushboolean(L, false);
4506 lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_builtin_effect>, 2);
4507 lua_rawset(L, -3); // Set the descr "metafunction"
4508 lua_setmetatable(L, -2); // Apply the metatable to the functor table
4509 }
4510
4511
4512 /**
4513 * Executes its upvalue as a wml action.
4514 */
cfun_wml_action(lua_State * L)4515 int game_lua_kernel::cfun_wml_action(lua_State *L)
4516 {
4517 game_events::wml_action::handler h = reinterpret_cast<game_events::wml_action::handler>
4518 (lua_touserdata(L, lua_upvalueindex(1)));
4519
4520 vconfig vcfg = luaW_checkvconfig(L, 1);
4521 h(get_event_info(), vcfg);
4522 return 0;
4523 }
4524
4525 /**
4526 * Registers a function for use as an action handler.
4527 */
set_wml_action(const std::string & cmd,game_events::wml_action::handler h)4528 void game_lua_kernel::set_wml_action(const std::string& cmd, game_events::wml_action::handler h)
4529 {
4530 lua_State *L = mState;
4531
4532 lua_getglobal(L, "wesnoth");
4533 lua_pushstring(L, "wml_actions");
4534 lua_rawget(L, -2);
4535 lua_pushstring(L, cmd.c_str());
4536 lua_pushlightuserdata(L, reinterpret_cast<void *>(h));
4537 lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_wml_action>, 1);
4538 lua_rawset(L, -3);
4539 lua_pop(L, 2);
4540 }
4541
4542 using wml_conditional_handler = bool(*)(const vconfig&);
4543
4544 /**
4545 * Executes its upvalue as a wml condition and returns the result.
4546 */
cfun_wml_condition(lua_State * L)4547 static int cfun_wml_condition(lua_State *L)
4548 {
4549 wml_conditional_handler h = reinterpret_cast<wml_conditional_handler>
4550 (lua_touserdata(L, lua_upvalueindex(1)));
4551
4552 vconfig vcfg = luaW_checkvconfig(L, 1);
4553 lua_pushboolean(L, h(vcfg));
4554 return 1;
4555 }
4556
4557 /**
4558 * Registers a function for use as a conditional handler.
4559 */
set_wml_condition(const std::string & cmd,wml_conditional_handler h)4560 void game_lua_kernel::set_wml_condition(const std::string& cmd, wml_conditional_handler h)
4561 {
4562 lua_State *L = mState;
4563
4564 lua_getglobal(L, "wesnoth");
4565 lua_pushstring(L, "wml_conditionals");
4566 lua_rawget(L, -2);
4567 lua_pushstring(L, cmd.c_str());
4568 lua_pushlightuserdata(L, reinterpret_cast<void *>(h));
4569 lua_pushcclosure(L, &cfun_wml_condition, 1);
4570 lua_rawset(L, -3);
4571 lua_pop(L, 2);
4572 }
4573
4574 /**
4575 * Runs a command from an event handler.
4576 * @return true if there is a handler for the command.
4577 * @note @a cfg should be either volatile or long-lived since the Lua
4578 * code may grab it for an arbitrary long time.
4579 */
run_wml_action(const std::string & cmd,const vconfig & cfg,const game_events::queued_event & ev)4580 bool game_lua_kernel::run_wml_action(const std::string& cmd, const vconfig& cfg,
4581 const game_events::queued_event& ev)
4582 {
4583 lua_State *L = mState;
4584
4585
4586 if (!luaW_getglobal(L, "wesnoth", "wml_actions", cmd))
4587 return false;
4588
4589 queued_event_context dummy(&ev, queued_events_);
4590 luaW_pushvconfig(L, cfg);
4591 luaW_pcall(L, 1, 0, true);
4592 return true;
4593 }
4594
4595
4596 /**
4597 * Evaluates a WML conidition.
4598 *
4599 * @returns Whether the condition passed.
4600 * @note @a cfg should be either volatile or long-lived since the Lua
4601 * code may grab it for an arbitrarily long time.
4602 */
run_wml_conditional(const std::string & cmd,const vconfig & cfg)4603 bool game_lua_kernel::run_wml_conditional(const std::string& cmd, const vconfig& cfg)
4604 {
4605 lua_State* L = mState;
4606
4607 // If an invalid coniditional tag is used, consider it a pass.
4608 if(!luaW_getglobal(L, "wesnoth", "wml_conditionals", cmd)) {
4609 lg::wml_error() << "unknown conditional wml: [" << cmd << "]\n";
4610 return true;
4611 }
4612
4613 luaW_pushvconfig(L, cfg);
4614
4615 // Any runtime error is considered a fail.
4616 if(!luaW_pcall(L, 1, 1, true)) {
4617 return false;
4618 }
4619
4620 bool b = luaW_toboolean(L, -1);
4621
4622 lua_pop(L, 1);
4623 return b;
4624 }
4625
4626
4627 /**
4628 * Runs a script from a location filter.
4629 * The script is an already compiled function given by its name.
4630 */
run_filter(char const * name,const map_location & l)4631 bool game_lua_kernel::run_filter(char const *name, const map_location& l)
4632 {
4633 lua_pushinteger(mState, l.wml_x());
4634 lua_pushinteger(mState, l.wml_y());
4635 return run_filter(name, 2);
4636 }
4637 /**
4638 * Runs a script from a unit filter.
4639 * The script is an already compiled function given by its name.
4640 */
run_filter(char const * name,const unit & u)4641 bool game_lua_kernel::run_filter(char const *name, const unit& u)
4642 {
4643 lua_State *L = mState;
4644 unit_map::const_unit_iterator ui = units().find(u.get_location());
4645 if (!ui.valid()) return false;
4646 // Pass the unit as argument.
4647 luaW_pushunit(L, ui->underlying_id());
4648
4649 return run_filter(name, 1);
4650 }
4651 /**
4652 * Runs a script from a filter.
4653 * The script is an already compiled function given by its name.
4654 */
run_filter(char const * name,int nArgs)4655 bool game_lua_kernel::run_filter(char const *name, int nArgs)
4656 {
4657 map_locker(this);
4658 lua_State *L = mState;
4659 // Get the user filter by name.
4660 const std::vector<std::string>& path = utils::split(name, '.', utils::STRIP_SPACES);
4661 if (!luaW_getglobal(L, path))
4662 {
4663 std::string message = std::string() + "function " + name + " not found";
4664 log_error(message.c_str(), "Lua SUF Error");
4665 //we pushed nothing and can safeley return.
4666 return false;
4667 }
4668 lua_insert(L, -nArgs - 1);
4669
4670 if (!luaW_pcall(L, nArgs, 1)) return false;
4671
4672 bool b = luaW_toboolean(L, -1);
4673 lua_pop(L, 1);
4674 return b;
4675 }
4676
apply_effect(const std::string & name,unit & u,const config & cfg,bool need_apply)4677 std::string game_lua_kernel::apply_effect(const std::string& name, unit& u, const config& cfg, bool need_apply)
4678 {
4679 lua_State *L = mState;
4680 int top = lua_gettop(L);
4681 std::string descr;
4682 // Stack: nothing
4683 lua_unit* lu = luaW_pushlocalunit(L, u);
4684 // Stack: unit
4685 // (Note: The unit needs to be on the stack twice to prevent untimely GC.)
4686 luaW_pushconfig(L, cfg);
4687 // Stack: unit, cfg
4688 if(luaW_getglobal(L, "wesnoth", "effects", name)) {
4689 map_locker(this);
4690 // Stack: unit, cfg, effect
4691 if(lua_istable(L, -1)) {
4692 // Effect is implemented by a table with __call and __descr
4693 if(need_apply) {
4694 lua_pushvalue(L, -1);
4695 // Stack: unit, cfg, effect, effect
4696 lua_pushvalue(L, top + 1);
4697 // Stack: unit, cfg, effect, effect, unit
4698 lua_pushvalue(L, top + 2);
4699 // Stack: unit, cfg, effect, effect, unit, cfg
4700 luaW_pcall(L, 2, 0);
4701 // Stack: unit, cfg, effect
4702 }
4703 if(luaL_getmetafield(L, -1, "__descr")) {
4704 // Stack: unit, cfg, effect, __descr
4705 if(lua_isstring(L, -1)) {
4706 // __descr was a static string
4707 descr = lua_tostring(L, -1);
4708 } else {
4709 lua_pushvalue(L, -2);
4710 // Stack: unit, cfg, effect, __descr, effect
4711 lua_pushvalue(L, top + 1);
4712 // Stack: unit, cfg, effect, __descr, effect, unit
4713 lua_pushvalue(L, top + 2);
4714 // Stack: unit, cfg, effect, __descr, effect, unit, cfg
4715 luaW_pcall(L, 3, 1);
4716 if(lua_isstring(L, -1) && !lua_isnumber(L, -1)) {
4717 descr = lua_tostring(L, -1);
4718 } else {
4719 ERR_LUA << "Effect __descr metafunction should have returned a string, but instead returned ";
4720 if(lua_isnone(L, -1)) {
4721 ERR_LUA << "nothing";
4722 } else {
4723 ERR_LUA << lua_typename(L, lua_type(L, -1));
4724 }
4725 }
4726 }
4727 }
4728 } else if(need_apply) {
4729 // Effect is assumed to be a simple function; no description is provided
4730 lua_pushvalue(L, top + 1);
4731 // Stack: unit, cfg, effect, unit
4732 lua_pushvalue(L, top + 2);
4733 // Stack: unit, cfg, effect, unit, cfg
4734 luaW_pcall(L, 2, 0);
4735 // Stack: unit, cfg
4736 }
4737 }
4738 lua_settop(L, top);
4739 lu->clear_ref();
4740 return descr;
4741 }
4742
create_lua_ai_context(char const * code,ai::engine_lua * engine)4743 ai::lua_ai_context* game_lua_kernel::create_lua_ai_context(char const *code, ai::engine_lua *engine)
4744 {
4745 return ai::lua_ai_context::create(mState,code,engine);
4746 }
4747
create_lua_ai_action_handler(char const * code,ai::lua_ai_context & context)4748 ai::lua_ai_action_handler* game_lua_kernel::create_lua_ai_action_handler(char const *code, ai::lua_ai_context &context)
4749 {
4750 return ai::lua_ai_action_handler::create(mState,code,context);
4751 }
4752
mouse_over_hex_callback(const map_location & loc)4753 void game_lua_kernel::mouse_over_hex_callback(const map_location& loc)
4754 {
4755 lua_State *L = mState;
4756
4757 if (!luaW_getglobal(L, "wesnoth", "game_events", "on_mouse_move")) {
4758 return;
4759 }
4760 lua_push(L, loc.wml_x());
4761 lua_push(L, loc.wml_y());
4762 luaW_pcall(L, 2, 0, false);
4763 return;
4764 }
4765
select_hex_callback(const map_location & loc)4766 void game_lua_kernel::select_hex_callback(const map_location& loc)
4767 {
4768 lua_State *L = mState;
4769
4770 if (!luaW_getglobal(L, "wesnoth", "game_events", "on_mouse_action")) {
4771 return;
4772 }
4773 lua_push(L, loc.wml_x());
4774 lua_push(L, loc.wml_y());
4775 luaW_pcall(L, 2, 0, false);
4776 return;
4777 }
4778