diff --git a/CMakeLists.txt b/CMakeLists.txt index 0629cf1ad..0c126bae0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -518,21 +518,19 @@ if(NOT ENABLE_MINIMAL_LIB) find_package(TBB REQUIRED) endif() -if(ENABLE_LUA) - find_package(luajit) +find_package(luajit) - if(TARGET luajit::luajit) - message(STATUS "Using LuaJIT provided by system") - else() - message(STATUS "Cannot find LuaJIT! Fallback to using usual Lua.") - find_package(Lua REQUIRED) - if(Lua_FOUND) - add_library(luajit::luajit UNKNOWN IMPORTED) - set_target_properties(luajit::luajit PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}") - set_target_properties(luajit::luajit PROPERTIES - IMPORTED_LOCATION "${LUA_LIBRARIES}") - endif() +if(TARGET luajit::luajit) + message(STATUS "Using LuaJIT provided by system") +else() + message(STATUS "Cannot find LuaJIT! Fallback to using usual Lua.") + find_package(Lua REQUIRED) + if(Lua_FOUND) + add_library(luajit::luajit UNKNOWN IMPORTED) + set_target_properties(luajit::luajit PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}") + set_target_properties(luajit::luajit PROPERTIES + IMPORTED_LOCATION "${LUA_LIBRARIES}") endif() endif() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 85e1c95a8..a0eaeffc7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -751,7 +751,7 @@ endif() set_target_properties(vcmi PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1") target_link_libraries(vcmi PUBLIC - minizip::minizip ZLIB::ZLIB TBB::tbb + minizip::minizip ZLIB::ZLIB TBB::tbb luajit::luajit ${SYSTEM_LIBS} Boost::boost Boost::thread Boost::filesystem Boost::program_options Boost::locale Boost::date_time ) diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index 6aa4e5483..fbd9b579f 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -20,6 +20,110 @@ #include "../IGameSettings.h" #include "../VCMI_Lib.h" +#include + +class LuaExpressionEvaluator : boost::noncopyable +{ + lua_State * luaState = nullptr; + const std::string expression; + int compiledReference = -1; + + void compile(); + void registerLibrary(); +public: + LuaExpressionEvaluator(const std::string & expression); + ~LuaExpressionEvaluator(); + + void setVariable(const std::string & name, double value); + double evaluate(); +}; + +void LuaExpressionEvaluator::registerLibrary() +{ + const auto & luaMax = [](lua_State * state) + { + lua_Number a = luaL_checknumber(state, 1); + lua_Number b = luaL_checknumber(state, 2); + lua_pushnumber(state, std::max(a,b)); + return 1; + }; + + const auto & luaMin = [](lua_State * state) + { + lua_Number a = luaL_checknumber(state, 1); + lua_Number b = luaL_checknumber(state, 2); + lua_pushnumber(state, std::min(a,b)); + return 1; + }; + + const auto & luaClamp = [](lua_State * state) + { + lua_Number a = luaL_checknumber(state, 1); + lua_Number b = luaL_checknumber(state, 2); + lua_Number c = luaL_checknumber(state, 3); + lua_pushnumber(state, std::clamp(a,b,c)); + return 1; + }; + + const luaL_Reg registry[] = { + { "max", luaMax }, + { "min", luaMin }, + { "clamp", luaClamp }, + { nullptr, nullptr } + }; + + lua_pushvalue(luaState, LUA_GLOBALSINDEX); + luaL_setfuncs(luaState,registry,0); +} + +LuaExpressionEvaluator::LuaExpressionEvaluator(const std::string & expression) + : expression(expression) +{ + luaState = luaL_newstate(); + registerLibrary(); + compile(); +} + +LuaExpressionEvaluator::~LuaExpressionEvaluator() +{ + lua_close(luaState); +} + +void LuaExpressionEvaluator::setVariable(const std::string & name, double value) +{ + lua_pushnumber(luaState, value); + lua_setglobal(luaState, name.c_str()); +} + +void LuaExpressionEvaluator::compile() +{ + int result = luaL_loadstring(luaState, expression.c_str()); + + if (result) + { + logGlobal->error("Lua compilation failure: %s", lua_tostring(luaState,-1)); + lua_pop(luaState,1); + } + + assert(result == LUA_OK); + compiledReference = luaL_ref(luaState, LUA_REGISTRYINDEX); +} + +double LuaExpressionEvaluator::evaluate() +{ + lua_rawgeti(luaState, LUA_REGISTRYINDEX, compiledReference); + int errorCode = lua_pcall(luaState,0,1,0); + if (errorCode) + { + logGlobal->error("Lua evaluation failure: %s", lua_tostring(luaState,-1)); + lua_pop(luaState,1); + return 0; + } + + double result = lua_tonumber(luaState,-1); + lua_pop(luaState,1); + return result; +} VCMI_LIB_NAMESPACE_BEGIN @@ -208,18 +312,12 @@ int DamageCalculator::getTargetDefenseIgnored() const double DamageCalculator::getAttackSkillFactor() const { - int attackAdvantage = getActorAttackEffective() - getTargetDefenseEffective(); + static LuaExpressionEvaluator evaluator("return clamp((attack - defense) * 0.05, 0, 4)"); - if(attackAdvantage > 0) - { - // FIXME: use cb to acquire these settings - const double attackMultiplier = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR); - const double attackMultiplierCap = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP); - const double attackFactor = std::min(attackMultiplier * attackAdvantage, attackMultiplierCap); - - return attackFactor; - } - return 0.f; + evaluator.setVariable("defense", getTargetDefenseEffective()); + evaluator.setVariable("attack", getActorAttackEffective()); + double result = evaluator.evaluate(); + return result; } double DamageCalculator::getAttackBlessFactor() const @@ -308,19 +406,12 @@ double DamageCalculator::getAttackRevengeFactor() const double DamageCalculator::getDefenseSkillFactor() const { - int defenseAdvantage = getTargetDefenseEffective() - getActorAttackEffective(); + static LuaExpressionEvaluator evaluator("return clamp((defense - attack) * 0.025, 0, 0.7)"); - //bonus from attack/defense skills - if(defenseAdvantage > 0) //decreasing dmg - { - // FIXME: use cb to acquire these settings - const double defenseMultiplier = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR); - const double defenseMultiplierCap = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP); - - const double dec = std::min(defenseMultiplier * defenseAdvantage, defenseMultiplierCap); - return dec; - } - return 0.0; + evaluator.setVariable("defense", getTargetDefenseEffective()); + evaluator.setVariable("attack", getActorAttackEffective()); + double result = evaluator.evaluate(); + return result; } double DamageCalculator::getDefenseArmorerFactor() const