Lua API: Add __dir metamethod to units metatable

This implements a new system for registering attributes, adapted from the system for widget attributes.
This commit is contained in:
Celtic Minstrel 2024-09-10 22:38:04 -04:00 committed by Celtic Minstrel
parent ab022c09ae
commit d4278fa1be
11 changed files with 782 additions and 307 deletions

View file

@ -1010,6 +1010,8 @@
<Unit filename="../../src/scripting/debug_lua.hpp" />
<Unit filename="../../src/scripting/game_lua_kernel.cpp" />
<Unit filename="../../src/scripting/game_lua_kernel.hpp" />
<Unit filename="../../src/scripting/lua_attributes.cpp" />
<Unit filename="../../src/scripting/lua_attributes.hpp" />
<Unit filename="../../src/scripting/lua_audio.cpp" />
<Unit filename="../../src/scripting/lua_audio.hpp" />
<Unit filename="../../src/scripting/lua_color.cpp" />

View file

@ -1046,6 +1046,8 @@
<Unit filename="../../src/scripting/debug_lua.hpp" />
<Unit filename="../../src/scripting/game_lua_kernel.cpp" />
<Unit filename="../../src/scripting/game_lua_kernel.hpp" />
<Unit filename="../../src/scripting/lua_attributes.cpp" />
<Unit filename="../../src/scripting/lua_attributes.hpp" />
<Unit filename="../../src/scripting/lua_audio.cpp" />
<Unit filename="../../src/scripting/lua_audio.hpp" />
<Unit filename="../../src/scripting/lua_color.cpp" />

View file

@ -718,6 +718,8 @@
91254C462C4FE519007695D3 /* optional_fwd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 91254C452C4FE519007695D3 /* optional_fwd.hpp */; };
913D26771D3C9697002FF3AB /* name_generator_factory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 913D26751D3C9697002FF3AB /* name_generator_factory.cpp */; };
914F2F861D35253900A42440 /* location_palette.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914F2F841D35253900A42440 /* location_palette.cpp */; };
9154743D2C8FBAC800EB1C94 /* lua_attributes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9154743C2C8FBAC700EB1C94 /* lua_attributes.cpp */; };
915474422C8FBACE00EB1C94 /* lua_attributes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9154743C2C8FBAC700EB1C94 /* lua_attributes.cpp */; };
915C68EA1DF1DCB000594B07 /* color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 915C68E81DF1DCB000594B07 /* color.cpp */; };
915C68EB1DF1DCB000594B07 /* color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 915C68E81DF1DCB000594B07 /* color.cpp */; };
915C68EC1DF1DCB000594B07 /* color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 915C68E81DF1DCB000594B07 /* color.cpp */; };
@ -1615,7 +1617,7 @@
26A04033A9545CFE8A226FBD /* test_schema_self_validator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_schema_self_validator.cpp; sourceTree = "<group>"; };
27764FB68F02032F1C0B6748 /* statistics_record.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = statistics_record.cpp; sourceTree = "<group>"; };
2CFD4922B64EA6C9F71F71A2 /* preferences.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = preferences.hpp; path = preferences/preferences.hpp; sourceTree = "<group>"; };
3D284B9A81882806D8B25006 /* spritesheet_generator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = spritesheet_generator.hpp; path = spritesheet_generator.hpp; sourceTree = "<group>"; };
3D284B9A81882806D8B25006 /* spritesheet_generator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = spritesheet_generator.hpp; sourceTree = "<group>"; };
46081FED2B2F0F6A006ACAD7 /* libpcre2-8.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libpcre2-8.0.dylib"; path = "lib/libpcre2-8.0.dylib"; sourceTree = "<group>"; };
46081FF02B2F103E006ACAD7 /* libwebpdemux.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libwebpdemux.2.dylib; path = lib/libwebpdemux.2.dylib; sourceTree = "<group>"; };
46081FF32B2F11F3006ACAD7 /* libsharpyuv.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsharpyuv.0.dylib; path = lib/libsharpyuv.0.dylib; sourceTree = "<group>"; };
@ -2148,7 +2150,7 @@
46F92F0C2174FEC000602C1C /* text.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = text.cpp; path = font/text.cpp; sourceTree = "<group>"; };
46F92F0D2174FEC000602C1C /* text.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = text.hpp; path = font/text.hpp; sourceTree = "<group>"; };
46F92F0E2174FEC000602C1C /* constants.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = constants.cpp; path = font/constants.cpp; sourceTree = "<group>"; };
492144E3AE22C0C7A2D5F27C /* spritesheet_generator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = spritesheet_generator.cpp; path = spritesheet_generator.cpp; sourceTree = "<group>"; };
492144E3AE22C0C7A2D5F27C /* spritesheet_generator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = spritesheet_generator.cpp; sourceTree = "<group>"; };
4944F41A1354FBFF0027E614 /* teleport.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = teleport.cpp; sourceTree = "<group>"; };
49478712172FF6F8002B7ABA /* tristate_button.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tristate_button.cpp; sourceTree = "<group>"; };
49478713172FF6F8002B7ABA /* tristate_button.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tristate_button.hpp; sourceTree = "<group>"; };
@ -2264,6 +2266,8 @@
913D26761D3C9697002FF3AB /* name_generator_factory.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = name_generator_factory.hpp; sourceTree = "<group>"; };
914F2F841D35253900A42440 /* location_palette.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = location_palette.cpp; sourceTree = "<group>"; };
914F2F851D35253900A42440 /* location_palette.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = location_palette.hpp; sourceTree = "<group>"; };
9154743B2C8FBA5D00EB1C94 /* lua_attributes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lua_attributes.hpp; sourceTree = "<group>"; };
9154743C2C8FBAC700EB1C94 /* lua_attributes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lua_attributes.cpp; sourceTree = "<group>"; };
915C68E81DF1DCB000594B07 /* color.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = color.cpp; sourceTree = "<group>"; };
915C68E91DF1DCB000594B07 /* color.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = color.hpp; sourceTree = "<group>"; };
915C68F31DF1F78600594B07 /* libintl.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libintl.8.dylib; path = lib/libintl.8.dylib; sourceTree = "<group>"; };
@ -5005,6 +5009,8 @@
91B621E21B76BAF300B00E0F /* context.hpp */,
EC218EA21A106673007C910C /* game_lua_kernel.cpp */,
91B621E41B76BB0100B00E0F /* game_lua_kernel.hpp */,
9154743C2C8FBAC700EB1C94 /* lua_attributes.cpp */,
9154743B2C8FBA5D00EB1C94 /* lua_attributes.hpp */,
EC3863621EB6286E0048B0C8 /* lua_audio.cpp */,
EC3863631EB6286E0048B0C8 /* lua_audio.hpp */,
461DC52B241F836200B9DD10 /* lua_color.cpp */,
@ -5557,6 +5563,7 @@
EC669C261DFC95AF00172EED /* surface.cpp in Sources */,
46F92DAB2174F6A300602C1C /* unit_recall.cpp in Sources */,
B5599B7B0EC62181008DD061 /* config_cache.cpp in Sources */,
9154743D2C8FBAC800EB1C94 /* lua_attributes.cpp in Sources */,
46F92EF22174FE5600602C1C /* show_dialog.cpp in Sources */,
B5599B7D0EC62181008DD061 /* config.cpp in Sources */,
EC0680291EA920A300EEE03B /* random_deterministic.cpp in Sources */,
@ -6312,6 +6319,7 @@
91E356B21CACC82D00774252 /* aspect.cpp in Sources */,
91E356B31CACC82D00774252 /* component.cpp in Sources */,
46F92D862174F6A300602C1C /* vertical_list.cpp in Sources */,
915474422C8FBACE00EB1C94 /* lua_attributes.cpp in Sources */,
91E356B41CACC82D00774252 /* contexts.cpp in Sources */,
46F92DF02174F6A400602C1C /* install_dependencies.cpp in Sources */,
46D60150255AFA6000E072F0 /* options.cpp in Sources */,

View file

@ -328,6 +328,7 @@ savegame.cpp
scripting/application_lua_kernel.cpp
scripting/debug_lua.cpp
scripting/game_lua_kernel.cpp
scripting/lua_attributes.cpp
scripting/lua_audio.cpp
scripting/lua_color.cpp
scripting/lua_common.cpp

View file

@ -735,7 +735,7 @@ tree_view_node& tree_view_node::get_child_at(int index)
return *children_[index];
}
std::vector<int> tree_view_node::describe_path()
std::vector<int> tree_view_node::describe_path() const
{
if(is_root_node()) {
return std::vector<int>();

View file

@ -237,7 +237,7 @@ public:
/**
* Calculates the node indices needed to get from the root node to this node.
*/
std::vector<int> describe_path();
std::vector<int> describe_path() const;
tree_view_node* get_last_visible_parent_node();
tree_view_node* get_node_above();

View file

@ -0,0 +1,90 @@
/*
Copyright (C) 2024 - 2024
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "scripting/lua_attributes.hpp"
#include "scripting/lua_common.hpp"
#include "scripting/lua_kernel_base.hpp" // for luaW_get_attributes
#include "scripting/push_check.hpp"
#include <sstream>
luaW_Registry::luaW_Registry(const std::initializer_list<std::string>& mt) : public_metatable(mt) {
private_metatable = public_metatable.back();
public_metatable.pop_back();
lookup.emplace(private_metatable, std::ref(*this));
}
luaW_Registry::~luaW_Registry() {
lookup.erase(private_metatable);
}
int luaW_Registry::get(lua_State* L) {
std::string_view str = lua_check<std::string_view>(L, 2);
auto it = getters.find(std::string(str));
if(it != getters.end()) {
if(it->second(L, false)) {
return 1;
}
}
if(!public_metatable.empty()) {
auto method = public_metatable;
method.push_back(std::string(str));
if(luaW_getglobal(L, method)) {
return 1;
}
}
std::ostringstream err;
err << "invalid property of " << private_metatable << ": " << str;
return luaL_argerror(L, 2, err.str().c_str());
}
int luaW_Registry::set(lua_State* L) {
std::string_view str = lua_check<std::string_view>(L, 2);
auto it = setters.find(std::string(str));
if(it != setters.end()) {
if(it->second(L, 3, false)) {
return 0;
}
}
std::ostringstream err;
err << "invalid modifiable property of " << private_metatable << ": " << str;
return luaL_argerror(L, 2, err.str().c_str());
}
int luaW_Registry::dir(lua_State *L) {
std::vector<std::string> keys;
// Add any readable keys
for(const auto& [key, func] : getters) {
if(func(L, true)){
keys.push_back(key);
}
}
// Add any writable keys
for(const auto& [key, func] : setters) {
if(func(L, 0, true)){
keys.push_back(key);
}
}
// Add the metatable methods
if(!public_metatable.empty()) {
luaW_getglobal(L, public_metatable);
auto methods = luaW_get_attributes(L, -1);
keys.insert(keys.end(), methods.begin(), methods.end());
}
lua_push(L, keys);
return 1;
}

View file

@ -0,0 +1,143 @@
/*
Copyright (C) 2009 - 2024
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/// Attribute registration system, mainly for objects with a lot of attributes, like units
/// Not used for GUI2 widgets, as they're even more complicated with a deep hierarchy.
#pragma once
struct lua_State;
class t_string;
class vconfig;
#include "config.hpp"
#include "map/location.hpp"
#include "variable_info.hpp"
#include <string>
#include <string_view>
#include <vector>
template<typename T>
std::decay_t<T> lua_check(lua_State *L, int n);
/// Holds a lookup table for members of one type of object.
struct luaW_Registry {
inline static std::map<std::string_view /* metatable */, std::reference_wrapper<luaW_Registry>> lookup;
using getters_list = std::map<std::string /* attribute */, std::function<bool(lua_State* L,bool nop)>>;
/// A map of callbacks that read data from the object.
getters_list getters;
using setters_list = std::map<std::string, std::function<bool(lua_State* L,int idx,bool nop)>>;
/// A map of callbacks that write data to the object.
setters_list setters;
/// The internal metatable string for the object (from __metatable)
std::string private_metatable;
/// Optional external metatable for the object (eg "wesnoth", "units")
/// All entries of this table will be treated as members of the object.
std::vector<std::string> public_metatable;
luaW_Registry() = delete;
luaW_Registry(const std::initializer_list<std::string>& mt);
~luaW_Registry();
/// Implement __index metamethod
int get(lua_State* L);
/// Implement __newindex metamethod
int set(lua_State* L);
/// Implement __dir metamethod
int dir(lua_State* L);
};
template<typename object_type, typename value_type>
struct lua_getter
{
virtual value_type get(lua_State* L, const object_type& obj) const = 0;
virtual ~lua_getter() = default;
};
template<typename object_type, typename value_type>
struct lua_setter
{
virtual void set(lua_State* L, object_type& obj, const value_type& value) const = 0;
virtual ~lua_setter() = default;
};
template<typename T> struct lua_object_traits;
template<typename object_type, typename value_type, typename action_type, bool setter>
void register_lua_attribute(const char* name)
{
using obj_traits = lua_object_traits<object_type>;
using map_type = std::conditional_t<setter, luaW_Registry::setters_list, luaW_Registry::getters_list>;
using callback_type = typename map_type::mapped_type;
map_type* map;
callback_type fcn;
if constexpr(setter) {
map = &luaW_Registry::lookup.at(obj_traits::metatable).get().setters;
fcn = [action = action_type()](lua_State* L, int idx, bool nop) {
if(nop) return true;
decltype(auto) obj = obj_traits::get(L, 1);
action.set(L, obj, lua_check<value_type>(L, idx));
return true;
};
} else {
map = &luaW_Registry::lookup.at(obj_traits::metatable).get().getters;
fcn = [action = action_type()](lua_State* L, bool nop) {
if(nop) return true;
lua_push(L, action.get(L, obj_traits::get(L, 1)));
return true;
};
}
(*map)[std::string(name)] = fcn;
}
#define LATTR_MAKE_UNIQUE_ID(base, id, obj_name) BOOST_PP_CAT(BOOST_PP_CAT(base, id), BOOST_PP_CAT(_for_, obj_name))
#define LATTR_GETTER5(name, value_type, obj_type, obj_name, id) \
struct LATTR_MAKE_UNIQUE_ID(getter_, id, obj_name) : public lua_getter<obj_type, value_type> { \
using object_type = obj_type; \
virtual value_type get(lua_State* L, const object_type& obj_name) const override; \
}; \
struct LATTR_MAKE_UNIQUE_ID(getter_adder_, id, obj_name) { \
LATTR_MAKE_UNIQUE_ID(getter_adder_, id, obj_name) () \
{ \
register_lua_attribute<obj_type, value_type, LATTR_MAKE_UNIQUE_ID(getter_, id, obj_name), false>(name); \
} \
}; \
static LATTR_MAKE_UNIQUE_ID(getter_adder_, id, obj_name) LATTR_MAKE_UNIQUE_ID(getter_adder_instance_, id, obj_name) ; \
value_type LATTR_MAKE_UNIQUE_ID(getter_, id, obj_name)::get([[maybe_unused]] lua_State* L, const LATTR_MAKE_UNIQUE_ID(getter_, id, obj_name)::object_type& obj_name) const
#define LATTR_SETTER5(name, value_type, obj_type, obj_name, id) \
struct LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name) : public lua_setter<obj_type, value_type> { \
using object_type = obj_type; \
void set(lua_State* L, object_type& obj_name, const value_type& value) const override; \
}; \
struct LATTR_MAKE_UNIQUE_ID(setter_adder_, id, obj_name) { \
LATTR_MAKE_UNIQUE_ID(setter_adder_, id, obj_name) ()\
{ \
register_lua_attribute<obj_type, value_type, LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name), true>(name); \
} \
}; \
static LATTR_MAKE_UNIQUE_ID(setter_adder_, id, obj_name) LATTR_MAKE_UNIQUE_ID(setter_adder_instance_, id, obj_name); \
void LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name)::set([[maybe_unused]] lua_State* L, LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name)::object_type& obj_name, const value_type& value) const
/**
* @param name: string attribute name
* @param value_type: the type of the attribute, for example int or std::string
* @param obj_type: the type of the object, for example lua_unit
* @param obj_name: a name for the variable that will hold the object
*/
#define LATTR_GETTER(name, value_type, obj_type, obj_name) LATTR_GETTER5(name, value_type, obj_type, obj_name, __LINE__)
#define LATTR_SETTER(name, value_type, obj_type, obj_name) LATTR_SETTER5(name, value_type, obj_type, obj_name, __LINE__)

View file

@ -21,15 +21,17 @@
#include "map/location.hpp" // for map_location
#include "map/map.hpp"
#include "resources.hpp"
#include "scripting/lua_attributes.hpp"
#include "scripting/lua_common.hpp"
#include "scripting/lua_unit_attacks.hpp"
#include "scripting/push_check.hpp"
#include "units/unit.hpp"
#include "units/map.hpp"
#include "units/animation_component.hpp"
#include "utils/optional_fwd.hpp"
#include "game_version.hpp"
#include "deprecation.hpp"
#include <vector>
static lg::log_domain log_scripting_lua("scripting/lua");
#define LOG_LUA LOG_STREAM(info, log_scripting_lua)
@ -269,6 +271,492 @@ static int impl_unit_tostring(lua_State* L)
return 1;
}
#define UNIT_GETTER(name, type) LATTR_GETTER(name, type, unit, u)
#define UNIT_SETTER(name, type) LATTR_SETTER(name, type, unit, u)
luaW_Registry unitReg{"wesnoth", "units", getunitKey};
template<> struct lua_object_traits<lua_unit*> {
inline static auto metatable = getunitKey;
inline static lua_unit* get(lua_State* L, int n) {
auto lu = luaW_tounit_ref(L, n);
if(!lu) unit_show_error(L, n, LU_NOT_UNIT);
return lu;
}
};
template<> struct lua_object_traits<unit> {
inline static auto metatable = getunitKey;
inline static unit& get(lua_State* L, int n) {
return luaW_checkunit(L, n);
}
};
static void handle_unit_move(lua_State* L, lua_unit* lu, map_location dst) {
if(!lu->on_map()) {
(*lu)->set_location(dst);
} else {
unit& u = *lu->get();
// Handle moving an on-map unit
game_board* gb = resources::gameboard;
if(!gb) {
return;
}
map_location src = u.get_location();
// TODO: could probably be relegated to a helper function.
if(src != dst) {
// If the dst isn't on the map, the unit will be clobbered. Guard against that.
if(!gb->map().on_board(dst)) {
std::string err_msg = formatter() << "destination hex not on map (excluding border): " << dst;
return void(luaL_argerror(L, 2, err_msg.c_str()));
}
auto [unit_iterator, success] = gb->units().move(src, dst);
if(success) {
unit_iterator->anim_comp().set_standing();
}
}
}
}
LATTR_GETTER("valid", utils::optional<std::string>, lua_unit*, lu) {
const unit* pu = lu->get();
if(!pu) {
return utils::nullopt;
}
using namespace std::literals;
if(lu->on_map()) {
return "map"s;
} else if(lu->on_recall_list()) {
return "recall"s;
}
return "private"s;
}
UNIT_GETTER("x", int) {
return u.get_location().wml_x();
}
LATTR_SETTER("x", int, lua_unit*, lu) {
if(!lu->get()) return;
map_location loc = (*lu)->get_location();
loc.set_wml_x(value);
handle_unit_move(L, lu, loc);
}
UNIT_GETTER("y", int) {
return u.get_location().wml_y();
}
LATTR_SETTER("y", int, lua_unit*, lu) {
if(!lu->get()) return;
map_location loc = (*lu)->get_location();
loc.set_wml_y(value);
handle_unit_move(L, lu, loc);
}
UNIT_GETTER("loc", map_location) {
return u.get_location();
}
LATTR_SETTER("loc", map_location, lua_unit*, lu) {
if(!lu->get()) return;
handle_unit_move(L, lu, value);
}
UNIT_GETTER("goto", map_location) {
return u.get_goto();
}
UNIT_SETTER("goto", map_location) {
u.set_goto(value);
}
UNIT_GETTER("side", int) {
return u.side();
}
UNIT_SETTER("side", int) {
u.set_side(value);
}
UNIT_GETTER("id", std::string) {
return u.id();
}
LATTR_SETTER("id", std::string, lua_unit*, lu) {
if(!lu->get()) return;
if(!lu->on_map()) luaL_argerror(L, 3, "can't modify id of on-map unit");
(*lu)->set_id(value);
}
UNIT_GETTER("type", std::string) {
return u.type_id();
}
UNIT_GETTER("image_mods", std::string) {
return u.effect_image_mods();
}
UNIT_GETTER("usage", std::string) {
return u.usage();
}
UNIT_SETTER("usage", std::string) {
u.set_usage(value);
}
UNIT_GETTER("ellipse", std::string) {
return u.image_ellipse();
}
UNIT_SETTER("ellipse", std::string) {
u.set_image_ellipse(value);
}
UNIT_GETTER("halo", std::string) {
return u.image_halo();
}
UNIT_SETTER("halo", std::string) {
u.set_image_halo(value);
}
UNIT_GETTER("hitpoints", int) {
return u.hitpoints();
}
UNIT_SETTER("hitpoints", int) {
u.set_hitpoints(value);
}
UNIT_GETTER("max_hitpoints", int) {
return u.max_hitpoints();
}
UNIT_SETTER("max_hitpoints", int) {
u.set_max_hitpoints(value);
}
UNIT_GETTER("experience", int) {
return u.experience();
}
UNIT_SETTER("experience", int) {
u.set_experience(value);
}
UNIT_GETTER("max_experience", int) {
return u.max_experience();
}
UNIT_SETTER("max_experience", int) {
u.set_max_experience(value);
}
UNIT_GETTER("recall_cost", int) {
return u.recall_cost();
}
UNIT_SETTER("recall_cost", int) {
u.set_recall_cost(value);
}
UNIT_GETTER("moves", int) {
return u.movement_left();
}
UNIT_SETTER("moves", int) {
u.set_movement(value);
}
UNIT_GETTER("max_moves", int) {
return u.total_movement();
}
UNIT_SETTER("max_moves", int) {
u.set_total_movement(value);
}
UNIT_GETTER("max_attacks", int) {
return u.max_attacks();
}
UNIT_SETTER("max_attacks", int) {
u.set_max_attacks(value);
}
UNIT_GETTER("attacks_left", int) {
return u.attacks_left();
}
UNIT_SETTER("attacks_left", int) {
u.set_attacks(value);
}
UNIT_GETTER("vision", int) {
return u.vision();
}
UNIT_GETTER("jamming", int) {
return u.jamming();
}
UNIT_GETTER("name", t_string) {
return u.name();
}
UNIT_SETTER("name", t_string) {
u.set_name(value);
}
UNIT_GETTER("description", t_string) {
return u.unit_description();
}
UNIT_SETTER("description", t_string) {
u.set_unit_description(value);
}
UNIT_GETTER("canrecruit", bool) {
return u.can_recruit();
}
UNIT_SETTER("canrecruit", bool) {
u.set_can_recruit(value);
}
UNIT_GETTER("renamable", bool) {
return !u.unrenamable();
}
UNIT_SETTER("renamable", bool) {
u.set_unrenamable(!value);
}
UNIT_GETTER("level", int) {
return u.level();
}
UNIT_SETTER("level", int) {
u.set_level(value);
}
UNIT_GETTER("cost", int) {
return u.cost();
}
UNIT_GETTER("extra_recruit", std::vector<std::string>) {
return u.recruits();
}
UNIT_SETTER("extra_recruit", std::vector<std::string>) {
u.set_recruits(value);
}
UNIT_GETTER("advances_to", std::vector<std::string>) {
return u.advances_to();
}
UNIT_SETTER("advances_to", std::vector<std::string>) {
u.set_advances_to(value);
}
UNIT_GETTER("alignment", std::string) {
return unit_alignments::get_string(u.alignment());
}
UNIT_SETTER("alignment", lua_index_raw) {
auto alignment = unit_alignments::get_enum(lua_check<std::string_view>(L, value.index));
if(!alignment) luaL_argerror(L, value.index, "invalid unit alignment");
u.set_alignment(*alignment);
}
UNIT_GETTER("upkeep", lua_index_raw) {
unit::upkeep_t upkeep = u.upkeep_raw();
// Need to keep these separate in order to ensure an int value is always used if applicable.
if(int* v = utils::get_if<int>(&upkeep)) {
lua_push(L, *v);
} else {
const std::string type = utils::visit(unit::upkeep_type_visitor(), upkeep);
lua_push(L, type);
}
return lua_index_raw(L);
}
UNIT_SETTER("upkeep", lua_index_raw) {
if(lua_isnumber(L, value.index)) {
u.set_upkeep(static_cast<int>(luaL_checkinteger(L, 3)));
return;
}
auto v = lua_check<std::string_view>(L, value.index);
if(v == "loyal" || v == "free") {
u.set_upkeep(unit::upkeep_loyal());
} else if(v == "full") {
u.set_upkeep(unit::upkeep_full());
} else {
std::string err_msg = "unknown upkeep value of unit: ";
err_msg += v;
luaL_argerror(L, 2, err_msg.c_str());
}
return;
}
UNIT_GETTER("advancements", std::vector<config>) {
return u.modification_advancements();
}
UNIT_SETTER("advancements", std::vector<config>) {
u.set_advancements(value);
}
UNIT_GETTER("overlays", std::vector<std::string>) {
return u.overlays();
}
UNIT_GETTER("traits", std::vector<std::string>) {
return u.get_traits_list();
}
UNIT_GETTER("abilities", std::vector<std::string>) {
return u.get_ability_list();
}
UNIT_GETTER("status", lua_index_raw) {
(void)u;
lua_createtable(L, 1, 0);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
luaL_setmetatable(L, ustatusKey);
return lua_index_raw(L);
}
UNIT_GETTER("variables", lua_index_raw) {
(void)u;
lua_createtable(L, 1, 0);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
luaL_setmetatable(L, unitvarKey);
return lua_index_raw(L);
}
UNIT_GETTER("attacks", lua_index_raw) {
(void)u;
push_unit_attacks_table(L, 1);
return lua_index_raw(L);
}
UNIT_GETTER("petrified", bool) {
deprecated_message("(unit).petrified", DEP_LEVEL::INDEFINITE, {1,17,0}, "use (unit).status.petrified instead");
return u.incapacitated();
}
UNIT_GETTER("animations", std::vector<std::string>) {
return u.anim_comp().get_flags();
}
UNIT_GETTER("recall_filter", config) {
return u.recall_filter();
}
UNIT_SETTER("recall_filter", config) {
u.set_recall_filter(value);
}
UNIT_GETTER("hidden", bool) {
return u.get_hidden();
}
UNIT_SETTER("hidden", bool) {
u.set_hidden(value);
}
UNIT_GETTER("resting", bool) {
return u.resting();
}
UNIT_SETTER("resting", bool) {
u.set_resting(value);
}
UNIT_GETTER("flying", bool) {
return u.is_flying();
}
UNIT_GETTER("fearless", bool) {
return u.is_fearless();
}
UNIT_GETTER("healthy", bool) {
return u.is_healthy();
}
UNIT_GETTER("zoc", bool) {
return u.get_emit_zoc();
}
UNIT_SETTER("zoc", bool) {
u.set_emit_zoc(value);
}
UNIT_GETTER("role", std::string) {
return u.get_role();
}
UNIT_SETTER("role", std::string) {
u.set_role(value);
}
UNIT_GETTER("race", std::string) {
return u.race()->id();
}
UNIT_GETTER("gender", std::string) {
return gender_string(u.gender());
}
UNIT_GETTER("variation", std::string) {
return u.variation();
}
UNIT_GETTER("undead_variation", std::string) {
return u.undead_variation();
}
UNIT_SETTER("undead_variation", std::string) {
u.set_undead_variation(value);
}
UNIT_GETTER("facing", std::string) {
return map_location::write_direction(u.facing());
}
UNIT_SETTER("facing", std::string) {
u.set_facing(map_location::parse_direction(value));
}
UNIT_GETTER("portrait", std::string) {
return u.big_profile() == u.absolute_image()
? u.absolute_image() + u.image_mods() + "~SCALE_SHARP(144,144)"
: u.big_profile();
}
UNIT_SETTER("portrait", std::string) {
u.set_big_profile(value);
}
UNIT_GETTER("__cfg", config) {
config cfg;
u.write(cfg);
u.get_location().write(cfg);
return cfg;
}
/**
* Gets some data on a unit (__index metamethod).
* - Arg 1: full userdata containing the unit id.
@ -277,149 +765,7 @@ static int impl_unit_tostring(lua_State* L)
*/
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");
}
const unit& u = *pu;
// Find the corresponding attribute.
return_int_attrib("x", u.get_location().wml_x());
return_int_attrib("y", u.get_location().wml_y());
if(strcmp(m, "loc") == 0) {
luaW_pushlocation(L, u.get_location());
return 1;
}
if(strcmp(m, "goto") == 0) {
luaW_pushlocation(L, u.get_goto());
return 1;
}
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_string_attrib("ellipse", u.image_ellipse());
return_string_attrib("halo", u.image_halo());
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_int_attrib("vision", u.vision());
return_int_attrib("jamming", u.jamming());
return_tstring_attrib("name", u.name());
return_tstring_attrib("description", u.unit_description());
return_bool_attrib("canrecruit", u.can_recruit());
return_bool_attrib("renamable", !u.unrenamable());
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, unit_alignments::get_string(u.alignment()));
return 1;
}
if(strcmp(m, "upkeep") == 0) {
unit::upkeep_t upkeep = u.upkeep_raw();
// Need to keep these separate in order to ensure an int value is always used if applicable.
if(int* v = utils::get_if<int>(&upkeep)) {
lua_push(L, *v);
} else {
const std::string type = utils::visit(unit::upkeep_type_visitor(), upkeep);
lua_push(L, type);
}
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);
luaL_setmetatable(L, ustatusKey);
return 1;
}
if(strcmp(m, "variables") == 0) {
lua_createtable(L, 1, 0);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
luaL_setmetatable(L, unitvarKey);
return 1;
}
if(strcmp(m, "attacks") == 0) {
push_unit_attacks_table(L, 1);
return 1;
}
if(strcmp(m, "petrified") == 0) {
deprecated_message("(unit).petrified", DEP_LEVEL::INDEFINITE, {1,17,0}, "use (unit).status.petrified instead");
lua_pushboolean(L, u.incapacitated());
return 1;
}
return_vector_string_attrib("animations", u.anim_comp().get_flags());
return_cfg_attrib("recall_filter", cfg = u.recall_filter());
return_bool_attrib("hidden", u.get_hidden());
return_bool_attrib("resting", u.resting());
return_bool_attrib("flying", u.is_flying());
return_bool_attrib("fearless", u.is_fearless());
return_bool_attrib("healthy", u.is_healthy());
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_string_attrib("undead_variation", u.undead_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() + "~SCALE_SHARP(144,144)"
: u.big_profile());
return_cfg_attrib("__cfg", u.write(cfg); u.get_location().write(cfg));
if(luaW_getglobal(L, "wesnoth", "units", m)) {
return 1;
}
return 0;
return unitReg.get(L);
}
/**
@ -430,132 +776,18 @@ static int impl_unit_get(lua_State *L)
*/
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;
return unitReg.set(L);
}
// 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("max_moves", u.set_total_movement(value));
modify_int_attrib("max_attacks", u.set_max_attacks(value));
modify_int_attrib("hitpoints", u.set_hitpoints(value));
modify_int_attrib("max_hitpoints", u.set_max_hitpoints(value));
modify_int_attrib("experience", u.set_experience(value));
modify_int_attrib("max_experience", u.set_max_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_tstring_attrib("description", u.set_unit_description(value));
modify_string_attrib("portrait", u.set_big_profile(value));
modify_string_attrib("role", u.set_role(value));
modify_string_attrib("facing", u.set_facing(map_location::parse_direction(value)));
modify_string_attrib("usage", u.set_usage(value));
modify_string_attrib("undead_variation", u.set_undead_variation(value));
modify_string_attrib("ellipse", u.set_image_ellipse(value));
modify_string_attrib("halo", u.set_image_halo(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_bool_attrib("renamable", u.set_unrenamable(!value));
modify_cfg_attrib("recall_filter", u.set_recall_filter(cfg));
modify_vector_string_attrib("extra_recruit", u.set_recruits(value));
modify_vector_string_attrib("advances_to", u.set_advances_to(value));
if(strcmp(m, "alignment") == 0) {
u.set_alignment(lua_enum_check<unit_alignments>(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(static_cast<int>(luaL_checkinteger(L, 3)));
return 0;
}
const char* v = luaL_checkstring(L, 3);
if((strcmp(v, "loyal") == 0) || (strcmp(v, "free") == 0)) {
u.set_upkeep(unit::upkeep_loyal());
} else if(strcmp(v, "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.set_wml_x(value); u.set_location(loc));
modify_int_attrib("y", loc.set_wml_y(value); u.set_location(loc));
modify_string_attrib("id", u.set_id(value));
if(strcmp(m, "loc") == 0) {
luaW_tolocation(L, 3, loc);
u.set_location(loc);
return 0;
}
} else {
const bool is_key_x = strcmp(m, "x") == 0;
const bool is_key_y = strcmp(m, "y") == 0;
const bool is_loc_key = strcmp(m, "loc") == 0;
// Handle moving an on-map unit
if(is_key_x || is_key_y || is_loc_key) {
game_board* gb = resources::gameboard;
if(!gb) {
return 0;
}
map_location src = u.get_location();
map_location dst = src;
if(is_key_x) {
dst.set_wml_x(luaL_checkinteger(L, 3));
} else if(is_key_y) {
dst.set_wml_y(luaL_checkinteger(L, 3));
} else {
dst = luaW_checklocation(L, 3);
}
// TODO: could probably be relegated to a helper function.
if(src != dst) {
// If the dst isn't on the map, the unit will be clobbered. Guard against that.
if(!gb->map().on_board(dst)) {
std::string err_msg = formatter() << "destination hex not on map (excluding border): " << dst;
return luaL_argerror(L, 2, err_msg.c_str());
}
auto [unit_iterator, success] = gb->units().move(src, dst);
if(success) {
unit_iterator->anim_comp().set_standing();
}
}
return 0;
}
}
if(strcmp(m, "goto") == 0) {
u.set_goto(luaW_checklocation(L, 3));
return 0;
}
std::string err_msg = "unknown modifiable property of unit: ";
err_msg += m;
return luaL_argerror(L, 2, err_msg.c_str());
/**
* Prints valid attributes on a unit (__dir metamethod).
* - Arg 1: full userdata containing the unit id.
* - Arg 2: string containing the name of the property.
* - Ret 1: a list of attributes.
*/
static int impl_unit_dir(lua_State *L)
{
return unitReg.dir(L);
}
/**
@ -673,6 +905,8 @@ namespace lua_units {
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, impl_unit_set);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, impl_unit_dir);
lua_setfield(L, -2, "__dir");
lua_pushstring(L, "unit");
lua_setfield(L, -2, "__metatable");

View file

@ -35,6 +35,7 @@
#include "scripting/lua_unit_type.hpp"
#include "scripting/push_check.hpp"
#include "scripting/lua_widget.hpp"
#include "scripting/lua_attributes.hpp"
#include "scripting/lua_widget_attributes.hpp"
#include "serialization/string_utils.hpp"
@ -103,20 +104,6 @@ static tgetters getters;
using tsetters = std::map<std::string, std::vector<std::function<bool(lua_State*, int, gui2::widget&, bool)>>>;
static tsetters setters;
template<typename widget_type, typename value_type>
struct widget_getter
{
virtual value_type get(lua_State* L, widget_type& w) const = 0;
virtual ~widget_getter() = default;
};
template<typename widget_type, typename value_type>
struct widget_setter
{
virtual void set(lua_State* L, widget_type& w, const value_type& value) const = 0;
virtual ~widget_setter() = default;
};
template<typename widget_type, typename value_type, typename action_type, bool setter>
void register_widget_attribute(const char* name)
{
@ -151,8 +138,8 @@ void register_widget_attribute(const char* name)
}
#define WIDGET_GETTER4(name, value_type, widgt_type, id) \
struct BOOST_PP_CAT(getter_, id) : public widget_getter<widgt_type, value_type> { \
value_type get(lua_State* L, widgt_type& w) const override; \
struct BOOST_PP_CAT(getter_, id) : public lua_getter<widgt_type, value_type> { \
value_type get(lua_State* L, const widgt_type& w) const override; \
}; \
struct BOOST_PP_CAT(getter_adder_, id) { \
BOOST_PP_CAT(getter_adder_, id) () \
@ -161,11 +148,11 @@ struct BOOST_PP_CAT(getter_adder_, id) { \
} \
}; \
static BOOST_PP_CAT(getter_adder_, id) BOOST_PP_CAT(getter_adder_instance_, id) ; \
value_type BOOST_PP_CAT(getter_, id)::get([[maybe_unused]] lua_State* L, widgt_type& w) const
value_type BOOST_PP_CAT(getter_, id)::get([[maybe_unused]] lua_State* L, const widgt_type& w) const
#define WIDGET_SETTER4(name, value_type, widgt_type, id) \
struct BOOST_PP_CAT(setter_, id) : public widget_setter<widgt_type, value_type> { \
struct BOOST_PP_CAT(setter_, id) : public lua_setter<widgt_type, value_type> { \
void set(lua_State* L, widgt_type& w, const value_type& value) const override; \
}; \
struct BOOST_PP_CAT(setter_adder_, id) { \
@ -431,13 +418,13 @@ WIDGET_SETTER("value_compat,label", t_string, gui2::styled_widget)
WIDGET_GETTER("type", std::string, gui2::widget)
{
if(gui2::styled_widget* sw = dynamic_cast<gui2::styled_widget*>(&w)) {
if(const gui2::styled_widget* sw = dynamic_cast<const gui2::styled_widget*>(&w)) {
return sw->get_control_type();
}
else if(dynamic_cast<gui2::tree_view_node*>(&w)) {
else if(dynamic_cast<const gui2::tree_view_node*>(&w)) {
return "tree_view_node";
}
else if(dynamic_cast<gui2::grid*>(&w)) {
else if(dynamic_cast<const gui2::grid*>(&w)) {
return "grid";
}
else {
@ -539,7 +526,9 @@ int impl_widget_get(lua_State* L)
return 1;
}
ERR_LUA << "invalid property of '" << typeid(w).name()<< "' widget :" << str;
return luaL_argerror(L, 2, "invalid property of widget");
std::string err = "invalid property of widget: ";
err += str;
return luaL_argerror(L, 2, err.c_str());
}
int impl_widget_set(lua_State* L)
@ -562,7 +551,9 @@ int impl_widget_set(lua_State* L)
}
ERR_LUA << "invalid modifiable property of '" << typeid(w).name()<< "' widget:" << str;
return luaL_argerror(L, 2, "invalid modifiable property of widget");
std::string err = "invalid modifiable property of widget: ";
err += str;
return luaL_argerror(L, 2, err.c_str());
}
int impl_widget_dir(lua_State* L)

View file

@ -25,7 +25,11 @@
#include <string_view>
#include <type_traits>
struct lua_index_raw { int index; };
struct lua_index_raw {
int index;
lua_index_raw(int i) : index(i) {}
lua_index_raw(lua_State* L) : index(lua_gettop(L)) {}
};
namespace lua_check_impl
{
@ -66,15 +70,21 @@ namespace lua_check_impl
template<typename T>
std::enable_if_t<std::is_same_v<T, lua_index_raw>, lua_index_raw>
lua_check(lua_State * /*L*/, int n)
lua_check(lua_State * L, int n)
{
return lua_index_raw{ n };
return lua_index_raw{ lua_absindex(L, n) };
}
template<typename T>
std::enable_if_t<std::is_same_v<T, lua_index_raw>, lua_index_raw>
lua_to_or_default(lua_State * /*L*/, int n, const T& /*def*/)
lua_to_or_default(lua_State * L, int n, const T& /*def*/)
{
return lua_index_raw{ n };
return lua_index_raw{ lua_absindex(L, n) };
}
template<typename T>
std::enable_if_t<std::is_same_v<T, lua_index_raw>, void>
lua_push(lua_State * L, lua_index_raw n)
{
lua_pushvalue(L, n.index);
}
//std::string
@ -374,12 +384,6 @@ namespace lua_check_impl
}
}
template<typename T>
typename T::type lua_enum_check(lua_State *L, int n)
{
return lua_check_impl::lua_check<T>(L, n);
}
template<typename T>
std::decay_t<T> lua_check(lua_State *L, int n)
{