1
0
mirror of https://github.com/vcmi/vcmi.git synced 2026-05-22 09:55:17 +02:00

All but 4 summon effect test now pass

- Added logging support for Lua
- Added exporting of enums to Lua
- Added few more missing pieces to support summon spell effect
This commit is contained in:
Ivan Savenko
2026-04-23 14:47:15 +03:00
parent 6ad93dd3eb
commit d556dd1590
25 changed files with 454 additions and 239 deletions
-1
View File
@@ -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;
+1
View File
@@ -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
+2 -1
View File
@@ -10,6 +10,7 @@
#pragma once
#include "BattleSide.h"
#include <vcmi/scripting/ApiTags.h>
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<BattleHex>
{
public:
+2 -2
View File
@@ -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);
}
}
+1
View File
@@ -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)
{
+2
View File
@@ -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
+38 -28
View File
@@ -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<luaL_Reg> STD_LIBS =
{
static constexpr std::array<luaL_Reg, 4> 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();
}
}
+2 -3
View File
@@ -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>)
{
ReturnType ret;
S.getOrThrow(-1, ret);
S.getOrThrow(S.absindex(-1), ret);
S.balance();
return ret;
}
+4 -49
View File
@@ -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<bool>(APPLICABLE_TARGET, parameters, m, targetJson);
bool result = context->callGlobalWithParameters<bool>(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<scripting::LuaContext> context = resolveScript(m);
context->callGlobalWithParameters<void>(APPLY, parameters, m, server, spellTargetToJson(target));
context->callGlobalWithParameters<void>(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<scripting::LuaContext> context = resolveScript(m);
JsonNode aimPointJson = spellTargetToJson(aimPoint);
JsonNode spellTargetJson = spellTargetToJson(spellTarget);
JsonNode response = context->callGlobalWithParameters<JsonNode>(TRANSFORM_TARGET, parameters, m, aimPointJson, spellTargetJson);
Target response = context->callGlobalWithParameters<Target>(TRANSFORM_TARGET, parameters, m, aimPoint, spellTarget);
return spellTargetFromJson(m, response);
return response;
}
void LuaSpellEffect::serializeJsonEffect(JsonSerializeFormat & handler)
@@ -129,45 +123,6 @@ std::shared_ptr<scripting::LuaContext> 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;
}
}
}
-3
View File
@@ -76,9 +76,6 @@ private:
JsonNode parameters;
std::shared_ptr<LuaContext> resolveScript(const Mechanics * m) const;
JsonNode spellTargetToJson(const Target & spellTarget) const;
Target spellTargetFromJson(const Mechanics * m, const JsonNode & config) const;
};
}
+9
View File
@@ -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)
{
+112 -67
View File
@@ -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<typename T>
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<typename T>
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<typename T, typename std::enable_if_t<std::is_base_of_v<scripting::TagCopyable, T>, int> = 0>
void push(const T & value)
{
using DataType = T;
using BaseType = DataType::ScriptingApiName;
static_assert(std::is_same_v<std::remove_const_t<DataType>, BaseType>, "Can not push derived class as copyable!");
static auto KEY = api::TypeRegistry::get()->getKey<BaseType>();
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<typename T>
void push(const std::vector<T> & 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<typename T>
void push(const std::map<std::string, T> & 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<typename T, typename std::enable_if_t<std::is_base_of_v<scripting::TagSerializable, T>, 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<T&>(value);
const auto & luaSerializer = [this, tableIndex]<typename Field>(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<typename T, typename std::enable_if_t<std::is_base_of_v<scripting::TagSerializable, T>, int> = 0>
STRONG_INLINE bool tryGet(int position, T & value)
{
LuaDeserializer serializer(*this, position);
value->serializeLua(serializer);
const auto & deserializer = [this, position]<typename Data>(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<typename T, typename std::enable_if_t<std::is_base_of_v<scripting::TagCopyable, T>, int> = 0>
STRONG_INLINE bool tryGet(int position, T & value)
{
using DataType = T;
using BaseType = DataType::ScriptingApiName;
static_assert(std::is_same_v<DataType, BaseType>, "Can not push derived class as copyable!");
return tryGetUData<BaseType>(position, value);
}
template<typename... Args>
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<typename BaseType>
bool tryGetUData(int position, BaseType & value)
{
if (lua_isnil(L, position))
if constexpr (std::is_assignable_v<std::remove_cvref_t<BaseType>&, std::nullptr_t>)
{
value = nullptr;
return true;
if (lua_isnil(L, position))
{
value = nullptr;
return true;
}
}
static auto KEY = api::TypeRegistry::get()->getKey<BaseType>();
@@ -466,41 +546,6 @@ private:
int initialTop;
};
template<typename T>
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<typename T>
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
+53 -1
View File
@@ -81,7 +81,7 @@ namespace detail
if(objPtr)
{
auto obj = static_cast<UDataType *>(objPtr);
obj->reset();
obj->~UDataType();
}
lua_settop(L, 0);
return 0;
@@ -117,6 +117,7 @@ public:
static_assert(std::is_base_of_v<TagRawPointer, ObjectType>, "Class must inherit from ApiRawPointer to be used with this class!");
static_assert(!std::is_base_of_v<TagSharedPointer, ObjectType>, "Class must not inherit from ApiSharedPointer to be used with this class!");
static_assert(!std::is_base_of_v<TagCopyable, ObjectType>, "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<TagSharedPointer, ObjectType>, "Class must inherit from ApiSharedPointer to be used with this class!");
static_assert(!std::is_base_of_v<TagRawPointer, ObjectType>, "Class must not inherit from ApiRawPointer to be used with this class!");
static_assert(!std::is_base_of_v<TagCopyable, ObjectType>, "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 T, class Proxy = T>
class CopyableWrapper : public RegistarBase
{
public:
using ObjectType = typename std::remove_cv_t<T>;
using UDataType = T;
using CustomRegType = detail::CustomRegType;
static_assert(std::is_base_of_v<TagCopyable, ObjectType>, "Class must inherit from ApiCopyable to be used with this class!");
static_assert(!std::is_base_of_v<TagRawPointer, ObjectType>, "Class must not inherit from ApiRawPointer to be used with this class!");
static_assert(!std::is_base_of_v<TagSharedPointer, ObjectType>, "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<UDataType>();
LuaStack S(L);
if(luaL_newmetatable(L, KEY) != 0)
{
adjustMetatable(L);
S.push("__gc");
lua_pushcfunction(L, &(detail::Dispatcher<Proxy, UDataType>::destructor));
lua_rawset(L, -3);
}
S.balance();
detail::Dispatcher<Proxy, UDataType>::pushStaticTable(L);
adjustStaticTable(L);
}
protected:
void adjustMetatable(lua_State * L) const override
{
detail::Dispatcher<Proxy, UDataType>::setIndexTable(L);
}
};
}
VCMI_LIB_NAMESPACE_END
+9 -13
View File
@@ -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
+4 -7
View File
@@ -11,21 +11,19 @@
#pragma once
#include <vcmi/scripting/Service.h>
#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<const CBattleInfoCallback, BattleCbProxy>
class BattleCbProxy : public RawPointerWrapper<const IBattleInfoCallback, BattleCbProxy>
{
public:
using Wrapper = RawPointerWrapper<const CBattleInfoCallback, BattleCbProxy>;
using Wrapper = RawPointerWrapper<const IBattleInfoCallback, BattleCbProxy>;
static const std::vector<typename Wrapper::CustomRegType> REGISTER_CUSTOM;
@@ -36,7 +34,6 @@ public:
static int getAnyUnitIf(lua_State * L);
};
}
}
VCMI_LIB_NAMESPACE_END
+35
View File
@@ -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<EHealLevel> Enums::exportHealLevel() const
{
return {
{ "heal", EHealLevel::HEAL },
{ "resurrect", EHealLevel::RESURRECT },
{ "overheal", EHealLevel::OVERHEAL },
};
}
Enums::EnumMap<EHealPower> Enums::exportHealPower() const
{
return {
{ "oneBattle", EHealPower::ONE_BATTLE },
{ "permanent", EHealPower::PERMANENT },
};
}
}
+39
View File
@@ -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 <vcmi/scripting/ApiTags.h>
#include "../../lib/constants/Enumerations.h"
VCMI_LIB_NAMESPACE_BEGIN
namespace scripting::api
{
class Enums : public scripting::ApiSerializable<Enums>
{
template<typename EnumType>
using EnumMap = std::map<std::string, EnumType>;
EnumMap<EHealLevel> exportHealLevel() const;
EnumMap<EHealPower> exportHealPower() const;
public:
template<typename Serializer>
void serializeScript(Serializer & s)
{
s("HealLevel", exportHealLevel());
s("HealPower", exportHealPower());
}
};
}
+34 -17
View File
@@ -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::CustomRegType> ServerCbProxy::REGISTER_CUSTOM =
{
// {
// "addToBattleLog",
// &ServerCbProxy::apply<BattleLogMessage>,
// false
// },
// {
// "moveUnit",
// &ServerCbProxy::apply<BattleStackMoved>,
// 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);
+1
View File
@@ -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);
};
+44 -1
View File
@@ -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::CustomRegType> BattleHexProxy::REGISTER_CUSTOM =
{
// {"isValid", LuaMethodWrapper<BattleHex, decltype(&BattleHex::isValid), &BattleHex::isValid>::invoke, false},
{"isValid", LuaFunctionWrapper<BattleHexProxy::isValid>::invoke, false},
{"toInteger", LuaFunctionWrapper<BattleHexProxy::toInteger>::invoke, false},
};
bool BattleHexProxy::isValid(BattleHex & hex)
{
return hex.isValid();
}
int BattleHexProxy::toInteger(BattleHex & hex)
{
return hex.toInt();
}
}
VCMI_LIB_NAMESPACE_END
+37 -7
View File
@@ -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 <vcmi/scripting/Service.h>
#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<const BattleHex, BattleHexProxy>
{
public:
using Wrapper = CopyableWrapper<const BattleHex, BattleHexProxy>;
static const std::vector<typename Wrapper::CustomRegType> REGISTER_CUSTOM;
static bool isValid(BattleHex & hex);
static int toInteger(BattleHex & hex);
};
}
}
}
VCMI_LIB_NAMESPACE_END
+9 -19
View File
@@ -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::CustomRegType> UnitProxy::REGISTER_CUSTOM =
{"isAlive", LuaMethodWrapper<Unit, decltype(&Unit::alive), &Unit::alive>::invoke, false},
{"isClone", LuaMethodWrapper<Unit, decltype(&Unit::isClone), &Unit::isClone>::invoke, false},
{"isSummoned", LuaMethodWrapper<Unit, decltype(&Unit::isSummoned), &Unit::isSummoned>::invoke, false},
// {"unitId", LuaMethodWrapper<Unit, decltype(&IUnitInfo::unitId), &IUnitInfo::unitId>::invoke, false},
{"getOwner", LuaMethodWrapper<Unit, decltype(&IUnitInfo::unitOwner), &IUnitInfo::unitOwner>::invoke, false},
{"getSlot", LuaMethodWrapper<Unit, decltype(&IUnitInfo::unitSlot), &IUnitInfo::unitSlot>::invoke, false},
{"getCreature", &UnitProxy::getCreature, false },
{"heal", LuaFunctionWrapper<UnitProxy::heal>::invoke, false},
{"getCreature", LuaFunctionWrapper<UnitProxy::getCreature>::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
+2 -1
View File
@@ -32,7 +32,8 @@ public:
using Wrapper = RawPointerWrapper<const Unit, UnitProxy>;
static const std::vector<typename Wrapper::CustomRegType> 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 *);
};
}
+13 -19
View File
@@ -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
+1
View File
@@ -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));