wesnoth/src/scripting/game_lua_kernel.cpp

5366 lines
156 KiB
C++

/*
Copyright (C) 2009 - 2016 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
Part of the Battle for Wesnoth Project http://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 "global.hpp"
#include "actions/attack.hpp" // for battle_context_unit_stats, 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/manager.hpp" // for add_event_handler
#include "game_events/pump.hpp" // for queued_event
#include "game_preferences.hpp" // for encountered_units
#include "help/help.hpp"
#include "image.hpp" // for get_image, locator
#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 "scripting/lua_api.hpp" // for luaW_toboolean, etc
#include "scripting/lua_common.hpp"
#include "scripting/lua_cpp_function.hpp"
#include "scripting/lua_formula_bridge.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_types.hpp" // for getunitKey, dlgclbkKey, etc
#include "scripting/lua_unit_type.hpp"
#include "scripting/push_check.hpp"
#include "sdl/utils.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, tod_color
#include "tod_manager.hpp" // for tod_manager
#include "tstring.hpp" // for t_string, operator+
#include "units/unit.hpp" // for unit, intrusive_ptr_add_ref, etc
#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 "util.hpp" // for lexical_cast
#include "utils/markov_generator.hpp"
#include "utils/context_free_grammar_generator.hpp"
#include "variable.hpp" // for vconfig, etc
#include "variable_info.hpp"
#include "wml_exception.hpp"
#include "utils/functional.hpp" // for bind_t, bind
#include <boost/intrusive_ptr.hpp> // for intrusive_ptr
#include <boost/optional.hpp>
#include <boost/range/algorithm/copy.hpp> // boost::copy
#include <boost/range/adaptors.hpp> // boost::adaptors::filtered
#include <boost/tuple/tuple.hpp> // for tuple
#include <cassert> // for assert
#include <cstring> // for strcmp
#include <iterator> // for distance, advance
#include <map> // for map, map<>::value_type, etc
#include <new> // for operator new
#include <set> // for set
#include <sstream> // for operator<<, basic_ostream, etc
#include <utility> // for pair
#include <algorithm>
#include <vector> // for vector, etc
#include <SDL_timer.h> // for SDL_GetTicks
#include <SDL_video.h> // for SDL_Color, SDL_Surface
#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
// Suppress uninitialized variables warnings, because of boost::optional constructors in this file which apparently confuses gcc
#if defined(__GNUC__) && !defined(__clang__) // we shouldn't need this for clang, but for gcc and tdm-gcc we probably do
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 ) // "GCC diagnostic ignored" is apparently not available at version 4.5.2
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#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<config> game_lua_kernel::preload_scripts;
config game_lua_kernel::preload_config;
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(config const &game_config)
{
game_lua_kernel::preload_scripts.clear();
for (config const &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(std::string const &caption, std::string const &msg)
{
if (game_display_) {
game_display_->get_chat_manager().add_chat_message(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<int> 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<game_lua_kernel>(L).map().special_locations().size());
return 1;
}
static int special_locations_next(lua_State *L)
{
const t_translation::tstarting_positions::left_map& left = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).map().special_locations().left;
t_translation::tstarting_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());
lua_createtable(L, 2, 0);
lua_pushnumber(L, it->second.x + 1);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, it->second.y + 1);
lua_rawseti(L, -2, 2);
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::tstarting_positions::left_map& left = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).map().special_locations().left;
auto it = left.find(luaL_checkstring(L, 2));
if (it == left.end()) {
return 0;
}
else {
lua_createtable(L, 2, 0);
lua_pushnumber(L, it->second.x + 1);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, it->second.y + 1);
lua_rawseti(L, -2, 2);
return 1;
}
}
static int special_locations_newindex(lua_State *L)
{
lua_pushstring(L, "special locations cannot be modified uwing wesnoth.special_locations");
return lua_error(L);
}
static void push_locations_talbe(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<qe const *> & stack_;
queued_event_context(qe const *new_qe, std::stack<qe const*> & stack)
: stack_(stack)
{
stack_.push(new_qe);
}
~queued_event_context()
{
stack_.pop();
}
};
}//unnamed namespace for queued_event_context
/**
* Destroys a unit object before it is collected (__gc metamethod).
*/
static int impl_unit_collect(lua_State *L)
{
lua_unit *u = static_cast<lua_unit *>(lua_touserdata(L, 1));
u->lua_unit::~lua_unit();
return 0;
}
/**
* Checks two lua proxy units for equality. (__eq metamethod)
*/
static int impl_unit_equality(lua_State* L)
{
unit& left = luaW_checkunit(L, 1);
unit& right = luaW_checkunit(L, 2);
const bool equal = left.underlying_id() == right.underlying_id();
lua_pushboolean(L, equal);
return 1;
}
/**
* Gets some data on a unit (__index metamethod).
* - Arg 1: full userdata containing the unit id.
* - Arg 2: string containing the name of the property.
* - Ret 1: something containing the attribute.
*/
static int impl_unit_get(lua_State *L)
{
lua_unit *lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
char const *m = luaL_checkstring(L, 2);
const unit* pu = lu->get();
if (strcmp(m, "valid") == 0)
{
if (!pu) return 0;
if (lu->on_map())
lua_pushstring(L, "map");
else if (lu->on_recall_list())
lua_pushstring(L, "recall");
else
lua_pushstring(L, "private");
return 1;
}
if (!pu) return luaL_argerror(L, 1, "unknown unit");
unit const &u = *pu;
// Find the corresponding attribute.
return_int_attrib("x", u.get_location().x + 1);
return_int_attrib("y", u.get_location().y + 1);
if (strcmp(m, "loc") == 0) {
lua_pushinteger(L, u.get_location().x + 1);
lua_pushinteger(L, u.get_location().y + 1);
return 2;
}
return_int_attrib("side", u.side());
return_string_attrib("id", u.id());
return_string_attrib("type", u.type_id());
return_string_attrib("image_mods", u.effect_image_mods());
return_string_attrib("usage", u.usage());
return_int_attrib("hitpoints", u.hitpoints());
return_int_attrib("max_hitpoints", u.max_hitpoints());
return_int_attrib("experience", u.experience());
return_int_attrib("max_experience", u.max_experience());
return_int_attrib("recall_cost", u.recall_cost());
return_int_attrib("moves", u.movement_left());
return_int_attrib("max_moves", u.total_movement());
return_int_attrib("max_attacks", u.max_attacks());
return_int_attrib("attacks_left", u.attacks_left());
return_tstring_attrib("name", u.name());
return_bool_attrib("canrecruit", u.can_recruit());
return_int_attrib("level", u.level());
return_int_attrib("cost", u.cost());
return_vector_string_attrib("extra_recruit", u.recruits());
return_vector_string_attrib("advances_to", u.advances_to());
if (strcmp(m, "alignment") == 0) {
lua_push(L, u.alignment());
return 1;
}
if (strcmp(m, "upkeep") == 0) {
unit::t_upkeep upkeep = u.upkeep_raw();
if(boost::get<unit::upkeep_full>(&upkeep) != nullptr){
lua_pushstring(L, "full");
}
if(boost::get<unit::upkeep_loyal>(&upkeep) != nullptr){
lua_pushstring(L, "loyal");
}
else {
lua_push(L, boost::get<int>(upkeep));
}
return 1;
}
if (strcmp(m, "advancements") == 0) {
lua_push(L, u.modification_advancements());
return 1;
}
if (strcmp(m, "overlays") == 0) {
lua_push(L, u.overlays());
return 1;
}
if (strcmp(m, "traits") == 0) {
lua_push(L, u.get_traits_list());
return 1;
}
if (strcmp(m, "abilities") == 0) {
lua_push(L, u.get_ability_list());
return 1;
}
if (strcmp(m, "status") == 0) {
lua_createtable(L, 1, 0);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
lua_pushlightuserdata(L
, ustatusKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
return 1;
}
if (strcmp(m, "variables") == 0) {
lua_createtable(L, 1, 0);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
lua_pushlightuserdata(L
, unitvarKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
return 1;
}
if (strcmp(m, "attacks") == 0) {
lua_createtable(L, 1, 0);
lua_pushvalue(L, 1);
// hack: store the unit at -1 becasue we want positive indexes to refers to the attacks.
lua_rawseti(L, -2, -1);
lua_pushlightuserdata(L, uattacksKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
return 1;
}
return_bool_attrib("hidden", u.get_hidden());
return_bool_attrib("petrified", u.incapacitated());
return_bool_attrib("resting", u.resting());
return_string_attrib("role", u.get_role());
return_string_attrib("race", u.race()->id());
return_string_attrib("gender", gender_string(u.gender()));
return_string_attrib("variation", u.variation());
return_bool_attrib("zoc", u.get_emit_zoc());
return_string_attrib("facing", map_location::write_direction(u.facing()));
return_string_attrib("portrait", u.big_profile() == u.absolute_image() ? u.absolute_image() + u.image_mods() : u.big_profile());
return_cfg_attrib("__cfg", u.write(cfg); u.get_location().write(cfg));
return lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).return_unit_method(L, m);
}
/**
* Sets some data on a unit (__newindex metamethod).
* - Arg 1: full userdata containing the unit id.
* - Arg 2: string containing the name of the property.
* - Arg 3: something containing the attribute.
*/
static int impl_unit_set(lua_State *L)
{
lua_unit *lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
char const *m = luaL_checkstring(L, 2);
unit* pu = lu->get();
if (!pu) return luaL_argerror(L, 1, "unknown unit");
unit &u = *pu;
// Find the corresponding attribute.
//modify_int_attrib_check_range("side", u.set_side(value), 1, static_cast<int>(teams().size())); TODO: Figure out if this is a good idea, to refer to teams() and make this depend on having a gamestate
modify_int_attrib("side", u.set_side(value));
modify_int_attrib("moves", u.set_movement(value));
modify_int_attrib("hitpoints", u.set_hitpoints(value));
modify_int_attrib("experience", u.set_experience(value));
modify_int_attrib("recall_cost", u.set_recall_cost(value));
modify_int_attrib("attacks_left", u.set_attacks(value));
modify_int_attrib("level", u.set_level(value));
modify_bool_attrib("resting", u.set_resting(value));
modify_tstring_attrib("name", u.set_name(value));
modify_string_attrib("role", u.set_role(value));
modify_string_attrib("facing", u.set_facing(map_location::parse_direction(value)));
modify_bool_attrib("hidden", u.set_hidden(value));
modify_bool_attrib("zoc", u.set_emit_zoc(value));
modify_bool_attrib("canrecruit", u.set_can_recruit(value));
modify_vector_string_attrib("extra_recruit", u.set_recruits(vector));
modify_vector_string_attrib("advances_to", u.set_advances_to(vector));
if (strcmp(m, "alignment") == 0) {
u.set_alignment(lua_check<unit_type::ALIGNMENT>(L, 3));
return 0;
}
if (strcmp(m, "advancements") == 0) {
u.set_advancements(lua_check<std::vector<config> >(L, 3));
return 0;
}
if (strcmp(m, "upkeep") == 0) {
if(lua_isnumber(L, 3)) {
u.set_upkeep(luaL_checkint(L, 3));
return 0;
}
const char* v = luaL_checkstring(L, 3);
if(strcmp(m, "loyal") == 0) {
u.set_upkeep(unit::upkeep_loyal());
}
else if(strcmp(m, "full") == 0) {
u.set_upkeep(unit::upkeep_full());
}
else {
std::string err_msg = "unknown upkeep value of unit: ";
err_msg += v;
return luaL_argerror(L, 2, err_msg.c_str());
}
return 0;
}
if (!lu->on_map()) {
map_location loc = u.get_location();
modify_int_attrib("x", loc.x = value - 1; u.set_location(loc));
modify_int_attrib("y", loc.y = value - 1; u.set_location(loc));
}
std::string err_msg = "unknown modifiable property of unit: ";
err_msg += m;
return luaL_argerror(L, 2, err_msg.c_str());
}
/**
* Gets the status of a unit (__index metamethod).
* - Arg 1: table containing the userdata containing the unit id.
* - Arg 2: string containing the name of the status.
* - Ret 1: boolean.
*/
static int impl_unit_status_get(lua_State *L)
{
if (!lua_istable(L, 1))
return luaL_typerror(L, 1, "unit status");
lua_rawgeti(L, 1, 1);
const unit* u = luaW_tounit(L, -1);
if (!u) return luaL_argerror(L, 1, "unknown unit");
char const *m = luaL_checkstring(L, 2);
lua_pushboolean(L, u->get_state(m));
return 1;
}
/**
* Sets the status of a unit (__newindex metamethod).
* - Arg 1: table containing the userdata containing the unit id.
* - Arg 2: string containing the name of the status.
* - Arg 3: boolean.
*/
static int impl_unit_status_set(lua_State *L)
{
if (!lua_istable(L, 1))
return luaL_typerror(L, 1, "unit status");
lua_rawgeti(L, 1, 1);
unit* u = luaW_tounit(L, -1);
if (!u) return luaL_argerror(L, 1, "unknown unit");
char const *m = luaL_checkstring(L, 2);
u->set_state(m, luaW_toboolean(L, 3));
return 0;
}
/**
* Gets the variable of a unit (__index metamethod).
* - Arg 1: table containing the userdata containing the unit id.
* - Arg 2: string containing the name of the status.
* - Ret 1: boolean.
*/
static int impl_unit_variables_get(lua_State *L)
{
if (!lua_istable(L, 1))
return luaL_typerror(L, 1, "unit variables");
lua_rawgeti(L, 1, 1);
const unit* u = luaW_tounit(L, -1);
if (!u) return luaL_argerror(L, 1, "unknown unit");
char const *m = luaL_checkstring(L, 2);
return_cfgref_attrib("__cfg", u->variables());
variable_access_const v(m, u->variables());
return luaW_pushvariable(L, v) ? 1 : 0;
}
/**
* Gets the attacks of a unit or unit type (__index metamethod).
* - Arg 1: table containing the userdata containing the unit or unit type.
* - Arg 2: index (int) or id (string) identifying a particular attack.
* - Ret 1: the unit's attacks.
*/
static int impl_unit_attacks_get(lua_State *L)
{
if (!lua_istable(L, 1)) {
return luaL_typerror(L, 1, "unit attacks");
}
lua_rawgeti(L, 1, -1);
const unit* u = luaW_tounit(L, -1);
const unit_type* ut = static_cast<const unit_type*>(luaL_testudata(L, -1, "unit type"));
if (!u && !ut) {
return luaL_argerror(L, 1, "unknown unit");
}
const attack_type* attack = nullptr;
const std::vector<attack_type>& attacks = u ? u->attacks() : ut->attacks();
if(!lua_isnumber(L,2)) {
std::string attack_id = luaL_checkstring(L, 2);
for (const attack_type& at : attacks) {
if(at.id() == attack_id) {
attack = &at;
break;
}
}
if (attack == nullptr) {
//return nil on invalid index, just like lua tables do.
return 0;
}
}
else
{
//
size_t index = luaL_checkinteger(L, 2) - 1;
if (index >= attacks.size()) {
//return nil on invalid index, just like lua tables do.
return 0;
}
attack = &attacks[index];
}
// stack { lua_unit }, id/index, lua_unit
lua_createtable(L, 2, 0);
// stack { lua_unit }, id/index, lua_unit, table
lua_pushvalue(L, -2);
// stack { lua_unit }, id/index, lua_unit, table, lua_unit
lua_rawseti(L, -2, 1);
// stack { lua_unit }, id/index, lua_unit, table
lua_pushstring(L, attack->id().c_str());
// stack { lua_unit }, id/index, lua_unit, table, attack id
lua_rawseti(L, -2, 2);
// stack { lua_unit }, id/index, lua_unit, table
lua_pushlightuserdata(L, uattackKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
return 1;
}
/**
* Counts the attacks of a unit (__len metamethod).
* - Arg 1: table containing the userdata containing the unit id.
* - Ret 1: size of unit attacks vector.
*/
static int impl_unit_attacks_len(lua_State *L)
{
if (!lua_istable(L, 1)) {
return luaL_typerror(L, 1, "unit attacks");
}
lua_rawgeti(L, 1, -1);
const unit* u = luaW_tounit(L, -1);
if (!u) {
return luaL_argerror(L, 1, "unknown unit");
}
lua_pushinteger(L, u->attacks().size());
return 1;
}
/**
* Gets a propoerty of a units attack (__index metamethod).
* - Arg 1: table containing the userdata containing the unit id. and a string identyfying the attack.
* - Arg 2: string
* - Ret 1:
*/
static int impl_unit_attack_get(lua_State *L)
{
if (!lua_istable(L, 1)) {
return luaL_typerror(L, 1, "unit attack");
}
lua_rawgeti(L, 1, 1);
const unit* u = luaW_tounit(L, -1);
if (!u) {
return luaL_argerror(L, 1, "unknown unit");
}
lua_rawgeti(L, 1, 2);
std::string attack_id = luaL_checkstring(L, -1);
char const *m = luaL_checkstring(L, 2);
for (const attack_type& attack : u->attacks())
{
if(attack.id() == attack_id)
{
return_string_attrib("description", attack.name());
return_string_attrib("name", attack.id());
return_string_attrib("type", attack.type());
return_string_attrib("icon", attack.icon());
return_string_attrib("range", attack.range());
// "min_range"
// "max_range"
return_int_attrib("damage", attack.damage());
return_int_attrib("number", attack.num_attacks());
return_int_attrib("attack_weight", attack.attack_weight());
return_int_attrib("defense_weight", attack.defense_weight());
return_int_attrib("accuracy", attack.accuracy());
return_int_attrib("movement_used", attack.movement_used());
return_int_attrib("parry", attack.parry());
return_cfgref_attrib("specials", attack.specials());
std::string err_msg = "unknown property of attack: ";
err_msg += m;
return luaL_argerror(L, 2, err_msg.c_str());
}
}
return luaL_argerror(L, 1, "invalid attack id");
}
/**
* Gets a propoerty of a units attack (__index metamethod).
* - Arg 1: table containing the userdata containing the unit id. and a string identyfying the attack.
* - Arg 2: string
* - Ret 1:
*/
static int impl_unit_attack_set(lua_State *L)
{
if (!lua_istable(L, 1)) {
return luaL_typerror(L, 1, "unit attack");
}
lua_rawgeti(L, 1, 1);
unit* u = luaW_tounit(L, -1);
if (!u) {
return luaL_argerror(L, 1, "unknown unit");
}
lua_rawgeti(L, 1, 2);
std::string attack_id = luaL_checkstring(L, -1);
char const *m = luaL_checkstring(L, 2);
for (attack_type& attack : u->attacks())
{
if(attack.id() == attack_id)
{
modify_tstring_attrib("description", attack.set_name(value));
// modify_string_attrib("name", attack.set_id(value));
modify_string_attrib("type", attack.set_type(value));
modify_string_attrib("icon", attack.set_icon(value));
modify_string_attrib("range", attack.set_range(value));
// "min_range"
// "max_range"
modify_int_attrib("damage", attack.set_damage(value));
modify_int_attrib("number", attack.set_num_attacks(value));
modify_int_attrib("attack_weight", attack.set_attack_weight(value));
modify_int_attrib("defense_weight", attack.set_defense_weight(value));
modify_int_attrib("accuracy", attack.set_accuracy(value));
modify_int_attrib("movement_used", attack.set_movement_used(value));
modify_int_attrib("parry", attack.set_parry(value));
if (strcmp(m, "specials") == 0) { \
attack.set_specials(luaW_checkconfig(L, 3));
return 0;
}
return_cfgref_attrib("specials", attack.specials());
std::string err_msg = "unknown modifyable property of attack: ";
err_msg += m;
return luaL_argerror(L, 2, err_msg.c_str());
}
}
return luaL_argerror(L, 1, "invalid attack id");
}
/**
* Sets the variable of a unit (__newindex metamethod).
* - Arg 1: table containing the userdata containing the unit id.
* - Arg 2: string containing the name of the status.
* - Arg 3: scalar.
*/
static int impl_unit_variables_set(lua_State *L)
{
if (!lua_istable(L, 1))
return luaL_typerror(L, 1, "unit variables");
lua_rawgeti(L, 1, 1);
unit* u = luaW_tounit(L, -1);
if (!u) return luaL_argerror(L, 1, "unknown unit");
char const *m = luaL_checkstring(L, 2);
if (strcmp(m, "__cfg") == 0) {
u->variables() = luaW_checkconfig(L, 3);
return 0;
}
variable_access_create v(m, u->variables());
luaW_checkvariable(L, v, 3);
return 0;
}
/**
* 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;
}
}
int game_lua_kernel::intf_animate_unit(lua_State *L)
{
// if (game_display_)
{
events::command_disabler disable_commands;
unit_display::wml_animation(luaW_checkvconfig(L, 1), get_event_info().loc1);
}
return 0;
}
int game_lua_kernel::intf_gamestate_inspector(lua_State *L)
{
if (game_display_) {
return lua_gui2::show_gamestate_inspector(game_display_->video(), luaW_checkvconfig(L, 1));
}
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) {
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(u.underlying_id());
lua_pushlightuserdata(L, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
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;
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(ui->underlying_id());
lua_pushlightuserdata(L
, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
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;
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(ui->underlying_id());
lua_pushlightuserdata(L
, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
return 1;
}
/**
* Gets all the units matching a given filter.
* - 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_units(lua_State *L)
{
vconfig filter = luaW_checkvconfig(L, 1, true);
// Go through all the units while keeping the following stack:
// 1: metatable, 2: return table, 3: userdata, 4: metatable copy
lua_settop(L, 0);
lua_pushlightuserdata(L
, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_newtable(L);
int i = 1;
// note that if filter is null, this yields a null filter matching everything (and doing no work)
filter_context & fc = game_state_;
for (const unit * ui : unit_filter(filter, &fc).all_matches_on_map()) {
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(ui->underlying_id());
lua_pushvalue(L, 1);
lua_setmetatable(L, 3);
lua_rawseti(L, 2, 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)
{
if (!luaW_hasmetatable(L, 1, getunitKey))
return luaL_typerror(L, 1, "unit");
lua_unit *lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
unit* u = lu->get();
if (!u) return luaL_argerror(L, 1, "unit not found");
vconfig filter = luaW_checkvconfig(L, 2, true);
if (filter.null()) {
lua_pushboolean(L, true);
return 1;
}
filter_context & fc = game_state_;
if (luaW_hasmetatable(L, 3, getunitKey)) {
if (int side = lu->on_recall_list()) {
WRN_LUA << "wesnoth.match_unit 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 = (teams())[side - 1];
scoped_recall_unit auto_store("this_unit", t.save_id(), t.recall_list().find_index(u->id()));
lua_pushboolean(L, unit_filter(filter, &fc).matches(*u, map_location()));
return 1;
}
lua_unit *lu_adj = static_cast<lua_unit *>(lua_touserdata(L, 1));
unit* u_adj = lu_adj->get();
if (!u_adj) {
return luaL_argerror(L, 3, "unit not found");
}
lua_pushboolean(L, unit_filter(filter, &fc).matches(*u, *u_adj));
} else if (int side = lu->on_recall_list()) {
map_location loc;
luaW_tolocation(L, 3, loc); // If argument 3 isn't a location, loc is unchanged
team &t = (teams())[side - 1];
scoped_recall_unit auto_store("this_unit", t.save_id(), t.recall_list().find_index(u->id()));
lua_pushboolean(L, unit_filter(filter, &fc).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, &fc).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: metatable, 2: return table, 3: userdata, 4: metatable copy
lua_settop(L, 0);
lua_pushlightuserdata(L
, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_newtable(L);
int i = 1, s = 1;
filter_context & fc = game_state_;
const unit_filter ufilt(filter, &fc);
for (team &t : teams())
{
for (unit_ptr & u : t.recall_list())
{
if (!filter.null()) {
scoped_recall_unit auto_store("this_unit",
t.save_id(), t.recall_list().find_index(u->id()));
if (!ufilt( *u, map_location() ))
continue;
}
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(s, u->underlying_id());
lua_pushvalue(L, 1);
lua_setmetatable(L, 3);
lua_rawseti(L, 2, i);
++i;
}
++s;
}
return 1;
}
/**
* Fires an event.
* - Arg 1: string containing the event name.
* - 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)
{
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 = 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;
}
/**
* Gets a side specific WML variable.
* - Arg 1: integer side number.
* - Arg 2: string containing the variable name.
* - Ret 1: value of the variable, if any.
*/
int game_lua_kernel::intf_get_side_variable(lua_State *L)
{
unsigned side_index = luaL_checkinteger(L, 1) - 1;
if(side_index >= teams().size()) {
return luaL_argerror(L, 1, "invalid side number");
}
char const *m = luaL_checkstring(L, 1);
variable_access_const v(m, teams()[side_index].variables());
return luaW_pushvariable(L, v) ? 1 : 0;
}
/**
* Gets a side specific WML variable.
* - Arg 1: integer side number.
* - Arg 2: string containing the variable name.
* - Arg 3: boolean/integer/string/table containing the value.
*/
int game_lua_kernel::intf_set_side_variable(lua_State *L)
{
unsigned side_index = luaL_checkinteger(L, 1) - 1;
if(side_index >= teams().size()) {
return luaL_argerror(L, 1, "invalid side number");
}
char const *m = luaL_checkstring(L, 1);
//TODO: maybe support removing values with an empty arg3.
variable_access_create v(m, teams()[side_index].variables());
luaW_checkvariable(L, v, 2);
return 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;
}
/**
* Returns a random numer, same interface as math.random.
*/
int game_lua_kernel::intf_random(lua_State *L)
{
if(lua_isnoneornil(L, 1)) {
double r = double (random_new::generator->next_random());
double r_max = double (std::numeric_limits<uint32_t>::max());
lua_push(L, r / (r_max + 1));
return 1;
}
else {
int32_t min;
int32_t max;
if(lua_isnumber(L, 2)) {
min = lua_check<int32_t>(L, 1);
max = lua_check<int32_t>(L, 2);
}
else {
min = 1;
max = lua_check<int32_t>(L, 1);
}
if(min > max) {
return luaL_argerror(L, 1, "min > max");
}
lua_push(L, random_new::generator->get_random_int(min, max));
return 1;
}
}
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 = const_cast<game_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 = const_cast<game_classification &> (play_controller_.get_classification());
classification.end_text = luaW_checktstring(L, 1);
if (lua_isnumber(L, 2)) {
classification.end_text_duration = static_cast<int> (lua_tonumber(L, 2));
}
return 0;
}
int game_lua_kernel::intf_set_next_scenario(lua_State *L)
{
gamedata().set_next_scenario(luaL_checkstring(L, 1));
return 0;
}
int game_lua_kernel::intf_shroud_op(lua_State *L, bool place_shroud)
{
vconfig cfg = luaW_checkvconfig(L, 1);
// Filter the sides.
std::vector<int> sides = get_sides_vector(cfg);
size_t index;
// Filter the locations.
std::set<map_location> locs;
const terrain_filter filter(cfg, &game_state_);
filter.get_locations(locs, true);
for (const int &side_num : sides)
{
index = side_num - 1;
team &t = teams()[index];
for (map_location const &loc : locs)
{
if (place_shroud) {
t.place_shroud(loc);
} else {
t.clear_shroud(loc);
}
}
}
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 = luaL_checkint(L, 1) - 1;
unsigned side_2 = luaL_checkint(L, 2) - 1;
if (side_1 >= teams().size() || side_2 >= teams().size()) return 0;
lua_pushboolean(L, teams()[side_1].is_enemy(side_2 + 1));
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);
t_translation::t_terrain const &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;
}
/**
* 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::t_terrain t = t_translation::read_terrain_code(m);
if (t == t_translation::NONE_TERRAIN) return 0;
terrain_type const &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_checkint(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);
int new_side = lua_isnoneornil(L, 2) ? 0 : luaL_checkint(L, 2);
if (!board().map().is_village(loc))
return 0;
int old_side = board().village_owner(loc) + 1;
if (new_side == old_side || new_side < 0 || new_side > static_cast<int>(teams().size()) || board().team_is_defeated(teams()[new_side - 1])) {
return 0;
}
if (old_side) {
teams()[old_side - 1].lose_village(loc);
}
if (new_side) {
teams()[new_side - 1].get_village(loc, old_side, (luaW_toboolean(L, 4) ? &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.x + 1);
lua_pushinteger(L, loc.y + 1);
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.x + 1);
lua_pushinteger(L, loc.y + 1);
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)
{
const int side = luaL_checkint(L, 1);
if(side < 1 || static_cast<int>(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;
lua_createtable(L, 2, 0);
lua_pushinteger(L, starting_pos.x + 1);
lua_rawseti(L, -2, 1);
lua_pushinteger(L, starting_pos.y + 1);
lua_rawseti(L, -2, 2);
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("base_income", game_config::base_income);
return_int_attrib("village_income", game_config::village_income);
return_int_attrib("village_support", game_config::village_support);
return_int_attrib("poison_amount", game_config::poison_amount);
return_int_attrib("rest_heal_amount", game_config::rest_heal_amount);
return_int_attrib("recall_cost", game_config::recall_cost);
return_int_attrib("kill_experience", game_config::kill_experience);
return_int_attrib("last_turn", tod_man().number_of_turns());
return_string_attrib("version", game_config::version);
return_bool_attrib("debug", game_config::debug);
return_bool_attrib("debug_lua", game_config::debug_lua);
return_bool_attrib("mp_debug", game_config::mp_debug);
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.first))["id"]);
++its.first;
for(; its.first != its.second; ++its.first) {
eras_list = eras_list + "," + (*(its.first))["id"];
}
return_string_attrib("eras", eras_list);
}
return 0;
}
/**
* 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("last_turn", tod_man().set_number_of_turns_by_wml(value));
std::string err_msg = "unknown modifiable property of game_config: ";
err_msg += m;
return luaL_argerror(L, 2, err_msg.c_str());
}
/**
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());
if (strcmp(m, "event_context") == 0)
{
const game_events::queued_event &ev = get_event_info();
config cfg;
cfg["name"] = ev.name;
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);
}
if (ev.loc1.valid()) {
cfg["x1"] = ev.loc1.filter_x() + 1;
cfg["y1"] = ev.loc1.filter_y() + 1;
// 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.x + 1;
cfg["unit_y"] = ev.loc1.y + 1;
}
if (ev.loc2.valid()) {
cfg["x2"] = ev.loc2.filter_x() + 1;
cfg["y2"] = ev.loc2.filter_y() + 1;
}
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(game_display_->video(), luaL_checkstring(L, 1));
}
return 0;
}
/**
* Dumps a wml table or userdata wml object into a pretty string.
* - Arg 1: wml table or vconfig userdata
* - Ret 1: string
*/
static int intf_debug(lua_State* L)
{
const config& arg = luaW_checkconfig(L, 1);
const std::string& result = arg.debug();
lua_pushstring(L, result.c_str());
return 1;
}
int game_lua_kernel::intf_check_end_level_disabled(lua_State * L)
{
lua_pushboolean(L, play_controller_.is_regular_game_end());
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;
}
int game_lua_kernel::intf_end_level(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
if (play_controller_.is_regular_game_end()) {
return 0;
}
end_level_data data;
data.proceed_to_next_level = cfg["proceed_to_next_level"].to_bool(true);
data.transient.custom_endlevel_music = cfg["music"].str();
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*)
{
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;
pathfind::cost_calculator *calc = nullptr;
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, "max_cost");
lua_rawget(L, arg);
if (!lua_isnil(L, -1))
stop_at = luaL_checknumber(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 <= int(teams().size())) viewing_side = i;
else see_all = true;
}
lua_pop(L, 1);
}
else if (lua_isfunction(L, arg))
{
calc = new lua_pathfind_cost_calculator(L, arg);
}
pathfind::teleport_map teleport_locations;
if (!calc) {
if (!u) return luaL_argerror(L, 1, "unit not found");
const team &viewing_team = board().teams()[(viewing_side ? viewing_side : u->side()) - 1];
if (!ignore_teleport) {
teleport_locations = pathfind::get_teleport_locations(
*u, viewing_team, see_all, ignore_units);
}
calc = 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);
delete calc;
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].x + 1);
lua_rawseti(L, -2, 1);
lua_pushinteger(L, res.steps[i].y + 1);
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 <= int(teams().size())) viewing_side = i;
else see_all = true;
}
lua_pop(L, 1);
}
const team &viewing_team = board().teams()[(viewing_side ? viewing_side : u->side()) - 1];
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.x + 1);
lua_rawseti(L, -2, 1);
lua_pushinteger(L, s.curr.y + 1);
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<typename T> // 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);
boost::tuple<map_location, int, std::string> tuple(src, side, unit_type);
fake_units.push_back(tuple);
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* u = luaW_tounit(L, arg, true);
vconfig filter = vconfig::unconstructed_vconfig();
luaW_tovconfig(L, arg, filter);
std::vector<const unit*> real_units;
typedef std::vector<boost::tuple<map_location, int, std::string> > unit_type_vector;
unit_type_vector fake_units;
if (u) // 1. arg - unit
{
real_units.push_back(u);
}
else if (!filter.null()) // 1. arg - filter
{
filter_context & fc = game_state_;
boost::copy(unit_filter(filter, &fc).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 <= int(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<map_location> 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 = board().teams()[(viewing_side ? viewing_side : 1) - 1];
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(fu.get<2>());
cost_map.add_unit(fu.get<0>(), ut, fu.get<1>());
}
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.x + 1);
lua_rawseti(L, -2, 1);
lua_pushinteger(L, loc.y + 1);
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_heal_unit(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
const game_events::queued_event &event_info = get_event_info();
unit_map & temp = units();
unit_map* units = & temp;
const vconfig & healers_filter = cfg.child("filter_second");
std::vector<unit*> healers;
if (!healers_filter.null()) {
const unit_filter ufilt(healers_filter, &game_state_);
for (unit& u : *units) {
if ( ufilt(u) && u.has_ability_type("heals") ) {
healers.push_back(&u);
}
}
}
const config::attribute_value amount = cfg["amount"];
const config::attribute_value moves = cfg["moves"];
const bool restore_attacks = cfg["restore_attacks"].to_bool(false);
const bool restore_statuses = cfg["restore_statuses"].to_bool(true);
const bool animate = cfg["animate"].to_bool(false);
const vconfig & healed_filter = cfg.child("filter");
bool only_unit_at_loc1 = healed_filter.null();
bool heal_amount_to_set = true;
const unit_filter ufilt(healed_filter, &game_state_);
for(unit_map::unit_iterator u = units->begin(); u != units->end(); ++u) {
if (only_unit_at_loc1)
{
u = units->find(event_info.loc1);
if(!u.valid()) return 0;
}
else if ( !ufilt(*u) ) continue;
int heal_amount = u->max_hitpoints() - u->hitpoints();
if(amount.blank() || amount == "full") u->set_hitpoints(u->max_hitpoints());
else {
heal_amount = lexical_cast_default<int, config::attribute_value> (amount, heal_amount);
const int new_hitpoints = std::max(1, std::min(u->max_hitpoints(), u->hitpoints() + heal_amount));
heal_amount = new_hitpoints - u->hitpoints();
u->set_hitpoints(new_hitpoints);
}
if(!moves.blank()) {
if(moves == "full") u->set_movement(u->total_movement());
else {
// set_movement doesn't set below 0
u->set_movement(std::min<int>(
u->total_movement(),
u->movement_left() + lexical_cast_default<int, config::attribute_value> (moves, 0)
));
}
}
if(restore_attacks) u->set_attacks(u->max_attacks());
if(restore_statuses)
{
u->set_state(unit::STATE_POISONED, false);
u->set_state(unit::STATE_SLOWED, false);
u->set_state(unit::STATE_PETRIFIED, false);
u->set_state(unit::STATE_UNHEALABLE, false);
u->anim_comp().set_standing();
}
if (heal_amount_to_set)
{
heal_amount_to_set = false;
gamedata().get_variable("heal_amount") = heal_amount;
}
if(animate) unit_display::unit_healing(*u, healers, heal_amount);
if(only_unit_at_loc1) return 0;
}
return 0;
}
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);
SDL_Color color = font::LABEL_COLOR;
if(!cfg["color"].empty()) {
color = string_to_color(cfg["color"]);
} else if(cfg.has_attribute("red") || cfg.has_attribute("green") || cfg.has_attribute("blue")) {
color = create_color(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.w/2,rect.h/2);
flabel.set_lifetime(lifetime);
flabel.set_clip_rect(rect);
floating_label = font::add_floating_label(flabel);
return 0;
}
/**
* 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");
}
int unit_arg = 1;
lua_unit *lu = nullptr;
unit_ptr u = unit_ptr();
map_location loc;
if (lua_isnumber(L, 1)) {
// Since this form is deprecated, I didn't bother updating it to luaW_tolocation.
unit_arg = 3;
loc.x = lua_tointeger(L, 1) - 1;
loc.y = luaL_checkinteger(L, 2) - 1;
if (!map().on_board(loc)) {
return luaL_argerror(L, 1, "invalid location");
}
} else if (luaW_tolocation(L, 2, loc)) {
if (!map().on_board(loc)) {
return luaL_argerror(L, 2, "invalid location");
}
}
if (luaW_hasmetatable(L, unit_arg, getunitKey))
{
lu = static_cast<lua_unit *>(lua_touserdata(L, unit_arg));
u = lu->get_shared();
if (!u) return luaL_argerror(L, unit_arg, "unit not found");
if (lu->on_map() && (unit_arg == 1 || 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");
} else if (unit_arg != 1) {
WRN_LUA << "wesnoth.put_unit(x, y, unit) is deprecated. Use wesnoth.put_unit(unit, x, y) instead\n";
}
}
else if (!lua_isnoneornil(L, unit_arg))
{
config cfg = luaW_checkconfig(L, unit_arg);
if (unit_arg == 1 && !map().on_board(loc)) {
loc.x = cfg["x"] - 1;
loc.y = cfg["y"] - 1;
if (!map().on_board(loc))
return luaL_argerror(L, 2, "invalid location");
} else if (unit_arg != 1) {
WRN_LUA << "wesnoth.put_unit(x, y, unit) is deprecated. Use wesnoth.put_unit(unit, x, y) instead\n";
}
u = unit_ptr (new unit(cfg, true));
}
if (game_display_) {
game_display_->invalidate(loc);
}
if (!u) {
if (unit_arg == 3) {
WRN_LUA << "wesnoth.put_unit(x, y) is deprecated. Use wesnoth.erase_unit(x, y) instead\n";
units().erase(loc);
}
return 0;
}
units().erase(loc);
if (lu) {
lu->put_map(loc);
lu->get_shared()->anim_comp().set_standing();
} else {
u->set_location(loc);
units().insert(u);
}
if(unit_arg != 1 || luaW_toboolean(L, 3)) {
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_hasmetatable(L, 1, getunitKey)) {
lua_unit *lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
unit_ptr u = lu->get_shared();
if (!lu->get()) {
return luaL_argerror(L, 1, "unit not found");
}
if (lu->on_map()) {
loc = u->get_location();
if (!map().on_board(loc)) {
return luaL_argerror(L, 1, "invalid location");
}
} else if (int side = lu->on_recall_list()) {
team &t = teams()[side - 1];
// 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 if (!lua_isnoneornil(L, 1)) {
config cfg = luaW_checkconfig(L, 1);
loc.x = cfg["x"] - 1;
loc.y = cfg["y"] - 1;
if (!map().on_board(loc)) {
return luaL_argerror(L, 1, "invalid location");
}
} else {
return luaL_argerror(L, 1, "expected unit or integer");
}
units().erase(loc);
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 (unsigned(side) > teams().size()) side = 0;
if (luaW_hasmetatable(L, 1, getunitKey))
{
lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
u = lu->get_shared();
if (!u || lu->on_recall_list())
return luaL_argerror(L, 1, "unit not found");
}
else
{
config cfg = luaW_checkconfig(L, 1);
u = unit_ptr(new unit(cfg, true));
}
if (!side) side = u->side();
team &t = teams()[side - 1];
// Avoid duplicates in the recall list.
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());
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");
}
if (!luaW_hasmetatable(L, 1, getunitKey))
return luaL_typerror(L, 1, "unit");
lua_unit *lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
unit_ptr u = lu->get_shared();
if (!u) return luaL_argerror(L, 1, "unit not found");
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 = teams()[side - 1];
unit_ptr v = unit_ptr(new unit(*u));
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 = unit_ptr();
if (!lua_isnoneornil(L, 2)) {
if (luaW_hasmetatable(L, 2, getunitKey)) {
u = static_cast<lua_unit *>(lua_touserdata(L, 2))->get_shared();
} else {
config cfg = luaW_checkconfig(L, 2);
u.reset(new unit(cfg, false));
}
}
map_location res = find_vacant_tile(loc, pathfind::VACANT_ANY, u.get());
if (!res.valid()) return 0;
lua_pushinteger(L, res.x + 1);
lua_pushinteger(L, res.y + 1);
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);
SDL_Color color = font::LABEL_COLOR;
t_string text = luaW_checktstring(L, 2);
if (!lua_isnoneornil(L, 3)) {
color = string_to_color(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)
{
config cfg = luaW_checkconfig(L, 1);
unit_ptr u = unit_ptr(new unit(cfg, true));
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(u);
lua_pushlightuserdata(L
, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
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);
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(unit_ptr(new unit(u)));
lua_pushlightuserdata(L
, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
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 = luaW_toboolean(L, 3);
map_location loc = u.get_location();
if (!lua_isnoneornil(L, 4)) {
loc = luaW_checklocation(L, 4);
}
lua_pushinteger(L, 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);
char const *m = luaL_checkstring(L, 2);
t_translation::t_terrain t = t_translation::read_terrain_code(m);
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);
char const *m = luaL_checkstring(L, 2);
t_translation::t_terrain t = t_translation::read_terrain_code(m);
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);
char const *m = luaL_checkstring(L, 2);
t_translation::t_terrain t = t_translation::read_terrain_code(m);
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);
char const *m = luaL_checkstring(L, 2);
t_translation::t_terrain t = t_translation::read_terrain_code(m);
lua_pushinteger(L, 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.
*/
static int 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: string.
*/
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");
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.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");
//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");
}
}
/**
* 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;
const unit& att = luaW_checkunit(L, arg_num);
++arg_num;
if (lua_isnumber(L, arg_num)) {
att_w = lua_tointeger(L, arg_num) - 1;
if (att_w < 0 || att_w >= int(att.attacks().size()))
return luaL_argerror(L, arg_num, "weapon index out of bounds");
++arg_num;
}
const unit& def = luaW_checkunit(L, arg_num, true);
++arg_num;
if (lua_isnumber(L, arg_num)) {
def_w = lua_tointeger(L, arg_num) - 1;
if (def_w < 0 || def_w >= int(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);
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)
{
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;
}
/**
* Scrolls to given tile.
* - Arg 1: location.
* - Arg 2: boolean preventing scroll to fog.
* - Arg 3: boolean specifying whether to warp instantly.
*/
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);
bool immediate = luaW_toboolean(L, 3);
if (game_display_) {
game_display_->scroll_to_tile(loc,
immediate ? game_display::WARP : game_display::SCROLL, check_fogged);
}
return 0;
}
int game_lua_kernel::intf_select_hex(lua_State *L)
{
ERR_LUA << "wesnoth.select_hex is deprecated, use wesnoth.select_unit and/or wesnoth.highlight_hex" << std::endl;
// 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)
{
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*)
{
const map_location loc;
play_controller_.get_mouse_handler_base().select_hex(
loc, false, false, false);
if (game_display_) {
game_display_->highlight_hex(loc);
}
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();
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
{
bool is_local_ai = lua_kernel_base::get_lua_kernel<game_lua_kernel>(L).teams()[side - 1].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
{
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<game_lua_kernel>(L).log_error("function returned to wesnoth.synchronize_choice a table which was partially invalid");
}
}
}
//Although luas 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 { 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 chracters 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<int> 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<std::vector<int> >(L, nextarg++);
lua_push(L, mp_sync::get_user_choice_multiple_sides(tagname, lua_synchronize(L, desc, human_func, null_func), std::set<int>(sides_for.begin(), sides_for.end())));
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 usualy 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.
* - Ret 1: array of integer pairs.
*/
int game_lua_kernel::intf_get_locations(lua_State *L)
{
vconfig filter = luaW_checkvconfig(L, 1);
std::set<map_location> res;
filter_context & fc = game_state_;
const terrain_filter t_filter(filter, &fc);
t_filter.get_locations(res, true);
lua_createtable(L, res.size(), 0);
int i = 1;
for (map_location const &loc : res)
{
lua_createtable(L, 2, 0);
lua_pushinteger(L, loc.x + 1);
lua_rawseti(L, -2, 1);
lua_pushinteger(L, loc.y + 1);
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<map_location> locs = map().villages();
lua_newtable(L);
int i = 1;
vconfig filter = luaW_checkvconfig(L, 1);
filter_context & fc = game_state_;
for(std::vector<map_location>::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->x + 1);
lua_rawseti(L, -2, 1);
lua_pushinteger(L, it->y + 1);
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.
* - 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);
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)
{
unsigned side = luaL_checkinteger(L, 1) - 1;
if (side >= teams().size()) return 0;
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);
lua_pushboolean(L, s_filter.match(side + 1));
return 1;
}
int game_lua_kernel::intf_modify_ai_wml(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
side_filter ssf(cfg, &game_state_);
std::vector<int> sides = ssf.get_teams();
for (const int &side_num : sides)
{
ai::manager::modify_active_ai_for_side(side_num,cfg.get_parsed_config());
}
return 0;
}
int game_lua_kernel::intf_modify_side(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
bool invalidate_screen = false;
std::string team_name = cfg["team_name"];
std::string user_team_name = cfg["user_team_name"];
std::string controller = cfg["controller"];
std::string defeat_condition = cfg["defeat_condition"];
std::string recruit_str = cfg["recruit"];
std::string shroud_data = cfg["shroud_data"];
std::string village_support = cfg["village_support"];
const config& parsed = cfg.get_parsed_config();
const config::const_child_itors &ai = parsed.child_range("ai");
std::string switch_ai = cfg["switch_ai"];
std::vector<int> sides = get_sides_vector(cfg);
size_t team_index;
for(const int &side_num : sides)
{
team_index = side_num - 1;
team & tm = teams()[team_index];
LOG_LUA << "modifying side: " << side_num << "\n";
if(!team_name.empty()) {
LOG_LUA << "change side's team to team_name '" << team_name << "'\n";
tm.change_team(team_name,
user_team_name);
} else if(!user_team_name.empty()) {
LOG_LUA << "change side's user_team_name to '" << user_team_name << "'\n";
tm.change_team(tm.team_name(),
user_team_name);
}
// Modify recruit list (override)
if (!recruit_str.empty()) {
tm.set_recruits(utils::set_split(recruit_str));
}
// Modify income
config::attribute_value income = cfg["income"];
if (!income.empty()) {
tm.set_base_income(income.to_int() + game_config::base_income);
}
// Modify total gold
config::attribute_value gold = cfg["gold"];
if (!gold.empty()) {
tm.set_gold(gold);
}
// Set controller
if (!controller.empty()) {
tm.change_controller_by_wml(controller);
}
// Set defeat_condition
if (!defeat_condition.empty()) {
tm.set_defeat_condition_string(defeat_condition);
}
// Set shroud
config::attribute_value shroud = cfg["shroud"];
if (!shroud.empty()) {
tm.set_shroud(shroud.to_bool(true));
invalidate_screen = true;
}
// Reset shroud
if ( cfg["reset_maps"].to_bool(false) ) {
tm.reshroud();
invalidate_screen = true;
}
// Merge shroud data
if (!shroud_data.empty()) {
tm.merge_shroud_map_data(shroud_data);
invalidate_screen = true;
}
// Set whether team is hidden in status table
config::attribute_value hidden = cfg["hidden"];
if (!hidden.empty()) {
tm.set_hidden(hidden.to_bool(true));
}
// Set fog
config::attribute_value fog = cfg["fog"];
if (!fog.empty()) {
tm.set_fog(fog.to_bool(true));
invalidate_screen = true;
}
// Reset fog
if ( cfg["reset_view"].to_bool(false) ) {
tm.refog();
invalidate_screen = true;
}
// Set income per village
config::attribute_value village_gold = cfg["village_gold"];
if (!village_gold.empty()) {
tm.set_village_gold(village_gold);
}
// Set support (unit levels supported per village, for upkeep purposes)
if (!village_support.empty()) {
tm.set_village_support(lexical_cast_default<int>(village_support, game_config::village_support));
}
// Redeploy ai from location (this ignores current AI parameters)
if (!switch_ai.empty()) {
ai::manager::add_ai_for_side_from_file(side_num,switch_ai,true);
}
// Override AI parameters
if (ai.first != ai.second) {
ai::manager::modify_active_ai_config_old_for_side(side_num,ai);
}
// Change team color
config::attribute_value color = cfg["color"];
if(!color.empty()) {
tm.set_color(color);
invalidate_screen = true;
}
// Change flag imageset
config::attribute_value flag = cfg["flag"];
if(!flag.empty()) {
tm.set_flag(flag);
// Needed especially when map isn't animated.
invalidate_screen = true;
}
// If either the flag set or the team color changed, we need to
// rebuild the team's flag cache to reflect the changes. Note that
// this is not required for flag icons (used by the theme UI only).
if((!color.empty() || !flag.empty()) && game_display_) {
game_display_->reinit_flags_for_side(team_index);
}
// Change flag icon
config::attribute_value flag_icon = cfg["flag_icon"];
if(!flag_icon.empty()) {
tm.set_flag_icon(flag_icon);
// Not needed.
//invalidate_screen = true;
}
tm.handle_legacy_share_vision(cfg.get_parsed_config());
// Suppress end turn confirmations?
config::attribute_value setc = cfg["suppress_end_turn_confirmation"];
if ( !setc.empty() ) {
tm.set_no_turn_confirmation(setc.to_bool());
}
// Change leader scrolling options
config::attribute_value stl = cfg["scroll_to_leader"];
if ( !stl.empty()) {
tm.set_scroll_to_leader(stl.to_bool(true));
}
}
// Flag an update of the screen, if needed.
if ( invalidate_screen && game_display_) {
game_display_->recalculate_minimap();
game_display_->invalidate_all();
}
return 0;
}
/**
* 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 = " << (formatter() << std::hex << this).str() << " myname = " << my_name() << std::endl;
std::vector<int> 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, teams()[side - 1]);
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";
lg::wml_error() << "(Lua) Modifications of type \"advance\" are deprecated, use \"advancement\" instead\n";
}
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);
}
config cfg = luaW_checkconfig(L, 3);
u.add_modification(sm, cfg, !write_to_mods);
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)
{
//TODO: check whether the unit is on the map.
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);
config cfg = luaW_checkconfig(L, 2);
if (game_display_) {
game_display_->add_overlay(loc, cfg["image"], cfg["halo"],
cfg["team_name"], cfg["name"], cfg["visible_in_fog"].to_bool(true));
}
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;
}
/// 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)
{
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 (int(final - SDL_GetTicks()) > 0);
return 0;
}
namespace { // Types
class recursion_preventer {
typedef std::map<map_location, int> t_counter;
static t_counter counter_;
static const int max_recursion = 10;
map_location loc_;
bool too_many_recursions_;
public:
recursion_preventer(map_location& loc) :
loc_(loc),
too_many_recursions_(false)
{
t_counter::iterator inserted = counter_.insert(std::make_pair(loc_, 0)).first;
++inserted->second;
too_many_recursions_ = inserted->second >= max_recursion;
}
~recursion_preventer()
{
t_counter::iterator itor = counter_.find(loc_);
if (--itor->second == 0)
{
counter_.erase(itor);
}
}
bool too_many_recursions() const
{
return too_many_recursions_;
}
};
recursion_preventer::t_counter recursion_preventer::counter_;
typedef boost::scoped_ptr<recursion_preventer> recursion_preventer_ptr;
} // end anonymouse namespace (types)
int game_lua_kernel::intf_kill(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
const game_events::queued_event &event_info = get_event_info();
size_t number_killed = 0;
bool secondary_unit = cfg.has_child("secondary_unit");
game_events::entity_location killer_loc(map_location(0, 0));
if(cfg["fire_event"].to_bool() && secondary_unit)
{
secondary_unit = false;
const unit_filter ufilt(cfg.child("secondary_unit"), &game_state_);
for(unit_map::const_unit_iterator unit = units().begin(); unit; ++unit) {
if ( ufilt( *unit) )
{
killer_loc = game_events::entity_location(*unit);
secondary_unit = true;
break;
}
}
if(!secondary_unit) {
WRN_LUA << "failed to match [secondary_unit] in [kill] with a single on-board unit" << std::endl;
}
}
//Find all the dead units first, because firing events ruins unit_map iteration
std::vector<unit *> dead_men_walking;
const unit_filter ufilt(cfg, &game_state_);
for (unit & u : units()){
if ( ufilt(u) ) {
dead_men_walking.push_back(&u);
}
}
for(unit * un : dead_men_walking) {
map_location loc(un->get_location());
bool fire_event = false;
game_events::entity_location death_loc(*un);
if(!secondary_unit) {
killer_loc = game_events::entity_location(*un);
}
if (cfg["fire_event"].to_bool())
{
// Prevent infinite recursion of 'die' events
fire_event = true;
recursion_preventer_ptr recursion_prevent;
if (event_info.loc1 == death_loc && (event_info.name == "die" || event_info.name == "last breath"))
{
recursion_prevent.reset(new recursion_preventer(death_loc));
if(recursion_prevent->too_many_recursions())
{
fire_event = false;
ERR_LUA << "tried to fire 'die' or 'last breath' event on primary_unit inside its own 'die' or 'last breath' event with 'first_time_only' set to false!" << std::endl;
}
}
}
if (fire_event) {
play_controller_.pump().fire("last breath", death_loc, killer_loc);
}
// Visual consequences of the kill.
if (game_display_) {
if (cfg["animate"].to_bool()) {
game_display_->scroll_to_tile(loc);
if (unit_map::iterator iun = units().find(loc)) {
unit_display::unit_die(loc, *iun);
}
} else {
// Make sure the unit gets (fully) cleaned off the screen.
game_display_->invalidate(loc);
if (unit_map::iterator iun = units().find(loc)) {
iun->anim_comp().invalidate(*game_display_);
}
}
game_display_->redraw_minimap();
}
if (fire_event) {
play_controller_.pump().fire("die", death_loc, killer_loc);
unit_map::iterator iun = units().find(death_loc);
if ( death_loc.matches_unit(iun) ) {
units().erase(iun);
}
}
else units().erase(loc);
++number_killed;
}
// If the filter doesn't contain positional information,
// then it may match units on all recall lists.
const config::attribute_value cfg_x = cfg["x"];
const config::attribute_value cfg_y = cfg["y"];
if((cfg_x.empty() || cfg_x == "recall")
&& (cfg_y.empty() || cfg_y == "recall"))
{
const unit_filter ufilt(cfg, &game_state_);
//remove the unit from the corresponding team's recall list
for(std::vector<team>::iterator pi = teams().begin();
pi!=teams().end(); ++pi)
{
for(std::vector<unit_ptr>::iterator j = pi->recall_list().begin(); j != pi->recall_list().end();) { //TODO: This block is really messy, cleanup somehow...
scoped_recall_unit auto_store("this_unit", pi->save_id(), j - pi->recall_list().begin());
if (ufilt( *(*j), map_location() )) {
j = pi->recall_list().erase(j);
++number_killed;
} else {
++j;
}
}
}
}
lua_pushinteger(L, number_killed);
return 1;
}
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;
}
/**
* Gets the dimension of an image.
* - Arg 1: string.
* - Ret 1: width.
* - Ret 2: height.
*/
static int intf_get_image_size(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
image::locator img(m);
if (!img.file_exists()) return 0;
surface s = get_image(img);
lua_pushinteger(L, s->w);
lua_pushinteger(L, s->h);
return 2;
}
/**
* Returns the time stamp, exactly as [set_variable] time=stamp does.
* - Ret 1: integer
*/
static int intf_get_time_stamp(lua_State *L)
{
lua_pushinteger(L, SDL_GetTicks());
return 1;
}
/**
* Lua frontend to the modify_ai functionality
* - Arg 1: config.
*/
static int intf_modify_ai(lua_State *L)
{
config cfg;
luaW_toconfig(L, 1, cfg);
int side = cfg["side"];
ai::manager::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<ai::candidate_action*>(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<ai::stage*>(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<std::string> c_types = c->get_children_types();
for (std::vector<std::string>::const_iterator t = c_types.begin(); t != c_types.end(); ++t)
{
std::vector<ai::component*> 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<ai::component*>::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<ai::candidate_action*>(*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 = lua_tointeger(L, 1);
lua_pop(L, 1);
ai::component* c = ai::manager::get_active_ai_holder_for_side_dbg(side).get_component(nullptr, "");
// Bad, but works
std::vector<ai::component*> engines = c->get_children("engine");
ai::engine_lua* lua_engine = nullptr;
for (std::vector<ai::component*>::const_iterator i = engines.begin(); i != engines.end(); ++i)
{
if ((*i)->get_name() == "lua")
{
lua_engine = dynamic_cast<ai::engine_lua *>(*i);
}
}
// Better way, but doesn't work
//ai::component* e = ai::manager::get_active_ai_holder_for_side_dbg(side).get_component(c, "engine[lua]");
//ai::engine_lua* lua_engine = dynamic_cast<ai::engine_lua *>(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<ai::ai_composite *>(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)
{
gamedata().set_allow_end_turn(luaW_toboolean(L, 1));
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().context_mutated(!luaW_toboolean(L, 1));
}
else {
play_controller_.pump().context_mutated(false);
}
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<map_location> 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;
size_t area_i = 0;
if (lua_isstring(L, 2)) {
area_id = lua_tostring(L, 1);
std::vector<std::string> area_ids = tod_man().get_area_ids();
area_i = std::find(area_ids.begin(), area_ids.end(), area_id) - area_ids.begin();
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<time_of_day>& 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)
{
vconfig cfg = luaW_checkvconfig(L, 1);
if (game_display_) {
const std::vector<int> side_list = get_sides_vector(cfg);
bool side_match = false;
for (int side : side_list) {
if(teams()[side-1].is_local_human()) {
side_match = true;
break;
}
}
if ((cfg["side"].empty() && !cfg.has_child("filter_side")) || side_match) {
game_display_->scroll(cfg["x"], cfg["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)
{
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<map_location> 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);
} catch (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;
}
/**
* 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<int> sides;
if(lua_isnumber(L, 1)) {
sides.insert(lua_tonumber(L, 1));
} else if(lua_istable(L, 2)) {
const auto& v = lua_check<std::vector<int>>(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<std::vector<map_location>>(L, lua_istable(L, 2) ? 2 : 1);
std::set<map_location> locs(v_locs.begin(), v_locs.end());
for(const int &side_num : sides) {
if(side_num < 1 || static_cast<size_t>(side_num) > teams().size()) {
continue;
}
team &t = teams()[side_num-1];
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;
}
static int intf_name_generator(lua_State *L)
{
std::string type = luaL_checkstring(L, 1);
name_generator* gen = nullptr;
if(type == "markov" || type == "markov_chain") {
std::vector<std::string> input;
if(lua_istable(L, 2)) {
input = lua_check<std::vector<std::string>>(L, 2);
} else {
input = utils::parenthetical_split(luaL_checkstring(L, 2));
}
int chain_sz = luaL_optinteger(L, 3, 2);
int max_len = luaL_optinteger(L, 4, 12);
gen = new(lua_newuserdata(L, sizeof(markov_generator)))
markov_generator(input, chain_sz, max_len);
// Ensure the pointer didn't change when cast
assert(static_cast<void*>(gen) == dynamic_cast<markov_generator*>(gen));
} else if(type == "context_free" || type == "cfg" || type == "CFG") {
void* buf = lua_newuserdata(L, sizeof(context_free_grammar_generator));
if(lua_istable(L, 2)) {
std::map<std::string, std::vector<std::string>> data;
for(lua_pushnil(L); lua_next(L, 2); lua_pop(L, 1)) {
if(!lua_isstring(L, -2)) {
lua_pushstring(L, "CFG generator: invalid nonterminal name (must be a string)");
return lua_error(L);
}
if(lua_isstring(L, -1)) {
data[lua_tostring(L,-2)] = utils::split(lua_tostring(L,-1),'|');
} else if(lua_istable(L, -1)) {
data[lua_tostring(L,-2)] = lua_check<std::vector<std::string>>(L, -1);
} else {
lua_pushstring(L, "CFG generator: invalid noterminal value (must be a string or list of strings)");
return lua_error(L);
}
}
if(!data.empty()) {
gen = new(buf) context_free_grammar_generator(data);
}
} else {
gen = new(buf) context_free_grammar_generator(luaL_checkstring(L, 2));
}
if(gen) {
assert(static_cast<void*>(gen) == dynamic_cast<context_free_grammar_generator*>(gen));
}
} else {
return luaL_argerror(L, 1, "should be either 'markov_chain' or 'context_free'");
}
static const char*const generic_err = "error initializing name generator";
if(!gen) {
lua_pushstring(L, generic_err);
return lua_error(L);
}
// We set the metatable now, even if the generator is invalid, so that it
// will be properly collected if it was invalid.
luaL_getmetatable(L, "name generator");
lua_setmetatable(L, -2);
if(!gen->is_valid()) {
lua_pushstring(L, generic_err);
return lua_error(L);
}
return 1;
}
// 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<team> & 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();
}
// 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 <member_callback method>
int dispatch(lua_State *L) {
return ((lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)).*method)(L);
}
// Pass a const bool also...
typedef int (game_lua_kernel::*member_callback2)(lua_State *, bool);
template <member_callback2 method, bool b>
int dispatch2(lua_State *L) {
return ((lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)).*method)(L, b);
}
game_lua_kernel::game_lua_kernel(CVideo * video, game_state & gs, play_controller & pc, reports & reports_object)
: lua_kernel_base(video)
, 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 },
{ "add_modification", &intf_add_modification },
{ "advance_unit", &intf_advance_unit },
{ "compile_formula", &lua_formula_bridge::intf_compile_formula},
{ "copy_unit", &intf_copy_unit },
{ "create_unit", &intf_create_unit },
{ "debug", &intf_debug },
{ "debug_ai", &intf_debug_ai },
{ "eval_conditional", &intf_eval_conditional },
{ "eval_formula", &lua_formula_bridge::intf_eval_formula},
{ "get_era", &intf_get_era },
{ "get_image_size", &intf_get_image_size },
{ "get_time_stamp", &intf_get_time_stamp },
{ "get_traits", &intf_get_traits },
{ "get_viewing_side", &intf_get_viewing_side },
{ "modify_ai", &intf_modify_ai },
{ "name_generator", &intf_name_generator },
{ "set_music", &intf_set_music },
{ "transform_unit", &intf_transform_unit },
{ "unit_ability", &intf_unit_ability },
{ "unit_defense", &intf_unit_defense },
{ "unit_movement_cost", &intf_unit_movement_cost },
{ "unit_vision_cost", &intf_unit_vision_cost },
{ "unit_jamming_cost", &intf_unit_jamming_cost },
{ "unit_resistance", &intf_unit_resistance },
{ "unsynced", &intf_do_unsynced },
{ "add_event_handler", &dispatch<&game_lua_kernel::intf_add_event > },
{ "add_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, true > },
{ "add_tile_overlay", &dispatch<&game_lua_kernel::intf_add_tile_overlay > },
{ "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 > },
{ "animate_unit", &dispatch<&game_lua_kernel::intf_animate_unit > },
{ "check_end_level_disabled", &dispatch<&game_lua_kernel::intf_check_end_level_disabled > },
{ "clear_menu_item", &dispatch<&game_lua_kernel::intf_clear_menu_item > },
{ "clear_messages", &dispatch<&game_lua_kernel::intf_clear_messages > },
{ "color_adjust", &dispatch<&game_lua_kernel::intf_color_adjust > },
{ "delay", &dispatch<&game_lua_kernel::intf_delay > },
{ "end_turn", &dispatch<&game_lua_kernel::intf_end_turn > },
{ "end_level", &dispatch<&game_lua_kernel::intf_end_level > },
{ "erase_unit", &dispatch<&game_lua_kernel::intf_erase_unit > },
{ "extract_unit", &dispatch<&game_lua_kernel::intf_extract_unit > },
{ "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", &dispatch<&game_lua_kernel::intf_fire_event > },
{ "fire_wml_menu_item", &dispatch<&game_lua_kernel::intf_fire_wml_menu_item > },
{ "float_label", &dispatch<&game_lua_kernel::intf_float_label > },
{ "gamestate_inspector", &dispatch<&game_lua_kernel::intf_gamestate_inspector > },
{ "get_all_vars", &dispatch<&game_lua_kernel::intf_get_all_vars > },
{ "get_locations", &dispatch<&game_lua_kernel::intf_get_locations > },
{ "get_map_size", &dispatch<&game_lua_kernel::intf_get_map_size > },
{ "get_mouseover_tile", &dispatch<&game_lua_kernel::intf_get_mouseover_tile > },
{ "get_recall_units", &dispatch<&game_lua_kernel::intf_get_recall_units > },
{ "get_selected_tile", &dispatch<&game_lua_kernel::intf_get_selected_tile > },
{ "get_sides", &dispatch<&game_lua_kernel::intf_get_sides > },
{ "get_sound_source", &dispatch<&game_lua_kernel::intf_get_sound_source > },
{ "get_starting_location", &dispatch<&game_lua_kernel::intf_get_starting_location > },
{ "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_unit", &dispatch<&game_lua_kernel::intf_get_unit > },
{ "get_units", &dispatch<&game_lua_kernel::intf_get_units > },
{ "get_variable", &dispatch<&game_lua_kernel::intf_get_variable > },
{ "get_side_variable", &dispatch<&game_lua_kernel::intf_get_side_variable > },
{ "get_villages", &dispatch<&game_lua_kernel::intf_get_villages > },
{ "get_village_owner", &dispatch<&game_lua_kernel::intf_get_village_owner > },
{ "get_displayed_unit", &dispatch<&game_lua_kernel::intf_get_displayed_unit > },
{ "heal_unit", &dispatch<&game_lua_kernel::intf_heal_unit > },
{ "highlight_hex", &dispatch<&game_lua_kernel::intf_highlight_hex > },
{ "is_enemy", &dispatch<&game_lua_kernel::intf_is_enemy > },
{ "kill", &dispatch<&game_lua_kernel::intf_kill > },
{ "label", &dispatch<&game_lua_kernel::intf_label > },
{ "lock_view", &dispatch<&game_lua_kernel::intf_lock_view > },
{ "log", &dispatch<&game_lua_kernel::intf_log > },
{ "match_location", &dispatch<&game_lua_kernel::intf_match_location > },
{ "match_side", &dispatch<&game_lua_kernel::intf_match_side > },
{ "match_unit", &dispatch<&game_lua_kernel::intf_match_unit > },
{ "message", &dispatch<&game_lua_kernel::intf_message > },
{ "modify_ai_wml", &dispatch<&game_lua_kernel::intf_modify_ai_wml > },
{ "modify_side", &dispatch<&game_lua_kernel::intf_modify_side > },
{ "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 > },
{ "put_recall_unit", &dispatch<&game_lua_kernel::intf_put_recall_unit > },
{ "put_unit", &dispatch<&game_lua_kernel::intf_put_unit > },
{ "random", &dispatch<&game_lua_kernel::intf_random > },
{ "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, false > },
{ "remove_tile_overlay", &dispatch<&game_lua_kernel::intf_remove_tile_overlay > },
{ "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 > },
{ "scroll", &dispatch<&game_lua_kernel::intf_scroll > },
{ "scroll_to_tile", &dispatch<&game_lua_kernel::intf_scroll_to_tile > },
{ "select_hex", &dispatch<&game_lua_kernel::intf_select_hex > },
{ "set_time_of_day", &dispatch<&game_lua_kernel::intf_set_time_of_day > },
{ "deselect_hex", &dispatch<&game_lua_kernel::intf_deselect_hex > },
{ "select_unit", &dispatch<&game_lua_kernel::intf_select_unit > },
{ "skip_messages", &dispatch<&game_lua_kernel::intf_skip_messages > },
{ "is_skipping_messages", &dispatch<&game_lua_kernel::intf_is_skipping_messages > },
{ "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 > },
{ "set_menu_item", &dispatch<&game_lua_kernel::intf_set_menu_item > },
{ "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_side_variable", &dispatch<&game_lua_kernel::intf_set_side_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 },
{ "teleport", &dispatch<&game_lua_kernel::intf_teleport > },
{ "view_locked", &dispatch<&game_lua_kernel::intf_view_locked > },
{ "place_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, true > },
{ "remove_shroud", &dispatch2<&game_lua_kernel::intf_shroud_op, false > },
{ nullptr, nullptr }
};
/*
lua_cpp::Reg const cpp_callbacks[] = {
};
*/
lua_getglobal(L, "wesnoth");
if (!lua_istable(L,-1)) {
lua_newtable(L);
}
luaL_setfuncs(L, callbacks, 0);
//lua_cpp::set_functions(L, cpp_callbacks);
lua_setglobal(L, "wesnoth");
// 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 getunit metatable.
cmd_log_ << "Adding getunit metatable...\n";
lua_pushlightuserdata(L
, getunitKey);
lua_createtable(L, 0, 5);
lua_pushcfunction(L, impl_unit_collect);
lua_setfield(L, -2, "__gc");
lua_pushcfunction(L, impl_unit_equality);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, impl_unit_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, impl_unit_set);
lua_setfield(L, -2, "__newindex");
lua_pushstring(L, "unit");
lua_setfield(L, -2, "__metatable");
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the unit status metatable.
cmd_log_ << "Adding unit status metatable...\n";
lua_pushlightuserdata(L
, ustatusKey);
lua_createtable(L, 0, 3);
lua_pushcfunction(L, impl_unit_status_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, impl_unit_status_set);
lua_setfield(L, -2, "__newindex");
lua_pushstring(L, "unit status");
lua_setfield(L, -2, "__metatable");
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the unit attacks metatable.
cmd_log_ << "Adding unit attacks metatable...\n";
lua_pushlightuserdata(L, uattacksKey);
lua_createtable(L, 0, 3);
lua_pushcfunction(L, impl_unit_attacks_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, impl_unit_attacks_len);
lua_setfield(L, -2, "__len");
lua_pushstring(L, "unit attacks");
lua_setfield(L, -2, "__metatable");
lua_rawset(L, LUA_REGISTRYINDEX);
lua_pushlightuserdata(L, uattackKey);
lua_createtable(L, 0, 3);
lua_pushcfunction(L, impl_unit_attack_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, impl_unit_attack_set);
lua_setfield(L, -2, "__newindex");
lua_pushstring(L, "unit attack");
lua_setfield(L, -2, "__metatable");
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the unit variables metatable.
cmd_log_ << "Adding unit variables metatable...\n";
lua_pushlightuserdata(L
, unitvarKey);
lua_createtable(L, 0, 3);
lua_pushcfunction(L, impl_unit_variables_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, impl_unit_variables_set);
lua_setfield(L, -2, "__newindex");
lua_pushstring(L, "unit variables");
lua_setfield(L, -2, "__metatable");
lua_rawset(L, LUA_REGISTRYINDEX);
// Create formula bridge metatables
cmd_log_ << lua_formula_bridge::register_metatables(L);
// Create the vconfig metatable.
cmd_log_ << lua_common::register_vconfig_metatable(L);
// Create the ai elements table.
cmd_log_ << "Adding ai elements table...\n";
ai::lua_ai_context::init(L);
// Create the game_config variable with its metatable.
cmd_log_ << "Adding game_config table...\n";
lua_getglobal(L, "wesnoth");
lua_newuserdata(L, 0);
lua_createtable(L, 0, 3);
lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_game_config_get>);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, &dispatch<&game_lua_kernel::impl_game_config_set>);
lua_setfield(L, -2, "__newindex");
lua_pushstring(L, "game config");
lua_setfield(L, -2, "__metatable");
lua_setmetatable(L, -2);
lua_setfield(L, -2, "game_config");
lua_pop(L, 1);
// 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 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);
// 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 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_talbe(L);
lua_setfield(L, -2, "special_locations");
lua_pop(L, 1);
// Create the theme_items table.
cmd_log_ << "Adding theme_items table...\n";
lua_getglobal(L, "wesnoth");
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, "theme_items");
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 sides table.
// note:
// This table is redundant to the return value of wesnoth.get_sides({}).
// Still needed for backwards compatibility.
lua_settop(L, 0);
lua_getglobal(L, "wesnoth");
lua_pushstring(L, "get_sides");
lua_rawget(L, -2);
lua_createtable(L, 0, 0);
if (!protected_call(1, 1, std::bind(&lua_kernel_base::log_error, this, _1, _2))) {
cmd_log_ << "Failed to compute wesnoth.sides\n";
} else {
lua_setfield(L, -2, "sides");
cmd_log_ << "Added wesnoth.sides\n";
}
// Create the unit_types table.
cmd_log_ << "Adding unit_types table...\n";
lua_settop(L, 0);
lua_getglobal(L, "wesnoth");
lua_newtable(L);
for (const unit_type_data::unit_type_map::value_type &ut : unit_types.types())
{
luaW_pushunittype(L, ut.first);
lua_setfield(L, -2, ut.first.c_str());
}
lua_setfield(L, -2, "unit_types");
//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);
}
load_game(level);
}
int game_lua_kernel::return_unit_method(lua_State *L, char const *m) {
static luaL_Reg const methods[] = {
{"matches", &dispatch<&game_lua_kernel::intf_match_unit>},
{"to_recall", &dispatch<&game_lua_kernel::intf_put_recall_unit>},
{"to_map", &dispatch<&game_lua_kernel::intf_put_unit>},
{"erase", &dispatch<&game_lua_kernel::intf_erase_unit>},
{"clone", intf_copy_unit},
{"extract", &dispatch<&game_lua_kernel::intf_extract_unit>},
{"advance", intf_advance_unit},
{"add_modification", intf_add_modification},
{"resistance", intf_unit_resistance},
{"defense", intf_unit_defense},
{"movement", intf_unit_movement_cost},
{"vision", intf_unit_vision_cost},
{"jamming", intf_unit_jamming_cost},
{"ability", intf_unit_ability},
{"transform", intf_transform_unit},
{"select", &dispatch<&game_lua_kernel::intf_select_unit>},
};
for (const luaL_Reg& r : methods) {
if (strcmp(m, r.name) == 0) {
lua_pushcfunction(L, r.func);
return 1;
}
}
return 0;
}
void game_lua_kernel::set_game_display(game_display * gd) {
game_display_ = gd;
if (gd) {
set_video(&gd->video());
}
}
/// 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 char const *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 (char const *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(game_events::queued_event const &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;
}
/**
* 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.get()->apply_builtin_effect(which_effect, cfg);
return 0;
} else {
std::string description = u.get()->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<game_events::wml_action::handler>
(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(std::string const &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<void *>(h));
lua_pushcclosure(L, &dispatch<&game_lua_kernel::cfun_wml_action>, 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(std::string const &cmd, vconfig const &cfg,
game_events::queued_event const &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;
}
/**
* 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_conditional(std::string const &cmd, vconfig const &cfg)
{
lua_State *L = mState;
if (!luaW_getglobal(L, "wesnoth", "wml_conditionals", cmd)) {
std::string err_msg = "unknown conditional wml: [";
err_msg += cmd;
err_msg += "]";
luaL_argerror(L, 1, err_msg.c_str());
}
luaW_pushvconfig(L, cfg);
luaW_pcall(L, 1, 1, true);
bool b = luaW_toboolean(L, -1);
lua_pop(L, 1);
return b;
}
/**
* 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, unit const &u)
{
lua_State *L = mState;
map_locker(this);
unit_map::const_unit_iterator ui = units().find(u.get_location());
if (!ui.valid()) return false;
// Get the user filter by name.
const std::vector<std::string>& 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;
}
// Pass the unit as argument.
new(lua_newuserdata(L, sizeof(lua_unit))) lua_unit(ui->underlying_id());
lua_pushlightuserdata(L
, getunitKey);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
if (!luaW_pcall(L, 1, 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.x + 1);
lua_push(L, loc.y + 1);
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.x + 1);
lua_push(L, loc.y + 1);
luaW_pcall(L, 2, 0, false);
return;
}