1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-04 21:40:03 +00:00
OpenMW/apps/openmw/mwlua/stats.cpp

399 lines
16 KiB
C++
Raw Normal View History

2022-03-25 20:03:13 +00:00
#include "stats.hpp"
#include <algorithm>
2022-03-25 20:03:13 +00:00
#include <memory>
#include <optional>
#include <string_view>
#include <variant>
#include <components/esm3/loadclas.hpp>
2022-09-22 21:26:05 +03:00
#include <components/lua/luastate.hpp>
2022-03-25 20:03:13 +00:00
2022-09-22 21:26:05 +03:00
#include "context.hpp"
2022-03-25 20:03:13 +00:00
#include "localscripts.hpp"
#include "luamanagerimp.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "objectvariant.hpp"
2022-03-25 20:03:13 +00:00
namespace
{
using SelfObject = MWLua::SelfObject;
using ObjectVariant = MWLua::ObjectVariant;
2023-06-06 18:34:25 +02:00
using Index = const SelfObject::CachedStat::Index&;
2023-06-06 18:34:25 +02:00
template <class T>
auto addIndexedAccessor(Index index)
2022-03-25 20:03:13 +00:00
{
return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); };
2022-03-25 20:03:13 +00:00
}
2022-09-22 21:26:05 +03:00
template <class T, class G>
void addProp(const MWLua::Context& context, sol::usertype<T>& type, std::string_view prop, G getter)
2022-03-25 20:03:13 +00:00
{
2022-09-22 21:26:05 +03:00
type[prop] = sol::property([=](const T& stat) { return stat.get(context, prop, getter); },
[=](const T& stat, const sol::object& value) { stat.cache(context, prop, value); });
2022-03-25 20:03:13 +00:00
}
2022-09-22 21:26:05 +03:00
template <class G>
sol::object getValue(const MWLua::Context& context, const ObjectVariant& obj, SelfObject::CachedStat::Setter setter,
2023-06-06 18:34:25 +02:00
Index index, std::string_view prop, G getter)
2022-03-25 20:03:13 +00:00
{
if (obj.isSelfObject())
{
SelfObject* self = obj.asSelfObject();
auto it = self->mStatsCache.find({ setter, index, prop });
if (it != self->mStatsCache.end())
return it->second;
}
return sol::make_object(context.mLua->sol(), getter(obj.ptr()));
2022-03-25 20:03:13 +00:00
}
}
namespace MWLua
{
2023-04-22 14:24:48 +02:00
static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj)
2022-04-11 01:04:55 +02:00
{
2023-04-22 14:24:48 +02:00
if (!obj.mStatsCache.empty())
return; // was already added before
manager->addAction(
[obj = Object(obj)] {
2022-04-11 01:04:55 +02:00
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
if (scripts)
scripts->applyStatsCache();
2023-04-22 14:24:48 +02:00
},
"StatUpdateAction");
2022-04-11 01:04:55 +02:00
}
2022-03-25 20:03:13 +00:00
class LevelStat
{
ObjectVariant mObject;
2022-03-25 20:03:13 +00:00
LevelStat(ObjectVariant object)
2022-09-22 21:26:05 +03:00
: mObject(std::move(object))
{
}
2022-03-25 20:03:13 +00:00
public:
sol::object getCurrent(const Context& context) const
{
return getValue(context, mObject, &LevelStat::setValue, 0, "current",
[](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); });
2022-03-25 20:03:13 +00:00
}
void setCurrent(const Context& context, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
2023-04-22 14:24:48 +02:00
addStatUpdateAction(context.mLuaManager, *obj);
2022-09-22 21:26:05 +03:00
obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, 0, "current" }] = value;
2022-03-25 20:03:13 +00:00
}
sol::object getProgress(const Context& context) const
{
const auto& ptr = mObject.ptr();
2022-09-22 21:26:05 +03:00
if (!ptr.getClass().isNpc())
2022-03-25 20:03:13 +00:00
return sol::nil;
return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress());
}
2023-06-06 18:34:25 +02:00
static std::optional<LevelStat> create(ObjectVariant object, Index)
2022-03-25 20:03:13 +00:00
{
if (!object.ptr().getClass().isActor())
2022-03-25 20:03:13 +00:00
return {};
2022-09-22 21:26:05 +03:00
return LevelStat{ std::move(object) };
2022-03-25 20:03:13 +00:00
}
2023-06-06 18:34:25 +02:00
static void setValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
2022-03-25 20:03:13 +00:00
{
auto& stats = ptr.getClass().getCreatureStats(ptr);
2022-09-22 21:26:05 +03:00
if (prop == "current")
stats.setLevel(LuaUtil::cast<int>(value));
2022-03-25 20:03:13 +00:00
}
};
class DynamicStat
{
ObjectVariant mObject;
2022-03-25 20:03:13 +00:00
int mIndex;
DynamicStat(ObjectVariant object, int index)
2022-09-22 21:26:05 +03:00
: mObject(std::move(object))
, mIndex(index)
{
}
2022-03-25 20:03:13 +00:00
public:
2022-09-22 21:26:05 +03:00
template <class G>
2022-03-25 20:03:13 +00:00
sol::object get(const Context& context, std::string_view prop, G getter) const
{
2022-09-22 21:26:05 +03:00
return getValue(
context, mObject, &DynamicStat::setValue, mIndex, prop, [this, getter](const MWWorld::Ptr& ptr) {
2022-09-22 21:26:05 +03:00
return (ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).*getter)();
});
2022-03-25 20:03:13 +00:00
}
2023-06-06 18:34:25 +02:00
static std::optional<DynamicStat> create(ObjectVariant object, Index i)
2022-03-25 20:03:13 +00:00
{
if (!object.ptr().getClass().isActor())
2022-03-25 20:03:13 +00:00
return {};
2023-06-06 18:34:25 +02:00
int index = std::get<int>(i);
2022-09-22 21:26:05 +03:00
return DynamicStat{ std::move(object), index };
2022-03-25 20:03:13 +00:00
}
void cache(const Context& context, std::string_view prop, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
2023-04-22 14:24:48 +02:00
addStatUpdateAction(context.mLuaManager, *obj);
2022-09-22 21:26:05 +03:00
obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value;
2022-03-25 20:03:13 +00:00
}
2023-06-06 18:34:25 +02:00
static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
2022-03-25 20:03:13 +00:00
{
2023-06-06 18:34:25 +02:00
int index = std::get<int>(i);
2022-03-25 20:03:13 +00:00
auto& stats = ptr.getClass().getCreatureStats(ptr);
auto stat = stats.getDynamic(index);
float floatValue = LuaUtil::cast<float>(value);
2022-09-22 21:26:05 +03:00
if (prop == "base")
2022-03-25 20:03:13 +00:00
stat.setBase(floatValue);
2022-09-22 21:26:05 +03:00
else if (prop == "current")
2022-03-25 20:03:13 +00:00
stat.setCurrent(floatValue, true, true);
2022-09-22 21:26:05 +03:00
else if (prop == "modifier")
2022-03-25 20:03:13 +00:00
stat.setModifier(floatValue);
stats.setDynamic(index, stat);
}
};
class AttributeStat
{
ObjectVariant mObject;
2022-03-25 20:03:13 +00:00
int mIndex;
AttributeStat(ObjectVariant object, int index)
2022-09-22 21:26:05 +03:00
: mObject(std::move(object))
, mIndex(index)
{
}
2022-03-25 20:03:13 +00:00
public:
2022-09-22 21:26:05 +03:00
template <class G>
2022-03-25 20:03:13 +00:00
sol::object get(const Context& context, std::string_view prop, G getter) const
{
2023-06-19 20:41:54 +02:00
auto id = static_cast<ESM::Attribute::AttributeID>(mIndex);
2022-09-22 21:26:05 +03:00
return getValue(
2023-06-19 20:41:54 +02:00
context, mObject, &AttributeStat::setValue, mIndex, prop, [id, getter](const MWWorld::Ptr& ptr) {
return (ptr.getClass().getCreatureStats(ptr).getAttribute(id).*getter)();
2022-09-22 21:26:05 +03:00
});
2022-03-25 20:03:13 +00:00
}
float getModified(const Context& context) const
{
auto base = LuaUtil::cast<float>(get(context, "base", &MWMechanics::AttributeValue::getBase));
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::AttributeValue::getDamage));
auto modifier = LuaUtil::cast<float>(get(context, "modifier", &MWMechanics::AttributeValue::getModifier));
return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified
}
2023-06-06 18:34:25 +02:00
static std::optional<AttributeStat> create(ObjectVariant object, Index i)
2022-03-25 20:03:13 +00:00
{
if (!object.ptr().getClass().isActor())
2022-03-25 20:03:13 +00:00
return {};
2023-06-06 18:34:25 +02:00
int index = std::get<int>(i);
2022-09-22 21:26:05 +03:00
return AttributeStat{ std::move(object), index };
2022-03-25 20:03:13 +00:00
}
void cache(const Context& context, std::string_view prop, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
2023-04-22 14:24:48 +02:00
addStatUpdateAction(context.mLuaManager, *obj);
2022-09-22 21:26:05 +03:00
obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mIndex, prop }] = value;
2022-03-25 20:03:13 +00:00
}
2023-06-06 18:34:25 +02:00
static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
2022-03-25 20:03:13 +00:00
{
2023-06-19 20:41:54 +02:00
auto id = static_cast<ESM::Attribute::AttributeID>(std::get<int>(i));
2022-03-25 20:03:13 +00:00
auto& stats = ptr.getClass().getCreatureStats(ptr);
2023-06-19 20:41:54 +02:00
auto stat = stats.getAttribute(id);
float floatValue = LuaUtil::cast<float>(value);
2022-09-22 21:26:05 +03:00
if (prop == "base")
2022-03-25 20:03:13 +00:00
stat.setBase(floatValue);
2022-09-22 21:26:05 +03:00
else if (prop == "damage")
2022-03-25 20:03:13 +00:00
{
stat.restore(stat.getDamage());
stat.damage(floatValue);
}
2022-09-22 21:26:05 +03:00
else if (prop == "modifier")
2022-03-25 20:03:13 +00:00
stat.setModifier(floatValue);
2023-06-19 20:41:54 +02:00
stats.setAttribute(id, stat);
2022-03-25 20:03:13 +00:00
}
};
class SkillStat
{
ObjectVariant mObject;
2023-06-05 21:21:30 +02:00
ESM::RefId mId;
2022-03-25 20:03:13 +00:00
2023-06-05 21:21:30 +02:00
SkillStat(ObjectVariant object, ESM::RefId id)
2022-09-22 21:26:05 +03:00
: mObject(std::move(object))
2023-06-05 21:21:30 +02:00
, mId(id)
2022-09-22 21:26:05 +03:00
{
}
2022-03-25 20:03:13 +00:00
2023-06-05 21:21:30 +02:00
static float getProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat)
2022-03-25 20:03:13 +00:00
{
float progress = stat.getProgress();
2022-09-22 21:26:05 +03:00
if (progress != 0.f)
2023-06-05 21:21:30 +02:00
progress /= getMaxProgress(ptr, id, stat);
2022-03-25 20:03:13 +00:00
return progress;
}
2023-06-05 21:21:30 +02:00
static float getMaxProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat)
2022-09-22 21:26:05 +03:00
{
2023-04-20 21:07:53 +02:00
const auto& store = *MWBase::Environment::get().getESMStore();
2022-03-25 20:03:13 +00:00
const auto cl = store.get<ESM::Class>().find(ptr.get<ESM::NPC>()->mBase->mClass);
2023-06-05 21:21:30 +02:00
return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(id, *cl);
2022-03-25 20:03:13 +00:00
}
2022-09-22 21:26:05 +03:00
2022-03-25 20:03:13 +00:00
public:
2022-09-22 21:26:05 +03:00
template <class G>
2022-03-25 20:03:13 +00:00
sol::object get(const Context& context, std::string_view prop, G getter) const
{
2023-06-06 18:34:25 +02:00
return getValue(context, mObject, &SkillStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) {
return (ptr.getClass().getNpcStats(ptr).getSkill(mId).*getter)();
});
2022-03-25 20:03:13 +00:00
}
float getModified(const Context& context) const
{
auto base = LuaUtil::cast<float>(get(context, "base", &MWMechanics::SkillValue::getBase));
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::SkillValue::getDamage));
auto modifier = LuaUtil::cast<float>(get(context, "modifier", &MWMechanics::SkillValue::getModifier));
return std::max(0.f, base - damage + modifier); // Should match SkillValue::getModified
}
2022-03-25 20:03:13 +00:00
sol::object getProgress(const Context& context) const
{
2023-06-06 18:34:25 +02:00
return getValue(context, mObject, &SkillStat::setValue, mId, "progress", [this](const MWWorld::Ptr& ptr) {
return getProgress(ptr, mId, ptr.getClass().getNpcStats(ptr).getSkill(mId));
});
2022-03-25 20:03:13 +00:00
}
2023-06-06 18:34:25 +02:00
static std::optional<SkillStat> create(ObjectVariant object, Index index)
2022-03-25 20:03:13 +00:00
{
if (!object.ptr().getClass().isNpc())
2022-03-25 20:03:13 +00:00
return {};
2023-06-06 18:34:25 +02:00
ESM::RefId id = std::get<ESM::RefId>(index);
2023-06-05 21:21:30 +02:00
return SkillStat{ std::move(object), id };
2022-03-25 20:03:13 +00:00
}
void cache(const Context& context, std::string_view prop, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
2023-04-22 14:24:48 +02:00
addStatUpdateAction(context.mLuaManager, *obj);
2023-06-06 18:34:25 +02:00
obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = value;
2022-03-25 20:03:13 +00:00
}
2023-06-06 18:34:25 +02:00
static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
2022-03-25 20:03:13 +00:00
{
2023-06-06 18:34:25 +02:00
ESM::RefId id = std::get<ESM::RefId>(index);
2022-03-25 20:03:13 +00:00
auto& stats = ptr.getClass().getNpcStats(ptr);
2023-06-05 21:21:30 +02:00
auto stat = stats.getSkill(id);
float floatValue = LuaUtil::cast<float>(value);
2022-09-22 21:26:05 +03:00
if (prop == "base")
2022-03-25 20:03:13 +00:00
stat.setBase(floatValue);
2022-09-22 21:26:05 +03:00
else if (prop == "damage")
2022-03-25 20:03:13 +00:00
{
stat.restore(stat.getDamage());
stat.damage(floatValue);
}
2022-09-22 21:26:05 +03:00
else if (prop == "modifier")
2022-03-25 20:03:13 +00:00
stat.setModifier(floatValue);
2022-09-22 21:26:05 +03:00
else if (prop == "progress")
2023-06-05 21:21:30 +02:00
stat.setProgress(floatValue * getMaxProgress(ptr, id, stat));
stats.setSkill(id, stat);
2022-03-25 20:03:13 +00:00
}
};
}
namespace sol
{
template <>
2022-09-22 21:26:05 +03:00
struct is_automagical<MWLua::LevelStat> : std::false_type
{
};
2022-03-25 20:03:13 +00:00
template <>
2022-09-22 21:26:05 +03:00
struct is_automagical<MWLua::DynamicStat> : std::false_type
{
};
2022-03-25 20:03:13 +00:00
template <>
2022-09-22 21:26:05 +03:00
struct is_automagical<MWLua::AttributeStat> : std::false_type
{
};
2022-03-25 20:03:13 +00:00
template <>
2022-09-22 21:26:05 +03:00
struct is_automagical<MWLua::SkillStat> : std::false_type
{
};
2022-03-25 20:03:13 +00:00
}
namespace MWLua
{
void addActorStatsBindings(sol::table& actor, const Context& context)
{
sol::table stats(context.mLua->sol(), sol::create);
actor["stats"] = LuaUtil::makeReadOnly(stats);
auto levelStatT = context.mLua->sol().new_usertype<LevelStat>("LevelStat");
2022-09-22 21:26:05 +03:00
levelStatT["current"] = sol::property([context](const LevelStat& stat) { return stat.getCurrent(context); },
2022-03-25 20:03:13 +00:00
[context](const LevelStat& stat, const sol::object& value) { stat.setCurrent(context, value); });
levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); });
stats["level"] = addIndexedAccessor<LevelStat>(0);
auto dynamicStatT = context.mLua->sol().new_usertype<DynamicStat>("DynamicStat");
addProp(context, dynamicStatT, "base", &MWMechanics::DynamicStat<float>::getBase);
addProp(context, dynamicStatT, "current", &MWMechanics::DynamicStat<float>::getCurrent);
addProp(context, dynamicStatT, "modifier", &MWMechanics::DynamicStat<float>::getModifier);
sol::table dynamic(context.mLua->sol(), sol::create);
stats["dynamic"] = LuaUtil::makeReadOnly(dynamic);
dynamic["health"] = addIndexedAccessor<DynamicStat>(0);
dynamic["magicka"] = addIndexedAccessor<DynamicStat>(1);
dynamic["fatigue"] = addIndexedAccessor<DynamicStat>(2);
auto attributeStatT = context.mLua->sol().new_usertype<AttributeStat>("AttributeStat");
addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase);
addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage);
2022-09-22 21:26:05 +03:00
attributeStatT["modified"]
= sol::property([=](const AttributeStat& stat) { return stat.getModified(context); });
2022-03-25 20:03:13 +00:00
addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier);
sol::table attributes(context.mLua->sol(), sol::create);
stats["attributes"] = LuaUtil::makeReadOnly(attributes);
2022-09-22 21:26:05 +03:00
for (int id = ESM::Attribute::Strength; id < ESM::Attribute::Length; ++id)
attributes[Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[id])]
= addIndexedAccessor<AttributeStat>(id);
2022-03-25 20:03:13 +00:00
}
void addNpcStatsBindings(sol::table& npc, const Context& context)
{
sol::table npcStats(context.mLua->sol(), sol::create);
sol::table baseMeta(context.mLua->sol(), sol::create);
baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(npc["baseType"]["stats"]);
npcStats[sol::metatable_key] = baseMeta;
npc["stats"] = LuaUtil::makeReadOnly(npcStats);
auto skillStatT = context.mLua->sol().new_usertype<SkillStat>("SkillStat");
addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase);
addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage);
skillStatT["modified"] = sol::property([=](const SkillStat& stat) { return stat.getModified(context); });
2022-03-25 20:03:13 +00:00
addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier);
2022-09-22 21:26:05 +03:00
skillStatT["progress"] = sol::property([context](const SkillStat& stat) { return stat.getProgress(context); },
2022-03-25 20:03:13 +00:00
[context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); });
sol::table skills(context.mLua->sol(), sol::create);
npcStats["skills"] = LuaUtil::makeReadOnly(skills);
2023-06-05 21:21:30 +02:00
for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[skill.mIndex])]
= addIndexedAccessor<SkillStat>(skill.mId);
2022-03-25 20:03:13 +00:00
}
}