diff --git a/include/vcmi/scripting/Service.h b/include/vcmi/scripting/Service.h index 5b5901fe8..a47992905 100644 --- a/include/vcmi/scripting/Service.h +++ b/include/vcmi/scripting/Service.h @@ -30,7 +30,6 @@ public: virtual ~Context() = default; virtual void run(const JsonNode & initialState) = 0; - virtual void run(ServerCallback * server, const JsonNode & initialState) = 0; virtual JsonNode callGlobal(const std::string & name, const JsonNode & parameters) = 0; diff --git a/include/vstd/CLoggerBase.h b/include/vstd/CLoggerBase.h index 4408a4cd8..1bfc8faa4 100644 --- a/include/vstd/CLoggerBase.h +++ b/include/vstd/CLoggerBase.h @@ -198,5 +198,6 @@ extern DLL_LINKAGE vstd::CLoggerBase * logAi; extern DLL_LINKAGE vstd::CLoggerBase * logAnim; extern DLL_LINKAGE vstd::CLoggerBase * logMod; extern DLL_LINKAGE vstd::CLoggerBase * logRng; +extern DLL_LINKAGE vstd::CLoggerBase * logScript; VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index 9c3d25f07..08cac562c 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -10,6 +10,7 @@ #pragma once #include "BattleSide.h" +#include VCMI_LIB_NAMESPACE_BEGIN @@ -30,7 +31,7 @@ class BattleHexArray; * Valid hexes are within the range 0 to 186, excluding some invalid values, ex. castle towers (-2, -3, -4). * Available hexes are those valid ones but NOT in the first or last column. */ -class DLL_LINKAGE BattleHex +class DLL_LINKAGE BattleHex : public scripting::ApiCopyable { public: diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index 68b79f9e8..9d176ddd4 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -286,14 +286,14 @@ void UnitInfo::save(JsonNode & data) { data.clear(); JsonSerializer ser(nullptr, data); - ser.serializeStruct("newUnitInfo", *this); + serializeJson(ser); } void UnitInfo::load(uint32_t id_, const JsonNode & data) { id = id_; JsonDeserializer deser(nullptr, data); - deser.serializeStruct("newUnitInfo", *this); + serializeJson(deser); } } diff --git a/lib/logging/CLogger.cpp b/lib/logging/CLogger.cpp index 725763fe4..7b1030639 100644 --- a/lib/logging/CLogger.cpp +++ b/lib/logging/CLogger.cpp @@ -98,6 +98,7 @@ DLL_LINKAGE vstd::CLoggerBase * logAi = CLogger::getLogger(CLoggerDomain("ai")); DLL_LINKAGE vstd::CLoggerBase * logAnim = CLogger::getLogger(CLoggerDomain("animation")); DLL_LINKAGE vstd::CLoggerBase * logMod = CLogger::getLogger(CLoggerDomain("mod")); DLL_LINKAGE vstd::CLoggerBase * logRng = CLogger::getLogger(CLoggerDomain("rng")); +DLL_LINKAGE vstd::CLoggerBase * logScript = CLogger::getLogger(CLoggerDomain("script")); CLogger * CLogger::getLogger(const CLoggerDomain & domain) { diff --git a/luascript/CMakeLists.txt b/luascript/CMakeLists.txt index f48eb5d06..a46440e3c 100644 --- a/luascript/CMakeLists.txt +++ b/luascript/CMakeLists.txt @@ -38,6 +38,7 @@ set(lib_SRCS api/BattleCb.cpp api/BonusSystem.cpp api/Creature.cpp + api/Enums.cpp api/Faction.cpp api/GameCb.cpp api/HeroClass.cpp @@ -89,6 +90,7 @@ set(lib_HDRS api/BattleCb.h api/BonusSystem.h api/Creature.h + api/Enums.h api/Faction.h api/GameCb.h api/HeroClass.h diff --git a/luascript/LuaContext.cpp b/luascript/LuaContext.cpp index 1aea65054..2b14f4b43 100644 --- a/luascript/LuaContext.cpp +++ b/luascript/LuaContext.cpp @@ -13,6 +13,8 @@ #include "LuaStack.h" #include "LuaReference.h" +#include "api/Enums.h" + #include "../lib/callback/IGameInfoCallback.h" #include "../lib/json/JsonNode.h" #include "../lib/filesystem/Filesystem.h" @@ -24,6 +26,31 @@ VCMI_LIB_NAMESPACE_BEGIN +/// Custom text printing function for use in scripting +/// based on luaB_print (part of Lua source code) +/// adapted to C++ & VCMI logging facilities +static int luaPrint(lua_State *L) { + int n = lua_gettop(L); + lua_getglobal(L, "tostring"); + std::string out; + for (int i = 1; i <= n; ++i) + { + lua_pushvalue(L, -1); + lua_pushvalue(L, i); + lua_call(L, 1, 1); + const char *s = lua_tostring(L, -1); + if (s) + out += s; + if (i > 1) + out += '\t'; + + lua_pop(L, 1); + } + + logScript->info("%s", out); + return 0; +} + namespace scripting { @@ -34,14 +61,13 @@ LuaContext::LuaContext(const Script * source, const Environment * env_): script(source), env(env_) { - static const std::vector STD_LIBS = - { + static constexpr std::array STD_LIBS = + {{ {"", luaopen_base}, {LUA_TABLIBNAME, luaopen_table}, {LUA_STRLIBNAME, luaopen_string}, - {LUA_MATHLIBNAME, luaopen_math}, - {LUA_BITLIBNAME, luaopen_bit} - }; + {LUA_MATHLIBNAME, luaopen_math} + }}; for(const luaL_Reg & lib : STD_LIBS) { @@ -65,6 +91,7 @@ LuaContext::LuaContext(const Script * source, const Environment * env_): popAll(); LuaStack S(L); + api::Enums enums; S.push(env->game()); lua_setglobal(L, "GAME"); @@ -72,6 +99,9 @@ LuaContext::LuaContext(const Script * source, const Environment * env_): S.push(env->services()); lua_setglobal(L, "LIBRARY"); + S.push(enums); + lua_setglobal(L, "ENUM"); + popAll(); } @@ -101,7 +131,7 @@ void LuaContext::cleanupGlobals() S.pushNil(); lua_setglobal(L, "loadstring"); - S.pushNil(); + lua_pushcfunction(L, luaPrint); lua_setglobal(L, "print"); S.clear(); @@ -119,32 +149,12 @@ void LuaContext::cleanupGlobals() S.pushNil(); lua_rawset(L, -3); - S.push("randomseed"); S.pushNil(); lua_rawset(L, -3); S.clear(); } -void LuaContext::run(ServerCallback * server, const JsonNode & initialState) -{ - { - LuaStack S(L); - S.push(server); - lua_setglobal(L, "SERVER"); - S.clear(); - } - - run(initialState); - -// { -// LuaStack S(L); -// S.pushNil(); -// lua_setglobal(L, "SERVER"); -// S.clear(); -// } -} - void LuaContext::run(const JsonNode & initialState) { setGlobal(STATE_FIELD, initialState); @@ -153,7 +163,7 @@ void LuaContext::run(const JsonNode & initialState) if(ret) { - logGlobal->error("Script '%s' failed to load, error: %s", script->getJsonKey(), toStringRaw(-1)); + logScript->error("Script '%s' failed to load, error: %s", script->getJsonKey(), toStringRaw(-1)); popAll(); return; } @@ -166,7 +176,7 @@ void LuaContext::run(const JsonNode & initialState) if(ret) { - logGlobal->error("Script '%s' failed to run, error: '%s'", script->getJsonKey(), toStringRaw(-1)); + logScript->error("Script '%s' failed to run, error: '%s'", script->getJsonKey(), toStringRaw(-1)); popAll(); } } diff --git a/luascript/LuaContext.h b/luascript/LuaContext.h index 4856d1cff..8f190beb0 100644 --- a/luascript/LuaContext.h +++ b/luascript/LuaContext.h @@ -30,7 +30,6 @@ public: virtual ~LuaContext(); void run(const JsonNode & initialState) override; - void run(ServerCallback * server, const JsonNode & initialState) override; //log error and return nil from LuaCFunction int errorRetVoid(const std::string & message); @@ -111,9 +110,9 @@ ReturnType LuaContext::callGlobalWithParameters(const std::string & name, Args&& if(lua_pcall(L, argc, 1, 0)) { + std::string error = lua_tostring(L, -1); S.clear(); - std::string error = lua_tostring(L, -1); boost::format fmt("Lua function %s failed with message: %s"); fmt % name % error; logGlobal->error(fmt.str()); @@ -123,7 +122,7 @@ ReturnType LuaContext::callGlobalWithParameters(const std::string & name, Args&& if constexpr (!std::is_void_v) { ReturnType ret; - S.getOrThrow(-1, ret); + S.getOrThrow(S.absindex(-1), ret); S.balance(); return ret; } diff --git a/luascript/LuaSpellEffect.cpp b/luascript/LuaSpellEffect.cpp index 2f344cd5d..995c20a9e 100644 --- a/luascript/LuaSpellEffect.cpp +++ b/luascript/LuaSpellEffect.cpp @@ -80,20 +80,16 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef if(target.empty()) return false; - JsonNode targetJson = spellTargetToJson(target); - bool result = context->callGlobalWithParameters(APPLICABLE_TARGET, parameters, m, targetJson); + bool result = context->callGlobalWithParameters(APPLICABLE_TARGET, parameters, m, target); return result; } void LuaSpellEffect::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const { - if(target.empty()) - return; - std::shared_ptr context = resolveScript(m); - context->callGlobalWithParameters(APPLY, parameters, m, server, spellTargetToJson(target)); + context->callGlobalWithParameters(APPLY, parameters, m, server, target); } EffectTarget LuaSpellEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const @@ -105,11 +101,9 @@ EffectTarget LuaSpellEffect::transformTarget(const Mechanics * m, const Target & { std::shared_ptr context = resolveScript(m); - JsonNode aimPointJson = spellTargetToJson(aimPoint); - JsonNode spellTargetJson = spellTargetToJson(spellTarget); - JsonNode response = context->callGlobalWithParameters(TRANSFORM_TARGET, parameters, m, aimPointJson, spellTargetJson); + Target response = context->callGlobalWithParameters(TRANSFORM_TARGET, parameters, m, aimPoint, spellTarget); - return spellTargetFromJson(m, response); + return response; } void LuaSpellEffect::serializeJsonEffect(JsonSerializeFormat & handler) @@ -129,45 +123,6 @@ std::shared_ptr LuaSpellEffect::resolveScript(const Mecha return luaContext; } -JsonNode LuaSpellEffect::spellTargetToJson(const Target & target) const -{ - JsonNode requestP; - - for(const auto & dest : target) - { - JsonNode targetData; - targetData.Vector().emplace_back(dest.hexValue.toInt()); - - if(dest.unitValue) - targetData.Vector().emplace_back(dest.unitValue->unitId()); - else - targetData.Vector().emplace_back(-1); - - requestP.Vector().push_back(targetData); - } - - return requestP; -} - -Target LuaSpellEffect::spellTargetFromJson(const Mechanics * m, const JsonNode & config) const -{ - Target result; - - for (const auto & entry : config.Vector()) - { - Destination dest; - - if (!entry[1].isNull() && entry[1].Integer() != -1 ) - dest = Destination(m->battle()->battleGetUnitByID(entry[1].Integer()), BattleHex(entry[0].Integer())); - else - dest = Destination(BattleHex(entry[0].Integer())); - - result.push_back(dest); - } - - return result; -} - } } diff --git a/luascript/LuaSpellEffect.h b/luascript/LuaSpellEffect.h index 20197dd1a..ca65796b3 100644 --- a/luascript/LuaSpellEffect.h +++ b/luascript/LuaSpellEffect.h @@ -76,9 +76,6 @@ private: JsonNode parameters; std::shared_ptr resolveScript(const Mechanics * m) const; - - JsonNode spellTargetToJson(const Target & spellTarget) const; - Target spellTargetFromJson(const Mechanics * m, const JsonNode & config) const; }; } diff --git a/luascript/LuaStack.cpp b/luascript/LuaStack.cpp index 3e1887ca2..307fda85d 100644 --- a/luascript/LuaStack.cpp +++ b/luascript/LuaStack.cpp @@ -35,6 +35,14 @@ void LuaStack::clear() lua_settop(L, 0); } +int LuaStack::absindex(int idx) +{ + if (idx > 0) + return idx; + + return lua_gettop(L) + 1 + idx; +} + void LuaStack::pushByIndex(lua_Integer index) { lua_pushvalue(L, index); @@ -168,6 +176,7 @@ bool LuaStack::tryGet(int position, int3 & value) bool LuaStack::tryGet(int position, JsonNode & value) { auto type = lua_type(L, position); + value.setModScope("game"); switch(type) { diff --git a/luascript/LuaStack.h b/luascript/LuaStack.h index 31e8f19ea..678bde12e 100644 --- a/luascript/LuaStack.h +++ b/luascript/LuaStack.h @@ -30,32 +30,6 @@ class LuaApiException : public std::runtime_error class LuaStack; -class LuaDeserializer -{ - LuaStack & stack; - lua_State * L; - int idx; - -public: - LuaDeserializer(LuaStack & stack, int idx); - - template - void operator()(const std::string &keyName, T & data) const; -}; - -class LuaSerializer -{ - LuaStack & stack; - lua_State * L; - int idx; - -public: - LuaSerializer(LuaStack & stack, int idx); - - template - void operator()(const std::string &keyName, const T & data) const; -}; - class LuaStack { public: @@ -72,6 +46,10 @@ public: return *this; } + /// Converts stack position relative to top (e.g. -1) into absolute position + /// Behavior is identical to `lua_absindex` function, available from Lua 5.2 + int absindex(int idx); + void pushNil(); void pushInteger(lua_Integer value); void push(bool value); @@ -164,6 +142,72 @@ public: lua_setmetatable(L, -2); } + template, int> = 0> + void push(const T & value) + { + using DataType = T; + using BaseType = DataType::ScriptingApiName; + static_assert(std::is_same_v, BaseType>, "Can not push derived class as copyable!"); + + static auto KEY = api::TypeRegistry::get()->getKey(); + + void * raw = lua_newuserdata(L, sizeof(BaseType)); + if(!raw) + throw LuaApiException("Failed to allocate new user data!"); + + new(raw) BaseType(value); + + luaL_getmetatable(L, KEY); + if(lua_isnil(L, -1)) + throw LuaApiException(std::string("Unregistered type pushed on Lua stack: ") + KEY); + + lua_setmetatable(L, -2); + } + + template + void push(const std::vector & value) + { + lua_newtable(L); + int tableIndex = lua_gettop(L); + + for (size_t i = 0; i < value.size(); ++i) + { + push(value[i]); + lua_rawseti(L, tableIndex, i + 1); + } + } + + template + void push(const std::map & value) + { + lua_newtable(L); + int tableIndex = lua_gettop(L); + + for (const auto &entry : value) + { + push(entry.second); + lua_setfield(L, tableIndex, entry.first.c_str()); + } + } + + template, int> = 0> + void push(const T & value) + { + lua_newtable(L); + int tableIndex = lua_gettop(L); + + // get non-const value - ugly, but required since same template method is used for deserialization + T & nonConstValue = const_cast(value); + + const auto & luaSerializer = [this, tableIndex](const std::string &keyName, const Field & data) + { + push(data); + lua_setfield(L, tableIndex, keyName.c_str()); + }; + + nonConstValue.serializeScript(luaSerializer); + } + bool tryGetInteger(int position, lua_Integer & value); bool tryGet(int position, bool & value); @@ -232,13 +276,36 @@ public: tryGet(-1, out[i]); lua_pop(L, 1); } + + return true; } template, int> = 0> STRONG_INLINE bool tryGet(int position, T & value) { - LuaDeserializer serializer(*this, position); - value->serializeLua(serializer); + const auto & deserializer = [this, position](const std::string &keyName, Data & data) + { + if (!lua_istable(L, position)) + throw LuaApiException("value at index is not a table"); + + // pushes table[keyName] on stack + // NOTE: if value is not present or set to nil, top of stack will contain nil + // do not handle it here, but in tryGet - attempt to tryGet values that don't support nil values + // will throw exceptions, while allowing null values (where supported) to load + lua_getfield(L, position, keyName.c_str()); + + try { + tryGet(absindex(-1), data); + } catch (...) { + // restore stack + lua_pop(L, 1); + throw; + } + + lua_pop(L, 1); // pop pushed value + }; + + value.serializeScript(deserializer); return true; } @@ -296,6 +363,16 @@ public: return result; } + template, int> = 0> + STRONG_INLINE bool tryGet(int position, T & value) + { + using DataType = T; + using BaseType = DataType::ScriptingApiName; + static_assert(std::is_same_v, "Can not push derived class as copyable!"); + + return tryGetUData(position, value); + } + template bool tryGetAll(int position, Args &...args) { bool failed = false; @@ -314,7 +391,7 @@ public: { const char * expectedType = typeid(T).name(); const char * actualType = lua_typename(L, position); - std::string message = std::string("Invalid Lua value! Expected ") + expectedType + "at position" + std::to_string(position) + ", but found " + actualType; + std::string message = std::string("Invalid Lua value! Expected ") + expectedType + " at position" + std::to_string(position) + ", but found " + actualType; throw LuaApiException( message ); } } @@ -322,10 +399,13 @@ public: template bool tryGetUData(int position, BaseType & value) { - if (lua_isnil(L, position)) + if constexpr (std::is_assignable_v&, std::nullptr_t>) { - value = nullptr; - return true; + if (lua_isnil(L, position)) + { + value = nullptr; + return true; + } } static auto KEY = api::TypeRegistry::get()->getKey(); @@ -466,41 +546,6 @@ private: int initialTop; }; -template -void LuaDeserializer::operator()(const std::string &keyName, T & data) const -{ - if (!lua_istable(L, idx)) - throw LuaApiException("value at index is not a table"); - - // pushes table[keyName] on stack - lua_getfield(L, idx, keyName.c_str()); - - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - throw LuaApiException("Missing required field '" + keyName + "'"); - } - - try { - stack.tryGet(-1, data); - } catch (...) { - // restore stack - lua_pop(L, 1); - throw; - } - - lua_pop(L, 1); // pop pushed value -} - -template -void LuaSerializer::operator()(const std::string &keyName, const T & data) const -{ - if (!lua_istable(L, idx)) - throw LuaApiException("value at index is not a table"); - - stack.push(data); - lua_setfield(L, idx, keyName.c_str()); -} - } VCMI_LIB_NAMESPACE_END diff --git a/luascript/LuaWrapper.h b/luascript/LuaWrapper.h index 4d7aa1b78..dbf1b03b4 100644 --- a/luascript/LuaWrapper.h +++ b/luascript/LuaWrapper.h @@ -81,7 +81,7 @@ namespace detail if(objPtr) { auto obj = static_cast(objPtr); - obj->reset(); + obj->~UDataType(); } lua_settop(L, 0); return 0; @@ -117,6 +117,7 @@ public: static_assert(std::is_base_of_v, "Class must inherit from ApiRawPointer to be used with this class!"); static_assert(!std::is_base_of_v, "Class must not inherit from ApiSharedPointer to be used with this class!"); + static_assert(!std::is_base_of_v, "Class must not inherit from ApiCopyable to be used with this class!"); void pushMetatable(lua_State * L) const override final { @@ -157,6 +158,7 @@ public: static_assert(std::is_base_of_v, "Class must inherit from ApiSharedPointer to be used with this class!"); static_assert(!std::is_base_of_v, "Class must not inherit from ApiRawPointer to be used with this class!"); + static_assert(!std::is_base_of_v, "Class must not inherit from ApiCopyable to be used with this class!"); static int constructor(lua_State * L) { @@ -195,6 +197,56 @@ protected: } }; +template +class CopyableWrapper : public RegistarBase +{ +public: + using ObjectType = typename std::remove_cv_t; + using UDataType = T; + using CustomRegType = detail::CustomRegType; + + static_assert(std::is_base_of_v, "Class must inherit from ApiCopyable to be used with this class!"); + static_assert(!std::is_base_of_v, "Class must not inherit from ApiRawPointer to be used with this class!"); + static_assert(!std::is_base_of_v, "Class must not inherit from ApiSharedPointer to be used with this class!"); + + static int constructor(lua_State * L) + { + LuaStack S(L); + S.clear();//we do not accept any parameters in constructor + ObjectType obj; + S.push(obj); + return 1; + } + + void pushMetatable(lua_State * L) const override final + { + static auto KEY = api::TypeRegistry::get()->getKey(); + + LuaStack S(L); + + if(luaL_newmetatable(L, KEY) != 0) + { + adjustMetatable(L); + + S.push("__gc"); + lua_pushcfunction(L, &(detail::Dispatcher::destructor)); + lua_rawset(L, -3); + } + + S.balance(); + + detail::Dispatcher::pushStaticTable(L); + + adjustStaticTable(L); + } +protected: + void adjustMetatable(lua_State * L) const override + { + detail::Dispatcher::setIndexTable(L); + } +}; + + } VCMI_LIB_NAMESPACE_END diff --git a/luascript/api/BattleCb.cpp b/luascript/api/BattleCb.cpp index 70798ed5a..94278fabc 100644 --- a/luascript/api/BattleCb.cpp +++ b/luascript/api/BattleCb.cpp @@ -21,9 +21,7 @@ VCMI_LIB_NAMESPACE_BEGIN -namespace scripting -{ -namespace api +namespace scripting::api { VCMI_REGISTER_CORE_SCRIPT_API(BattleCbProxy, "game.Battle"); @@ -46,7 +44,7 @@ int BattleCbProxy::getBattlefieldType(lua_State * L) { LuaStack S(L); - const CBattleInfoCallback * object; + const IBattleInfoCallback * object; if(!S.tryGet(1, object)) return S.retVoid(); @@ -59,7 +57,7 @@ int BattleCbProxy::getTerrainType(lua_State * L) { LuaStack S(L); - const CBattleInfoCallback * object; + const IBattleInfoCallback * object; if(!S.tryGet(1, object)) return S.retVoid(); @@ -70,7 +68,7 @@ int BattleCbProxy::getAvailableHex(lua_State * L) { LuaStack S(L); - const CBattleInfoCallback * object; + const IBattleInfoCallback * object; if(!S.tryGet(1, object)) return S.retVoid(); @@ -85,16 +83,15 @@ int BattleCbProxy::getAvailableHex(lua_State * L) S.clear(); BattleHex result = object->getAvailableHex(creature, side, hexVal); - S.push(result.toInt()); + S.push(result); return 1; } - int BattleCbProxy::getUnitByPos(lua_State * L) { LuaStack S(L); - const CBattleInfoCallback * object; + const IBattleInfoCallback * object; if(!S.tryGet(1, object)) return S.retVoid(); @@ -119,7 +116,7 @@ int BattleCbProxy::getAnyUnitIf(lua_State * L) { LuaStack S(L); - const CBattleInfoCallback * object; + const IBattleInfoCallback * object; if(!S.tryGet(1, object)) return S.retVoid(); @@ -134,14 +131,14 @@ int BattleCbProxy::getAnyUnitIf(lua_State * L) S2.push(unit); if (lua_pcall(L, 1, 1, 0) != LUA_OK) { - std::string error = lua_tostring(L, -1); + std::string error = lua_tostring(L, S2.absindex(-1)); logGlobal->error("Lua getAnyUnitIf callback failed with message: %s", error); S2.clear(); return false; } bool result = false; - S2.tryGet(-1, result); + S2.tryGet(S2.absindex(-1), result); S2.balance(); return result; }); @@ -156,7 +153,6 @@ int BattleCbProxy::getAnyUnitIf(lua_State * L) return 1; } -} } VCMI_LIB_NAMESPACE_END diff --git a/luascript/api/BattleCb.h b/luascript/api/BattleCb.h index 12afee057..74200cb56 100644 --- a/luascript/api/BattleCb.h +++ b/luascript/api/BattleCb.h @@ -11,21 +11,19 @@ #pragma once #include -#include "../../lib/battle/CBattleInfoCallback.h" +#include "../../lib/battle/IBattleInfoCallback.h" #include "../LuaWrapper.h" VCMI_LIB_NAMESPACE_BEGIN -namespace scripting -{ -namespace api +namespace scripting::api { -class BattleCbProxy : public RawPointerWrapper +class BattleCbProxy : public RawPointerWrapper { public: - using Wrapper = RawPointerWrapper; + using Wrapper = RawPointerWrapper; static const std::vector REGISTER_CUSTOM; @@ -36,7 +34,6 @@ public: static int getAnyUnitIf(lua_State * L); }; -} } VCMI_LIB_NAMESPACE_END diff --git a/luascript/api/Enums.cpp b/luascript/api/Enums.cpp new file mode 100644 index 000000000..b9b0eb8ba --- /dev/null +++ b/luascript/api/Enums.cpp @@ -0,0 +1,35 @@ +/* + * Enums.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "Enums.h" + + +namespace scripting::api +{ + +Enums::EnumMap Enums::exportHealLevel() const +{ + return { + { "heal", EHealLevel::HEAL }, + { "resurrect", EHealLevel::RESURRECT }, + { "overheal", EHealLevel::OVERHEAL }, + }; +} + +Enums::EnumMap Enums::exportHealPower() const +{ + return { + { "oneBattle", EHealPower::ONE_BATTLE }, + { "permanent", EHealPower::PERMANENT }, + }; +} + +} diff --git a/luascript/api/Enums.h b/luascript/api/Enums.h new file mode 100644 index 000000000..7021847a7 --- /dev/null +++ b/luascript/api/Enums.h @@ -0,0 +1,39 @@ +/* + * Enums.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include + +#include "../../lib/constants/Enumerations.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace scripting::api +{ + +class Enums : public scripting::ApiSerializable +{ + template + using EnumMap = std::map; + + EnumMap exportHealLevel() const; + EnumMap exportHealPower() const; + +public: + template + void serializeScript(Serializer & s) + { + s("HealLevel", exportHealLevel()); + s("HealPower", exportHealPower()); + } +}; + +} diff --git a/luascript/api/ServerCb.cpp b/luascript/api/ServerCb.cpp index d868d1266..8189559f2 100644 --- a/luascript/api/ServerCb.cpp +++ b/luascript/api/ServerCb.cpp @@ -15,6 +15,7 @@ #include "../LuaStack.h" #include "../../lib/networkPacks/PacksForClientBattle.h" +#include "../../lib/battle/Unit.h" VCMI_LIB_NAMESPACE_BEGIN @@ -27,24 +28,10 @@ VCMI_REGISTER_CORE_SCRIPT_API(ServerCbProxy, "game.Server"); const std::vector ServerCbProxy::REGISTER_CUSTOM = { -// { -// "addToBattleLog", -// &ServerCbProxy::apply, -// false -// }, -// { -// "moveUnit", -// &ServerCbProxy::apply, -// false -// }, { "createUnit", &ServerCbProxy::createUnit, false }, { "updateUnit", &ServerCbProxy::updateUnit, false }, + { "healUnit", &ServerCbProxy::healUnit, false }, { "removeUnit", &ServerCbProxy::removeUnit, false }, -// { -// "commitPackage", -// &ServerCbProxy::commitPackage, -// false -// } }; int ServerCbProxy::commitPackage(lua_State * L) @@ -106,9 +93,9 @@ int ServerCbProxy::createUnit(lua_State * L) ServerCallback * object = nullptr; BattleUnitsChanged buc; UnitChanges uc; - uc.operation = UnitChanges::EOperation::UPDATE; + uc.operation = UnitChanges::EOperation::ADD; - if (!S.tryGetAll(1, object, buc.battleID, uc.data)) + if (!S.tryGetAll(1, object, buc.battleID, uc.id, uc.data)) return S.retVoid(); buc.changedStacks.push_back(uc); @@ -117,6 +104,36 @@ int ServerCbProxy::createUnit(lua_State * L) return S.retVoid(); } +int ServerCbProxy::healUnit(lua_State * L) +{ + LuaStack S(L); + + ServerCallback * object = nullptr; + BattleID battleID; + const battle::Unit * unit = nullptr; + int64_t healthDelta; + EHealPower healPower; + EHealLevel healLevel; + + if (!S.tryGetAll(1, object, battleID, unit, healthDelta, healLevel, healPower)) + return S.retVoid(); + + auto changedUnit = unit->acquire(); + changedUnit->heal(healthDelta, healLevel, healPower); + + BattleUnitsChanged buc; + UnitChanges uc; + buc.battleID = battleID; + uc.operation = UnitChanges::EOperation::UPDATE; + uc.id = unit->unitId(); + uc.data = changedUnit->save(); + uc.healthDelta = healthDelta; + buc.changedStacks.push_back(uc); + + object->apply(buc); + return S.retVoid(); +} + int ServerCbProxy::updateUnit(lua_State * L) { LuaStack S(L); diff --git a/luascript/api/ServerCb.h b/luascript/api/ServerCb.h index 4284c5e89..b36713d38 100644 --- a/luascript/api/ServerCb.h +++ b/luascript/api/ServerCb.h @@ -34,6 +34,7 @@ public: static int createUnit(lua_State * L); static int updateUnit(lua_State * L); + static int healUnit(lua_State * L); static int removeUnit(lua_State * L); }; diff --git a/luascript/api/battle/BattleHexProxy.cpp b/luascript/api/battle/BattleHexProxy.cpp index e4c14e44f..e31a436f2 100644 --- a/luascript/api/battle/BattleHexProxy.cpp +++ b/luascript/api/battle/BattleHexProxy.cpp @@ -1,3 +1,46 @@ +/* + * BattleHexProxy.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" + #include "BattleHexProxy.h" -BattleHexProxy::BattleHexProxy() {} +#include "../../../lib/GameLibrary.h" +#include "../../LuaStack.h" +#include "../../LuaCallWrapper.h" +#include "../Registry.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace scripting::api::battle +{ + +VCMI_REGISTER_CORE_SCRIPT_API(BattleHexProxy, "battle.BattleHex") + +const std::vector BattleHexProxy::REGISTER_CUSTOM = +{ +// {"isValid", LuaMethodWrapper::invoke, false}, + {"isValid", LuaFunctionWrapper::invoke, false}, + {"toInteger", LuaFunctionWrapper::invoke, false}, +}; + +bool BattleHexProxy::isValid(BattleHex & hex) +{ + return hex.isValid(); +} + +int BattleHexProxy::toInteger(BattleHex & hex) +{ + return hex.toInt(); +} + +} + +VCMI_LIB_NAMESPACE_END diff --git a/luascript/api/battle/BattleHexProxy.h b/luascript/api/battle/BattleHexProxy.h index bdef2fddb..688207ba6 100644 --- a/luascript/api/battle/BattleHexProxy.h +++ b/luascript/api/battle/BattleHexProxy.h @@ -1,10 +1,40 @@ -#ifndef BATTLEHEXPROXY_H -#define BATTLEHEXPROXY_H +/* + * BattleHexProxy.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ -class BattleHexProxy +#pragma once + +#include +#include "../../../lib/battle/BattleHex.h" +#include "../../LuaWrapper.h" + +VCMI_LIB_NAMESPACE_BEGIN + + namespace scripting { -public: - BattleHexProxy(); -}; + namespace api + { + namespace battle + { -#endif // BATTLEHEXPROXY_H + class BattleHexProxy : public CopyableWrapper + { + public: + using Wrapper = CopyableWrapper; + static const std::vector REGISTER_CUSTOM; + + static bool isValid(BattleHex & hex); + static int toInteger(BattleHex & hex); + }; + + } + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/luascript/api/battle/UnitProxy.cpp b/luascript/api/battle/UnitProxy.cpp index 09e2c8428..9fff6ea58 100644 --- a/luascript/api/battle/UnitProxy.cpp +++ b/luascript/api/battle/UnitProxy.cpp @@ -19,11 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN -namespace scripting -{ -namespace api -{ -namespace battle +namespace scripting::api::battle { VCMI_REGISTER_CORE_SCRIPT_API(UnitProxy, "battle.Unit") @@ -37,29 +33,23 @@ const std::vector UnitProxy::REGISTER_CUSTOM = {"isAlive", LuaMethodWrapper::invoke, false}, {"isClone", LuaMethodWrapper::invoke, false}, {"isSummoned", LuaMethodWrapper::invoke, false}, -// {"unitId", LuaMethodWrapper::invoke, false}, {"getOwner", LuaMethodWrapper::invoke, false}, {"getSlot", LuaMethodWrapper::invoke, false}, - {"getCreature", &UnitProxy::getCreature, false }, + {"heal", LuaFunctionWrapper::invoke, false}, + {"getCreature", LuaFunctionWrapper::invoke, false }, }; -int UnitProxy::getCreature(lua_State * L) +void UnitProxy::heal(Unit * unit, int64_t & amount, EHealLevel level, EHealPower power) { - LuaStack S(L); - - const Unit * object; - if(!S.tryGet(1, object)) - return S.retVoid(); - - S.clear(); - const Creature * result = object->creatureId().toEntity(LIBRARY); - S.push(result); - return 1; + unit->heal(amount, level, power); } +const Creature * UnitProxy::getCreature(const Unit * unit) +{ + return unit->creatureId().toEntity(LIBRARY); } -} + } VCMI_LIB_NAMESPACE_END diff --git a/luascript/api/battle/UnitProxy.h b/luascript/api/battle/UnitProxy.h index fc2123e4e..1cdebbaea 100644 --- a/luascript/api/battle/UnitProxy.h +++ b/luascript/api/battle/UnitProxy.h @@ -32,7 +32,8 @@ public: using Wrapper = RawPointerWrapper; static const std::vector REGISTER_CUSTOM; - static int getCreature(lua_State * L); + static void heal(Unit *, int64_t & amount, EHealLevel level, EHealPower power); + static const Creature * getCreature(const Unit *); }; } diff --git a/scripts/SpellEffectSummon.lua b/scripts/SpellEffectSummon.lua index 45a31a21a..2fbfd7d1b 100644 --- a/scripts/SpellEffectSummon.lua +++ b/scripts/SpellEffectSummon.lua @@ -111,31 +111,25 @@ apply = function(parameters, mechanics, server, target) local creature = LIBRARY:getCreatureByName(parameters.id) for _, dest in ipairs(target) do - if dest.unitValue then - local summoned = dest.unitValue - local state = summoned:acquire() - local healthValue = summonedCreatureHealth(parameters, mechanics) - - state:heal( - healthValue, - EHealLevel.OVERHEAL, - (parameters.permanent and EHealPower.PERMANENT or EHealPower.ONE_BATTLE) - ) - - server:updateUnit( + if dest.unit ~= nil then + server:healUnit( mechanics:getBattleID(), - summoned:unitId(), - state:save(), - healthValue + dest.unit, + summonedCreatureHealth(parameters, mechanics), + ENUM.HealLevel.overheal, + parameters.permanent and ENUM.HealPower.permanent or ENUM.HealPower.oneBattle ) else + print("SpellEffectSummon. Hex: ", dest.hex) + assert(dest.hex ~= nil) server:createUnit( mechanics:getBattleID(), + mechanics:getBattle():getNextUnitId(), { count = summonedCreatureAmount(parameters, mechanics), type = creature:getJsonKey(), side = mechanics:getCasterSide(), - position = dest.hexValue, + position = dest.hex:toInteger(), summoned = not parameters.permanent } ) @@ -158,7 +152,7 @@ transformTarget = function(parameters, mechanics, aimPoint, spellTarget) if sameSummoned == nil or not parameters.summonSameUnit then local hex = mechanics:getBattle():getAvailableHex(creature, mechanics:getCasterSide()) - if not hex.isValid() then + if not hex:isValid() then return {} -- no free space. FIXME: should be in isApplicable else return { @@ -171,8 +165,8 @@ transformTarget = function(parameters, mechanics, aimPoint, spellTarget) else return { { - hex = sameSummoned[1]:getPosition(), - unit = sameSummoned[1] + hex = sameSummoned:getPosition(), + unit = sameSummoned } } end diff --git a/test/spells/effects/EffectFixture.cpp b/test/spells/effects/EffectFixture.cpp index 5222f1af8..a89e84ee3 100644 --- a/test/spells/effects/EffectFixture.cpp +++ b/test/spells/effects/EffectFixture.cpp @@ -87,6 +87,7 @@ void EffectFixture::setUp() EXPECT_CALL(mechanicsMock, game()).WillRepeatedly(Return(&gameMock)); EXPECT_CALL(mechanicsMock, battle()).WillRepeatedly(Return(battleFake.get())); + EXPECT_CALL(mechanicsMock, getBattleID()).WillRepeatedly(Return(BattleID())); EXPECT_CALL(mechanicsMock, getHeroCaster()).WillRepeatedly(Return(nullptr)); EXPECT_CALL(*battleFake, getScriptContextPool()).WillRepeatedly(ReturnRef(*pool));