#include "stats.hpp" #include #include #include #include #include #include #include "localscripts.hpp" #include "luamanagerimp.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" namespace { template auto addIndexedAccessor(int index) { return sol::overload( [index](MWLua::LocalScripts::SelfObject& o) { return T::create(&o, index); }, [index](const MWLua::LObject& o) { return T::create(o, index); }, [index](const MWLua::GObject& o) { return T::create(o, index); } ); } template void addProp(const MWLua::Context& context, sol::usertype& type, std::string_view prop, G getter, bool readOnly = false) { if(readOnly) type[prop] = sol::property([=](const T& stat) { return stat.get(context, prop, getter); }); else 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); }); } using SelfObject = MWLua::LocalScripts::SelfObject; using StatObject = std::variant; const MWLua::Object* getObject(const StatObject& obj) { return std::visit([] (auto&& variant) -> const MWLua::Object* { using T = std::decay_t; if constexpr(std::is_same_v) return variant; else if constexpr(std::is_same_v) return &variant; else if constexpr(std::is_same_v) return &variant; }, obj); } template sol::object getValue(const MWLua::Context& context, const StatObject& obj, SelfObject::CachedStat::Setter setter, int index, std::string_view prop, G getter) { return std::visit([&] (auto&& variant) { using T = std::decay_t; if constexpr(std::is_same_v) { auto it = variant->mStatsCache.find({ setter, index, prop }); if(it != variant->mStatsCache.end()) return it->second; return sol::make_object(context.mLua->sol(), getter(variant)); } else if constexpr(std::is_same_v) return sol::make_object(context.mLua->sol(), getter(&variant)); else if constexpr(std::is_same_v) return sol::make_object(context.mLua->sol(), getter(&variant)); }, obj); } } namespace MWLua { namespace { class StatUpdateAction final : public LuaManager::Action { ObjectId mId; public: StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} void apply(WorldView& worldView) const override { LObject obj(mId, worldView.getObjectRegistry()); LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); if (scripts) scripts->applyStatsCache(); } std::string toString() const override { return "StatUpdateAction"; } }; } class LevelStat { StatObject mObject; LevelStat(StatObject object) : mObject(std::move(object)) {} public: sol::object getCurrent(const Context& context) const { return getValue(context, mObject, &LevelStat::setValue, 0, "current", [](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return ptr.getClass().getCreatureStats(ptr).getLevel(); }); } void setCurrent(const Context& context, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&LevelStat::setValue, 0, "current"}] = value; } sol::object getProgress(const Context& context) const { const auto& ptr = getObject(mObject)->ptr(); if(!ptr.getClass().isNpc()) return sol::nil; return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isActor()) return {}; return LevelStat{std::move(object)}; } static void setValue(int, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getCreatureStats(ptr); if(prop == "current") stats.setLevel(value.as()); } }; class DynamicStat { StatObject mObject; int mIndex; DynamicStat(StatObject object, int index) : mObject(std::move(object)), mIndex(index) {} public: template sol::object get(const Context& context, std::string_view prop, G getter) const { return getValue(context, mObject, &DynamicStat::setValue, mIndex, prop, [=](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return (ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).*getter)(); }); } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isActor()) return {}; return DynamicStat{std::move(object), index}; } void cache(const Context& context, std::string_view prop, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&DynamicStat::setValue, mIndex, prop}] = value; } static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getCreatureStats(ptr); auto stat = stats.getDynamic(index); float floatValue = value.as(); if(prop == "base") stat.setBase(floatValue); else if(prop == "current") stat.setCurrent(floatValue, true, true); else if(prop == "modifier") stat.setModifier(floatValue); stats.setDynamic(index, stat); } }; class AttributeStat { StatObject mObject; int mIndex; AttributeStat(StatObject object, int index) : mObject(std::move(object)), mIndex(index) {} public: template sol::object get(const Context& context, std::string_view prop, G getter) const { return getValue(context, mObject, &AttributeStat::setValue, mIndex, prop, [=](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return (ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).*getter)(); }); } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isActor()) return {}; return AttributeStat{std::move(object), index}; } void cache(const Context& context, std::string_view prop, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&AttributeStat::setValue, mIndex, prop}] = value; } static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getCreatureStats(ptr); auto stat = stats.getAttribute(index); float floatValue = value.as(); if(prop == "base") stat.setBase(floatValue); else if(prop == "damage") { stat.restore(stat.getDamage()); stat.damage(floatValue); } else if(prop == "modifier") stat.setModifier(floatValue); stats.setAttribute(index, stat); } }; class SkillStat { StatObject mObject; int mIndex; SkillStat(StatObject object, int index) : mObject(std::move(object)), mIndex(index) {} static float getProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) { float progress = stat.getProgress(); if(progress != 0.f) progress /= getMaxProgress(ptr, index, stat); return progress; } static float getMaxProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) { const auto& store = MWBase::Environment::get().getWorld()->getStore(); const auto cl = store.get().find(ptr.get()->mBase->mClass); return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(index, *cl); } public: template sol::object get(const Context& context, std::string_view prop, G getter) const { return getValue(context, mObject, &SkillStat::setValue, mIndex, prop, [=](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return (ptr.getClass().getNpcStats(ptr).getSkill(mIndex).*getter)(); }); } sol::object getProgress(const Context& context) const { return getValue(context, mObject, &SkillStat::setValue, mIndex, "progress", [=](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return getProgress(ptr, mIndex, ptr.getClass().getNpcStats(ptr).getSkill(mIndex)); }); } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isNpc()) return {}; return SkillStat{std::move(object), index}; } void cache(const Context& context, std::string_view prop, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&SkillStat::setValue, mIndex, prop}] = value; } static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getNpcStats(ptr); auto stat = stats.getSkill(index); float floatValue = value.as(); if(prop == "base") stat.setBase(floatValue); else if(prop == "damage") { stat.restore(stat.getDamage()); stat.damage(floatValue); } else if(prop == "modifier") stat.setModifier(floatValue); else if(prop == "progress") stat.setProgress(floatValue * getMaxProgress(ptr, index, stat)); stats.setSkill(index, stat); } }; } namespace sol { template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; } 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"); levelStatT["current"] = sol::property( [context](const LevelStat& stat) { return stat.getCurrent(context); }, [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(0); auto dynamicStatT = context.mLua->sol().new_usertype("DynamicStat"); addProp(context, dynamicStatT, "base", &MWMechanics::DynamicStat::getBase); addProp(context, dynamicStatT, "current", &MWMechanics::DynamicStat::getCurrent); addProp(context, dynamicStatT, "modifier", &MWMechanics::DynamicStat::getModifier); sol::table dynamic(context.mLua->sol(), sol::create); stats["dynamic"] = LuaUtil::makeReadOnly(dynamic); dynamic["health"] = addIndexedAccessor(0); dynamic["magicka"] = addIndexedAccessor(1); dynamic["fatigue"] = addIndexedAccessor(2); auto attributeStatT = context.mLua->sol().new_usertype("AttributeStat"); addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase); addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage); addProp(context, attributeStatT, "modified", &MWMechanics::AttributeValue::getModified, true); addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier); sol::table attributes(context.mLua->sol(), sol::create); stats["attributes"] = LuaUtil::makeReadOnly(attributes); for(int id = ESM::Attribute::Strength; id < ESM::Attribute::Length; ++id) attributes[Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[id])] = addIndexedAccessor(id); } 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"); addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase); addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage); addProp(context, skillStatT, "modified", &MWMechanics::SkillValue::getModified, true); addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier); skillStatT["progress"] = sol::property( [context](const SkillStat& stat) { return stat.getProgress(context); }, [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); for(int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id) skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id])] = addIndexedAccessor(id); } }