2009-07-14 16:02:00 +00:00

1193 lines
33 KiB
C++

/* $Id$ */
/*
Copyright (C) 2009 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 version 2
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 scripting/lua.cpp
* Provides a Lua interpreter.
*
* @warning Lua's error handling is done by setjmp/longjmp, so be careful
* never to call a Lua error function that will jump while in the scope
* of a C++ object with a destructor. This is why this file uses goto-s
* to force object unscoping before erroring out.
*/
#ifdef DISABLE_LUA
#include "log.hpp"
#include "scripting/lua.hpp"
static lg::log_domain lua("scripting/lua");
LuaKernel::LuaKernel() {LOG_STREAM(err, lua) << "Lua support disabled in this build";}
LuaKernel::~LuaKernel() {}
void LuaKernel::run_event(vconfig const&, game_events::queued_event const&) {}
void LuaKernel::run(char const *prog) {}
void LuaKernel::execute(char const *prog, int nArgs, int nRets) {}
#else // HAVE LUA
extern "C" {
#include <lualib.h>
#include <lauxlib.h>
}
#include <cassert>
#include <cstring>
#include "actions.hpp"
#include "filesystem.hpp"
#include "foreach.hpp"
#include "gamestatus.hpp"
#include "log.hpp"
#include "map.hpp"
#include "scripting/lua.hpp"
#include "terrain_translation.hpp"
#include "unit.hpp"
static lg::log_domain log_scripting_lua("scripting/lua");
#define LOG_LUA LOG_STREAM(info, log_scripting_lua)
#define ERR_LUA LOG_STREAM(err, log_scripting_lua)
/* Dummy pointer for getting unique keys for Lua's registry. */
static char const executeKey = 0;
static char const getsideKey = 0;
static char const gettextKey = 0;
static char const gettypeKey = 0;
static char const getunitKey = 0;
static char const tstringKey = 0;
static char const uactionKey = 0;
/**
* Pushes a t_string on the top of the stack.
*/
static void lua_pushtstring(lua_State *L, t_string const &v)
{
new(lua_newuserdata(L, sizeof(t_string))) t_string(v);
lua_pushlightuserdata(L, (void *)&tstringKey);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
}
/**
* Converts a string into a Lua object pushed at the top of the stack.
* Boolean ("yes"/"no") and numbers are detected and typed accordingly.
*/
static void scalar_of_wml_string(lua_State *L, t_string const &v)
{
if (!v.translatable())
{
char *pe;
char const *pb = v.c_str();
double d = strtod(v.c_str(), &pe);
if (pe != pb && *pe == '\0')
lua_pushnumber(L, d);
else if (v == "yes")
lua_pushboolean(L, true);
else if (v == "no")
lua_pushboolean(L, false);
else
lua_pushstring(L, pb);
}
else
{
lua_pushtstring(L, v);
}
}
/**
* Converts a config object to a Lua table.
* The destination table should be at the top of the stack on entry. It is
* still at the top on exit.
*/
static void table_of_wml_config(lua_State *L, config const &cfg)
{
if (!lua_checkstack(L, LUA_MINSTACK))
return;
int k = 1;
foreach (const config::any_child &ch, cfg.all_children_range())
{
lua_createtable(L, 2, 0);
lua_pushstring(L, ch.key.c_str());
lua_rawseti(L, -2, 1);
lua_newtable(L);
table_of_wml_config(L, ch.cfg);
lua_rawseti(L, -2, 2);
lua_rawseti(L, -2, k++);
}
foreach (const config::attribute &attr, cfg.attribute_range())
{
scalar_of_wml_string(L, attr.second);
lua_setfield(L, -2, attr.first.c_str());
}
}
#define return_misformed() \
do { lua_settop(L, initial_top); return false; } while (0)
/**
* Converts a Lua table to a config object.
* The source table should be at the top of the stack on entry. It is
* still at the top on exit.
* @param tstring_meta absolute stack position of t_string's metatable, or 0 if none.
* @return false if some attributes had not the proper type.
* @note If the table has holes in the integer keys or floating-point keys,
* some keys will be ignored and the error will go undetected.
*/
static bool wml_config_of_table(lua_State *L, config &cfg, int tstring_meta = 0)
{
if (!lua_checkstack(L, LUA_MINSTACK))
return false;
// Get t_string's metatable, so that it can be used later to detect t_string object.
int initial_top = lua_gettop(L);
if (!tstring_meta) {
lua_pushlightuserdata(L, (void *)&tstringKey);
lua_gettable(L, LUA_REGISTRYINDEX);
tstring_meta = lua_gettop(L);
lua_pushvalue(L, -2);
}
// First convert the children (integer indices).
for (int i = 1, i_end = lua_objlen(L, -1); i <= i_end; ++i)
{
lua_rawgeti(L, -1, i);
if (!lua_istable(L, -1)) return_misformed();
lua_rawgeti(L, -1, 1);
char const *m = lua_tostring(L, -1);
if (!m) return_misformed();
lua_rawgeti(L, -2, 2);
if (!lua_istable(L, -1) ||
!wml_config_of_table(L, cfg.add_child(m), tstring_meta))
return_misformed();
lua_pop(L, 3);
}
// Then convert the attributes (string indices).
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1))
{
if (lua_isnumber(L, -2)) continue;
if (!lua_isstring(L, -2)) return_misformed();
t_string v;
switch (lua_type(L, -1)) {
case LUA_TBOOLEAN:
v = lua_toboolean(L, -1) ? "yes" : "no";
break;
case LUA_TNUMBER:
case LUA_TSTRING:
v = lua_tostring(L, -1);
break;
case LUA_TUSERDATA:
{
if (!lua_getmetatable(L, -1)) return_misformed();
bool tstr = lua_rawequal(L, -1, tstring_meta) != 0;
lua_pop(L, 1);
if (!tstr) return_misformed();
v = *static_cast<t_string *>(lua_touserdata(L, -1));
break;
}
default:
return_misformed();
}
cfg[lua_tostring(L, -2)] = v;
}
lua_settop(L, initial_top);
return true;
}
#undef return_misformed
/**
* Creates a t_string object (__call metamethod).
* - Arg 1: userdata containing the domain.
* - Arg 2: string to translate.
* - Ret 1: string containing the translatable string.
*/
static int lua_gettext(lua_State *L)
{
char const *m = luaL_checkstring(L, 2);
char const *d = static_cast<char *>(lua_touserdata(L, 1));
// Hidden metamethod, so d has to be a string. Use it to create a t_string.
lua_pushtstring(L, t_string(m, d));
return 1;
}
/**
* Creates an interface for gettext
* - Arg 1: string containing the domain.
* - Ret 1: a full userdata with __call pointing to lua_gettext.
*/
static int lua_textdomain(lua_State *L)
{
size_t l;
char const *m = luaL_checklstring(L, 1, &l);
void *p = lua_newuserdata(L, l + 1);
memcpy(p, m, l + 1);
lua_pushlightuserdata(L, (void *)&gettextKey);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
return 1;
}
/**
* Converts a Lua value at position @a src and appends it to @a dst.
* @note This function is private to lua_tstring_concat. It expects two things.
* First, the t_string metatable is at the top of the stack on entry. (It
* is still there on exit.) Second, the caller hasn't any valuable object
* with dynamic lifetime, since they would leaked on error.
*/
static void lua_tstring_concat_aux(lua_State *L, t_string &dst, int src)
{
switch (lua_type(L, src)) {
case LUA_TNUMBER:
case LUA_TSTRING:
dst += lua_tostring(L, src);
break;
case LUA_TUSERDATA:
// Compare its metatable with t_string's metatable.
if (!lua_getmetatable(L, src) || !lua_rawequal(L, -1, -2))
luaL_typerror(L, src, "string");
dst += *static_cast<t_string *>(lua_touserdata(L, src));
lua_pop(L, 1);
break;
default:
luaL_typerror(L, src, "string");
}
}
/**
* Appends a scalar to a t_string object (__concat metamethod).
*/
static int lua_tstring_concat(lua_State *L)
{
// Create a new t_string.
t_string *t = new(lua_newuserdata(L, sizeof(t_string))) t_string;
lua_pushlightuserdata(L, (void *)&tstringKey);
lua_gettable(L, LUA_REGISTRYINDEX);
// Append both arguments to t.
lua_tstring_concat_aux(L, *t, 1);
lua_tstring_concat_aux(L, *t, 2);
lua_setmetatable(L, -2);
return 1;
}
/**
* Destroys a t_string object before it is collected (__gc metamethod).
*/
static int lua_tstring_collect(lua_State *L)
{
t_string *t = static_cast<t_string *>(lua_touserdata(L, 1));
t->t_string::~t_string();
return 0;
}
/**
* Converts a t_string object to a string (__tostring metamethod);
* that is, performs a translation.
*/
static int lua_tstring_tostring(lua_State *L)
{
t_string *t = static_cast<t_string *>(lua_touserdata(L, 1));
lua_pushstring(L, t->c_str());
return 1;
}
#define return_tstring_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
lua_pushtstring(L, accessor); \
return 1; \
}
#define return_string_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
lua_pushstring(L, accessor.c_str()); \
return 1; \
}
#define return_int_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
lua_pushinteger(L, accessor); \
return 1; \
}
#define return_bool_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
lua_pushboolean(L, accessor); \
return 1; \
}
#define return_cfg_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
config cfg; \
accessor; \
lua_newtable(L); \
table_of_wml_config(L, cfg); \
return 1; \
}
#define return_cfgref_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
lua_newtable(L); \
table_of_wml_config(L, accessor); \
return 1; \
}
#define modify_tstring_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
if (lua_type(L, -1) == LUA_TUSERDATA) { \
lua_pushlightuserdata(L, (void *)&tstringKey); \
lua_gettable(L, LUA_REGISTRYINDEX); \
if (!lua_getmetatable(L, -2) || !lua_rawequal(L, -1, -2)) \
return luaL_typerror(L, -3, "(translatable) string"); \
const t_string &value = *static_cast<t_string *>(lua_touserdata(L, -3)); \
accessor; \
} else { \
const char *value = luaL_checkstring(L, -1); \
accessor; \
} \
return 0; \
}
#define modify_string_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
const char *value = luaL_checkstring(L, -1); \
accessor; \
return 0; \
}
#define modify_int_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
int value = luaL_checkint(L, -1); \
accessor; \
return 0; \
}
#define modify_bool_attrib(name, accessor) \
if (strcmp(m, name) == 0) { \
int value = lua_toboolean(L, -1); \
accessor; \
return 0; \
}
/**
* Gets some data on a unit type (__index metamethod).
* - Arg 1: table containing an "id" field.
* - Arg 2: string containing the name of the property.
* - Ret 1: something containing the attribute.
*/
static int lua_unit_type_get(lua_State *L)
{
char const *m = luaL_checkstring(L, 2);
lua_getfield(L, 1, "id");
unit_type_data::unit_type_map_wrapper &ut_map = unit_type_data::types();
unit_type_data::unit_type_map::const_iterator
uti = ut_map.find_unit_type(lua_tostring(L, -1));
if (uti == ut_map.end()) return 0;
unit_type const &ut = uti->second;
// Find the corresponding attribute.
return_tstring_attrib("name", ut.type_name());
return_int_attrib("max_hitpoints", ut.hitpoints());
return_int_attrib("max_moves", ut.movement());
return_int_attrib("max_experience", ut.experience_needed());
return_int_attrib("cost", ut.cost());
return_int_attrib("level", ut.level());
return_cfgref_attrib("__cfg", ut.get_cfg());
return 0;
}
/**
* Gets the unit type corresponding to an id.
* - Arg 1: string containing the unit type id.
* - Ret 1: table with an "id" field and with __index pointing to lua_unit_type_get.
*/
static int lua_get_unit_type(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
unit_type_data::unit_type_map_wrapper &ut_map = unit_type_data::types();
if (!ut_map.unit_type_exists(m)) return 0;
lua_createtable(L, 0, 1);
lua_pushvalue(L, 1);
lua_setfield(L, -2, "id");
lua_pushlightuserdata(L, (void *)&gettypeKey);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
return 1;
}
/**
* Gets the ids of all the unit types.
* - Ret 1: table containing the ids.
*/
static int lua_get_unit_type_ids(lua_State *L)
{
lua_newtable(L);
int i = 1;
unit_type_data::unit_type_map_wrapper &ut_map = unit_type_data::types();
for (unit_type_data::unit_type_map::const_iterator uti = ut_map.begin(),
uti_end = ut_map.end(); uti != uti_end; ++uti)
{
lua_pushstring(L, uti->first.c_str());
lua_rawseti(L, -2, i);
++i;
}
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 lua_unit_get(lua_State *L)
{
size_t id = *static_cast<size_t *>(lua_touserdata(L, 1));
char const *m = luaL_checkstring(L, 2);
unit_map::const_unit_iterator ui = game_events::resources->units->find(id);
if (!ui.valid()) return 0;
unit const &u = ui->second;
// Find the corresponding attribute.
return_int_attrib("x", ui->first.x);
return_int_attrib("y", ui->first.y);
return_int_attrib("side", u.side());
return_string_attrib("id", u.id());
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("moves", u.movement_left());
return_int_attrib("max_moves", u.total_movement());
return_tstring_attrib("name", u.name());
return_string_attrib("side_id", u.side_id());
return_bool_attrib("canrecruit", u.can_recruit());
return_bool_attrib("petrified", u.incapacitated());
return_bool_attrib("resting", u.resting());
return_string_attrib("role", u.get_role());
return_string_attrib("facing", map_location::write_direction(u.facing()));
return_cfg_attrib("__cfg", u.write(cfg));
return 0;
}
/**
* 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 lua_unit_set(lua_State *L)
{
size_t id = *static_cast<size_t *>(lua_touserdata(L, 1));
char const *m = luaL_checkstring(L, 2);
lua_settop(L, 3);
unit_map::unit_iterator ui = game_events::resources->units->find(id);
if (!ui.valid()) return 0;
unit &u = ui->second;
// Find the corresponding attribute.
modify_int_attrib("side", u.set_side(value));
modify_int_attrib("moves", u.set_movement(value));
modify_bool_attrib("resting", u.set_resting(value != 0));
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)));
return 0;
}
/**
* Gets the numeric ids of all the units.
* - Arg 1: optional table containing a filter
* - Ret 1: table containing full userdata with __index pointing to
* lua_unit_get and __newindex pointing to lua_unit_set.
*/
static int lua_get_units(lua_State *L)
{
bool has_filter = lua_gettop(L) >= 1;
if (has_filter && !lua_istable(L, 1)) {
error_call_destructors:
return luaL_typerror(L, 1, "WML table");
}
config filter;
if (has_filter) {
lua_settop(L, 1);
if (!wml_config_of_table(L, filter))
goto error_call_destructors;
}
// 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, (void *)&getunitKey);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_newtable(L);
int i = 1;
unit_map &units = *game_events::resources->units;
for (unit_map::const_unit_iterator ui = units.begin(), ui_end = units.end();
ui != ui_end; ++ui)
{
if (has_filter && !ui->second.matches_filter(vconfig(filter), ui->first))
continue;
size_t *p = static_cast<size_t *>(lua_newuserdata(L, sizeof(size_t)));
*p = ui->second.underlying_id();
lua_pushvalue(L, 1);
lua_setmetatable(L, 3);
lua_rawseti(L, 2, i);
++i;
}
return 1;
}
/**
* Fires a WML event handler.
* - Arg 1: string containing the handler name.
* - Arg 2: optional WML config.
* - Arg 3,4: optional first location.
* - Arg 5,6: optional second location.
*/
static int lua_fire(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
bool has_config = lua_gettop(L) >= 2;
if (has_config && !lua_istable(L, 2)) {
error_call_destructors:
return luaL_typerror(L, 2, "WML table");
}
config cfg;
if (has_config) {
lua_pushvalue(L, 2);
if (!wml_config_of_table(L, cfg))
goto error_call_destructors;
lua_pop(L, 1);
}
map_location l1, l2;
if (lua_gettop(L) >= 4)
l1 = map_location(lua_tointeger(L, 3) - 1, lua_tointeger(L, 4) - 1);
if (lua_gettop(L) >= 6)
l2 = map_location(lua_tointeger(L, 5) - 1, lua_tointeger(L, 6) - 1);
game_events::handle_event_command
(m, game_events::queued_event("_from_lua", l1, l2, config()), vconfig(cfg, true));
return 0;
}
/**
* Fires an event the same way the fire_event WML tag does it.
* - Arg 1: string containing the event name.
* - Arg 2,3: optional first location.
* - Arg 4,5: optional second location.
* - Ret 1: boolean indicating whether the event was processed or not.
*/
static int lua_fire_event(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
map_location l1, l2;
if (lua_gettop(L) >= 3)
l1 = map_location(lua_tointeger(L, 2) - 1, lua_tointeger(L, 3) - 1);
if (lua_gettop(L) >= 5)
l2 = map_location(lua_tointeger(L, 4) - 1, lua_tointeger(L, 5) - 1);
bool b = game_events::fire(m, l1, l2);
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.
*/
static int lua_get_variable(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
variable_info v(m, false, variable_info::TYPE_SCALAR);
if (v.is_valid) {
scalar_of_wml_string(L, v.as_scalar());
return 1;
} else {
variable_info w(m, false, variable_info::TYPE_CONTAINER);
if (w.is_valid) {
lua_newtable(L);
if (lua_toboolean(L, 2))
table_of_wml_config(L, w.as_container());
return 1;
}
}
return 0;
}
/**
* Sets a WML variable.
* - Arg 1: string containing the variable name.
* - Arg 2: bool/int/string/table containing the value.
*/
static int lua_set_variable(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
if (false) {
error_call_destructors:
return luaL_typerror(L, 2, "WML table or scalar");
}
if (lua_isnoneornil(L, 2)) {
game_events::get_state_of_game()->clear_variable(m);
return 0;
}
variable_info v(m);
switch (lua_type(L, 2)) {
case LUA_TBOOLEAN:
v.as_scalar() = lua_toboolean(L, 2) ? "yes" : "no";
break;
case LUA_TNUMBER:
case LUA_TSTRING:
v.as_scalar() = lua_tostring(L, 2);
break;
case LUA_TUSERDATA:
// Compare its metatable with t_string's metatable.
lua_pushlightuserdata(L, (void *)&tstringKey);
lua_gettable(L, LUA_REGISTRYINDEX);
if (!lua_getmetatable(L, 2) || !lua_rawequal(L, -1, -2))
goto error_call_destructors;
v.as_scalar() = *static_cast<t_string *>(lua_touserdata(L, 2));
break;
case LUA_TTABLE:
lua_settop(L, 2);
if (!wml_config_of_table(L, v.as_container()))
goto error_call_destructors;
break;
default:
goto error_call_destructors;
}
return 0;
}
/**
* Loads and executes a Lua file.
* - Arg 1: string containing the file name.
* - Ret *: values returned by executing the file body.
*/
static int lua_dofile(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
if (false) {
error_call_destructors_1:
return luaL_argerror(L, 1, "file not found");
error_call_destructors_2:
return lua_error(L);
continue_call_destructor:
lua_call(L, 0, 1);
return 1;
}
std::string p = get_wml_location(m);
if (p.empty())
goto error_call_destructors_1;
if (luaL_loadfile(L, p.c_str()))
goto error_call_destructors_2;
goto continue_call_destructor;
}
/**
* Proxy class for calling WML action handlers defined in Lua.
*/
struct lua_action_handler : game_events::action_handler
{
lua_State *L;
int num;
lua_action_handler(lua_State *l, int n) : L(l), num(n) {}
void handle(const game_events::queued_event &, const vconfig &);
};
void lua_action_handler::handle(const game_events::queued_event &, const vconfig &cfg)
{
// Load the error handler from the registry.
lua_pushlightuserdata(L, (void *)&executeKey);
lua_gettable(L, LUA_REGISTRYINDEX);
// Load the user function from the registry.
lua_pushlightuserdata(L, (void *)&uactionKey);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_rawgeti(L, -1, num);
lua_remove(L, -2);
// Push the WML table argument.
lua_newtable(L);
table_of_wml_config(L, cfg.get_parsed_config());
int res = lua_pcall(L, 1, 0, -3);
if (res)
{
ERR_LUA
<< "Failure while running Lua action handler: "
<< lua_tostring(L, -1) << '\n';
lua_pop(L, 2);
return;
}
lua_pop(L, 1);
}
/**
* Registers a function as WML action handler.
* - Arg 1: string containing the WML tag.
* - Arg 2: function taking a WML table as argument.
*/
static int lua_register_wml_action(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
// Retrieve the user action table from the registry.
lua_pushlightuserdata(L, (void *)&uactionKey);
lua_gettable(L, LUA_REGISTRYINDEX);
size_t length = lua_objlen(L, -1);
// Push the function on it so that it is not collected.
lua_pushvalue(L, 2);
lua_rawseti(L, -2, length + 1);
// Create the proxy C++ action handler.
game_events::register_action_handler(m, new lua_action_handler(L, length + 1));
return 0;
}
/**
* Gets some data on a side (__index metamethod).
* - Arg 1: full userdata containing the team.
* - Arg 2: string containing the name of the property.
* - Ret 1: something containing the attribute.
*/
static int lua_side_get(lua_State *L)
{
// Hidden metamethod, so arg1 has to be a pointer to a team.
team &t = **static_cast<team **>(lua_touserdata(L, 1));
char const *m = luaL_checkstring(L, 2);
// Find the corresponding attribute.
return_int_attrib("gold", t.gold());
return_tstring_attrib("objectives", t.objectives());
return_int_attrib("village_gold", t.village_gold());
return_int_attrib("base_income", t.base_income());
return_int_attrib("total_income", t.total_income());
return_bool_attrib("objectives_changed", t.objectives_changed());
return_tstring_attrib("user_team_name", t.user_team_name());
return_string_attrib("team_name", t.team_name());
return_cfg_attrib("__cfg", t.write(cfg));
return 0;
}
/**
* Sets some data on a side (__newindex metamethod).
* - Arg 1: full userdata containing the team.
* - Arg 2: string containing the name of the property.
* - Arg 3: something containing the attribute.
*/
static int lua_side_set(lua_State *L)
{
// Hidden metamethod, so arg1 has to be a pointer to a team.
team &t = **static_cast<team **>(lua_touserdata(L, 1));
char const *m = luaL_checkstring(L, 2);
lua_settop(L, 3);
// Find the corresponding attribute.
modify_int_attrib("gold", t.set_gold(value));
modify_tstring_attrib("objectives", t.set_objectives(value, true));
modify_int_attrib("village_gold", t.set_village_gold(value));
modify_int_attrib("base_income", t.set_base_income(value));
modify_bool_attrib("objectives_changed", t.set_objectives_changed(value != 0));
modify_tstring_attrib("user_team_name", t.change_team(t.team_name(), value));
modify_string_attrib("team_name", t.change_team(value, t.user_team_name()));
return 0;
}
/**
* Gets a proxy userdata for a side.
* - Arg 1: integer for the side.
* - Ret 1: full userdata with __index pointing to lua_side_get
* and __newindex pointing to lua_side_set.
*/
static int lua_get_side(lua_State *L)
{
int s = luaL_checkint(L, 1);
size_t t = s - 1;
std::vector<team> &teams = *game_events::resources->teams;
if (t >= teams.size()) return 0;
// Create a full userdata containing a pointer to the team.
team **p = static_cast<team **>(lua_newuserdata(L, sizeof(team *)));
*p = &teams[t];
// Get the metatable from the registry and set it.
lua_pushlightuserdata(L, (void *)&getsideKey);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, 2);
return 1;
}
/**
* Gets a terrain code.
* - Args 1,2: map location.
* - Ret 1: string.
*/
static int lua_get_terrain(lua_State *L)
{
int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2);
t_translation::t_terrain const &t = game_events::resources->game_map->
get_terrain(map_location(x - 1, y - 1));
lua_pushstring(L, t_translation::write_terrain_code(t).c_str());
return 1;
}
/**
* Sets a terrain code.
* - Args 1,2: map location.
* - Arg 3: terrain code string.
*/
static int lua_set_terrain(lua_State *L)
{
int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2);
char const *m = luaL_checkstring(L, 3);
t_translation::t_terrain t = t_translation::read_terrain_code(m);
if (t != t_translation::NONE_TERRAIN)
change_terrain(map_location(x - 1, y - 1), t, gamemap::BOTH, false);
return 0;
}
/**
* Gets details about a terrain.
* - Arg 1: terrain code string.
* - Ret 1: table.
*/
static int lua_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 = game_events::resources->game_map->get_terrain_info(t);
lua_newtable(L);
lua_pushstring(L, info.id().c_str());
lua_setfield(L, -2, "id");
lua_pushtstring(L, info.name());
lua_setfield(L, -2, "name");
lua_pushtstring(L, info.description());
lua_setfield(L, -2, "description");
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 the side of a village owner.
* - Args 1,2: map location.
* - Ret 1: integer.
*/
static int lua_get_village_owner(lua_State *L)
{
int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2);
map_location loc(x - 1, y - 1);
if (!game_events::resources->game_map->is_village(loc))
return 0;
int side = village_owner(loc, *game_events::resources->teams) + 1;
if (!side) return 0;
lua_pushinteger(L, side);
return 1;
}
/**
* Sets the owner of a village.
* - Args 1,2: map location.
* - Arg 3: integer for the side or empty to remove ownership.
*/
static int lua_set_village_owner(lua_State *L)
{
int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2);
int new_side = lua_isnoneornil(L, 3) ? 0 : luaL_checkint(L, 3);
std::vector<team> &teams = *game_events::resources->teams;
map_location loc(x - 1, y - 1);
if (!game_events::resources->game_map->is_village(loc))
return 0;
int old_side = village_owner(loc, teams) + 1;
if (new_side == old_side || new_side < 0 || new_side > (int)teams.size())
return 0;
if (old_side) teams[old_side - 1].lose_village(loc);
if (new_side) teams[new_side - 1].get_village(loc);
return 0;
}
/**
* Returns the map size.
* - Ret 1: width.
* - Ret 2: height.
*/
static int lua_get_map_size(lua_State *L)
{
const gamemap &map = *game_events::resources->game_map;
lua_pushinteger(L, map.w());
lua_pushinteger(L, map.h());
return 2;
}
static int lua_message(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
LOG_LUA << "Script says: \"" << m << "\"\n";
return 0;
}
LuaKernel::LuaKernel()
: mState(luaL_newstate())
{
lua_State *L = mState;
// Open safe libraries. (Debug is not, but it will be closed below.)
static const luaL_Reg safe_libs[] = {
{ "", luaopen_base },
{ "table", luaopen_table },
{ "string", luaopen_string },
{ "math", luaopen_math },
{ "debug", luaopen_debug },
{ NULL, NULL }
};
for (luaL_Reg const *lib = safe_libs; lib->func; ++lib)
{
lua_pushcfunction(L, lib->func);
lua_pushstring(L, lib->name);
lua_call(L, 1, 0);
}
// Put some callback functions in the scripting environment.
static luaL_reg const callbacks[] = {
{ "dofile", &lua_dofile },
{ "fire", &lua_fire },
{ "fire_event", &lua_fire_event },
{ "get_map_size", &lua_get_map_size },
{ "get_side", &lua_get_side },
{ "get_terrain", &lua_get_terrain },
{ "get_terrain_info", &lua_get_terrain_info },
{ "get_unit_type", &lua_get_unit_type },
{ "get_unit_type_ids", &lua_get_unit_type_ids },
{ "get_units", &lua_get_units },
{ "get_variable", &lua_get_variable },
{ "get_village_owner", &lua_get_village_owner },
{ "message", &lua_message },
{ "register_wml_action", &lua_register_wml_action },
{ "set_terrain", &lua_set_terrain },
{ "set_variable", &lua_set_variable },
{ "set_village_owner", &lua_set_village_owner },
{ "textdomain", &lua_textdomain },
{ NULL, NULL }
};
luaL_register(L, "wesnoth", callbacks);
// Create the getside metatable.
lua_pushlightuserdata(L, (void *)&getsideKey);
lua_createtable(L, 0, 2);
lua_pushcfunction(L, lua_side_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, lua_side_set);
lua_setfield(L, -2, "__newindex");
lua_pushstring(L, "Hands off! (getside metatable)");
lua_setfield(L, -2, "__metatable");
lua_settable(L, LUA_REGISTRYINDEX);
// Create the gettext metatable.
lua_pushlightuserdata(L, (void *)&gettextKey);
lua_createtable(L, 0, 1);
lua_pushcfunction(L, lua_gettext);
lua_setfield(L, -2, "__call");
lua_pushstring(L, "Hands off! (gettext metatable)");
lua_setfield(L, -2, "__metatable");
lua_settable(L, LUA_REGISTRYINDEX);
// Create the gettype metatable.
lua_pushlightuserdata(L, (void *)&gettypeKey);
lua_createtable(L, 0, 1);
lua_pushcfunction(L, lua_unit_type_get);
lua_setfield(L, -2, "__index");
lua_pushstring(L, "Hands off! (gettype metatable)");
lua_setfield(L, -2, "__metatable");
lua_settable(L, LUA_REGISTRYINDEX);
// Create the getunit metatable.
lua_pushlightuserdata(L, (void *)&getunitKey);
lua_createtable(L, 0, 2);
lua_pushcfunction(L, lua_unit_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, lua_unit_set);
lua_setfield(L, -2, "__newindex");
lua_pushstring(L, "Hands off! (getunit metatable)");
lua_setfield(L, -2, "__metatable");
lua_settable(L, LUA_REGISTRYINDEX);
// Create the tstring metatable.
lua_pushlightuserdata(L, (void *)&tstringKey);
lua_createtable(L, 0, 3);
lua_pushcfunction(L, lua_tstring_concat);
lua_setfield(L, -2, "__concat");
lua_pushcfunction(L, lua_tstring_collect);
lua_setfield(L, -2, "__gc");
lua_pushcfunction(L, lua_tstring_tostring);
lua_setfield(L, -2, "__tostring");
lua_pushstring(L, "Hands off! (tstring metatable)");
lua_setfield(L, -2, "__metatable");
lua_settable(L, LUA_REGISTRYINDEX);
// Delete dofile and loadfile.
lua_pushnil(L);
lua_setglobal(L, "dofile");
lua_pushnil(L);
lua_setglobal(L, "loadfile");
// Create the user action table.
lua_pushlightuserdata(L, (void *)&uactionKey);
lua_newtable(L);
lua_settable(L, LUA_REGISTRYINDEX);
// Store the error handler, then close debug.
lua_pushlightuserdata(L, (void *)&executeKey);
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_remove(L, -2);
lua_settable(L, LUA_REGISTRYINDEX);
lua_pushnil(L);
lua_setglobal(L, "debug");
lua_settop(L, 0);
}
LuaKernel::~LuaKernel()
{
lua_close(mState);
}
/**
* Runs a script from an event handler.
*/
void LuaKernel::run_event(vconfig const &cfg, game_events::queued_event const &ev)
{
lua_State *L = mState;
// Get user-defined arguments; append locations and weapons to it.
config args;
vconfig vargs = cfg.child("args");
if (!vargs.null()) {
args = vargs.get_parsed_config();
}
if (const config &weapon = ev.data.child("first")) {
args.add_child("weapon", weapon);
}
if (const config &weapon = ev.data.child("first")) {
args.add_child("second_weapon", weapon);
}
lua_newtable(L);
table_of_wml_config(L, args);
if (ev.loc1.valid()) {
lua_pushinteger(L, ev.loc1.x + 1);
lua_setfield(L, -2, "x1");
lua_pushinteger(L, ev.loc1.y + 1);
lua_setfield(L, -2, "y1");
}
if (ev.loc2.valid()) {
lua_pushinteger(L, ev.loc2.x + 1);
lua_setfield(L, -2, "x2");
lua_pushinteger(L, ev.loc2.y + 1);
lua_setfield(L, -2, "y2");
}
// Get the code from the uninterpolated config object, so that $ symbols
// are not messed with.
const std::string &prog = cfg.get_config()["code"];
execute(prog.c_str(), 1, 0);
}
/**
* Runs a plain script.
*/
void LuaKernel::run(char const *prog)
{
execute(prog, 0, 0);
}
/**
* Runs a script on a preset stack.
*/
void LuaKernel::execute(char const *prog, int nArgs, int nRets)
{
lua_State *L = mState;
// Load the error handler before the function arguments.
lua_pushlightuserdata(L, (void *)&executeKey);
lua_gettable(L, LUA_REGISTRYINDEX);
if (nArgs)
lua_insert(L, -1 - nArgs);
// Compile script into a variadic function.
int res = luaL_loadstring(L, prog);
if (res)
{
ERR_LUA
<< "Failure while loading Lua script: "
<< lua_tostring(L, -1) << '\n';
lua_pop(L, 2);
return;
}
// Place the function before its arguments.
if (nArgs)
lua_insert(L, -1 - nArgs);
res = lua_pcall(L, nArgs, nRets, -2 - nArgs);
if (res)
{
ERR_LUA
<< "Failure while running Lua script: "
<< lua_tostring(L, -1) << '\n';
lua_pop(L, 2);
return;
}
lua_remove(L, -1 - nRets);
}
#endif // HAVE LUA