/* Copyright (C) 2009 - 2018 by Guillaume Melquiond Part of the Battle for Wesnoth Project https://www.wesnoth.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ /** * @file * Provides a Lua interpreter, to be embedded in WML. * * @note Naming conventions: * - intf_ functions are exported in the wesnoth domain, * - impl_ functions are hidden inside metatables, * - cfun_ functions are closures, * - luaW_ functions are helpers in Lua style. */ #include "scripting/game_lua_kernel.hpp" #include "actions/attack.hpp" // for battle_context_unit_stats, etc #include "actions/advancement.hpp" // for advance_unit_at, etc #include "actions/move.hpp" // for clear_shroud #include "actions/vision.hpp" // for clear_shroud #include "ai/composite/ai.hpp" // for ai_composite #include "ai/composite/component.hpp" // for component, etc #include "ai/composite/contexts.hpp" // for ai_context #include "ai/lua/engine_lua.hpp" // for engine_lua #include "ai/composite/rca.hpp" // for candidate_action #include "ai/composite/stage.hpp" // for stage #include "ai/configuration.hpp" // for configuration #include "ai/lua/core.hpp" // for lua_ai_context, etc #include "ai/manager.hpp" // for manager, holder #include "attack_prediction.hpp" // for combatant #include "chat_events.hpp" // for chat_handler, etc #include "config.hpp" // for config, etc #include "display_chat_manager.hpp" // for clear_chat_messages #include "formatter.hpp" #include "game_board.hpp" // for game_board #include "game_classification.hpp" // for game_classification, etc #include "game_config.hpp" // for debug, base_income, etc #include "game_config_manager.hpp" // for game_config_manager #include "game_data.hpp" // for game_data, etc #include "game_display.hpp" // for game_display #include "game_errors.hpp" // for game_error #include "game_events/conditional_wml.hpp" // for conditional_passed #include "game_events/entity_location.hpp" #include "game_events/pump.hpp" // for queued_event #include "preferences/game.hpp" // for encountered_units #include "help/help.hpp" #include "log.hpp" // for LOG_STREAM, logger, etc #include "utils/make_enum.hpp" // for operator<< #include "map/map.hpp" // for gamemap #include "map/label.hpp" #include "map/location.hpp" // for map_location #include "mouse_events.hpp" // for mouse_handler #include "mp_game_settings.hpp" // for mp_game_settings #include "pathfind/pathfind.hpp" // for full_cost_map, plain_route, etc #include "pathfind/teleport.hpp" // for get_teleport_locations, etc #include "play_controller.hpp" // for play_controller #include "recall_list_manager.hpp" // for recall_list_manager #include "replay.hpp" // for get_user_choice, etc #include "reports.hpp" // for register_generator, etc #include "resources.hpp" // for whiteboard #include "scripting/lua_audio.hpp" #include "scripting/lua_unit.hpp" #include "scripting/lua_unit_attacks.hpp" #include "scripting/lua_common.hpp" #include "scripting/lua_cpp_function.hpp" #include "scripting/lua_gui2.hpp" // for show_gamestate_inspector #include "scripting/lua_pathfind_cost_calculator.hpp" #include "scripting/lua_race.hpp" #include "scripting/lua_team.hpp" #include "scripting/lua_unit_type.hpp" #include "scripting/push_check.hpp" #include "synced_commands.hpp" #include "color.hpp" // for surface #include "sdl/surface.hpp" // for surface #include "side_filter.hpp" // for side_filter #include "sound.hpp" // for commit_music_changes, etc #include "soundsource.hpp" #include "synced_context.hpp" // for synced_context, etc #include "synced_user_choice.hpp" #include "team.hpp" // for team, village_owner #include "terrain/terrain.hpp" // for terrain_type #include "terrain/filter.hpp" // for terrain_filter #include "terrain/translation.hpp" // for read_terrain_code, etc #include "terrain/type_data.hpp" #include "time_of_day.hpp" // for time_of_day #include "tod_manager.hpp" // for tod_manager #include "tstring.hpp" // for t_string, operator+ #include "units/unit.hpp" // for unit #include "units/animation_component.hpp" // for unit_animation_component #include "units/udisplay.hpp" #include "units/filter.hpp" #include "units/map.hpp" // for unit_map, etc #include "units/ptr.hpp" // for unit_const_ptr, unit_ptr #include "units/types.hpp" // for unit_type_data, unit_types, etc #include "variable.hpp" // for vconfig, etc #include "variable_info.hpp" #include "whiteboard/manager.hpp" // for whiteboard #include "wml_exception.hpp" #include "deprecation.hpp" #include "utils/functional.hpp" // for bind_t, bind #include // boost::copy #include // boost::adaptors::filtered #include #include // for assert #include // for strcmp #include // for distance, advance #include // for map, map<>::value_type, etc #include // for operator new #include // for set #include // for operator<<, basic_ostream, etc #include // for pair #include #include // for vector, etc #include // for SDL_GetTicks #include "lua/lauxlib.h" // for luaL_checkinteger, etc #include "lua/lua.h" // for lua_setfield, etc class CVideo; #ifdef DEBUG_LUA #include "scripting/debug_lua.hpp" #endif static lg::log_domain log_scripting_lua("scripting/lua"); #define LOG_LUA LOG_STREAM(info, log_scripting_lua) #define WRN_LUA LOG_STREAM(warn, log_scripting_lua) #define ERR_LUA LOG_STREAM(err, log_scripting_lua) std::vector game_lua_kernel::preload_scripts; config game_lua_kernel::preload_config; // Template which allows to push member functions to the lua kernel base into lua as C functions, using a shim typedef int (game_lua_kernel::*member_callback)(lua_State *); template int dispatch(lua_State *L) { return ((lua_kernel_base::get_lua_kernel(L)).*method)(L); } // Pass a const bool also... typedef int (game_lua_kernel::*member_callback2)(lua_State *, bool); template int dispatch2(lua_State *L) { return ((lua_kernel_base::get_lua_kernel(L)).*method)(L, b); } struct map_locker { map_locker(game_lua_kernel* kernel) : kernel_(kernel) { ++kernel_->map_locked_; } ~map_locker() { --kernel_->map_locked_; } game_lua_kernel* kernel_; }; void game_lua_kernel::extract_preload_scripts(const config& game_config) { game_lua_kernel::preload_scripts.clear(); for (const config& cfg : game_config.child_range("lua")) { game_lua_kernel::preload_scripts.push_back(cfg); } game_lua_kernel::preload_config = game_config.child("game_config"); } void game_lua_kernel::log_error(char const * msg, char const * context) { lua_kernel_base::log_error(msg, context); lua_chat(context, msg); } void game_lua_kernel::lua_chat(const std::string& caption, const std::string& msg) { if (game_display_) { game_display_->get_chat_manager().add_chat_message(std::time(nullptr), caption, 0, msg, events::chat_handler::MESSAGE_PUBLIC, false); } } /** * Gets a vector of sides from side= attribute in a given config node. * Promotes consistent behavior. */ std::vector game_lua_kernel::get_sides_vector(const vconfig& cfg) { const config::attribute_value sides = cfg["side"]; const vconfig &ssf = cfg.child("filter_side"); if (!ssf.null()) { if(!sides.empty()) { WRN_LUA << "ignoring duplicate side filter information (inline side=)" << std::endl; } side_filter filter(ssf, &game_state_); return filter.get_teams(); } side_filter filter(sides.str(), &game_state_); return filter.get_teams(); } static int special_locations_len(lua_State *L) { lua_pushnumber(L, lua_kernel_base::get_lua_kernel(L).map().special_locations().size()); return 1; } static int special_locations_next(lua_State *L) { const t_translation::starting_positions::left_map& left = lua_kernel_base::get_lua_kernel(L).map().special_locations().left; t_translation::starting_positions::left_const_iterator it; if (lua_isnoneornil(L, 2)) { it = left.begin(); } else { it = left.find(luaL_checkstring(L, 2)); if (it == left.end()) { return 0; } ++it; } if (it == left.end()) { return 0; } lua_pushstring(L, it->first.c_str()); luaW_pushlocation(L, it->second); return 2; } static int special_locations_pairs(lua_State *L) { lua_pushcfunction(L, &special_locations_next); lua_pushvalue(L, -2); lua_pushnil(L); return 3; } static int special_locations_index(lua_State *L) { const t_translation::starting_positions::left_map& left = lua_kernel_base::get_lua_kernel(L).map().special_locations().left; auto it = left.find(luaL_checkstring(L, 2)); if (it == left.end()) { return 0; } else { luaW_pushlocation(L, it->second); return 1; } } static int special_locations_newindex(lua_State *L) { lua_pushstring(L, "special locations cannot be modified using wesnoth.special_locations"); return lua_error(L); } static void push_locations_table(lua_State *L) { lua_newtable(L); // The functor table lua_newtable(L); // The functor metatable lua_pushstring(L, "__len"); lua_pushcfunction(L, &special_locations_len); lua_rawset(L, -3); lua_pushstring(L, "__index"); lua_pushcfunction(L, &special_locations_index); lua_rawset(L, -3); lua_pushstring(L, "__newindex"); lua_pushcfunction(L, &special_locations_newindex); lua_rawset(L, -3); lua_pushstring(L, "__pairs"); lua_pushcfunction(L, &special_locations_pairs); lua_rawset(L, -3); lua_setmetatable(L, -2); // Apply the metatable to the functor table } namespace { /** * Temporary entry to a queued_event stack */ struct queued_event_context { typedef game_events::queued_event qe; std::stack & stack_; queued_event_context(qe const *new_qe, std::stack & stack) : stack_(stack) { stack_.push(new_qe); } ~queued_event_context() { stack_.pop(); } }; }//unnamed namespace for queued_event_context /** * Gets currently viewing side. * - Ret 1: integer specifying the currently viewing side * - Ret 2: Bool whether the vision is not limited to that team, this can for example be true during replays. */ static int intf_get_viewing_side(lua_State *L) { if(const display* disp = display::get_singleton()) { lua_pushinteger(L, disp->viewing_side()); lua_pushboolean(L, disp->show_everything()); return 2; } else { return 0; } } static const char animatorKey[] = "unit animator"; static int impl_animator_collect(lua_State* L) { unit_animator& anim = *static_cast(luaL_checkudata(L, 1, animatorKey)); anim.~unit_animator(); return 0; } static int impl_add_animation(lua_State* L) { unit_animator& anim = *static_cast(luaL_checkudata(L, 1, animatorKey)); unit& u = luaW_checkunit(L, 2); std::string which = luaL_checkstring(L, 3); using hit_type = unit_animation::hit_type; std::string hits_str = luaL_checkstring(L, 4); hit_type hits = hit_type::string_to_enum(hits_str, hit_type::INVALID); map_location dest; int v1 = 0, v2 = 0; bool bars = false; t_string text; color_t color{255, 255, 255}; const_attack_ptr primary, secondary; if(lua_istable(L, 5)) { lua_getfield(L, 5, "target"); if(luaW_tolocation(L, -1, dest)) { if(dest == u.get_location()) { return luaL_argerror(L, 5, "target location must be different from animated unit's location"); } else if(!tiles_adjacent(dest, u.get_location())) { return luaL_argerror(L, 5, "target location must be adjacent to the animated unit"); } } else { // luaW_tolocation may set the location to (0,0) if it fails dest = map_location(); if(!lua_isnoneornil(L, -1)) { return luaW_type_error(L, 5, "target", "location table"); } } lua_pop(L, 1); lua_getfield(L, 5, "value"); if(lua_isnumber(L, -1)) { v1 = lua_tonumber(L, -1); } else if(lua_istable(L, -1)) { lua_rawgeti(L, -1, 1); v1 = lua_tonumber(L, -1); lua_pop(L, 1); lua_rawgeti(L, -1, 2); v2 = lua_tonumber(L, -1); lua_pop(L, 1); } else if(!lua_isnoneornil(L, -1)) { return luaW_type_error(L, 5, "value", "number or array of two numbers"); } lua_pop(L, 1); lua_getfield(L, 5, "with_bars"); if(lua_isboolean(L, -1)) { bars = luaW_toboolean(L, -1); } else if(!lua_isnoneornil(L, -1)) { return luaW_type_error(L, 5, "with_bars", lua_typename(L, LUA_TBOOLEAN)); } lua_pop(L, 1); lua_getfield(L, 5, "text"); if(lua_isstring(L, -1)) { text = lua_tostring(L, -1); } else if(luaW_totstring(L, -1, text)) { // Do nothing; luaW_totstring already assigned the value } else if(!lua_isnoneornil(L, -1)) { return luaW_type_error(L, 5, "text", lua_typename(L, LUA_TSTRING)); } lua_pop(L, 1); lua_getfield(L, 5, "color"); if(lua_istable(L, -1) && lua_rawlen(L, -1) == 3) { int idx = lua_absindex(L, -1); lua_rawgeti(L, idx, 1); // red @ -3 lua_rawgeti(L, idx, 2); // green @ -2 lua_rawgeti(L, idx, 3); // blue @ -1 color = color_t(lua_tonumber(L, -3), lua_tonumber(L, -2), lua_tonumber(L, -1)); lua_pop(L, 3); } else if(!lua_isnoneornil(L, -1)) { return luaW_type_error(L, 5, "color", "array of three numbers"); } lua_pop(L, 1); lua_getfield(L, 5, "primary"); primary = luaW_toweapon(L, -1); if(!primary && !lua_isnoneornil(L, -1)) { return luaW_type_error(L, 5, "primary", "weapon"); } lua_pop(L, 1); lua_getfield(L, 5, "secondary"); secondary = luaW_toweapon(L, -1); if(!secondary && !lua_isnoneornil(L, -1)) { return luaW_type_error(L, 5, "secondary", "weapon"); } lua_pop(L, 1); } else if(!lua_isnoneornil(L, 5)) { return luaW_type_error(L, 5, "table of options"); } anim.add_animation(&u, which, u.get_location(), dest, v1, bars, text, color, hits, primary, secondary, v2); return 0; } int game_lua_kernel::impl_run_animation(lua_State* L) { CVideo& v = CVideo::get_singleton(); if(v.update_locked() || v.faked()) { return 0; } events::command_disabler command_disabler; unit_animator& anim = *static_cast(luaL_checkudata(L, 1, animatorKey)); play_controller_.play_slice(false); anim.start_animations(); anim.wait_for_end(); anim.set_all_standing(); anim.clear(); return 0; } static int impl_clear_animation(lua_State* L) { unit_animator& anim = *static_cast(luaL_checkudata(L, 1, animatorKey)); anim.clear(); return 0; } static int impl_animator_get(lua_State* L) { const char* m = lua_tostring(L, 2); return luaW_getmetafield(L, 1, m); } int game_lua_kernel::intf_create_animator(lua_State* L) { new(L) unit_animator; if(luaL_newmetatable(L, animatorKey)) { luaL_Reg metafuncs[] { {"__gc", impl_animator_collect}, {"__index", impl_animator_get}, {"add", impl_add_animation}, {"run", &dispatch<&game_lua_kernel::impl_run_animation>}, {"clear", impl_clear_animation}, {nullptr, nullptr}, }; luaL_setfuncs(L, metafuncs, 0); lua_pushstring(L, "__metatable"); lua_setfield(L, -2, animatorKey); } lua_setmetatable(L, -2); return 1; } int game_lua_kernel::intf_gamestate_inspector(lua_State *L) { if (game_display_) { return lua_gui2::show_gamestate_inspector(luaW_checkvconfig(L, 1), gamedata(), game_state_); } return 0; } /** * Gets the unit at the given location or with the given id. * - Arg 1: location * OR * - Arg 1: string ID * - Ret 1: full userdata with __index pointing to impl_unit_get and * __newindex pointing to impl_unit_set. */ int game_lua_kernel::intf_get_unit(lua_State *L) { map_location loc; if(lua_isstring(L, 1) && !lua_isnumber(L, 1)) { std::string id = luaL_checkstring(L, 1); for(const unit& u : units()) { if(u.id() == id) { luaW_pushunit(L, u.underlying_id()); return 1; } } return 0; } if(!luaW_tolocation(L, 1, loc)) { return luaL_argerror(L, 1, "expected string or location"); } unit_map::const_iterator ui = units().find(loc); if (!ui.valid()) return 0; luaW_pushunit(L, ui->underlying_id()); return 1; } /** * Gets the unit displayed in the sidebar. * - Ret 1: full userdata with __index pointing to impl_unit_get and * __newindex pointing to impl_unit_set. */ int game_lua_kernel::intf_get_displayed_unit(lua_State *L) { if (!game_display_) { return 0; } unit_map::const_iterator ui = board().find_visible_unit( game_display_->displayed_unit_hex(), teams()[game_display_->viewing_team()], game_display_->show_everything()); if (!ui.valid()) return 0; luaW_pushunit(L, ui->underlying_id()); return 1; } /** * Gets all the units matching a given filter. * - Arg 1: optional table containing a filter * - Arg 2: optional location (to find all units that would match on that location) OR unit (to find all units that would match adjacent to that unit) * - Ret 1: table containing full userdata with __index pointing to * impl_unit_get and __newindex pointing to impl_unit_set. */ int game_lua_kernel::intf_get_units(lua_State *L) { vconfig filter = luaW_checkvconfig(L, 1, true); unit_filter filt(filter); std::vector units; if(unit* u_adj = luaW_tounit(L, 2)) { if(!u_adj) { return luaL_argerror(L, 2, "unit not found"); } units = filt.all_matches_with_unit(*u_adj); } else if(!lua_isnoneornil(L, 2)) { map_location loc; luaW_tolocation(L, 2, loc); if(!loc.valid()) { return luaL_argerror(L, 2, "invalid location"); } units = filt.all_matches_at(loc); } else { units = filt.all_matches_on_map(); } // Go through all the units while keeping the following stack: // 1: return table, 2: userdata lua_settop(L, 0); lua_newtable(L); int i = 1; for (const unit * ui : units) { luaW_pushunit(L, ui->underlying_id()); lua_rawseti(L, 1, i); ++i; } return 1; } /** * Matches a unit against the given filter. * - Arg 1: full userdata. * - Arg 2: table containing a filter * - Arg 3: optional location OR optional "adjacent" unit * - Ret 1: boolean. */ int game_lua_kernel::intf_match_unit(lua_State *L) { lua_unit& u = *luaW_checkunit_ref(L, 1); vconfig filter = luaW_checkvconfig(L, 2, true); if (filter.null()) { lua_pushboolean(L, true); return 1; } if(unit* u_adj = luaW_tounit(L, 3)) { if(int side = u.on_recall_list()) { WRN_LUA << "wesnoth.units.matches called with a secondary unit (3rd argument), "; WRN_LUA << "but unit to match was on recall list. "; WRN_LUA << "Thus the 3rd argument is ignored.\n"; team &t = board().get_team(side); scoped_recall_unit auto_store("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id())); lua_pushboolean(L, unit_filter(filter).matches(*u, map_location())); return 1; } if (!u_adj) { return luaL_argerror(L, 3, "unit not found"); } lua_pushboolean(L, unit_filter(filter).matches(*u, *u_adj)); } else if(int side = u.on_recall_list()) { map_location loc; luaW_tolocation(L, 3, loc); // If argument 3 isn't a location, loc is unchanged team &t = board().get_team(side); scoped_recall_unit auto_store("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id())); lua_pushboolean(L, unit_filter(filter).matches(*u, loc)); return 1; } else { map_location loc = u->get_location(); luaW_tolocation(L, 3, loc); // If argument 3 isn't a location, loc is unchanged lua_pushboolean(L, unit_filter(filter).matches(*u, loc)); } return 1; } /** * Gets the numeric ids of all the units matching a given filter on the recall lists. * - Arg 1: optional table containing a filter * - Ret 1: table containing full userdata with __index pointing to * impl_unit_get and __newindex pointing to impl_unit_set. */ int game_lua_kernel::intf_get_recall_units(lua_State *L) { vconfig filter = luaW_checkvconfig(L, 1, true); // Go through all the units while keeping the following stack: // 1: return table, 2: userdata lua_settop(L, 0); lua_newtable(L); int i = 1, s = 1; const unit_filter ufilt(filter); for (team &t : teams()) { for (unit_ptr & u : t.recall_list()) { if (!filter.null()) { scoped_recall_unit auto_store("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id())); if (!ufilt( *u, map_location() )) continue; } luaW_pushunit(L, s, u->underlying_id()); lua_rawseti(L, 1, i); ++i; } ++s; } return 1; } /** * Fires an event. * - Arg 1: string containing the event name or id. * - Arg 2: optional first location. * - Arg 3: optional second location. * - Arg 4: optional WML table used as the [weapon] tag. * - Arg 5: optional WML table used as the [second_weapon] tag. * - Ret 1: boolean indicating whether the event was processed or not. */ int game_lua_kernel::intf_fire_event(lua_State *L, const bool by_id) { char const *m = luaL_checkstring(L, 1); int pos = 2; map_location l1, l2; config data; if (luaW_tolocation(L, 2, l1)) { if (luaW_tolocation(L, 3, l2)) { pos = 4; } else { pos = 3; } } if (!lua_isnoneornil(L, pos)) { data.add_child("first", luaW_checkconfig(L, pos)); } ++pos; if (!lua_isnoneornil(L, pos)) { data.add_child("second", luaW_checkconfig(L, pos)); } bool b = false; if (by_id) { b = std::get<0>(play_controller_.pump().fire("", m, l1, l2, data)); } else { b = std::get<0>(play_controller_.pump().fire(m, l1, l2, data)); } lua_pushboolean(L, b); return 1; } /** * Fires a wml menu item. * - Arg 1: id of the item. it is not possible to fire items that don't have ids with this function. * - Arg 2: optional first location. * - Ret 1: boolean, true indicating that the event was fired successfully * * NOTE: This is not an "official" feature, it may currently cause assertion failures if used with * menu items which have "needs_select". It is not supported right now to use it this way. * The purpose of this function right now is to make it possible to have automated sanity tests for * the wml menu items system. */ int game_lua_kernel::intf_fire_wml_menu_item(lua_State *L) { char const *m = luaL_checkstring(L, 1); map_location l1 = luaW_checklocation(L, 2); bool b = game_state_.get_wml_menu_items().fire_item(m, l1, gamedata(), game_state_, units()); lua_pushboolean(L, b); return 1; } /** * Gets a WML variable. * - Arg 1: string containing the variable name. * - Arg 2: optional bool indicating if tables for containers should be left empty. * - Ret 1: value of the variable, if any. */ int game_lua_kernel::intf_get_variable(lua_State *L) { char const *m = luaL_checkstring(L, 1); variable_access_const v = gamedata().get_variable_access_read(m); return luaW_pushvariable(L, v) ? 1 : 0; } /** * Sets a WML variable. * - Arg 1: string containing the variable name. * - Arg 2: boolean/integer/string/table containing the value. */ int game_lua_kernel::intf_set_variable(lua_State *L) { const std::string m = luaL_checkstring(L, 1); if(m.empty()) return luaL_argerror(L, 1, "empty variable name"); if (lua_isnoneornil(L, 2)) { gamedata().clear_variable(m); return 0; } variable_access_create v = gamedata().get_variable_access_write(m); luaW_checkvariable(L, v, 2); return 0; } int game_lua_kernel::intf_create_side(lua_State *L) { config cfg = luaW_checkconfig(L, 1); game_state_.add_side_wml(cfg); return 0; } int game_lua_kernel::intf_set_menu_item(lua_State *L) { game_state_.get_wml_menu_items().set_item(luaL_checkstring(L, 1), luaW_checkvconfig(L,2)); return 0; } int game_lua_kernel::intf_clear_menu_item(lua_State *L) { std::string ids(luaL_checkstring(L, 1)); for(const std::string& id : utils::split(ids, ',', utils::STRIP_SPACES)) { if(id.empty()) { WRN_LUA << "[clear_menu_item] has been given an empty id=, ignoring" << std::endl; continue; } game_state_.get_wml_menu_items().erase(id); } return 0; } int game_lua_kernel::intf_set_end_campaign_credits(lua_State *L) { game_classification &classification = play_controller_.get_classification(); classification.end_credits = luaW_toboolean(L, 1); return 0; } int game_lua_kernel::intf_set_end_campaign_text(lua_State *L) { game_classification &classification = play_controller_.get_classification(); classification.end_text = luaW_checktstring(L, 1); if (lua_isnumber(L, 2)) { classification.end_text_duration = static_cast (lua_tonumber(L, 2)); } return 0; } int game_lua_kernel::intf_set_next_scenario(lua_State *L) { deprecated_message("wesnoth.set_next_scenario", DEP_LEVEL::INDEFINITE, ""); gamedata().set_next_scenario(luaL_checkstring(L, 1)); return 0; } int game_lua_kernel::intf_shroud_op(lua_State *L, bool place_shroud) { int side_num = luaL_checkinteger(L, 1); if(lua_isstring(L, 2)) { std::string data = lua_tostring(L, 2); // Special case - using a shroud_data string, or "all" team& side = board().get_team(side_num); if(place_shroud) { side.reshroud(); } if(data != "all") { side.merge_shroud_map_data(data); } else if(!place_shroud) { bool was_shrouded = side.uses_shroud(); side.set_shroud(false); actions::clear_shroud(side.side()); side.set_shroud(was_shrouded); } return 0; } else if(lua_istable(L, 2)) { std::vector locs_v = lua_check>(L, 2); std::set locs(locs_v.begin(), locs_v.end()); team &t = board().get_team(side_num); for (const map_location& loc : locs) { if (place_shroud) { t.place_shroud(loc); } else { t.clear_shroud(loc); } } } else { return luaL_argerror(L, 2, "expected list of locations or shroud data string"); } game_display_->labels().recalculate_shroud(); game_display_->recalculate_minimap(); game_display_->invalidate_all(); return 0; } /** * Highlights the given location on the map. * - Arg 1: location. */ int game_lua_kernel::intf_highlight_hex(lua_State *L) { if (!game_display_) { return 0; } const map_location loc = luaW_checklocation(L, 1); if(!map().on_board(loc)) return luaL_argerror(L, 1, "not on board"); game_display_->highlight_hex(loc); game_display_->display_unit_hex(loc); return 0; } /** * Returns whether the first side is an enemy of the second one. * - Args 1,2: side numbers. * - Ret 1: boolean. */ int game_lua_kernel::intf_is_enemy(lua_State *L) { unsigned side_1, side_2; if(team* t = luaW_toteam(L, 1)) { side_1 = t->side(); } else { side_1 = luaL_checkinteger(L, 1); } if(team* t = luaW_toteam(L, 2)) { side_2 = t->side(); } else { side_2 = luaL_checkinteger(L, 2); } if (side_1 > teams().size() || side_2 > teams().size()) return 0; lua_pushboolean(L, board().get_team(side_1).is_enemy(side_2)); return 1; } /** * Gets whether gamemap scrolling is disabled for the user. * - Ret 1: boolean. */ int game_lua_kernel::intf_view_locked(lua_State *L) { if (!game_display_) { return 0; } lua_pushboolean(L, game_display_->view_locked()); return 1; } /** * Sets whether gamemap scrolling is disabled for the user. * - Arg 1: boolean, specifying the new locked/unlocked status. */ int game_lua_kernel::intf_lock_view(lua_State *L) { bool lock = luaW_toboolean(L, 1); if (game_display_) { game_display_->set_view_locked(lock); } return 0; } /** * Gets a terrain code. * - Arg 1: map location. * - Ret 1: string. */ int game_lua_kernel::intf_get_terrain(lua_State *L) { map_location loc = luaW_checklocation(L, 1); const t_translation::terrain_code& t = board().map(). get_terrain(loc); lua_pushstring(L, t_translation::write_terrain_code(t).c_str()); return 1; } /** * Sets a terrain code. * - Arg 1: map location. * - Arg 2: terrain code string. * - Arg 3: layer: (overlay|base|both, default=both) * - Arg 4: replace_if_failed, default = no */ int game_lua_kernel::intf_set_terrain(lua_State *L) { map_location loc = luaW_checklocation(L, 1); std::string t_str(luaL_checkstring(L, 2)); std::string mode_str = "both"; bool replace_if_failed = false; if (!lua_isnone(L, 3)) { if (!lua_isnil(L, 3)) { mode_str = luaL_checkstring(L, 3); } if(!lua_isnoneornil(L, 4)) { replace_if_failed = luaW_toboolean(L, 4); } } bool result = board().change_terrain(loc, t_str, mode_str, replace_if_failed); if (game_display_) { game_display_->needs_rebuild(result); } return 0; } /** * Reaplces part of the map. * - Arg 1: map location. * - Arg 2: map data string. * - Arg 3: table for optional named arguments * - is_odd: boolen, if Arg2 has the odd mapo format (as if it was cut from a odd map location) * - ignore_special_locations: boolean * - rules: table of tables */ int game_lua_kernel::intf_terrain_mask(lua_State *L) { map_location loc = luaW_checklocation(L, 1); std::string t_str(luaL_checkstring(L, 2)); bool is_odd = false; bool ignore_special_locations = false; std::vector rules; if(lua_istable(L, 3)) { if(luaW_tableget(L, 3, "is_odd")) { is_odd = luaW_toboolean(L, -1); lua_pop(L, 1); } if(luaW_tableget(L, 3, "ignore_special_locations")) { ignore_special_locations = luaW_toboolean(L, -1); lua_pop(L, 1); } if(luaW_tableget(L, 3, "rules")) { //todo: reduce code dublication by using read_rules_vector. if(!lua_istable(L, -1)) { return luaL_argerror(L, 3, "rules must be a table"); } for (int i = 1, i_end = lua_rawlen(L, -1); i <= i_end; ++i) { lua_rawgeti(L, -1, i); if(!lua_istable(L, -1)) { return luaL_argerror(L, 3, "rules must be a table of tables"); } rules.push_back(gamemap::overlay_rule()); auto& rule = rules.back(); if(luaW_tableget(L, -1, "old")) { rule.old_ = t_translation::read_list(luaW_tostring(L, -1)); lua_pop(L, 1); } if(luaW_tableget(L, -1, "new")) { rule.new_ = t_translation::read_list(luaW_tostring(L, -1)); lua_pop(L, 1); } if(luaW_tableget(L, -1, "mode")) { auto str = luaW_tostring(L, -1); rule.mode_ = str == "base" ? terrain_type_data::BASE : (str == "overlay" ? terrain_type_data::OVERLAY : terrain_type_data::BOTH); lua_pop(L, 1); } if(luaW_tableget(L, -1, "terrain")) { const t_translation::ter_list terrain = t_translation::read_list(luaW_tostring(L, -1)); if(!terrain.empty()) { rule.terrain_ = terrain[0]; } lua_pop(L, 1); } if(luaW_tableget(L, -1, "use_old")) { rule.use_old_ = luaW_toboolean(L, -1); lua_pop(L, 1); } if(luaW_tableget(L, -1, "replace_if_failed")) { rule.replace_if_failed_ = luaW_toboolean(L, -1); lua_pop(L, 1); } lua_pop(L, 1); } lua_pop(L, 1); } } gamemap mask_map(board().map().tdata(), ""); mask_map.read(t_str, false); board().map_->overlay(mask_map, loc, rules, is_odd, ignore_special_locations); for(team& t : board().teams()) { t.fix_villages(board().map()); } if (game_display_) { game_display_->needs_rebuild(true); } return 0; } /** * Gets details about a terrain. * - Arg 1: terrain code string. * - Ret 1: table. */ int game_lua_kernel::intf_get_terrain_info(lua_State *L) { char const *m = luaL_checkstring(L, 1); t_translation::terrain_code t = t_translation::read_terrain_code(m); if (t == t_translation::NONE_TERRAIN) return 0; const terrain_type& info = board().map().tdata()->get_terrain_info(t); lua_newtable(L); lua_pushstring(L, info.id().c_str()); lua_setfield(L, -2, "id"); luaW_pushtstring(L, info.name()); lua_setfield(L, -2, "name"); luaW_pushtstring(L, info.editor_name()); lua_setfield(L, -2, "editor_name"); luaW_pushtstring(L, info.description()); lua_setfield(L, -2, "description"); lua_push(L, info.icon_image()); lua_setfield(L, -2, "icon"); lua_push(L, info.editor_image()); lua_setfield(L, -2, "editor_image"); lua_pushinteger(L, info.light_bonus(0)); lua_setfield(L, -2, "light"); lua_pushboolean(L, info.is_village()); lua_setfield(L, -2, "village"); lua_pushboolean(L, info.is_castle()); lua_setfield(L, -2, "castle"); lua_pushboolean(L, info.is_keep()); lua_setfield(L, -2, "keep"); lua_pushinteger(L, info.gives_healing()); lua_setfield(L, -2, "healing"); return 1; } /** * Gets time of day information. * - Arg 1: optional turn number * - Arg 2: optional location * - Arg 3: optional boolean (consider_illuminates) * - Ret 1: table. */ int game_lua_kernel::intf_get_time_of_day(lua_State *L) { unsigned arg = 1; int for_turn = tod_man().turn(); map_location loc = map_location(); bool consider_illuminates = false; if(lua_isnumber(L, arg)) { ++arg; for_turn = luaL_checkinteger(L, 1); int number_of_turns = tod_man().number_of_turns(); if(for_turn < 1 || (number_of_turns != -1 && for_turn > number_of_turns)) { return luaL_argerror(L, 1, "turn number out of range"); } } else if(lua_isnil(L, arg)) ++arg; if(luaW_tolocation(L, arg, loc)) { if(!board().map().on_board(loc)) return luaL_argerror(L, arg, "coordinates are not on board"); if(lua_istable(L, arg)) { lua_rawgeti(L, arg, 3); consider_illuminates = luaW_toboolean(L, -1); lua_pop(L, 1); } else if(lua_isboolean(L, arg + 1)) { consider_illuminates = luaW_toboolean(L, arg + 1); } } const time_of_day& tod = consider_illuminates ? tod_man().get_illuminated_time_of_day(board().units(), board().map(), loc, for_turn) : tod_man().get_time_of_day(loc, for_turn); lua_newtable(L); lua_pushstring(L, tod.id.c_str()); lua_setfield(L, -2, "id"); lua_pushinteger(L, tod.lawful_bonus); lua_setfield(L, -2, "lawful_bonus"); lua_pushinteger(L, tod.bonus_modified); lua_setfield(L, -2, "bonus_modified"); lua_pushstring(L, tod.image.c_str()); lua_setfield(L, -2, "image"); luaW_pushtstring(L, tod.name); lua_setfield(L, -2, "name"); lua_pushinteger(L, tod.color.r); lua_setfield(L, -2, "red"); lua_pushinteger(L, tod.color.g); lua_setfield(L, -2, "green"); lua_pushinteger(L, tod.color.b); lua_setfield(L, -2, "blue"); return 1; } /** * Gets the side of a village owner. * - Arg 1: map location. * - Ret 1: integer. */ int game_lua_kernel::intf_get_village_owner(lua_State *L) { map_location loc = luaW_checklocation(L, 1); if (!board().map().is_village(loc)) return 0; int side = board().village_owner(loc) + 1; if (!side) return 0; lua_pushinteger(L, side); return 1; } /** * Sets the owner of a village. * - Arg 1: map location. * - Arg 2: integer for the side or empty to remove ownership. */ int game_lua_kernel::intf_set_village_owner(lua_State *L) { map_location loc = luaW_checklocation(L, 1); if(!board().map().is_village(loc)) { return 0; } const int old_side_num = board().village_owner(loc) + 1; const int new_side_num = lua_isnoneornil(L, 2) ? 0 : luaL_checkinteger(L, 2); team* old_side = nullptr; team* new_side = nullptr; if(old_side_num == new_side_num) { return 0; } try { old_side = &board().get_team(old_side_num); } catch(const std::out_of_range&) { // old_side_num is invalid, most likely because the village wasn't captured. old_side = nullptr; } try { new_side = &board().get_team(new_side_num); } catch(const std::out_of_range&) { // new_side_num is invalid. new_side = nullptr; } // The new side was valid, but already defeated. Do nothing. if(new_side && board().team_is_defeated(*new_side)) { return 0; } // Even if the new side is not valid, we still want to remove the village from the old side. // This covers the case where new_side_num equals 0. The behavior in that case is to simply // un-assign the village from the old side, which of course we also want to happen if the new // side IS valid. If the village in question hadn't been captured, this won't fire (old_side // will be a nullptr). if(old_side) { old_side->lose_village(loc); } // If the new side was valid, re-assign the village. if(new_side) { new_side->get_village(loc, old_side_num, (luaW_toboolean(L, 3) ? &gamedata() : nullptr)); } return 0; } /** * Returns the map size. * - Ret 1: width. * - Ret 2: height. * - Ret 3: border size. */ int game_lua_kernel::intf_get_map_size(lua_State *L) { const gamemap &map = board().map(); lua_pushinteger(L, map.w()); lua_pushinteger(L, map.h()); lua_pushinteger(L, map.border_size()); return 3; } /** * Returns the currently overed tile. * - Ret 1: x. * - Ret 2: y. */ int game_lua_kernel::intf_get_mouseover_tile(lua_State *L) { if (!game_display_) { return 0; } const map_location &loc = game_display_->mouseover_hex(); if (!board().map().on_board(loc)) return 0; lua_pushinteger(L, loc.wml_x()); lua_pushinteger(L, loc.wml_y()); return 2; } /** * Returns the currently selected tile. * - Ret 1: x. * - Ret 2: y. */ int game_lua_kernel::intf_get_selected_tile(lua_State *L) { if (!game_display_) { return 0; } const map_location &loc = game_display_->selected_hex(); if (!board().map().on_board(loc)) return 0; lua_pushinteger(L, loc.wml_x()); lua_pushinteger(L, loc.wml_y()); return 2; } /** * Returns the starting position of a side. * Arg 1: side number * Ret 1: table with unnamed indices holding wml coordinates x and y */ int game_lua_kernel::intf_get_starting_location(lua_State* L) { int side; if(team* t = luaW_toteam(L, 1)) { side = t->side(); } else { side = luaL_checkinteger(L, 1); } if(side < 1 || static_cast(teams().size()) < side) return luaL_argerror(L, 1, "out of bounds"); const map_location& starting_pos = board().map().starting_position(side); if(!board().map().on_board(starting_pos)) return 0; luaW_pushlocation(L, starting_pos); return 1; } /** * Gets a table for an era tag. * - Arg 1: userdata (ignored). * - Arg 2: string containing id of the desired era * - Ret 1: config for the era */ static int intf_get_era(lua_State *L) { char const *m = luaL_checkstring(L, 1); luaW_pushconfig(L, game_config_manager::get()->game_config().find_child("era","id",m)); return 1; } /** * Gets some game_config data (__index metamethod). * - Arg 1: userdata (ignored). * - Arg 2: string containing the name of the property. * - Ret 1: something containing the attribute. */ int game_lua_kernel::impl_game_config_get(lua_State *L) { LOG_LUA << "impl_game_config_get\n"; char const *m = luaL_checkstring(L, 2); // Find the corresponding attribute. return_int_attrib("last_turn", tod_man().number_of_turns()); return_bool_attrib("do_healing", play_controller_.gamestate().do_healing_); return_string_attrib("next_scenario", gamedata().next_scenario()); return_string_attrib("theme", gamedata().get_theme()); return_string_attrib("scenario_id", gamedata().get_id()); return_vector_string_attrib("defeat_music", gamedata().get_defeat_music()); return_vector_string_attrib("victory_music", gamedata().get_victory_music()); const mp_game_settings& mp_settings = play_controller_.get_mp_settings(); const game_classification & classification = play_controller_.get_classification(); return_string_attrib("campaign_type", classification.campaign_type.to_string()); if(classification.campaign_type==game_classification::CAMPAIGN_TYPE::MULTIPLAYER) { return_cfgref_attrib("mp_settings", mp_settings.to_config()); return_cfgref_attrib("era", game_config_manager::get()->game_config().find_child("era","id",mp_settings.mp_era)); //^ finds the era with name matching mp_era, and creates a lua reference from the config of that era. //This code for SigurdFD, not the cleanest implementation but seems to work just fine. config::const_child_itors its = game_config_manager::get()->game_config().child_range("era"); std::string eras_list(its.front()["id"]); its.pop_front(); for(const auto& cfg : its) { eras_list = eras_list + "," + cfg["id"]; } return_string_attrib("eras", eras_list); } return lua_kernel_base::impl_game_config_get(L); } /** * Sets some game_config data (__newindex metamethod). * - Arg 1: userdata (ignored). * - Arg 2: string containing the name of the property. * - Arg 3: something containing the attribute. */ int game_lua_kernel::impl_game_config_set(lua_State *L) { LOG_LUA << "impl_game_config_set\n"; char const *m = luaL_checkstring(L, 2); // Find the corresponding attribute. modify_int_attrib("base_income", game_config::base_income = value); modify_int_attrib("village_income", game_config::village_income = value); modify_int_attrib("village_support", game_config::village_support = value); modify_int_attrib("poison_amount", game_config::poison_amount = value); modify_int_attrib("rest_heal_amount", game_config::rest_heal_amount = value); modify_int_attrib("recall_cost", game_config::recall_cost = value); modify_int_attrib("kill_experience", game_config::kill_experience = value); modify_int_attrib("combat_experience", game_config::combat_experience = value); modify_int_attrib("last_turn", tod_man().set_number_of_turns_by_wml(value)); modify_bool_attrib("do_healing", play_controller_.gamestate().do_healing_ = value); modify_string_attrib("next_scenario", gamedata().set_next_scenario(value)); modify_string_attrib("theme", gamedata().set_theme(value); const config& game_config = game_config_manager::get()->game_config(); game_display_->set_theme(play_controller_.get_theme(game_config, value)); ); modify_vector_string_attrib("defeat_music", gamedata().set_defeat_music(std::move(value))); modify_vector_string_attrib("victory_music", gamedata().set_victory_music(std::move(value))); return lua_kernel_base::impl_game_config_set(L); } /** converts synced_context::get_synced_state() to a string. */ std::string game_lua_kernel::synced_state() { //maybe return "initial" for game_data::INITIAL? if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::INITIAL) { return "preload"; } switch(synced_context::get_synced_state()) { case synced_context::LOCAL_CHOICE: return "local_choice"; case synced_context::SYNCED: return "synced"; case synced_context::UNSYNCED: return "unsynced"; default: throw game::game_error("Found corrupt synced_context::synced_state"); } } /** * Gets some data about current point of game (__index metamethod). * - Arg 1: userdata (ignored). * - Arg 2: string containing the name of the property. * - Ret 1: something containing the attribute. */ int game_lua_kernel::impl_current_get(lua_State *L) { char const *m = luaL_checkstring(L, 2); // Find the corresponding attribute. return_int_attrib("side", play_controller_.current_side()); return_int_attrib("turn", play_controller_.turn()); return_string_attrib("synced_state", synced_state()); 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); if (strcmp(m, "event_context") == 0) { const game_events::queued_event &ev = get_event_info(); config cfg; cfg["name"] = ev.name; cfg["id"] = ev.id; if (const config &weapon = ev.data.child("first")) { cfg.add_child("weapon", weapon); } if (const config &weapon = ev.data.child("second")) { cfg.add_child("second_weapon", weapon); } const config::attribute_value di = ev.data["damage_inflicted"]; if(!di.empty()) { cfg["damage_inflicted"] = di; } if (ev.loc1.valid()) { cfg["x1"] = ev.loc1.filter_loc().wml_x(); cfg["y1"] = ev.loc1.filter_loc().wml_y(); // 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 cfg["unit_x"] = ev.loc1.wml_x(); cfg["unit_y"] = ev.loc1.wml_y(); } if (ev.loc2.valid()) { cfg["x2"] = ev.loc2.filter_loc().wml_x(); cfg["y2"] = ev.loc2.filter_loc().wml_y(); } luaW_pushconfig(L, cfg); return 1; } return 0; } /** * Displays a message in the chat window and in the logs. * - Arg 1: optional message header. * - Arg 2 (or 1): message. */ int game_lua_kernel::intf_message(lua_State *L) { t_string m = luaW_checktstring(L, 1); t_string h = m; if (lua_isnone(L, 2)) { h = "Lua"; } else { m = luaW_checktstring(L, 2); } lua_chat(h, m); LOG_LUA << "Script says: \"" << m << "\"\n"; return 0; } int game_lua_kernel::intf_open_help(lua_State *L) { if (game_display_) { help::show_help(luaL_checkstring(L, 1)); } return 0; } int game_lua_kernel::intf_zoom(lua_State* L) { if(!game_display_) { return 0; } double factor = luaL_checknumber(L, 1); bool relative = luaW_toboolean(L, 2); if(relative) { factor *= game_display_->get_zoom_factor(); } // Passing true explicitly to avoid casting to int. // Without doing one of the two, the call is ambiguous. game_display_->set_zoom(factor * game_config::tile_size, true); lua_pushnumber(L, game_display_->get_zoom_factor()); return 1; } /** * Removes all messages from the chat window. */ int game_lua_kernel::intf_clear_messages(lua_State*) { if (game_display_) { game_display_->get_chat_manager().clear_chat_messages(); } return 0; } static int impl_end_level_data_get(lua_State* L) { const end_level_data& data = *static_cast(lua_touserdata(L, 1)); const char* m = luaL_checkstring(L, 2); return_bool_attrib("linger_mode", data.transient.linger_mode); return_bool_attrib("reveal_map", data.transient.reveal_map); return_bool_attrib("carryover_report", data.transient.carryover_report); return_bool_attrib("prescenario_save", data.prescenario_save); return_bool_attrib("replay_save", data.replay_save); return_bool_attrib("proceed_to_next_level", data.proceed_to_next_level); return_bool_attrib("is_victory", data.is_victory); return_bool_attrib("is_loss", !data.is_victory); return_cstring_attrib("result", data.is_victory ? "victory" : "loss"); // to match wesnoth.end_level() return_cfg_attrib("__cfg", data.to_config_full()); return 0; } namespace { struct end_level_committer { end_level_committer(end_level_data& data, play_controller& pc) : data_(data), pc_(pc) {} ~end_level_committer() { pc_.set_end_level_data(data_); } private: end_level_data& data_; play_controller& pc_; }; } int game_lua_kernel::impl_end_level_data_set(lua_State* L) { end_level_data& data = *static_cast(lua_touserdata(L, 1)); const char* m = luaL_checkstring(L, 2); end_level_committer commit(data, play_controller_); modify_bool_attrib("linger_mode", data.transient.linger_mode = value); modify_bool_attrib("reveal_map", data.transient.reveal_map = value); modify_bool_attrib("carryover_report", data.transient.carryover_report = value); modify_bool_attrib("prescenario_save", data.prescenario_save = value); modify_bool_attrib("replay_save", data.replay_save = value); return 0; } static int impl_end_level_data_collect(lua_State* L) { end_level_data* data = static_cast(lua_touserdata(L, 1)); UNUSED(data); // Suppress an erroneous MSVC warning (a destructor call doesn't count as a reference) data->~end_level_data(); return 0; } int game_lua_kernel::intf_get_end_level_data(lua_State* L) { if (!play_controller_.is_regular_game_end()) { return 0; } auto data = play_controller_.get_end_level_data_const(); new(L) end_level_data(data); if(luaL_newmetatable(L, "end level data")) { static luaL_Reg const callbacks[] { { "__index", &impl_end_level_data_get}, { "__newindex", &dispatch<&game_lua_kernel::impl_end_level_data_set>}, { "__gc", &impl_end_level_data_collect}, { nullptr, nullptr } }; luaL_setfuncs(L, callbacks, 0); } lua_setmetatable(L, -2); return 1; } int game_lua_kernel::intf_end_level(lua_State *L) { vconfig cfg(luaW_checkvconfig(L, 1)); end_level_data data; data.proceed_to_next_level = cfg["proceed_to_next_level"].to_bool(true); data.transient.carryover_report = cfg["carryover_report"].to_bool(true); data.prescenario_save = cfg["save"].to_bool(true); data.replay_save = cfg["replay_save"].to_bool(true); data.transient.linger_mode = cfg["linger_mode"].to_bool(true) && !teams().empty(); data.transient.reveal_map = cfg["reveal_map"].to_bool(true); data.is_victory = cfg["result"] == "victory"; play_controller_.set_end_level_data(data); return 0; } int game_lua_kernel::intf_end_turn(lua_State* L) { //note that next_player_number = 1, next_player_number = nteams+1 both set the next team to be the first team //but the later will make the turn counter change aswell fire turn end events accoringly etc. if (!lua_isnoneornil(L, 1)) { int npn = luaL_checknumber(L, 1); if (npn <= 0 /*TODO: || npn > 2*nteams*/) { return luaL_argerror(L, 1, "side number out of range"); } resources::controller->gamestate().next_player_number_ = npn; } play_controller_.force_end_turn(); return 0; } /** * Evaluates a boolean WML conditional. * - Arg 1: WML table. * - Ret 1: boolean. */ static int intf_eval_conditional(lua_State *L) { vconfig cond = luaW_checkvconfig(L, 1); bool b = game_events::conditional_passed(cond); lua_pushboolean(L, b); return 1; } /** * Finds a path between two locations. * - Arg 1: source location. (Or Arg 1: unit.) * - Arg 2: destination. * - Arg 3: optional cost function or * table (optional fields: ignore_units, ignore_teleport, max_cost, viewing_side). * - Ret 1: array of pairs containing path steps. * - Ret 2: path cost. */ int game_lua_kernel::intf_find_path(lua_State *L) { int arg = 1; map_location src, dst; const unit* u = nullptr; if (lua_isuserdata(L, arg)) { u = &luaW_checkunit(L, arg); src = u->get_location(); ++arg; } else { src = luaW_checklocation(L, arg); unit_map::const_unit_iterator ui = units().find(src); if (ui.valid()) { u = ui.get_shared_ptr().get(); } ++arg; } dst = luaW_checklocation(L, arg); ++arg; if (!board().map().on_board(src)) return luaL_argerror(L, 1, "invalid location"); if (!board().map().on_board(dst)) return luaL_argerror(L, arg - 2, "invalid location"); const gamemap &map = board().map(); int viewing_side = 0; bool ignore_units = false, see_all = false, ignore_teleport = false; double stop_at = 10000; std::unique_ptr calc; if (lua_istable(L, arg)) { ignore_units = luaW_table_get_def(L, arg, "ignore_units", false); ignore_teleport = luaW_table_get_def(L, arg, "ignore_teleport", false); stop_at = luaW_table_get_def(L, arg, "stop_at", stop_at); lua_pushstring(L, "viewing_side"); lua_rawget(L, arg); if (!lua_isnil(L, -1)) { int i = luaL_checkinteger(L, -1); if (i >= 1 && i <= static_cast(teams().size())) viewing_side = i; else see_all = true; } lua_pop(L, 1); lua_pushstring(L, "calculate"); lua_rawget(L, arg); if(lua_isfunction(L, -1)) { calc.reset(new lua_pathfind_cost_calculator(L, lua_gettop(L))); } // Don't pop, the lua_pathfind_cost_calculator requires it to stay on the stack. } else if (lua_isfunction(L, arg)) { deprecated_message("wesnoth.find_path with cost_function as last argument", DEP_LEVEL::FOR_REMOVAL, {1, 17, 0}, "Use calculate=cost_function inside the path options table instead."); calc.reset(new lua_pathfind_cost_calculator(L, arg)); } const team& viewing_team = viewing_side ? board().get_team(viewing_side) : board().get_team(u->side()); pathfind::teleport_map teleport_locations; if(!ignore_teleport) { teleport_locations = pathfind::get_teleport_locations(*u, viewing_team, see_all, ignore_units); } if (!calc) { if (!u) return luaL_argerror(L, 1, "unit not found"); calc.reset(new pathfind::shortest_path_calculator(*u, viewing_team, teams(), map, ignore_units, false, see_all)); } pathfind::plain_route res = pathfind::a_star_search(src, dst, stop_at, *calc, map.w(), map.h(), &teleport_locations); int nb = res.steps.size(); lua_createtable(L, nb, 0); for (int i = 0; i < nb; ++i) { lua_createtable(L, 2, 0); lua_pushinteger(L, res.steps[i].wml_x()); lua_rawseti(L, -2, 1); lua_pushinteger(L, res.steps[i].wml_y()); lua_rawseti(L, -2, 2); lua_rawseti(L, -2, i + 1); } lua_pushinteger(L, res.move_cost); return 2; } /** * Finds all the locations reachable by a unit. * - Arg 1: source location OR unit. * - Arg 2: optional table (optional fields: ignore_units, ignore_teleport, additional_turns, viewing_side). * - Ret 1: array of triples (coordinates + remaining movement). */ int game_lua_kernel::intf_find_reach(lua_State *L) { int arg = 1; const unit* u = nullptr; if (lua_isuserdata(L, arg)) { u = &luaW_checkunit(L, arg); ++arg; } else { map_location src = luaW_checklocation(L, arg); unit_map::const_unit_iterator ui = units().find(src); if (!ui.valid()) return luaL_argerror(L, 1, "unit not found"); u = ui.get_shared_ptr().get(); ++arg; } int viewing_side = 0; bool ignore_units = false, see_all = false, ignore_teleport = false; int additional_turns = 0; if (lua_istable(L, arg)) { lua_pushstring(L, "ignore_units"); lua_rawget(L, arg); ignore_units = luaW_toboolean(L, -1); lua_pop(L, 1); lua_pushstring(L, "ignore_teleport"); lua_rawget(L, arg); ignore_teleport = luaW_toboolean(L, -1); lua_pop(L, 1); lua_pushstring(L, "additional_turns"); lua_rawget(L, arg); additional_turns = lua_tointeger(L, -1); lua_pop(L, 1); lua_pushstring(L, "viewing_side"); lua_rawget(L, arg); if (!lua_isnil(L, -1)) { int i = luaL_checkinteger(L, -1); if (i >= 1 && i <= static_cast(teams().size())) viewing_side = i; else see_all = true; } lua_pop(L, 1); } const team& viewing_team = viewing_side ? board().get_team(viewing_side) : board().get_team(u->side()); pathfind::paths res(*u, ignore_units, !ignore_teleport, viewing_team, additional_turns, see_all, ignore_units); int nb = res.destinations.size(); lua_createtable(L, nb, 0); for (int i = 0; i < nb; ++i) { pathfind::paths::step &s = res.destinations[i]; lua_createtable(L, 2, 0); lua_pushinteger(L, s.curr.wml_x()); lua_rawseti(L, -2, 1); lua_pushinteger(L, s.curr.wml_y()); lua_rawseti(L, -2, 2); lua_pushinteger(L, s.move_left); lua_rawseti(L, -2, 3); lua_rawseti(L, -2, i + 1); } return 1; } static bool intf_find_cost_map_helper(const unit * ptr) { return ptr->get_location().valid(); } template // This is only a template so I can avoid typing out the long typename. >_> static int load_fake_units(lua_State* L, int arg, T& fake_units) { for (int i = 1, i_end = lua_rawlen(L, arg); i <= i_end; ++i) { map_location src; lua_rawgeti(L, arg, i); int entry = lua_gettop(L); if (!lua_istable(L, entry)) { goto error; } if (!luaW_tolocation(L, entry, src)) { goto error; } lua_rawgeti(L, entry, 3); if (!lua_isnumber(L, -1)) { lua_getfield(L, entry, "side"); if (!lua_isnumber(L, -1)) { goto error; } } int side = lua_tointeger(L, -1); lua_rawgeti(L, entry, 4); if (!lua_isstring(L, -1)) { lua_getfield(L, entry, "type"); if (!lua_isstring(L, -1)) { goto error; } } std::string unit_type = lua_tostring(L, -1); fake_units.emplace_back(src, side, unit_type); lua_settop(L, entry - 1); } return 0; error: 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"); } /** * Is called with one or more units and builds a cost map. * - Arg 1: source location. (Or Arg 1: unit. Or Arg 1: table containing a filter) * - Arg 2: optional array of tables with 4 elements (coordinates + side + unit type string) * - Arg 3: optional table (optional fields: ignore_units, ignore_teleport, viewing_side, debug). * - Arg 4: optional table: standard location filter. * - Ret 1: array of triples (coordinates + array of tuples(summed cost + reach counter)). */ int game_lua_kernel::intf_find_cost_map(lua_State *L) { int arg = 1; unit* unit = luaW_tounit(L, arg, true); vconfig filter = vconfig::unconstructed_vconfig(); luaW_tovconfig(L, arg, filter); std::vector real_units; typedef std::vector> unit_type_vector; unit_type_vector fake_units; if (unit) // 1. arg - unit { real_units.push_back(unit); } else if (!filter.null()) // 1. arg - filter { boost::copy(unit_filter(filter).all_matches_on_map() | boost::adaptors::filtered(&intf_find_cost_map_helper), std::back_inserter(real_units)); } else // 1. arg - coordinates { map_location src = luaW_checklocation(L, arg); unit_map::const_unit_iterator ui = units().find(src); if (ui.valid()) { real_units.push_back(&(*ui)); } } ++arg; if (lua_istable(L, arg)) // 2. arg - optional types { load_fake_units(L, arg, fake_units); ++arg; } if(real_units.empty() && fake_units.empty()) { return luaL_argerror(L, 1, "unit(s) not found"); } int viewing_side = 0; bool ignore_units = true, see_all = true, ignore_teleport = false, debug = false, use_max_moves = false; if (lua_istable(L, arg)) // 4. arg - options { lua_pushstring(L, "ignore_units"); lua_rawget(L, arg); if (!lua_isnil(L, -1)) { ignore_units = luaW_toboolean(L, -1); } lua_pop(L, 1); lua_pushstring(L, "ignore_teleport"); lua_rawget(L, arg); if (!lua_isnil(L, -1)) { ignore_teleport = luaW_toboolean(L, -1); } lua_pop(L, 1); lua_pushstring(L, "viewing_side"); lua_rawget(L, arg); if (!lua_isnil(L, -1)) { int i = luaL_checkinteger(L, -1); if (i >= 1 && i <= static_cast(teams().size())) { viewing_side = i; see_all = false; } } lua_pushstring(L, "debug"); lua_rawget(L, arg); if (!lua_isnil(L, -1)) { debug = luaW_toboolean(L, -1); } lua_pop(L, 1); lua_pushstring(L, "use_max_moves"); lua_rawget(L, arg); if (!lua_isnil(L, -1)) { use_max_moves = luaW_toboolean(L, -1); } lua_pop(L, 1); ++arg; } // 5. arg - location filter filter = vconfig::unconstructed_vconfig(); std::set location_set; luaW_tovconfig(L, arg, filter); if (filter.null()) { filter = vconfig(config(), true); } filter_context & fc = game_state_; const terrain_filter t_filter(filter, &fc); t_filter.get_locations(location_set, true); ++arg; // build cost_map const team& viewing_team = viewing_side ? board().get_team(viewing_side) : board().teams()[0]; pathfind::full_cost_map cost_map( ignore_units, !ignore_teleport, viewing_team, see_all, ignore_units); for (const ::unit* const u : real_units) { cost_map.add_unit(*u, use_max_moves); } for (const unit_type_vector::value_type& fu : fake_units) { const unit_type* ut = unit_types.find(std::get<2>(fu)); cost_map.add_unit(std::get<0>(fu), ut, std::get<1>(fu)); } if (debug) { if (game_display_) { game_display_->labels().clear_all(); for (const map_location& loc : location_set) { std::stringstream s; s << cost_map.get_pair_at(loc.x, loc.y).first; s << " / "; s << cost_map.get_pair_at(loc.x, loc.y).second; game_display_->labels().set_label(loc, s.str()); } } } // create return value lua_createtable(L, location_set.size(), 0); int counter = 1; for (const map_location& loc : location_set) { lua_createtable(L, 4, 0); lua_pushinteger(L, loc.wml_x()); lua_rawseti(L, -2, 1); lua_pushinteger(L, loc.wml_y()); lua_rawseti(L, -2, 2); lua_pushinteger(L, cost_map.get_pair_at(loc.x, loc.y).first); lua_rawseti(L, -2, 3); lua_pushinteger(L, cost_map.get_pair_at(loc.x, loc.y).second); lua_rawseti(L, -2, 4); lua_rawseti(L, -2, counter); ++counter; } return 1; } int game_lua_kernel::intf_print(lua_State *L) { vconfig cfg(luaW_checkvconfig(L, 1)); // Remove any old message. static int floating_label = 0; if (floating_label) font::remove_floating_label(floating_label); // Display a message on-screen std::string text = cfg["text"]; if(text.empty() || !game_display_) return 0; int size = cfg["size"].to_int(font::SIZE_SMALL); int lifetime = cfg["duration"].to_int(50); color_t color = font::LABEL_COLOR; if(!cfg["color"].empty()) { color = color_t::from_rgb_string(cfg["color"]); } else if(cfg.has_attribute("red") || cfg.has_attribute("green") || cfg.has_attribute("blue")) { color = color_t(cfg["red"], cfg["green"], cfg["blue"]); } const SDL_Rect& rect = game_display_->map_outside_area(); font::floating_label flabel(text); flabel.set_font_size(size); flabel.set_color(color); flabel.set_position(rect.x + rect.w/2, rect.y + rect.h/2); flabel.set_lifetime(lifetime); flabel.set_clip_rect(rect); floating_label = font::add_floating_label(flabel); return 0; } void game_lua_kernel::put_unit_helper(const map_location& loc) { if(game_display_) { game_display_->invalidate(loc); } units().erase(loc); resources::whiteboard->on_kill_unit(); } /** * Places a unit on the map. * - Arg 1: (optional) location. * - Arg 2: Unit (WML table or proxy), or nothing/nil to delete. * OR * - Arg 1: Unit (WML table or proxy) * - Arg 2: (optional) location * - Arg 3: (optional) boolean */ int game_lua_kernel::intf_put_unit(lua_State *L) { if(map_locked_) { return luaL_error(L, "Attempted to move a unit while the map is locked"); } map_location loc; if (luaW_tolocation(L, 2, loc)) { if (!map().on_board(loc)) { return luaL_argerror(L, 2, "invalid location"); } } if((luaW_isunit(L, 1))) { lua_unit& u = *luaW_checkunit_ref(L, 1); if(u.on_map() && u->get_location() == loc) { return 0; } if (!loc.valid()) { loc = u->get_location(); if (!map().on_board(loc)) return luaL_argerror(L, 1, "invalid location"); } put_unit_helper(loc); u.put_map(loc); u.get_shared()->anim_comp().set_standing(); } else if(!lua_isnoneornil(L, 1)) { const vconfig* vcfg = nullptr; config cfg = luaW_checkconfig(L, 1, vcfg); if (!map().on_board(loc)) { loc.set_wml_x(cfg["x"]); loc.set_wml_y(cfg["y"]); if (!map().on_board(loc)) return luaL_argerror(L, 2, "invalid location"); } unit_ptr u = unit::create(cfg, true, vcfg); put_unit_helper(loc); u->set_location(loc); units().insert(u); } // Fire event if using the deprecated version or if the final argument is not false // If the final boolean argument is omitted, the actual final argument (the unit or location) will always yield true. if(luaW_toboolean(L, -1)) { play_controller_.pump().fire("unit_placed", loc); } return 0; } /** * Erases a unit from the map * - Arg 1: Unit to erase OR Location to erase unit */ int game_lua_kernel::intf_erase_unit(lua_State *L) { if(map_locked_) { return luaL_error(L, "Attempted to remove a unit while the map is locked"); } map_location loc; if(luaW_isunit(L, 1)) { lua_unit& u = *luaW_checkunit_ref(L, 1); if (u.on_map()) { loc = u->get_location(); if (!map().on_board(loc)) { return luaL_argerror(L, 1, "invalid location"); } } else if (int side = u.on_recall_list()) { team &t = board().get_team(side); // Should it use underlying ID instead? t.recall_list().erase_if_matches_id(u->id()); } else { return luaL_argerror(L, 1, "can't erase private units"); } } else if (luaW_tolocation(L, 1, loc)) { if (!map().on_board(loc)) { return luaL_argerror(L, 1, "invalid location"); } } else { return luaL_argerror(L, 1, "expected unit or location"); } units().erase(loc); resources::whiteboard->on_kill_unit(); return 0; } /** * Puts a unit on a recall list. * - Arg 1: WML table or unit. * - Arg 2: (optional) side. */ int game_lua_kernel::intf_put_recall_unit(lua_State *L) { if(map_locked_) { return luaL_error(L, "Attempted to move a unit while the map is locked"); } lua_unit *lu = nullptr; unit_ptr u = unit_ptr(); int side = lua_tointeger(L, 2); if (static_cast(side) > teams().size()) side = 0; if(luaW_isunit(L, 1)) { lu = luaW_checkunit_ref(L, 1); u = lu->get_shared(); if(lu->on_recall_list() && lu->on_recall_list() == side) { return luaL_argerror(L, 1, "unit already on recall list"); } } else { const vconfig* vcfg = nullptr; config cfg = luaW_checkconfig(L, 1, vcfg); u = unit::create(cfg, true, vcfg); } if (!side) { side = u->side(); } else { u->set_side(side); } team &t = board().get_team(side); // Avoid duplicates in the recall list. std::size_t uid = u->underlying_id(); t.recall_list().erase_by_underlying_id(uid); t.recall_list().add(u); if (lu) { if (lu->on_map()) { units().erase(u->get_location()); resources::whiteboard->on_kill_unit(); u->anim_comp().clear_haloes(); } lu->lua_unit::~lua_unit(); new(lu) lua_unit(side, uid); } return 0; } /** * Extracts a unit from the map or a recall list and gives it to Lua. * - Arg 1: unit userdata. */ int game_lua_kernel::intf_extract_unit(lua_State *L) { if(map_locked_) { return luaL_error(L, "Attempted to remove a unit while the map is locked"); } lua_unit* lu = luaW_checkunit_ref(L, 1); unit_ptr u = lu->get_shared(); if (lu->on_map()) { u = units().extract(u->get_location()); assert(u); u->anim_comp().clear_haloes(); } else if (int side = lu->on_recall_list()) { team &t = board().get_team(side); unit_ptr v = u->clone(); t.recall_list().erase_if_matches_id(u->id()); u = v; } else { return 0; } lu->lua_unit::~lua_unit(); new(lu) lua_unit(u); return 0; } /** * Finds a vacant tile. * - Arg 1: location. * - Arg 2: optional unit for checking movement type. * - Rets 1,2: location. */ int game_lua_kernel::intf_find_vacant_tile(lua_State *L) { map_location loc = luaW_checklocation(L, 1); unit_ptr u; if (!lua_isnoneornil(L, 2)) { if(luaW_isunit(L, 2)) { u = luaW_checkunit_ptr(L, 2, false); } else { const vconfig* vcfg = nullptr; config cfg = luaW_checkconfig(L, 2, vcfg); u = unit::create(cfg, false, vcfg); } } map_location res = find_vacant_tile(loc, pathfind::VACANT_ANY, u.get()); if (!res.valid()) return 0; lua_pushinteger(L, res.wml_x()); lua_pushinteger(L, res.wml_y()); return 2; } /** * Floats some text on the map. * - Arg 1: location. * - Arg 2: string. * - Arg 3: color. */ int game_lua_kernel::intf_float_label(lua_State *L) { map_location loc = luaW_checklocation(L, 1); color_t color = font::LABEL_COLOR; t_string text = luaW_checktstring(L, 2); if (!lua_isnoneornil(L, 3)) { color = color_t::from_rgb_string(luaL_checkstring(L, 3)); } if (game_display_) { game_display_->float_label(loc, text, color); } return 0; } /** * Creates a unit from its WML description. * - Arg 1: WML table. * - Ret 1: unit userdata. */ static int intf_create_unit(lua_State *L) { const vconfig* vcfg = nullptr; config cfg = luaW_checkconfig(L, 1, vcfg); unit_ptr u = unit::create(cfg, true, vcfg); luaW_pushunit(L, u); return 1; } /** * Copies a unit. * - Arg 1: unit userdata. * - Ret 1: unit userdata. */ static int intf_copy_unit(lua_State *L) { unit& u = luaW_checkunit(L, 1); luaW_pushunit(L, u.clone()); return 1; } /** * Returns unit resistance against a given attack type. * - Arg 1: unit userdata. * - Arg 2: string containing the attack type. * - Arg 3: boolean indicating if attacker. * - Arg 4: optional location. * - Ret 1: integer. */ static int intf_unit_resistance(lua_State *L) { const unit& u = luaW_checkunit(L, 1); char const *m = luaL_checkstring(L, 2); bool a = false; map_location loc = u.get_location(); if(lua_isboolean(L, 3)) { a = luaW_toboolean(L, 3); if(!lua_isnoneornil(L, 4)) { loc = luaW_checklocation(L, 4); } } else if(!lua_isnoneornil(L, 3)) { loc = luaW_checklocation(L, 3); } lua_pushinteger(L, 100 - u.resistance_against(m, a, loc)); return 1; } /** * Returns unit movement cost on a given terrain. * - Arg 1: unit userdata. * - Arg 2: string containing the terrain type. * - Ret 1: integer. */ static int intf_unit_movement_cost(lua_State *L) { const unit& u = luaW_checkunit(L, 1); t_translation::terrain_code t; map_location loc; if(luaW_tolocation(L, 2, loc)) { t = resources::gameboard->map()[loc]; } else if(lua_isstring(L, 2)) { char const *m = luaL_checkstring(L, 2); t = t_translation::read_terrain_code(m); } else return luaW_type_error(L, 2, "location or terrain string"); lua_pushinteger(L, u.movement_cost(t)); return 1; } /** * Returns unit vision cost on a given terrain. * - Arg 1: unit userdata. * - Arg 2: string containing the terrain type. * - Ret 1: integer. */ static int intf_unit_vision_cost(lua_State *L) { const unit& u = luaW_checkunit(L, 1); t_translation::terrain_code t; map_location loc; if(luaW_tolocation(L, 2, loc)) { t = resources::gameboard->map()[loc]; } else if(lua_isstring(L, 2)) { char const *m = luaL_checkstring(L, 2); t = t_translation::read_terrain_code(m); } else return luaW_type_error(L, 2, "location or terrain string"); lua_pushinteger(L, u.vision_cost(t)); return 1; } /** * Returns unit jamming cost on a given terrain. * - Arg 1: unit userdata. * - Arg 2: string containing the terrain type. * - Ret 1: integer. */ static int intf_unit_jamming_cost(lua_State *L) { const unit& u = luaW_checkunit(L, 1); t_translation::terrain_code t; map_location loc; if(luaW_tolocation(L, 2, loc)) { t = resources::gameboard->map()[loc]; } else if(lua_isstring(L, 2)) { char const *m = luaL_checkstring(L, 2); t = t_translation::read_terrain_code(m); } else return luaW_type_error(L, 2, "location or terrain string"); lua_pushinteger(L, u.jamming_cost(t)); return 1; } /** * Returns unit defense on a given terrain. * - Arg 1: unit userdata. * - Arg 2: string containing the terrain type. * - Ret 1: integer. */ static int intf_unit_defense(lua_State *L) { const unit& u = luaW_checkunit(L, 1); t_translation::terrain_code t; map_location loc; if(luaW_tolocation(L, 2, loc)) { t = resources::gameboard->map()[loc]; } else if(lua_isstring(L, 2)) { char const *m = luaL_checkstring(L, 2); t = t_translation::read_terrain_code(m); } else return luaW_type_error(L, 2, "location or terrain string"); lua_pushinteger(L, 100 - u.defense_modifier(t)); return 1; } /** * Returns true if the unit has the given ability enabled. * - Arg 1: unit userdata. * - Arg 2: string. * - Ret 1: boolean. */ int game_lua_kernel::intf_unit_ability(lua_State *L) { const unit& u = luaW_checkunit(L, 1); char const *m = luaL_checkstring(L, 2); lua_pushboolean(L, u.get_ability_bool(m)); return 1; } /** * Changes a unit to the given unit type. * - Arg 1: unit userdata. * - Arg 2: unit type name * - Arg 3: (optional) unit variation name */ static int intf_transform_unit(lua_State *L) { unit& u = luaW_checkunit(L, 1); char const *m = luaL_checkstring(L, 2); const unit_type *utp = unit_types.find(m); if (!utp) return luaL_argerror(L, 2, "unknown unit type"); if(lua_isstring(L, 3)) { const std::string& m2 = lua_tostring(L, 3); if(!utp->has_variation(m2)) return luaL_argerror(L, 2, "unknown unit variation"); utp = &utp->get_variation(m2); } u.advance_to(*utp); return 0; } /** * Puts a table at the top of the stack with some combat result. */ static void luaW_pushsimdata(lua_State *L, const combatant &cmb) { int n = cmb.hp_dist.size(); lua_createtable(L, 0, 4); lua_pushnumber(L, cmb.poisoned); lua_setfield(L, -2, "poisoned"); lua_pushnumber(L, cmb.slowed); lua_setfield(L, -2, "slowed"); lua_pushnumber(L, cmb.untouched); lua_setfield(L, -2, "untouched"); lua_pushnumber(L, cmb.average_hp()); lua_setfield(L, -2, "average_hp"); lua_createtable(L, n, 0); for (int i = 0; i < n; ++i) { lua_pushnumber(L, cmb.hp_dist[i]); lua_rawseti(L, -2, i); } lua_setfield(L, -2, "hp_chance"); } /** * Puts a table at the top of the stack with information about the combatants' weapons. */ static void luaW_pushsimweapon(lua_State *L, const battle_context_unit_stats &bcustats) { lua_createtable(L, 0, 16); lua_pushnumber(L, bcustats.num_blows); lua_setfield(L, -2, "num_blows"); lua_pushnumber(L, bcustats.damage); lua_setfield(L, -2, "damage"); lua_pushnumber(L, bcustats.chance_to_hit); lua_setfield(L, -2, "chance_to_hit"); lua_pushboolean(L, bcustats.poisons); lua_setfield(L, -2, "poisons"); lua_pushboolean(L, bcustats.slows); lua_setfield(L, -2, "slows"); lua_pushboolean(L, bcustats.petrifies); lua_setfield(L, -2, "petrifies"); lua_pushboolean(L, bcustats.plagues); lua_setfield(L, -2, "plagues"); lua_pushstring(L, bcustats.plague_type.c_str()); lua_setfield(L, -2, "plague_type"); lua_pushboolean(L, bcustats.backstab_pos); lua_setfield(L, -2, "backstabs"); lua_pushnumber(L, bcustats.rounds); lua_setfield(L, -2, "rounds"); lua_pushboolean(L, bcustats.firststrike); lua_setfield(L, -2, "firststrike"); lua_pushboolean(L, bcustats.drains); lua_setfield(L, -2, "drains"); lua_pushnumber(L, bcustats.drain_constant); lua_setfield(L, -2, "drain_constant"); lua_pushnumber(L, bcustats.drain_percent); lua_setfield(L, -2, "drain_percent"); //if we called simulate_combat without giving an explicit weapon this can be useful. lua_pushnumber(L, bcustats.attack_num); lua_setfield(L, -2, "attack_num"); // DEPRECATED lua_pushnumber(L, bcustats.attack_num + 1); lua_setfield(L, -2, "number"); //this is nullptr when there is no counter weapon if(bcustats.weapon != nullptr) { lua_pushstring(L, bcustats.weapon->id().c_str()); lua_setfield(L, -2, "name"); luaW_pushweapon(L, bcustats.weapon); lua_setfield(L, -2, "weapon"); } } /** * Simulates a combat between two units. * - Arg 1: attacker userdata. * - Arg 2: optional weapon index. * - Arg 3: defender userdata. * - Arg 4: optional weapon index. * * - Ret 1: attacker results. * - Ret 2: defender results. * - Ret 3: info about the attacker weapon. * - Ret 4: info about the defender weapon. */ int game_lua_kernel::intf_simulate_combat(lua_State *L) { int arg_num = 1, att_w = -1, def_w = -1; unit_const_ptr att = luaW_checkunit(L, arg_num).shared_from_this(); ++arg_num; if (lua_isnumber(L, arg_num)) { att_w = lua_tointeger(L, arg_num) - 1; if (att_w < 0 || att_w >= static_cast(att->attacks().size())) return luaL_argerror(L, arg_num, "weapon index out of bounds"); ++arg_num; } unit_const_ptr def = luaW_checkunit(L, arg_num).shared_from_this(); ++arg_num; if (lua_isnumber(L, arg_num)) { def_w = lua_tointeger(L, arg_num) - 1; if (def_w < 0 || def_w >= static_cast(def->attacks().size())) return luaL_argerror(L, arg_num, "weapon index out of bounds"); ++arg_num; } battle_context context(units(), att->get_location(), def->get_location(), att_w, def_w, 0.0, nullptr, att.get(), def.get()); luaW_pushsimdata(L, context.get_attacker_combatant()); luaW_pushsimdata(L, context.get_defender_combatant()); luaW_pushsimweapon(L, context.get_attacker_stats()); luaW_pushsimweapon(L, context.get_defender_stats()); return 4; } /** * Modifies the music playlist. * - Arg 1: WML table, or nil to force changes. */ static int intf_set_music(lua_State *L) { deprecated_message("wesnoth.set_music", DEP_LEVEL::INDEFINITE, "", "Use the wesnoth.playlist table instead!"); if (lua_isnoneornil(L, 1)) { sound::commit_music_changes(); return 0; } config cfg = luaW_checkconfig(L, 1); sound::play_music_config(cfg); return 0; } /** * Plays a sound, possibly repeated. * - Arg 1: string. * - Arg 2: optional integer. */ int game_lua_kernel::intf_play_sound(lua_State *L) { char const *m = luaL_checkstring(L, 1); if (play_controller_.is_skipping_replay()) return 0; int repeats = lua_tointeger(L, 2); sound::play_sound(m, sound::SOUND_FX, repeats); return 0; } /** * Gets/sets the current sound volume * - Arg 1: (optional) New volume to set * - Return: Original volume */ static int intf_sound_volume(lua_State* L) { int vol = preferences::sound_volume(); lua_pushnumber(L, sound::get_sound_volume() * 100.0 / vol); if(lua_isnumber(L, 1)) { float rel = lua_tonumber(L, 1); if(rel < 0.0f || rel > 100.0f) { return luaL_argerror(L, 1, "volume must be in range 0..100"); } vol = static_cast(rel*vol / 100.0f); sound::set_sound_volume(vol); } return 1; } /** * Scrolls to given tile. * - Arg 1: location. * - Arg 2: boolean preventing scroll to fog. * - Arg 3: boolean specifying whether to warp instantly. * - Arg 4: boolean specifying whether to skip if already onscreen */ int game_lua_kernel::intf_scroll_to_tile(lua_State *L) { map_location loc = luaW_checklocation(L, 1); bool check_fogged = luaW_toboolean(L, 2); game_display::SCROLL_TYPE scroll = luaW_toboolean(L, 4) ? luaW_toboolean(L, 3) ? game_display::ONSCREEN_WARP : game_display::ONSCREEN : luaW_toboolean(L, 3) ? game_display::WARP : game_display::SCROLL ; if (game_display_) { game_display_->scroll_to_tile(loc, scroll, check_fogged); } return 0; } int game_lua_kernel::intf_select_hex(lua_State *L) { events::command_disabler command_disabler; deprecated_message("wesnoth.select_hex", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use wesnoth.units.select and/or wesnoth.interface.highlight_hex instead."); // Need this because check_location may change the stack // By doing this now, we ensure that it won't do so when // intf_select_unit and intf_highlight_hex call it. const map_location loc = luaW_checklocation(L, 1); luaW_pushlocation(L, loc); lua_replace(L, 1); intf_select_unit(L); if(!lua_isnoneornil(L, 2) && luaW_toboolean(L,2)) { intf_highlight_hex(L); } return 0; } /** * Selects and highlights the given location on the map. * - Arg 1: location. * - Args 2,3: booleans */ int game_lua_kernel::intf_select_unit(lua_State *L) { events::command_disabler command_disabler; if(lua_isnoneornil(L, 1)) { play_controller_.get_mouse_handler_base().select_hex(map_location::null_location(), false, false, false); return 0; } const map_location loc = luaW_checklocation(L, 1); if(!map().on_board(loc)) return luaL_argerror(L, 1, "not on board"); bool highlight = true; if(!lua_isnoneornil(L, 2)) highlight = luaW_toboolean(L, 2); const bool fire_event = luaW_toboolean(L, 3); play_controller_.get_mouse_handler_base().select_hex( loc, false, highlight, fire_event); return 0; } /** * Deselects any highlighted hex on the map. * No arguments or return values */ int game_lua_kernel::intf_deselect_hex(lua_State*) { if(game_display_) { game_display_->highlight_hex(map_location::null_location()); } return 0; } /** * Return true if a replay is in progress but the player has chosen to skip it */ int game_lua_kernel::intf_is_skipping_messages(lua_State *L) { bool skipping = play_controller_.is_skipping_replay() || play_controller_.is_skipping_story(); if (!skipping) { skipping = game_state_.events_manager_->pump().context_skip_messages(); } lua_pushboolean(L, skipping); return 1; } /** * Set whether to skip messages * Arg 1 (optional) - boolean */ int game_lua_kernel::intf_skip_messages(lua_State *L) { bool skip = true; if (!lua_isnone(L, 1)) { skip = luaW_toboolean(L, 1); } game_state_.events_manager_->pump().context_skip_messages(skip); return 0; } namespace { struct lua_synchronize : mp_sync::user_choice { lua_State *L; int user_choice_index; int random_choice_index; int ai_choice_index; std::string desc; lua_synchronize(lua_State *l, const std::string& descr, int user_index, int random_index = 0, int ai_index = 0) : L(l) , user_choice_index(user_index) , random_choice_index(random_index) , ai_choice_index(ai_index != 0 ? ai_index : user_index) , desc(descr) {} virtual config query_user(int side) const override { bool is_local_ai = lua_kernel_base::get_lua_kernel(L).board().get_team(side).is_local_ai(); config cfg; query_lua(side, is_local_ai ? ai_choice_index : user_choice_index, cfg); return cfg; } virtual config random_choice(int side) const override { config cfg; if(random_choice_index != 0 && lua_isfunction(L, random_choice_index)) { query_lua(side, random_choice_index, cfg); } return cfg; } virtual std::string description() const override { return desc; } void query_lua(int side, int function_index, config& cfg) const { assert(cfg.empty()); lua_pushvalue(L, function_index); lua_pushnumber(L, side); if (luaW_pcall(L, 1, 1, false)) { if(!luaW_toconfig(L, -1, cfg)) { lua_kernel_base::get_lua_kernel(L).log_error("function returned to wesnoth.synchronize_choice a table which was partially invalid"); } } } //Although lua's sync_choice can show a dialog, (and will in most cases) //we return false to enable other possible things that do not contain UI things. //it's in the responsibility of the umc dev to not show dialogs during prestart events. virtual bool is_visible() const override { return false; } }; }//unnamed namespace for lua_synchronize /** * Ensures a value is synchronized among all the clients. * - Arg 1: optional string specifying the type id of the choice. * - Arg 2: function to compute the value, called if the client is the master. * - Arg 3: optional function, called instead of the first function if the user is not human. * - Arg 4: optional integer specifying, on which side the function should be evaluated. * - Ret 1: WML table returned by the function. */ static int intf_synchronize_choice(lua_State *L) { std::string tagname = "input"; t_string desc = _("input"); int human_func = 0; int ai_func = 0; int side_for; int nextarg = 1; if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) { ++nextarg; } if(lua_isfunction(L, nextarg)) { human_func = nextarg++; } else { return luaL_argerror(L, nextarg, "expected a function"); } if(lua_isfunction(L, nextarg)) { ai_func = nextarg++; } side_for = lua_tointeger(L, nextarg); config cfg = mp_sync::get_user_choice(tagname, lua_synchronize(L, desc, human_func, 0, ai_func), side_for); luaW_pushconfig(L, cfg); return 1; } /** * Ensures a value is synchronized among all the clients. * - Arg 1: optional string the id of this type of user input, may only contain characters a-z and '_' * - Arg 2: function to compute the value, called if the client is the master. * - Arg 3: an optional function to compute the value, if the side was null/empty controlled. * - Arg 4: an array of integers specifying, on which side the function should be evaluated. * - Ret 1: a map int -> WML tabls. */ static int intf_synchronize_choices(lua_State *L) { std::string tagname = "input"; t_string desc = _("input"); int human_func = 0; int null_func = 0; std::vector sides_for; int nextarg = 1; if(!lua_isfunction(L, nextarg) && luaW_totstring(L, nextarg, desc) ) { ++nextarg; } if(lua_isfunction(L, nextarg)) { human_func = nextarg++; } else { return luaL_argerror(L, nextarg, "expected a function"); } if(lua_isfunction(L, nextarg)) { null_func = nextarg++; }; sides_for = lua_check>(L, nextarg++); lua_push(L, mp_sync::get_user_choice_multiple_sides(tagname, lua_synchronize(L, desc, human_func, null_func), std::set(sides_for.begin(), sides_for.end()))); return 1; } /** * Calls a function in an unsynced context (this specially means that all random calls used by that function will be unsynced). * This is usually used together with an unsynced if like 'if controller != network' * - Arg 1: function that will be called during the unsynced context. */ static int intf_do_unsynced(lua_State *L) { set_scontext_unsynced sync; lua_pushvalue(L, 1); luaW_pcall(L, 0, 0, false); return 0; } /** * Gets all the locations matching a given filter. * - Arg 1: WML table. * - Arg 2: Optional reference unit (teleport_unit) * - Ret 1: array of integer pairs. */ int game_lua_kernel::intf_get_locations(lua_State *L) { vconfig filter = luaW_checkvconfig(L, 1); std::set res; filter_context & fc = game_state_; const terrain_filter t_filter(filter, &fc); if(luaW_isunit(L, 2)) { t_filter.get_locations(res, *luaW_tounit(L, 2), true); } else { t_filter.get_locations(res, true); } lua_createtable(L, res.size(), 0); int i = 1; for (const map_location& loc : res) { lua_createtable(L, 2, 0); lua_pushinteger(L, loc.wml_x()); lua_rawseti(L, -2, 1); lua_pushinteger(L, loc.wml_y()); lua_rawseti(L, -2, 2); lua_rawseti(L, -2, i); ++i; } return 1; } /** * Gets all the villages matching a given filter, or all the villages on the map if no filter is given. * - Arg 1: WML table (optional). * - Ret 1: array of integer pairs. */ int game_lua_kernel::intf_get_villages(lua_State *L) { std::vector locs = map().villages(); lua_newtable(L); int i = 1; vconfig filter = luaW_checkvconfig(L, 1); filter_context & fc = game_state_; for(std::vector::const_iterator it = locs.begin(); it != locs.end(); ++it) { bool matches = terrain_filter(filter, &fc).match(*it); if (matches) { lua_createtable(L, 2, 0); lua_pushinteger(L, it->wml_x()); lua_rawseti(L, -2, 1); lua_pushinteger(L, it->wml_y()); lua_rawseti(L, -2, 2); lua_rawseti(L, -2, i); ++i; } } return 1; } /** * Matches a location against the given filter. * - Arg 1: location. * - Arg 2: WML table. * - Arg 3: Optional reference unit (teleport_unit) * - Ret 1: boolean. */ int game_lua_kernel::intf_match_location(lua_State *L) { map_location loc = luaW_checklocation(L, 1); vconfig filter = luaW_checkvconfig(L, 2, true); if (filter.null()) { lua_pushboolean(L, true); return 1; } filter_context & fc = game_state_; const terrain_filter t_filter(filter, &fc); if(luaW_isunit(L, 3)) { lua_pushboolean(L, t_filter.match(loc, *luaW_tounit(L, 3))); } else { lua_pushboolean(L, t_filter.match(loc)); } return 1; } /** * Matches a side against the given filter. * - Args 1: side number. * - Arg 2: WML table. * - Ret 1: boolean. */ int game_lua_kernel::intf_match_side(lua_State *L) { vconfig filter = luaW_checkvconfig(L, 2, true); if (filter.null()) { lua_pushboolean(L, true); return 1; } filter_context & fc = game_state_; side_filter s_filter(filter, &fc); if(team* t = luaW_toteam(L, 1)) { lua_pushboolean(L, s_filter.match(*t)); } else { unsigned side = luaL_checkinteger(L, 1) - 1; if (side >= teams().size()) return 0; lua_pushboolean(L, s_filter.match(side + 1)); } return 1; } int game_lua_kernel::intf_set_side_id(lua_State *L) { int team_i; if(team* t = luaW_toteam(L, 1)) { team_i = t->side(); } else { team_i = luaL_checkinteger(L, 1); } std::string flag = luaL_optlstring(L, 2, "", nullptr); std::string color = luaL_optlstring(L, 3, "", nullptr); if(flag.empty() && color.empty()) { return 0; } if(team_i < 1 || static_cast(team_i) > teams().size()) { return luaL_error(L, "set_side_id: side number %d out of range", team_i); } team& side = board().get_team(team_i); if(!color.empty()) { side.set_color(color); } if(!flag.empty()) { side.set_flag(flag); } game_display_->reinit_flags_for_team(side); return 0; } static int intf_modify_ai(lua_State *L, const char* action) { int side_num; if(team* t = luaW_toteam(L, 1)) { side_num = t->side(); } else { side_num = luaL_checkinteger(L, 1); } std::string path = luaL_checkstring(L, 2); config cfg { "action", action, "path", path }; if(strcmp(action, "delete") == 0) { ai::manager::get_singleton().modify_active_ai_for_side(side_num, cfg); return 0; } config component = luaW_checkconfig(L, 3); std::size_t len = std::string::npos, open_brak = path.find_last_of('['); std::size_t dot = path.find_last_of('.'); if(open_brak != len) { len = open_brak - dot - 1; } cfg.add_child(path.substr(dot + 1, len), component); ai::manager::get_singleton().modify_active_ai_for_side(side_num, cfg); return 0; } static int intf_switch_ai(lua_State *L) { int side_num; if(team* t = luaW_toteam(L, 1)) { side_num = t->side(); } else { side_num = luaL_checkinteger(L, 1); } if(lua_isstring(L, 2)) { std::string file = luaL_checkstring(L, 2); if(!ai::manager::get_singleton().add_ai_for_side_from_file(side_num, file)) { std::string err = formatter() << "Could not load AI for side " << side_num << " from file " << file; lua_pushlstring(L, err.c_str(), err.length()); return lua_error(L); } } else { ai::manager::get_singleton().add_ai_for_side_from_config(side_num, luaW_checkconfig(L, 2)); } return 0; } static int intf_append_ai(lua_State *L) { int side_num; if(team* t = luaW_toteam(L, 1)) { side_num = t->side(); } else { side_num = luaL_checkinteger(L, 1); } config cfg = luaW_checkconfig(L, 2); if(!cfg.has_child("ai")) { cfg = config {"ai", cfg}; } bool added_dummy_stage = false; if(!cfg.child("ai").has_child("stage")) { added_dummy_stage = true; cfg.child("ai").add_child("stage", config {"name", "empty"}); } ai::configuration::expand_simplified_aspects(side_num, cfg); if(added_dummy_stage) { for(auto iter = cfg.ordered_begin(); iter != cfg.ordered_end(); iter++) { if(iter->key == "stage" && iter->cfg["name"] == "empty") { iter = cfg.erase(iter); } } } ai::manager::get_singleton().append_active_ai_for_side(side_num, cfg.child("ai")); return 0; } int game_lua_kernel::intf_get_side(lua_State* L) { unsigned i = luaL_checkinteger(L, 1); if(i < 1 || i > teams().size()) return 0; luaW_pushteam(L, board().get_team(i)); return 1; } /** * Returns a proxy table array for all sides matching the given SSF. * - Arg 1: SSF * - Ret 1: proxy table array */ int game_lua_kernel::intf_get_sides(lua_State* L) { LOG_LUA << "intf_get_sides called: this = " << std::hex << this << std::dec << " myname = " << my_name() << std::endl; std::vector sides; const vconfig ssf = luaW_checkvconfig(L, 1, true); if(ssf.null()) { for (unsigned side_number = 1; side_number <= teams().size(); ++side_number) { sides.push_back(side_number); } } else { filter_context & fc = game_state_; side_filter filter(ssf, &fc); sides = filter.get_teams(); } lua_settop(L, 0); lua_createtable(L, sides.size(), 0); unsigned index = 1; for(int side : sides) { luaW_pushteam(L, board().get_team(side)); lua_rawseti(L, -2, index); ++index; } return 1; } /** * .Returns information about the global traits known to the engine. * - Ret 1: Table with named fields holding wml tables describing the traits. */ static int intf_get_traits(lua_State* L) { lua_newtable(L); for(const config& trait : unit_types.traits()) { const std::string& id = trait["id"]; //It seems the engine does nowhere check the id field for emptyness or duplicates //(also not later on). //However, the worst thing to happen is that the trait read later overwrites the older one, //and this is not the right place for such checks. lua_pushstring(L, id.c_str()); luaW_pushconfig(L, trait); lua_rawset(L, -3); } return 1; } /** * Adds a modification to a unit. * - Arg 1: unit. * - Arg 2: string. * - Arg 3: WML table. * - Arg 4: (optional) Whether to add to [modifications] - default true */ static int intf_add_modification(lua_State *L) { unit& u = luaW_checkunit(L, 1); char const *m = luaL_checkstring(L, 2); std::string sm = m; if (sm == "advance") { // Maintain backwards compatibility sm = "advancement"; deprecated_message("\"advance\" modification type", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use \"advancement\" instead."); } if (sm != "advancement" && sm != "object" && sm != "trait") { return luaL_argerror(L, 2, "unknown modification type"); } bool write_to_mods = true; if (!lua_isnone(L, 4)) { write_to_mods = luaW_toboolean(L, 4); } if(sm.empty()) { write_to_mods = false; } config cfg = luaW_checkconfig(L, 3); u.add_modification(sm, cfg, !write_to_mods); return 0; } /** * Removes modifications from a unit * - Arg 1: unit * - Arg 2: table (filter as [filter_wml]) * - Arg 3: type of modification (default "object") */ static int intf_remove_modifications(lua_State *L) { unit& u = luaW_checkunit(L, 1); config filter = luaW_checkconfig(L, 2); std::string tag = luaL_optstring(L, 3, "object"); //TODO if(filter.attribute_count() == 1 && filter.all_children_count() == 0 && filter.attribute_range().front().first == "duration") { u.expire_modifications(filter["duration"]); } else { for(config& obj : u.get_modifications().child_range(tag)) { if(obj.matches(filter)) { obj["duration"] = "now"; } } u.expire_modifications("now"); } return 0; } /** * Advances a unit if the unit has enough xp. * - Arg 1: unit. * - Arg 2: optional boolean whether to animate the advancement. * - Arg 3: optional boolean whether to fire advancement events. */ static int intf_advance_unit(lua_State *L) { events::command_disabler command_disabler; unit& u = luaW_checkunit(L, 1, true); advance_unit_params par(u.get_location()); if(lua_isboolean(L, 2)) { par.animate(luaW_toboolean(L, 2)); } if(lua_isboolean(L, 3)) { par.fire_events(luaW_toboolean(L, 3)); } advance_unit_at(par); return 0; } /** * Adds a new known unit type to the help system. * - Arg 1: string. */ static int intf_add_known_unit(lua_State *L) { char const *ty = luaL_checkstring(L, 1); if(!unit_types.find(ty)) { std::stringstream ss; ss << "unknown unit type: '" << ty << "'"; return luaL_argerror(L, 1, ss.str().c_str()); } preferences::encountered_units().insert(ty); return 0; } /** * Adds an overlay on a tile. * - Arg 1: location. * - Arg 2: WML table. */ int game_lua_kernel::intf_add_tile_overlay(lua_State *L) { map_location loc = luaW_checklocation(L, 1); vconfig cfg = luaW_checkvconfig(L, 2); const vconfig &ssf = cfg.child("filter_team"); std::string team_name; if (!ssf.null()) { const std::vector& teams = side_filter(ssf, &game_state_).get_teams(); std::vector team_names; std::transform(teams.begin(), teams.end(), std::back_inserter(team_names), [&](int team) { return game_state_.get_disp_context().get_team(team).team_name(); }); team_name = utils::join(team_names); } else { team_name = cfg["team_name"].str(); } if (game_display_) { game_display_->add_overlay(loc, cfg["image"], cfg["halo"], team_name, cfg["name"], cfg["visible_in_fog"].to_bool(true), cfg["z_order"].to_double(0)); } return 0; } /** * Removes an overlay from a tile. * - Arg 1: location. * - Arg 2: optional string. */ int game_lua_kernel::intf_remove_tile_overlay(lua_State *L) { map_location loc = luaW_checklocation(L, 1); char const *m = lua_tostring(L, 2); if (m) { if (game_display_) { game_display_->remove_single_overlay(loc, m); } } else { if (game_display_) { game_display_->remove_overlay(loc); } } return 0; } int game_lua_kernel::intf_log_replay(lua_State* L) { replay& recorder = play_controller_.get_replay(); const int nargs = lua_gettop(L); if(nargs < 2 || nargs > 3) { return luaL_error(L, "Wrong number of arguments to ai.log_replay() - should be 2 or 3 arguments."); } const std::string key = nargs == 2 ? luaL_checkstring(L, 1) : luaL_checkstring(L, 2); config cfg; if(nargs == 2) { recorder.add_log_data(key, luaL_checkstring(L, 2)); } else if(luaW_toconfig(L, 3, cfg)) { recorder.add_log_data(luaL_checkstring(L, 1), key, cfg); } else if(!lua_isstring(L, 3)) { return luaL_argerror(L, 3, "accepts only string or config"); } else { recorder.add_log_data(luaL_checkstring(L, 1), key, luaL_checkstring(L, 3)); } return 0; } /// Adding new events int game_lua_kernel::intf_add_event(lua_State *L) { vconfig cfg(luaW_checkvconfig(L, 1)); game_events::manager & man = *game_state_.events_manager_; if (!cfg["delayed_variable_substitution"].to_bool(true)) { man.add_event_handler(cfg.get_parsed_config()); } else { man.add_event_handler(cfg.get_config()); } return 0; } int game_lua_kernel::intf_remove_event(lua_State *L) { game_state_.events_manager_->remove_event_handler(luaL_checkstring(L, 1)); return 0; } int game_lua_kernel::intf_color_adjust(lua_State *L) { if (game_display_) { vconfig cfg(luaW_checkvconfig(L, 1)); game_display_->adjust_color_overlay(cfg["red"], cfg["green"], cfg["blue"]); game_display_->invalidate_all(); game_display_->draw(true,true); } return 0; } /** * Delays engine for a while. * - Arg 1: integer. * - Arg 2: boolean (optional). */ int game_lua_kernel::intf_delay(lua_State *L) { if(gamedata().phase() == game_data::PRELOAD || gamedata().phase() == game_data::PRESTART || gamedata().phase() == game_data::INITIAL) { //don't call play_slice if the game ui is not active yet. return 0; } events::command_disabler command_disabler; lua_Integer delay = luaL_checkinteger(L, 1); if(delay == 0) { play_controller_.play_slice(false); return 0; } if(luaW_toboolean(L, 2) && game_display_ && game_display_->turbo_speed() > 0) { delay /= game_display_->turbo_speed(); } const unsigned final = SDL_GetTicks() + delay; do { play_controller_.play_slice(false); CVideo::delay(10); } while (static_cast(final - SDL_GetTicks()) > 0); return 0; } int game_lua_kernel::intf_label(lua_State *L) { if (game_display_) { vconfig cfg(luaW_checkvconfig(L, 1)); game_display &screen = *game_display_; terrain_label label(screen.labels(), cfg.get_config()); screen.labels().set_label(label.location(), label.text(), label.creator(), label.team_name(), label.color(), label.visible_in_fog(), label.visible_in_shroud(), label.immutable(), label.category(), label.tooltip()); } return 0; } int game_lua_kernel::intf_redraw(lua_State *L) { if (game_display_) { game_display & screen = *game_display_; vconfig cfg(luaW_checkvconfig(L, 1)); bool clear_shroud(luaW_toboolean(L, 2)); // We do this twice so any applicable redraws happen both before and after // any events caused by redrawing shroud are fired bool result = screen.maybe_rebuild(); if (!result) { screen.invalidate_all(); } if (clear_shroud) { side_filter filter(cfg, &game_state_); for (const int side : filter.get_teams()){ actions::clear_shroud(side); } screen.recalculate_minimap(); } result = screen.maybe_rebuild(); if (!result) { screen.invalidate_all(); } screen.draw(true,true); } return 0; } /** * Lua frontend to the modify_ai functionality * - Arg 1: config. */ static int intf_modify_ai_old(lua_State *L) { config cfg; luaW_toconfig(L, 1, cfg); int side = cfg["side"]; 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."); ai::manager::get_singleton().modify_active_ai_for_side(side, cfg); return 0; } static int cfun_exec_candidate_action(lua_State *L) { bool exec = luaW_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "ca_ptr"); ai::candidate_action *ca = static_cast(lua_touserdata(L, -1)); lua_pop(L, 2); if (exec) { ca->execute(); return 0; } lua_pushinteger(L, ca->evaluate()); return 1; } static int cfun_exec_stage(lua_State *L) { lua_getfield(L, -1, "stg_ptr"); ai::stage *stg = static_cast(lua_touserdata(L, -1)); lua_pop(L, 2); stg->play_stage(); return 0; } static void push_component(lua_State *L, ai::component* c, const std::string &ct = "") { lua_createtable(L, 0, 0); // Table for a component lua_pushstring(L, "name"); lua_pushstring(L, c->get_name().c_str()); lua_rawset(L, -3); lua_pushstring(L, "engine"); lua_pushstring(L, c->get_engine().c_str()); lua_rawset(L, -3); lua_pushstring(L, "id"); lua_pushstring(L, c->get_id().c_str()); lua_rawset(L, -3); if (ct == "candidate_action") { lua_pushstring(L, "ca_ptr"); lua_pushlightuserdata(L, c); lua_rawset(L, -3); lua_pushstring(L, "exec"); lua_pushcclosure(L, &cfun_exec_candidate_action, 0); lua_rawset(L, -3); } if (ct == "stage") { lua_pushstring(L, "stg_ptr"); lua_pushlightuserdata(L, c); lua_rawset(L, -3); lua_pushstring(L, "exec"); lua_pushcclosure(L, &cfun_exec_stage, 0); lua_rawset(L, -3); } std::vector c_types = c->get_children_types(); for (std::vector::const_iterator t = c_types.begin(); t != c_types.end(); ++t) { std::vector children = c->get_children(*t); std::string type = *t; if (type == "aspect" || type == "goal" || type == "engine") { continue; } lua_pushstring(L, type.c_str()); lua_createtable(L, 0, 0); // this table will be on top of the stack during recursive calls for (std::vector::const_iterator i = children.begin(); i != children.end(); ++i) { lua_pushstring(L, (*i)->get_name().c_str()); push_component(L, *i, type); lua_rawset(L, -3); //if (type == "candidate_action") //{ // ai::candidate_action *ca = dynamic_cast(*i); // ca->execute(); //} } lua_rawset(L, -3); // setting the child table } } /** * Debug access to the ai tables * - Arg 1: int * - Ret 1: ai table */ static int intf_debug_ai(lua_State *L) { if (!game_config::debug) { // This function works in debug mode only return 0; } int side; if(team* t = luaW_toteam(L, 1)) { side = t->side(); } else { side = luaL_checkinteger(L, 1); } lua_pop(L, 1); ai::component* c = ai::manager::get_singleton().get_active_ai_holder_for_side_dbg(side).get_component(nullptr, ""); // Bad, but works std::vector engines = c->get_children("engine"); ai::engine_lua* lua_engine = nullptr; for (std::vector::const_iterator i = engines.begin(); i != engines.end(); ++i) { if ((*i)->get_name() == "lua") { lua_engine = dynamic_cast(*i); } } // Better way, but doesn't work //ai::component* e = ai::manager::get_singleton().get_active_ai_holder_for_side_dbg(side).get_component(c, "engine[lua]"); //ai::engine_lua* lua_engine = dynamic_cast(e); if (lua_engine == nullptr) { //no lua engine is defined for this side. //so set up a dummy engine ai::ai_composite * ai_ptr = dynamic_cast(c); assert(ai_ptr); ai::ai_context& ai_context = ai_ptr->get_ai_context(); config cfg = ai::configuration::get_default_ai_parameters(); lua_engine = new ai::engine_lua(ai_context, cfg); LOG_LUA << "Created new dummy lua-engine for debug_ai(). \n"; //and add the dummy engine as a component //to the manager, so we could use it later cfg.add_child("engine", lua_engine->to_config()); ai::component_manager::add_component(c, "engine[]", cfg); } lua_engine->push_ai_table(); // stack: [-1: ai_context] lua_pushstring(L, "components"); push_component(L, c); // stack: [-1: component tree; -2: ai context] lua_rawset(L, -3); return 1; } /// Allow undo sets the flag saying whether the event has mutated the game to false. int game_lua_kernel::intf_allow_end_turn(lua_State * L) { bool allow; t_string reason; // The extra iststring is required to prevent totstring from converting a bool value if(luaW_iststring(L, 1) && luaW_totstring(L, 1, reason)) { allow = false; } else { allow = luaW_toboolean(L, 1); luaW_totstring(L, 2, reason); } gamedata().set_allow_end_turn(allow, reason); return 0; } /// Allow undo sets the flag saying whether the event has mutated the game to false. int game_lua_kernel::intf_allow_undo(lua_State * L) { if(lua_isboolean(L, 1)) { play_controller_.pump().set_undo_disabled(!luaW_toboolean(L, 1)); } else { play_controller_.pump().set_undo_disabled(false); } return 0; } int game_lua_kernel::intf_cancel_action(lua_State*) { play_controller_.pump().set_action_canceled(); return 0; } /// Adding new time_areas dynamically with Standard Location Filters. int game_lua_kernel::intf_add_time_area(lua_State * L) { log_scope("time_area"); vconfig cfg(luaW_checkvconfig(L, 1)); const std::string id = cfg["id"]; std::set locs; const terrain_filter filter(cfg, &game_state_); filter.get_locations(locs, true); config parsed_cfg = cfg.get_parsed_config(); tod_man().add_time_area(id, locs, parsed_cfg); LOG_LUA << "Lua inserted time_area '" << id << "'\n"; return 0; } /// Removing new time_areas dynamically with Standard Location Filters. int game_lua_kernel::intf_remove_time_area(lua_State * L) { log_scope("remove_time_area"); const char * id = luaL_checkstring(L, 1); tod_man().remove_time_area(id); LOG_LUA << "Lua removed time_area '" << id << "'\n"; return 0; } /// Replacing the current time of day schedule. int game_lua_kernel::intf_replace_schedule(lua_State * L) { vconfig cfg = luaW_checkvconfig(L, 1); if(cfg.get_children("time").empty()) { ERR_LUA << "attempted to to replace ToD schedule with empty schedule" << std::endl; } else { tod_man().replace_schedule(cfg.get_parsed_config()); if (game_display_) { game_display_->new_turn(); } LOG_LUA << "replaced ToD schedule\n"; } return 0; } int game_lua_kernel::intf_set_time_of_day(lua_State * L) { if(!game_display_) { return 0; } std::string area_id; std::size_t area_i = 0; if (lua_isstring(L, 2)) { area_id = lua_tostring(L, 1); std::vector area_ids = tod_man().get_area_ids(); area_i = std::distance(area_ids.begin(), std::find(area_ids.begin(), area_ids.end(), area_id)); if(area_i >= area_ids.size()) { return luaL_argerror(L, 1, "invalid time area ID"); } } int is_num = false; int new_time = lua_tonumberx(L, 1, &is_num) - 1; const std::vector& times = area_id.empty() ? tod_man().times() : tod_man().times(area_i); int num_times = times.size(); if(!is_num) { std::string time_id = luaL_checkstring(L, 1); new_time = 0; for(const time_of_day& time : times) { if(time_id == time.id) { break; } new_time++; } if(new_time >= num_times) { return luaL_argerror(L, 1, "invalid time of day ID"); } } if(new_time < 0 || new_time >= num_times) { return luaL_argerror(L, 1, "invalid time of day index"); } if(area_id.empty()) { tod_man().set_current_time(new_time); } else { tod_man().set_current_time(new_time, area_i); } return 0; } int game_lua_kernel::intf_scroll(lua_State * L) { int x = luaL_checkinteger(L, 1), y = luaL_checkinteger(L, 2); if (game_display_) { game_display_->scroll(x, y, true); game_display_->draw(true, true); } return 0; } namespace { struct lua_report_generator : reports::generator { lua_State *mState; std::string name; lua_report_generator(lua_State *L, const std::string &n) : mState(L), name(n) {} virtual config generate(reports::context & rc); }; config lua_report_generator::generate(reports::context & /*rc*/) { lua_State *L = mState; config cfg; if (!luaW_getglobal(L, "wesnoth", "theme_items", name)) return cfg; if (!luaW_pcall(L, 0, 1)) return cfg; luaW_toconfig(L, -1, cfg); lua_pop(L, 1); return cfg; } }//unnamed namespace for lua_report_generator /** * Executes its upvalue as a theme item generator. */ int game_lua_kernel::impl_theme_item(lua_State *L, std::string m) { reports::context temp_context = reports::context(board(), *game_display_, tod_man(), play_controller_.get_whiteboard(), play_controller_.get_mouse_handler_base()); luaW_pushconfig(L, reports_.generate_report(m.c_str(), temp_context , true)); return 1; } /** * Creates a field of the theme_items table and returns it (__index metamethod). */ int game_lua_kernel::impl_theme_items_get(lua_State *L) { char const *m = luaL_checkstring(L, 2); lua_cpp::push_closure(L, std::bind(&game_lua_kernel::impl_theme_item, this, _1, std::string(m)), 0); lua_pushvalue(L, 2); lua_pushvalue(L, -2); lua_rawset(L, 1); reports_.register_generator(m, new lua_report_generator(L, m)); return 1; } /** * Sets a field of the theme_items table (__newindex metamethod). */ int game_lua_kernel::impl_theme_items_set(lua_State *L) { char const *m = luaL_checkstring(L, 2); lua_pushvalue(L, 2); lua_pushvalue(L, 3); lua_rawset(L, 1); reports_.register_generator(m, new lua_report_generator(L, m)); return 0; } /** * Gets all the WML variables currently set. * - Ret 1: WML table */ int game_lua_kernel::intf_get_all_vars(lua_State *L) { luaW_pushconfig(L, gamedata().get_variables()); return 1; } /** * Teeleports a unit to a location. * Arg 1: unit * Arg 2: target location * Arg 3: bool (ignore_passability) * Arg 4: bool (clear_shroud) * Arg 5: bool (animate) */ int game_lua_kernel::intf_teleport(lua_State *L) { events::command_disabler command_disabler; unit_ptr u = luaW_checkunit_ptr(L, 1, true); map_location dst = luaW_checklocation(L, 2); bool check_passability = !luaW_toboolean(L, 3); bool clear_shroud = luaW_toboolean(L, 4); bool animate = luaW_toboolean(L, 5); if (dst == u->get_location() || !map().on_board(dst)) { return 0; } const map_location vacant_dst = find_vacant_tile(dst, pathfind::VACANT_ANY, check_passability ? u.get() : nullptr); if (!map().on_board(vacant_dst)) { return 0; } // Clear the destination hex before the move (so the animation can be seen). actions::shroud_clearer clearer; if ( clear_shroud ) { clearer.clear_dest(vacant_dst, *u); } map_location src_loc = u->get_location(); std::vector teleport_path; teleport_path.push_back(src_loc); teleport_path.push_back(vacant_dst); unit_display::move_unit(teleport_path, u, animate); units().move(src_loc, vacant_dst); unit::clear_status_caches(); u = &*units().find(vacant_dst); u->anim_comp().set_standing(); if ( clear_shroud ) { // Now that the unit is visibly in position, clear the shroud. clearer.clear_unit(vacant_dst, *u); } if (map().is_village(vacant_dst)) { actions::get_village(vacant_dst, u->side()); } game_display_->invalidate_unit_after_move(src_loc, vacant_dst); game_display_->draw(); // Sighted events. clearer.fire_events(); return 0; } /** * Removes a sound source by its ID * Arg 1: sound source ID */ int game_lua_kernel::intf_remove_sound_source(lua_State *L) { soundsource::manager* man = play_controller_.get_soundsource_man(); std::string id = luaL_checkstring(L, 1); man->remove(id); return 0; } /** * Add a new sound source * Arg 1: Table containing keyword arguments */ int game_lua_kernel::intf_add_sound_source(lua_State *L) { soundsource::manager* man = play_controller_.get_soundsource_man(); config cfg = luaW_checkconfig(L, 1); try { soundsource::sourcespec spec(cfg); man->add(spec); man->update(); } catch (const bad_lexical_cast &) { ERR_LUA << "Error when parsing sound_source config: invalid parameter." << std::endl; ERR_LUA << "sound_source config was: " << cfg.debug() << std::endl; ERR_LUA << "Skipping this sound source..." << std::endl; } return 0; } /** * Get an existing sound source * Arg 1: The sound source ID * Return: Config of sound source info, or nil if it didn't exist * This is a copy of the sound source info, so you need to call * add_sound_source again after changing it. */ int game_lua_kernel::intf_get_sound_source(lua_State *L) { soundsource::manager* man = play_controller_.get_soundsource_man(); std::string id = luaL_checkstring(L, 1); config cfg = man->get(id); if(cfg.empty()) { return 0; } // Sound sources do not know their own string ID // Thus, we need to add this manually cfg["id"] = id; luaW_pushconfig(L, cfg); return 1; } /** * Logs a message * Arg 1: (optional) Logger; "wml" for WML errors or deprecations * Arg 2: Message * Arg 3: Whether to print to chat (always true if arg 1 is "wml") */ int game_lua_kernel::intf_log(lua_State *L) { const std::string& logger = lua_isstring(L, 2) ? luaL_checkstring(L, 1) : ""; const std::string& msg = lua_isstring(L, 2) ? luaL_checkstring(L, 2) : luaL_checkstring(L, 1); if(logger == "wml" || logger == "WML") { lg::wml_error() << msg << '\n'; } else { bool in_chat = luaW_toboolean(L, -1); game_state_.events_manager_->pump().put_wml_message(logger,msg,in_chat); } return 0; } int game_lua_kernel::intf_get_fog_or_shroud(lua_State *L, bool fog) { int side = luaL_checknumber(L, 1); map_location loc = luaW_checklocation(L, 2); if(side < 1 || static_cast(side) > teams().size()) { std::string error = "side " + std::to_string(side) + " does not exist"; return luaL_argerror(L, 1, error.c_str()); } team& t = board().get_team(side); lua_pushboolean(L, fog ? t.fogged(loc) : t.shrouded(loc)); return 1; } /** * Implements the lifting and resetting of fog via WML. * Keeping affect_normal_fog as false causes only the fog override to be affected. * Otherwise, fog lifting will be implemented similar to normal sight (cannot be * individually reset and ends at the end of the turn), and fog resetting will, in * addition to removing overrides, extend the specified teams' normal fog to all * hexes. * * Arg 1: (optional) Side number, or list of side numbers * Arg 2: List of locations; each is a two-element array or a table with x and y keys * Arg 3: (optional) boolean */ int game_lua_kernel::intf_toggle_fog(lua_State *L, const bool clear) { bool affect_normal_fog = false; if(lua_isboolean(L, -1)) { affect_normal_fog = luaW_toboolean(L, -1); } std::set sides; if(lua_isnumber(L, 1)) { sides.insert(lua_tonumber(L, 1)); } else if(lua_istable(L, 1) && lua_istable(L, 2)) { const auto& v = lua_check>(L, 1); sides.insert(v.begin(), v.end()); } else { for(const team& t : teams()) { sides.insert(t.side()+1); } } const auto& v_locs = lua_check>(L, lua_istable(L, 2) ? 2 : 1); std::set locs(v_locs.begin(), v_locs.end()); for(const int &side_num : sides) { if(side_num < 1 || static_cast(side_num) > teams().size()) { continue; } team &t = board().get_team(side_num); if(!clear) { // Extend fog. t.remove_fog_override(locs); if(affect_normal_fog) { t.refog(); } } else if(!affect_normal_fog) { // Force the locations clear of fog. t.add_fog_override(locs); } else { // Simply clear fog from the locations. for(const map_location &hex : locs) { t.clear_fog(hex); } } } // Flag a screen update. game_display_->recalculate_minimap(); game_display_->invalidate_all(); return 0; } // Invokes a synced command static int intf_invoke_synced_command(lua_State* L) { const std::string name = luaL_checkstring(L, 1); auto it = synced_command::registry().find(name); config cmd; if(it == synced_command::registry().end()) { // Custom command if(!luaW_getglobal(L, "wesnoth", "custom_synced_commands", name)) { return luaL_argerror(L, 1, "Unknown synced command"); } config& cmd_tag = cmd.child_or_add("custom_command"); cmd_tag["name"] = name; if(!lua_isnoneornil(L, 2)) { cmd_tag.add_child("data", luaW_checkconfig(L, 2)); } } else { // Built-in command cmd.add_child(name, luaW_checkconfig(L, 2)); } // Now just forward to the WML action. luaW_getglobal(L, "wesnoth", "wml_actions", "do_command"); luaW_pushconfig(L, cmd); luaW_pcall(L, 1, 0); return 0; } // END CALLBACK IMPLEMENTATION game_board & game_lua_kernel::board() { return game_state_.board_; } unit_map & game_lua_kernel::units() { return game_state_.board_.units_; } std::vector & game_lua_kernel::teams() { return game_state_.board_.teams_; } const gamemap & game_lua_kernel::map() const { return game_state_.board_.map(); } game_data & game_lua_kernel::gamedata() { return game_state_.gamedata_; } tod_manager & game_lua_kernel::tod_man() { return game_state_.tod_manager_; } const game_events::queued_event & game_lua_kernel::get_event_info() { return *queued_events_.top(); } game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports & reports_object) : lua_kernel_base() , game_display_(nullptr) , game_state_(gs) , play_controller_(pc) , reports_(reports_object) , level_lua_() , queued_events_() , map_locked_(0) { static game_events::queued_event default_queued_event("_from_lua", "", map_location(), map_location(), config()); queued_events_.push(&default_queued_event); lua_State *L = mState; cmd_log_ << "Registering game-specific wesnoth lib functions...\n"; // Put some callback functions in the scripting environment. static luaL_Reg const callbacks[] { { "add_known_unit", &intf_add_known_unit }, { "create_animator", &dispatch<&game_lua_kernel::intf_create_animator> }, { "eval_conditional", &intf_eval_conditional }, { "get_era", &intf_get_era }, { "get_traits", &intf_get_traits }, { "get_viewing_side", &intf_get_viewing_side }, { "invoke_synced_command", &intf_invoke_synced_command }, { "modify_ai", &intf_modify_ai_old }, { "set_music", &intf_set_music }, { "sound_volume", &intf_sound_volume }, { "unsynced", &intf_do_unsynced }, { "add_event_handler", &dispatch<&game_lua_kernel::intf_add_event > }, { "add_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, false > }, { "add_time_area", &dispatch<&game_lua_kernel::intf_add_time_area > }, { "add_sound_source", &dispatch<&game_lua_kernel::intf_add_sound_source > }, { "allow_end_turn", &dispatch<&game_lua_kernel::intf_allow_end_turn > }, { "allow_undo", &dispatch<&game_lua_kernel::intf_allow_undo > }, { "cancel_action", &dispatch<&game_lua_kernel::intf_cancel_action > }, { "clear_messages", &dispatch<&game_lua_kernel::intf_clear_messages > }, { "end_turn", &dispatch<&game_lua_kernel::intf_end_turn > }, { "end_level", &dispatch<&game_lua_kernel::intf_end_level > }, { "find_cost_map", &dispatch<&game_lua_kernel::intf_find_cost_map > }, { "find_path", &dispatch<&game_lua_kernel::intf_find_path > }, { "find_reach", &dispatch<&game_lua_kernel::intf_find_reach > }, { "find_vacant_tile", &dispatch<&game_lua_kernel::intf_find_vacant_tile > }, { "fire_event", &dispatch2<&game_lua_kernel::intf_fire_event, false > }, { "fire_event_by_id", &dispatch2<&game_lua_kernel::intf_fire_event, true > }, { "get_all_vars", &dispatch<&game_lua_kernel::intf_get_all_vars > }, { "get_end_level_data", &dispatch<&game_lua_kernel::intf_get_end_level_data > }, { "get_locations", &dispatch<&game_lua_kernel::intf_get_locations > }, { "get_map_size", &dispatch<&game_lua_kernel::intf_get_map_size > }, { "get_sound_source", &dispatch<&game_lua_kernel::intf_get_sound_source > }, { "get_terrain", &dispatch<&game_lua_kernel::intf_get_terrain > }, { "get_terrain_info", &dispatch<&game_lua_kernel::intf_get_terrain_info > }, { "get_time_of_day", &dispatch<&game_lua_kernel::intf_get_time_of_day > }, { "get_variable", &dispatch<&game_lua_kernel::intf_get_variable > }, { "get_villages", &dispatch<&game_lua_kernel::intf_get_villages > }, { "get_village_owner", &dispatch<&game_lua_kernel::intf_get_village_owner > }, { "label", &dispatch<&game_lua_kernel::intf_label > }, { "log_replay", &dispatch<&game_lua_kernel::intf_log_replay > }, { "log", &dispatch<&game_lua_kernel::intf_log > }, { "match_location", &dispatch<&game_lua_kernel::intf_match_location > }, { "message", &dispatch<&game_lua_kernel::intf_message > }, { "open_help", &dispatch<&game_lua_kernel::intf_open_help > }, { "play_sound", &dispatch<&game_lua_kernel::intf_play_sound > }, { "print", &dispatch<&game_lua_kernel::intf_print > }, { "redraw", &dispatch<&game_lua_kernel::intf_redraw > }, { "remove_event_handler", &dispatch<&game_lua_kernel::intf_remove_event > }, { "remove_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, true > }, { "remove_time_area", &dispatch<&game_lua_kernel::intf_remove_time_area > }, { "remove_sound_source", &dispatch<&game_lua_kernel::intf_remove_sound_source > }, { "replace_schedule", &dispatch<&game_lua_kernel::intf_replace_schedule > }, { "select_hex", &dispatch<&game_lua_kernel::intf_select_hex > }, { "set_time_of_day", &dispatch<&game_lua_kernel::intf_set_time_of_day > }, { "is_fogged", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, true > }, { "is_shrouded", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, false > }, { "set_end_campaign_credits", &dispatch<&game_lua_kernel::intf_set_end_campaign_credits > }, { "set_end_campaign_text", &dispatch<&game_lua_kernel::intf_set_end_campaign_text > }, { "create_side", &dispatch<&game_lua_kernel::intf_create_side > }, { "set_next_scenario", &dispatch<&game_lua_kernel::intf_set_next_scenario > }, { "set_terrain", &dispatch<&game_lua_kernel::intf_set_terrain > }, { "set_variable", &dispatch<&game_lua_kernel::intf_set_variable > }, { "set_village_owner", &dispatch<&game_lua_kernel::intf_set_village_owner > }, { "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > }, { "synchronize_choice", &intf_synchronize_choice }, { "synchronize_choices", &intf_synchronize_choices }, { "terrain_mask", &dispatch<&game_lua_kernel::intf_terrain_mask > }, { "teleport", &dispatch<&game_lua_kernel::intf_teleport > }, { "place_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, true > }, { "remove_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, false > }, { nullptr, nullptr } };lua_getglobal(L, "wesnoth"); if (!lua_istable(L,-1)) { lua_newtable(L); } luaL_setfuncs(L, callbacks, 0); if(play_controller_.get_classification().campaign_type == game_classification::CAMPAIGN_TYPE::TEST) { static luaL_Reg const test_callbacks[] { { "fire_wml_menu_item", &dispatch<&game_lua_kernel::intf_fire_wml_menu_item > }, { nullptr, nullptr } }; luaL_setfuncs(L, test_callbacks , 0); } lua_setglobal(L, "wesnoth"); lua_getglobal(L, "gui"); lua_pushcfunction(L, &dispatch<&game_lua_kernel::intf_gamestate_inspector>); lua_setfield(L, -2, "show_inspector"); lua_pop(L, 1); // Create the getside metatable. cmd_log_ << lua_team::register_metatable(L); // Create the gettype metatable. cmd_log_ << lua_unit_type::register_metatable(L); //Create the getrace metatable cmd_log_ << lua_race::register_metatable(L); //Create the unit metatables cmd_log_ << lua_units::register_metatables(L); cmd_log_ << lua_units::register_attacks_metatables(L); // Create the vconfig metatable. cmd_log_ << lua_common::register_vconfig_metatable(L); // Create the unit_types table cmd_log_ << lua_unit_type::register_table(L); // Create the ai elements table. cmd_log_ << "Adding ai elements table...\n"; ai::lua_ai_context::init(L); // Create the current variable with its metatable. cmd_log_ << "Adding wesnoth current table...\n"; lua_getglobal(L, "wesnoth"); lua_newuserdata(L, 0); lua_createtable(L, 0, 2); lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_current_get>); lua_setfield(L, -2, "__index"); lua_pushstring(L, "current config"); lua_setfield(L, -2, "__metatable"); lua_setmetatable(L, -2); lua_setfield(L, -2, "current"); lua_pop(L, 1); // Create the units module cmd_log_ << "Adding units module...\n"; static luaL_Reg const unit_callbacks[] { {"advance", &intf_advance_unit}, {"clone", &intf_copy_unit}, {"erase", &dispatch<&game_lua_kernel::intf_erase_unit>}, {"extract", &dispatch<&game_lua_kernel::intf_extract_unit>}, {"matches", &dispatch<&game_lua_kernel::intf_match_unit>}, {"select", &dispatch<&game_lua_kernel::intf_select_unit>}, {"to_map", &dispatch<&game_lua_kernel::intf_put_unit>}, {"to_recall", &dispatch<&game_lua_kernel::intf_put_recall_unit>}, {"transform", &intf_transform_unit}, {"ability", &dispatch<&game_lua_kernel::intf_unit_ability>}, {"defense_on", &intf_unit_defense}, {"jamming_on", &intf_unit_jamming_cost}, {"movement_on", &intf_unit_movement_cost}, {"resistance_against", intf_unit_resistance}, {"vision_on", &intf_unit_vision_cost}, {"add_modification", &intf_add_modification}, {"remove_modifications", &intf_remove_modifications}, // Static functions {"create", &intf_create_unit}, {"find_on_map", &dispatch<&game_lua_kernel::intf_get_units>}, {"find_on_recall", &dispatch<&game_lua_kernel::intf_get_recall_units>}, {"get", &dispatch<&game_lua_kernel::intf_get_unit>}, {"get_hovered", &dispatch<&game_lua_kernel::intf_get_displayed_unit>}, { nullptr, nullptr } }; lua_getglobal(L, "wesnoth"); lua_newtable(L); luaL_setfuncs(L, unit_callbacks, 0); lua_setfield(L, -2, "units"); lua_pop(L, 1); // Create sides module cmd_log_ << "Adding sides module...\n"; static luaL_Reg const side_callbacks[] { { "get_starting_location", &dispatch<&game_lua_kernel::intf_get_starting_location> }, { "is_enemy", &dispatch<&game_lua_kernel::intf_is_enemy> }, { "matches", &dispatch<&game_lua_kernel::intf_match_side> }, { "set_id", &dispatch<&game_lua_kernel::intf_set_side_id> }, { "append_ai", &intf_append_ai }, { "debug_ai", &intf_debug_ai }, { "switch_ai", &intf_switch_ai }, // Static functions { "find", &dispatch<&game_lua_kernel::intf_get_sides> }, { "get", &dispatch<&game_lua_kernel::intf_get_side> }, { nullptr, nullptr } }; std::vector const cpp_side_callbacks { {"add_ai_component", std::bind(intf_modify_ai, _1, "add")}, {"delete_ai_component", std::bind(intf_modify_ai, _1, "delete")}, {"change_ai_component", std::bind(intf_modify_ai, _1, "change")}, {nullptr, nullptr} }; lua_getglobal(L, "wesnoth"); lua_newtable(L); luaL_setfuncs(L, side_callbacks, 0); lua_cpp::set_functions(L, cpp_side_callbacks); lua_setfield(L, -2, "sides"); lua_pop(L, 1); // Create the interface module cmd_log_ << "Adding interface module...\n"; static luaL_Reg const intf_callbacks[] { {"add_hex_overlay", &dispatch<&game_lua_kernel::intf_add_tile_overlay>}, {"remove_hex_overlay", &dispatch<&game_lua_kernel::intf_remove_tile_overlay>}, {"color_adjust", &dispatch<&game_lua_kernel::intf_color_adjust>}, {"delay", &dispatch<&game_lua_kernel::intf_delay>}, {"deselect_hex", &dispatch<&game_lua_kernel::intf_deselect_hex>}, {"highlight_hex", &dispatch<&game_lua_kernel::intf_highlight_hex>}, {"float_label", &dispatch<&game_lua_kernel::intf_float_label>}, {"get_displayed_unit", &dispatch<&game_lua_kernel::intf_get_displayed_unit>}, {"get_hovered_hex", &dispatch<&game_lua_kernel::intf_get_mouseover_tile>}, {"get_selected_hex", &dispatch<&game_lua_kernel::intf_get_selected_tile>}, {"lock", &dispatch<&game_lua_kernel::intf_lock_view>}, {"is_locked", &dispatch<&game_lua_kernel::intf_view_locked>}, {"scroll", &dispatch<&game_lua_kernel::intf_scroll>}, {"scroll_to_hex", &dispatch<&game_lua_kernel::intf_scroll_to_tile>}, {"skip_messages", &dispatch<&game_lua_kernel::intf_skip_messages>}, {"is_skipping_messages", &dispatch<&game_lua_kernel::intf_is_skipping_messages>}, {"zoom", &dispatch<&game_lua_kernel::intf_zoom>}, {"clear_menu_item", &dispatch<&game_lua_kernel::intf_clear_menu_item>}, {"set_menu_item", &dispatch<&game_lua_kernel::intf_set_menu_item>}, { nullptr, nullptr } }; lua_getglobal(L, "wesnoth"); lua_newtable(L); luaL_setfuncs(L, intf_callbacks, 0); lua_setfield(L, -2, "interface"); lua_pop(L, 1); // Create the playlist table with its metatable cmd_log_ << lua_audio::register_table(L); // Create the wml_actions table. cmd_log_ << "Adding wml_actions table...\n"; lua_getglobal(L, "wesnoth"); lua_newtable(L); lua_setfield(L, -2, "wml_actions"); lua_pop(L, 1); // Create the wml_conditionals table. cmd_log_ << "Adding wml_conditionals table...\n"; lua_getglobal(L, "wesnoth"); lua_newtable(L); lua_setfield(L, -2, "wml_conditionals"); lua_pop(L, 1); set_wml_condition("have_unit", &game_events::builtin_conditions::have_unit); set_wml_condition("have_location", &game_events::builtin_conditions::have_location); set_wml_condition("variable", &game_events::builtin_conditions::variable_matches); // Create the effects table. cmd_log_ << "Adding effects table...\n"; lua_getglobal(L, "wesnoth"); lua_newtable(L); lua_setfield(L, -2, "effects"); lua_pop(L, 1); // Create the custom_synced_commands table. cmd_log_ << "Adding custom_synced_commands table...\n"; lua_getglobal(L, "wesnoth"); lua_newtable(L); lua_setfield(L, -2, "custom_synced_commands"); lua_pop(L, 1); // Create the game_events table. cmd_log_ << "Adding game_events table...\n"; lua_getglobal(L, "wesnoth"); lua_newtable(L); lua_setfield(L, -2, "game_events"); push_locations_table(L); lua_setfield(L, -2, "special_locations"); lua_pop(L, 1); // Create the theme_items table. cmd_log_ << "Adding game_display table...\n"; luaW_getglobal(L, "wesnoth", "interface"); lua_newtable(L); lua_createtable(L, 0, 2); lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_theme_items_get>); lua_setfield(L, -2, "__index"); lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_theme_items_set>); lua_setfield(L, -2, "__newindex"); lua_setmetatable(L, -2); lua_setfield(L, -2, "game_display"); lua_pop(L, 1); lua_settop(L, 0); for(const auto& handler : game_events::wml_action::registry()) { set_wml_action(handler.first, handler.second); } luaW_getglobal(L, "wesnoth", "effects"); for(const std::string& effect : unit::builtin_effects) { lua_pushstring(L, effect.c_str()); push_builtin_effect(); lua_rawset(L, -3); } lua_settop(L, 0); } void game_lua_kernel::initialize(const config& level) { lua_State *L = mState; assert(level_lua_.empty()); level_lua_.append_children(level, "lua"); //Create the races table. cmd_log_ << "Adding races table...\n"; lua_settop(L, 0); lua_getglobal(L, "wesnoth"); luaW_pushracetable(L); lua_setfield(L, -2, "races"); lua_pop(L, 1); // Execute the preload scripts. cmd_log_ << "Running preload scripts...\n"; game_config::load_config(game_lua_kernel::preload_config); for (const config &cfg : game_lua_kernel::preload_scripts) { run_lua_tag(cfg); } for (const config &cfg : level_lua_.child_range("lua")) { run_lua_tag(cfg); } } void game_lua_kernel::set_game_display(game_display * gd) { game_display_ = gd; } /// These are the child tags of [scenario] (and the like) that are handled /// elsewhere (in the C++ code). /// Any child tags not in this list will be passed to Lua's on_load event. static const std::array handled_file_tags {{ "color_palette", "color_range", "display", "end_level_data", "era", "event", "generator", "label", "lua", "map", "menu_item", "modification", "music", "options", "side", "sound_source", "story", "terrain_graphics", "time", "time_area", "tunnel", "undo_stack", "variables" }}; static bool is_handled_file_tag(const std::string& s) { for(const std::string& t : handled_file_tags) { if (s == t) return true; } return false; } /** * Executes the game_events.on_load function and passes to it all the * scenario tags not yet handled. */ void game_lua_kernel::load_game(const config& level) { lua_State *L = mState; if (!luaW_getglobal(L, "wesnoth", "game_events", "on_load")) return; lua_newtable(L); int k = 1; for (const config::any_child &v : level.all_children_range()) { if (is_handled_file_tag(v.key)) continue; lua_createtable(L, 2, 0); lua_pushstring(L, v.key.c_str()); lua_rawseti(L, -2, 1); luaW_pushconfig(L, v.cfg); lua_rawseti(L, -2, 2); lua_rawseti(L, -2, k++); } luaW_pcall(L, 1, 0, true); } /** * Executes the game_events.on_save function and adds to @a cfg the * returned tags. Also flushes the [lua] tags. */ void game_lua_kernel::save_game(config &cfg) { lua_State *L = mState; if (!luaW_getglobal(L, "wesnoth", "game_events", "on_save")) return; if (!luaW_pcall(L, 0, 1, false)) return; config v; luaW_toconfig(L, -1, v); lua_pop(L, 1); for (;;) { config::all_children_iterator i = v.ordered_begin(); if (i == v.ordered_end()) break; if (is_handled_file_tag(i->key)) { /* * It seems the only tags appearing in the config v variable here * are the core-lua-handled (currently [item] and [objectives]) * and the extra UMC ones. */ const std::string m = "Tag is already used: [" + i->key + "]"; log_error(m.c_str()); v.erase(i); continue; } cfg.splice_children(v, i->key); } } /** * Executes the game_events.on_event function. * Returns false if there was no lua handler for this event */ bool game_lua_kernel::run_event(const game_events::queued_event& ev) { lua_State *L = mState; if (!luaW_getglobal(L, "wesnoth", "game_events", "on_event")) return false; queued_event_context dummy(&ev, queued_events_); lua_pushstring(L, ev.name.c_str()); luaW_pcall(L, 1, 0, false); return true; } void game_lua_kernel::custom_command(const std::string& name, const config& cfg) { lua_State *L = mState; if (!luaW_getglobal(L, "wesnoth", "custom_synced_commands", name)) { return; } luaW_pushconfig(L, cfg); luaW_pcall(L, 1, 0, false); } /** * Applies its upvalue as an effect * Arg 1: The unit to apply to * Arg 3: The [effect] tag contents * Arg 3: If false, only build description * Return: The description of the effect */ int game_lua_kernel::cfun_builtin_effect(lua_State *L) { std::string which_effect = lua_tostring(L, lua_upvalueindex(1)); bool need_apply = luaW_toboolean(L, lua_upvalueindex(2)); // Argument 1 is the implicit "self" argument, which isn't needed here lua_unit u(luaW_checkunit(L, 2)); config cfg = luaW_checkconfig(L, 3); // The times= key is supposed to be ignored by the effect function. // However, just in case someone doesn't realize this, we will set it to 1 here. cfg["times"] = 1; if(need_apply) { u->apply_builtin_effect(which_effect, cfg); return 0; } else { std::string description = u->describe_builtin_effect(which_effect, cfg); lua_pushstring(L, description.c_str()); return 1; } } /** * Registers a function for use as an effect handler. */ void game_lua_kernel::push_builtin_effect() { lua_State *L = mState; // The effect name is at the top of the stack int str_i = lua_gettop(L); lua_newtable(L); // The functor table lua_newtable(L); // The functor metatable lua_pushstring(L, "__call"); lua_pushvalue(L, str_i); lua_pushboolean(L, true); lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_builtin_effect>, 2); lua_rawset(L, -3); // Set the call metafunction lua_pushstring(L, "__descr"); lua_pushvalue(L, str_i); lua_pushboolean(L, false); lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_builtin_effect>, 2); lua_rawset(L, -3); // Set the descr "metafunction" lua_setmetatable(L, -2); // Apply the metatable to the functor table } /** * Executes its upvalue as a wml action. */ int game_lua_kernel::cfun_wml_action(lua_State *L) { game_events::wml_action::handler h = reinterpret_cast (lua_touserdata(L, lua_upvalueindex(1))); vconfig vcfg = luaW_checkvconfig(L, 1); h(get_event_info(), vcfg); return 0; } /** * Registers a function for use as an action handler. */ void game_lua_kernel::set_wml_action(const std::string& cmd, game_events::wml_action::handler h) { lua_State *L = mState; lua_getglobal(L, "wesnoth"); lua_pushstring(L, "wml_actions"); lua_rawget(L, -2); lua_pushstring(L, cmd.c_str()); lua_pushlightuserdata(L, reinterpret_cast(h)); lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_wml_action>, 1); lua_rawset(L, -3); lua_pop(L, 2); } using wml_conditional_handler = bool(*)(const vconfig&); /** * Executes its upvalue as a wml condition and returns the result. */ static int cfun_wml_condition(lua_State *L) { wml_conditional_handler h = reinterpret_cast (lua_touserdata(L, lua_upvalueindex(1))); vconfig vcfg = luaW_checkvconfig(L, 1); lua_pushboolean(L, h(vcfg)); return 1; } /** * Registers a function for use as a conditional handler. */ void game_lua_kernel::set_wml_condition(const std::string& cmd, wml_conditional_handler h) { lua_State *L = mState; lua_getglobal(L, "wesnoth"); lua_pushstring(L, "wml_conditionals"); lua_rawget(L, -2); lua_pushstring(L, cmd.c_str()); lua_pushlightuserdata(L, reinterpret_cast(h)); lua_pushcclosure(L, &cfun_wml_condition, 1); lua_rawset(L, -3); lua_pop(L, 2); } /** * Runs a command from an event handler. * @return true if there is a handler for the command. * @note @a cfg should be either volatile or long-lived since the Lua * code may grab it for an arbitrary long time. */ bool game_lua_kernel::run_wml_action(const std::string& cmd, const vconfig& cfg, const game_events::queued_event& ev) { lua_State *L = mState; if (!luaW_getglobal(L, "wesnoth", "wml_actions", cmd)) return false; queued_event_context dummy(&ev, queued_events_); luaW_pushvconfig(L, cfg); luaW_pcall(L, 1, 0, true); return true; } /** * Evaluates a WML conidition. * * @returns Whether the condition passed. * @note @a cfg should be either volatile or long-lived since the Lua * code may grab it for an arbitrarily long time. */ bool game_lua_kernel::run_wml_conditional(const std::string& cmd, const vconfig& cfg) { lua_State* L = mState; // If an invalid coniditional tag is used, consider it a pass. if(!luaW_getglobal(L, "wesnoth", "wml_conditionals", cmd)) { lg::wml_error() << "unknown conditional wml: [" << cmd << "]\n"; return true; } luaW_pushvconfig(L, cfg); // Any runtime error is considered a fail. if(!luaW_pcall(L, 1, 1, true)) { return false; } bool b = luaW_toboolean(L, -1); lua_pop(L, 1); return b; } /** * Runs a script from a location filter. * The script is an already compiled function given by its name. */ bool game_lua_kernel::run_filter(char const *name, const map_location& l) { lua_pushinteger(mState, l.wml_x()); lua_pushinteger(mState, l.wml_y()); return run_filter(name, 2); } /** * Runs a script from a unit filter. * The script is an already compiled function given by its name. */ bool game_lua_kernel::run_filter(char const *name, const unit& u) { lua_State *L = mState; lua_unit* lu = luaW_pushlocalunit(L, const_cast(u)); // stack: unit // put the unit to the stack twice to prevent gc. lua_pushvalue(L, -1); // stack: unit, unit bool res = run_filter(name, 1); // stack: unit lu->clear_ref(); lua_pop(L, 1); return res; } /** * Runs a script from a filter. * The script is an already compiled function given by its name. */ bool game_lua_kernel::run_filter(char const *name, int nArgs) { map_locker(this); lua_State *L = mState; // Get the user filter by name. const std::vector& path = utils::split(name, '.', utils::STRIP_SPACES); if (!luaW_getglobal(L, path)) { std::string message = std::string() + "function " + name + " not found"; log_error(message.c_str(), "Lua SUF Error"); //we pushed nothing and can safeley return. return false; } lua_insert(L, -nArgs - 1); if (!luaW_pcall(L, nArgs, 1)) return false; bool b = luaW_toboolean(L, -1); lua_pop(L, 1); return b; } std::string game_lua_kernel::apply_effect(const std::string& name, unit& u, const config& cfg, bool need_apply) { lua_State *L = mState; int top = lua_gettop(L); std::string descr; // Stack: nothing lua_unit* lu = luaW_pushlocalunit(L, u); // Stack: unit // (Note: The unit needs to be on the stack twice to prevent untimely GC.) luaW_pushconfig(L, cfg); // Stack: unit, cfg if(luaW_getglobal(L, "wesnoth", "effects", name)) { map_locker(this); // Stack: unit, cfg, effect if(lua_istable(L, -1)) { // Effect is implemented by a table with __call and __descr if(need_apply) { lua_pushvalue(L, -1); // Stack: unit, cfg, effect, effect lua_pushvalue(L, top + 1); // Stack: unit, cfg, effect, effect, unit lua_pushvalue(L, top + 2); // Stack: unit, cfg, effect, effect, unit, cfg luaW_pcall(L, 2, 0); // Stack: unit, cfg, effect } if(luaL_getmetafield(L, -1, "__descr")) { // Stack: unit, cfg, effect, __descr if(lua_isstring(L, -1)) { // __descr was a static string descr = lua_tostring(L, -1); } else { lua_pushvalue(L, -2); // Stack: unit, cfg, effect, __descr, effect lua_pushvalue(L, top + 1); // Stack: unit, cfg, effect, __descr, effect, unit lua_pushvalue(L, top + 2); // Stack: unit, cfg, effect, __descr, effect, unit, cfg luaW_pcall(L, 3, 1); if(lua_isstring(L, -1) && !lua_isnumber(L, -1)) { descr = lua_tostring(L, -1); } else { ERR_LUA << "Effect __descr metafunction should have returned a string, but instead returned "; if(lua_isnone(L, -1)) { ERR_LUA << "nothing"; } else { ERR_LUA << lua_typename(L, lua_type(L, -1)); } } } } } else if(need_apply) { // Effect is assumed to be a simple function; no description is provided lua_pushvalue(L, top + 1); // Stack: unit, cfg, effect, unit lua_pushvalue(L, top + 2); // Stack: unit, cfg, effect, unit, cfg luaW_pcall(L, 2, 0); // Stack: unit, cfg } } lua_settop(L, top); lu->clear_ref(); return descr; } ai::lua_ai_context* game_lua_kernel::create_lua_ai_context(char const *code, ai::engine_lua *engine) { return ai::lua_ai_context::create(mState,code,engine); } ai::lua_ai_action_handler* game_lua_kernel::create_lua_ai_action_handler(char const *code, ai::lua_ai_context &context) { return ai::lua_ai_action_handler::create(mState,code,context); } void game_lua_kernel::mouse_over_hex_callback(const map_location& loc) { lua_State *L = mState; if (!luaW_getglobal(L, "wesnoth", "game_events", "on_mouse_move")) { return; } lua_push(L, loc.wml_x()); lua_push(L, loc.wml_y()); luaW_pcall(L, 2, 0, false); return; } void game_lua_kernel::select_hex_callback(const map_location& loc) { lua_State *L = mState; if (!luaW_getglobal(L, "wesnoth", "game_events", "on_mouse_action")) { return; } lua_push(L, loc.wml_x()); lua_push(L, loc.wml_y()); luaW_pcall(L, 2, 0, false); return; }