Dehardcode skill and level progression

pull/3234/head
Mads Buvik Sandvei 4 months ago
parent 9350222e1a
commit 011d9d6493

@ -62,6 +62,7 @@ namespace MWBase
virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0;
virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0;
virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0;
virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback)

@ -22,6 +22,7 @@
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@ -655,7 +656,7 @@ namespace MWClass
ESM::RefId weapskill = ESM::Skill::HandToHand;
if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon);
skillUsageSucceeded(ptr, weapskill, 0);
skillUsageSucceeded(ptr, weapskill, ESM::Skill::Weapon_SuccessfulHit);
const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence();
@ -845,7 +846,7 @@ namespace MWClass
ESM::RefId skill = armor.getClass().getEquipmentSkill(armor);
if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, skill, 0);
skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent);
if (skill == ESM::Skill::LightArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
@ -855,7 +856,7 @@ namespace MWClass
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
}
else if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, ESM::Skill::Armor_HitByOpponent);
}
}
@ -1131,16 +1132,7 @@ namespace MWClass
void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const
{
MWMechanics::NpcStats& stats = getNpcStats(ptr);
if (stats.isWerewolf())
return;
MWWorld::LiveCellRef<ESM::NPC>* ref = ptr.get<ESM::NPC>();
const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get<ESM::Class>().find(ref->mBase->mClass);
stats.useSkill(skill, *class_, usageType, extraFactor);
MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor);
}
float Npc::getArmorRating(const MWWorld::Ptr& ptr) const

@ -543,7 +543,8 @@ namespace MWDialogue
mPermanentDispositionChange += perm;
MWWorld::Ptr player = MWMechanics::getPlayer();
player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1);
player.getClass().skillUsageSucceeded(
player, ESM::Skill::Speechcraft, success ? ESM::Skill::Speechcraft_Success : ESM::Skill::Speechcraft_Fail);
if (success)
{

@ -134,7 +134,7 @@ namespace MWGui
return false;
}
else
player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1);
player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_PickPocket);
return true;
}

@ -94,6 +94,15 @@ namespace MWLua
if (auto* scripts = getLocalScripts(actor))
scripts->onAnimationTextKey(event.mGroupname, event.mKey);
}
void operator()(const OnSkillUse& event) const
{
MWWorld::Ptr actor = getPtr(event.mActor);
if (actor.isEmpty())
return;
if (auto* scripts = getLocalScripts(actor))
scripts->onSkillUse(event.mSkill, event.useType, event.scale);
}
private:
MWWorld::Ptr getPtr(ESM::RefNum id) const

@ -57,8 +57,21 @@ namespace MWLua
std::string mGroupname;
std::string mKey;
};
struct OnAnimationTextKey
{
ESM::RefNum mActor;
std::string mGroupname;
std::string mKey;
};
struct OnSkillUse
{
ESM::RefNum mActor;
std::string mSkill;
int useType;
float scale;
};
using Event = std::variant<OnActive, OnInactive, OnConsume, OnActivate, OnUseItem, OnNewExterior, OnTeleported,
OnAnimationTextKey>;
OnAnimationTextKey, OnSkillUse>;
void clear() { mQueue.clear(); }
void addToQueue(Event e) { mQueue.push_back(std::move(e)); }

@ -176,7 +176,7 @@ namespace MWLua
{
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers,
&mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers });
&mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse });
}
void LocalScripts::setActive(bool active)

@ -79,6 +79,10 @@ namespace MWLua
{
callEngineHandlers(mOnPlayAnimationHandlers, groupname, options);
}
void onSkillUse(std::string_view skillId, int useType, float scale)
{
callEngineHandlers(mOnSkillUse, skillId, useType, scale);
}
void applyStatsCache();
@ -93,6 +97,7 @@ namespace MWLua
EngineHandlerList mOnTeleportedHandlers{ "onTeleported" };
EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" };
EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" };
EngineHandlerList mOnSkillUse{ "_onSkillUse" };
};
}

@ -406,6 +406,11 @@ namespace MWLua
scripts->onPlayAnimation(groupname, options);
}
void LuaManager::skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale)
{
mEngineEvents.addToQueue(EngineEvents::OnSkillUse{ getId(actor), skillId.serializeText(), useType, scale });
}
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.

@ -84,6 +84,7 @@ namespace MWLua
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, uint32_t loops,
bool loopfallback) override;
void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override;
void exteriorCreated(MWWorld::CellStore& cell) override
{
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });

@ -73,6 +73,94 @@ namespace MWLua
"StatUpdateAction");
}
static void setCreatureValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
{
auto& stats = ptr.getClass().getCreatureStats(ptr);
if (prop == "current")
stats.setLevel(LuaUtil::cast<int>(value));
}
static void setNpcValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
{
auto& stats = ptr.getClass().getNpcStats(ptr);
if (prop == "progress")
stats.setLevelProgress(LuaUtil::cast<int>(value));
else if (prop == "skillIncreasesForAttribute")
stats.setSkillIncreasesForAttribute(
*std::get<ESM::RefId>(index).getIf<ESM::StringRefId>(), LuaUtil::cast<int>(value));
else if (prop == "skillIncreasesForSpecialization")
stats.setSkillIncreasesForSpecialization(std::get<int>(index), LuaUtil::cast<int>(value));
}
class SkillIncreasesForAttributeStats
{
ObjectVariant mObject;
public:
SkillIncreasesForAttributeStats(ObjectVariant object)
: mObject(std::move(object))
{
}
sol::object get(const Context& context, ESM::StringRefId attributeId) const
{
const auto& ptr = mObject.ptr();
if (!ptr.getClass().isNpc())
return sol::nil;
return getValue(context, mObject, &setNpcValue, attributeId, "skillIncreasesForAttribute",
[attributeId](const MWWorld::Ptr& ptr) {
return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForAttribute(attributeId);
});
}
void set(const Context& context, ESM::StringRefId attributeId, const sol::object& value) const
{
const auto& ptr = mObject.ptr();
if (!ptr.getClass().isNpc())
return;
SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value;
}
};
class SkillIncreasesForSpecializationStats
{
ObjectVariant mObject;
public:
SkillIncreasesForSpecializationStats(ObjectVariant object)
: mObject(std::move(object))
{
}
sol::object get(const Context& context, int specialization) const
{
const auto& ptr = mObject.ptr();
if (!ptr.getClass().isNpc())
return sol::nil;
return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization",
[specialization](const MWWorld::Ptr& ptr) {
return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization(specialization);
});
}
void set(const Context& context, int specialization, const sol::object& value) const
{
const auto& ptr = mObject.ptr();
if (!ptr.getClass().isNpc())
return;
SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }]
= value;
}
};
class LevelStat
{
ObjectVariant mObject;
@ -85,7 +173,7 @@ namespace MWLua
public:
sol::object getCurrent(const Context& context) const
{
return getValue(context, mObject, &LevelStat::setValue, std::monostate{}, "current",
return getValue(context, mObject, &setCreatureValue, std::monostate{}, "current",
[](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); });
}
@ -93,7 +181,7 @@ namespace MWLua
{
SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, std::monostate{}, "current" }] = value;
obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value;
}
sol::object getProgress(const Context& context) const
@ -101,7 +189,30 @@ namespace MWLua
const auto& ptr = mObject.ptr();
if (!ptr.getClass().isNpc())
return sol::nil;
return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress());
return getValue(context, mObject, &setNpcValue, std::monostate{}, "progress",
[](const MWWorld::Ptr& ptr) { return ptr.getClass().getNpcStats(ptr).getLevelProgress(); });
}
void setProgress(const Context& context, const sol::object& value) const
{
const auto& ptr = mObject.ptr();
if (!ptr.getClass().isNpc())
return;
SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value;
}
SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const
{
return SkillIncreasesForAttributeStats{ mObject };
}
SkillIncreasesForSpecializationStats getSkillIncreasesForSpecializationStats() const
{
return SkillIncreasesForSpecializationStats{ mObject };
}
static std::optional<LevelStat> create(ObjectVariant object, Index)
@ -110,13 +221,6 @@ namespace MWLua
return {};
return LevelStat{ std::move(object) };
}
static void setValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
{
auto& stats = ptr.getClass().getCreatureStats(ptr);
if (prop == "current")
stats.setLevel(LuaUtil::cast<int>(value));
}
};
class DynamicStat
@ -323,6 +427,14 @@ namespace MWLua
namespace sol
{
template <>
struct is_automagical<MWLua::SkillIncreasesForAttributeStats> : std::false_type
{
};
template <>
struct is_automagical<MWLua::SkillIncreasesForSpecializationStats> : std::false_type
{
};
template <>
struct is_automagical<MWLua::LevelStat> : std::false_type
{
@ -360,10 +472,39 @@ namespace MWLua
sol::table stats(context.mLua->sol(), sol::create);
actor["stats"] = LuaUtil::makeReadOnly(stats);
auto skillIncreasesForAttributeStatsT
= context.mLua->sol().new_usertype<SkillIncreasesForAttributeStats>("SkillIncreasesForAttributeStats");
for (auto attribute : MWBase::Environment::get().getESMStore()->get<ESM::Attribute>())
{
skillIncreasesForAttributeStatsT[ESM::RefId(attribute.mId).serializeText()] = sol::property(
[=](const SkillIncreasesForAttributeStats& stat) { return stat.get(context, attribute.mId); },
[=](const SkillIncreasesForAttributeStats& stat, const sol::object& value) {
stat.set(context, attribute.mId, value);
});
}
// ESM::Class::specializationIndexToLuaId.at(rec.mData.mSpecialization)
auto skillIncreasesForSpecializationStatsT
= context.mLua->sol().new_usertype<SkillIncreasesForSpecializationStats>(
"skillIncreasesForSpecializationStats");
for (int i = 0; i < 3; i++)
{
std::string_view index = ESM::Class::specializationIndexToLuaId.at(i);
skillIncreasesForSpecializationStatsT[index]
= sol::property([=](const SkillIncreasesForSpecializationStats& stat) { return stat.get(context, i); },
[=](const SkillIncreasesForSpecializationStats& stat, const sol::object& value) {
stat.set(context, i, value);
});
}
auto levelStatT = context.mLua->sol().new_usertype<LevelStat>("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); });
levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); },
[context](const LevelStat& stat, const sol::object& value) { stat.setProgress(context, value); });
levelStatT["skillIncreasesForAttribute"]
= sol::readonly_property([](const LevelStat& stat) { return stat.getSkillIncreasesForAttributeStats(); });
levelStatT["skillIncreasesForSpecialization"] = sol::readonly_property(
[](const LevelStat& stat) { return stat.getSkillIncreasesForSpecializationStats(); });
stats["level"] = addIndexedAccessor<LevelStat>(0);
auto dynamicStatT = context.mLua->sol().new_usertype<DynamicStat>("DynamicStat");
@ -461,6 +602,14 @@ namespace MWLua
skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string {
return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText();
});
skillT["skillGain1"]
= sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[0]; });
skillT["skillGain2"]
= sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[1]; });
skillT["skillGain3"]
= sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[2]; });
skillT["skillGain4"]
= sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[3]; });
auto schoolT = context.mLua->sol().new_usertype<ESM::MagicSchool>("MagicSchool");
schoolT[sol::meta_function::to_string]

@ -1962,7 +1962,7 @@ namespace MWMechanics
mSneakSkillTimer = 0.f;
if (avoidedNotice && mSneakSkillTimer == 0.f)
player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0);
player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_AvoidNotice);
if (!detected)
MWBase::Environment::get().getWindowManager()->setSneakVisibility(true);

@ -335,7 +335,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name)
void MWMechanics::Alchemy::increaseSkill()
{
mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, 0);
mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, ESM::Skill::Alchemy_CreatePotion);
}
float MWMechanics::Alchemy::getAlchemyFactor() const

@ -2088,7 +2088,7 @@ namespace MWMechanics
mSecondsOfSwimming += duration;
while (mSecondsOfSwimming > 1)
{
cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1);
cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_SwimOneSecond);
mSecondsOfSwimming -= 1;
}
}
@ -2097,7 +2097,7 @@ namespace MWMechanics
mSecondsOfRunning += duration;
while (mSecondsOfRunning > 1)
{
cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0);
cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_RunOneSecond);
mSecondsOfRunning -= 1;
}
}
@ -2215,7 +2215,7 @@ namespace MWMechanics
{
// report acrobatics progression
if (isPlayer)
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Fall);
}
}

@ -167,7 +167,7 @@ namespace MWMechanics
blockerStats.setBlock(true);
if (blocker == getPlayer())
blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, ESM::Skill::Block_Success);
return true;
}
@ -267,7 +267,7 @@ namespace MWMechanics
applyWerewolfDamageMult(victim, projectile, damage);
if (attacker == getPlayer())
attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, ESM::Skill::Weapon_SuccessfulHit);
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
bool unaware = attacker == getPlayer() && !sequence.isInCombat()

@ -84,7 +84,8 @@ namespace MWMechanics
if (getEnchantChance() <= (Misc::Rng::roll0to99(prng)))
return false;
mEnchanter.getClass().skillUsageSucceeded(mEnchanter, ESM::Skill::Enchant, 2);
mEnchanter.getClass().skillUsageSucceeded(
mEnchanter, ESM::Skill::Enchant, ESM::Skill::Enchant_CreateMagicItem);
}
enchantment.mEffects = mEffectList;

@ -298,6 +298,11 @@ int MWMechanics::NpcStats::getLevelProgress() const
return mLevelProgress;
}
void MWMechanics::NpcStats::setLevelProgress(int progress)
{
mLevelProgress = progress;
}
void MWMechanics::NpcStats::levelUp()
{
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
@ -344,11 +349,33 @@ int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::Attribute::Attribu
return MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>().find(gmst.str())->mValue.getInteger();
}
int MWMechanics::NpcStats::getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const
{
auto it = mSkillIncreases.find(attribute);
if (it == mSkillIncreases.end())
return 0;
return it->second;
}
void MWMechanics::NpcStats::setSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute, int increases)
{
if (increases == 0)
mSkillIncreases.erase(attribute);
else
mSkillIncreases[attribute] = increases;
}
int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const
{
return mSpecIncreases[spec];
}
void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(int spec, int increases)
{
assert(spec >= 0 && spec < 3);
mSpecIncreases[spec] = increases;
}
void MWMechanics::NpcStats::flagAsUsed(const ESM::RefId& id)
{
mUsedIds.insert(id);

@ -92,10 +92,14 @@ namespace MWMechanics
void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false);
int getLevelProgress() const;
void setLevelProgress(int progress);
int getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const;
int getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const;
void setSkillIncreasesForAttribute(ESM::Attribute::AttributeID, int increases);
int getSkillIncreasesForSpecialization(int spec) const;
void setSkillIncreasesForSpecialization(int spec, int increases);
void levelUp();

@ -84,7 +84,7 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Enchant Fail"));
}
player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0);
player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, ESM::Skill::Enchant_Recharge);
gem.getContainerStore()->remove(gem, 1);
if (gem.getCellRef().getCount() == 0)

@ -70,7 +70,7 @@ namespace MWMechanics
stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1);
// increase skill
player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0);
player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, ESM::Skill::Armorer_Repair);
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair"));
MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}");

@ -64,7 +64,7 @@ namespace MWMechanics
lock.getCellRef().unlock();
resultMessage = "#{sLockSuccess}";
resultSound = "Open Lock";
mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1);
mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_PickLock);
}
else
resultMessage = "#{sLockFail}";
@ -115,7 +115,7 @@ namespace MWMechanics
resultSound = "Disarm Trap";
resultMessage = "#{sTrapSuccess}";
mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0);
mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_DisarmTrap);
}
else
resultMessage = "#{sTrapFail}";

@ -354,7 +354,7 @@ namespace MWMechanics
if (type == ESM::Enchantment::WhenUsed)
{
if (mCaster == getPlayer())
mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 1);
mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_UseMagicItem);
}
else if (type == ESM::Enchantment::CastOnce)
{
@ -364,7 +364,7 @@ namespace MWMechanics
else if (type == ESM::Enchantment::WhenStrikes)
{
if (mCaster == getPlayer())
mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 3);
mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_CastOnStrike);
}
if (isProjectile)
@ -439,7 +439,7 @@ namespace MWMechanics
}
if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
mCaster.getClass().skillUsageSucceeded(mCaster, school, 0);
mCaster.getClass().skillUsageSucceeded(mCaster, school, ESM::Skill::Spellcast_Success);
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
if (!mCaster.getClass().isActor())

@ -79,7 +79,8 @@ namespace MWMechanics
{
skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer);
}
player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain);
player.getClass().skillUsageSucceeded(
player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain);
return true;
}

@ -69,7 +69,7 @@ namespace
// Advance acrobatics and set flag for GetPCJumping
if (isPlayer)
{
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Jump);
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
}

@ -11,7 +11,7 @@ namespace MWWorld
void ActionEat::executeImp(const Ptr& actor)
{
if (actor.getClass().consume(getTarget(), actor) && actor == MWMechanics::getPlayer())
actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, 1);
actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, ESM::Skill::Alchemy_UseIngredient);
}
ActionEat::ActionEat(const MWWorld::Ptr& object)

@ -47,13 +47,45 @@ namespace ESM
uint32_t mRecordFlags;
SkillId mId;
//! Enum that defines the index into SKDTstruct::mUseValue for all vanilla skill uses
enum UseType
{
// These are shared by multiple skills
Armor_HitByOpponent = 0,
Block_Success = 0,
Spellcast_Success = 0,
Weapon_SuccessfulHit = 0,
// Skill-specific use types
Alchemy_CreatePotion = 0,
Alchemy_UseIngredient = 1,
Enchant_Recharge = 0,
Enchant_UseMagicItem = 1,
Enchant_CreateMagicItem = 2,
Enchant_CastOnStrike = 3,
Acrobatics_Jump = 0,
Acrobatics_Fall = 1,
Mercantile_Success = 0,
Mercantile_Bribe = 1, //!< \Note This is bugged in vanilla and is not actually in use.
Security_DisarmTrap = 0,
Security_PickLock = 1,
Sneak_AvoidNotice = 0,
Sneak_PickPocket = 1,
Speechcraft_Success = 0,
Speechcraft_Fail = 1,
Armorer_Repair = 0,
Athletics_RunOneSecond = 0,
Athletics_SwimOneSecond = 0,
};
struct SKDTstruct
{
int32_t mAttribute; // see defs.hpp
int32_t mSpecialization; // 0 - Combat, 1 - Magic, 2 - Stealth
float mUseValue[4]; // How much skill improves through use. Meaning
// of each field depends on what skill this
// is. We should document this better later.
// is. See UseType above
}; // Total size: 24 bytes
SKDTstruct mData;

@ -9,5 +9,6 @@ paths=(
scripts/omw/settings/player.lua
scripts/omw/ui.lua
scripts/omw/usehandlers.lua
scripts/omw/skillhandlers.lua
)
printf '%s\n' "${paths[@]}"

@ -40,6 +40,7 @@ Lua API reference
interface_item_usage
interface_mwui
interface_settings
interface_skill_progression
interface_ui
iterables

@ -0,0 +1,6 @@
Interface SkillProgression
===================
.. raw:: html
:file: generated_html/scripts_omw_skillhandlers.html

@ -25,6 +25,10 @@
- by global scripts
- | Allows to extend or override built-in item usage
| mechanics.
* - :ref:`SkillProgression <Interface SkillProgression>`
- by local scripts
- | Control, extend, and override skill progression of the
- | player.
* - :ref:`Settings <Interface Settings>`
- by player and global scripts
- Save, display and track changes of setting values.

@ -90,6 +90,7 @@ set(BUILTIN_DATA_FILES
scripts/omw/mwui/textEdit.lua
scripts/omw/mwui/space.lua
scripts/omw/mwui/init.lua
scripts/omw/skillhandlers.lua
scripts/omw/ui.lua
scripts/omw/usehandlers.lua
scripts/omw/worldeventhandlers.lua

@ -16,6 +16,7 @@ PLAYER: scripts/omw/playercontrols.lua
PLAYER: scripts/omw/camera/camera.lua
PLAYER: scripts/omw/input/actionbindings.lua
PLAYER: scripts/omw/input/smoothmovement.lua
PLAYER: scripts/omw/skillhandlers.lua
NPC,CREATURE: scripts/omw/ai.lua
# User interface

@ -1,7 +1,13 @@
local ambient = require('openmw.ambient')
local core = require('openmw.core')
local Skill = core.stats.Skill
local I = require('openmw.interfaces')
local nearby = require('openmw.nearby')
local self = require('openmw.self')
local types = require('openmw.types')
local NPC = types.NPC
local Actor = types.Actor
local ui = require('openmw.ui')
local cell = nil
local autodoors = {}
@ -31,6 +37,69 @@ local function processAutomaticDoors()
end
end
local function skillLevelUpHandler(skillid, source, params)
local skillStat = NPC.stats.skills[skillid](self)
if skillStat.base >= 100 then
return false
end
if params.skillIncreaseValue then
skillStat.base = skillStat.base + params.skillIncreaseValue
end
local levelStat = Actor.stats.level(self)
if params.levelUpProgress then
levelStat.progress = levelStat.progress + params.levelUpProgress
end
if params.levelUpAttribute and params.levelUpAttributeIncreaseValue then
levelStat.skillIncreasesForAttribute[params.levelUpAttribute]
= levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + params.levelUpAttributeIncreaseValue
end
if params.levelUpSpecialization and params.levelUpSpecializationIncreaseValue then
levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization]
= levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue;
end
local skillRecord = Skill.record(skillid)
local npcRecord = NPC.record(self)
local class = NPC.classes.record(npcRecord.class)
ambient.playSound("skillraise")
local message = core.getGMST('sNotifyMessage39')
message = message:gsub("%%s", skillRecord.name)
message = message:gsub("%%d", tostring(skillStat.base))
if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then
message = '#{sBookSkillMessage}\n'..message
end
ui.showMessage(message)
if levelStat.progress >= core.getGMST('iLevelUpTotal') then
ui.showMessage('#{sLevelUpMsg}')
end
if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end
end
local function skillUsedHandler(skillid, useType, params)
if NPC.isWerewolf(self) then
return false
end
if params.skillGain then
local skillStat = NPC.stats.skills[skillid](self)
skillStat.progress = skillStat.progress + params.skillGain
if skillStat.progress >= 1 then
I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage)
end
end
end
local function onUpdate()
if self.cell ~= cell then
cell = self.cell
@ -39,8 +108,14 @@ local function onUpdate()
processAutomaticDoors()
end
local function onActive()
I.SkillProgression.addSkillUsedHandler(skillUsedHandler)
I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler)
end
return {
engineHandlers = {
onUpdate = onUpdate,
onActive = onActive,
},
}

@ -0,0 +1,283 @@
local self = require('openmw.self')
local types = require('openmw.types')
local core = require('openmw.core')
local NPC = require('openmw.types').NPC
local Skill = core.stats.Skill
---
-- Table of skill use types defined by morrowind.
-- Each entry corresponds to an index into the available skill gain values
-- of a @{openmw.types#SkillRecord}
-- @type SkillUseType
-- @field #number Armor_HitByOpponent 0
-- @field #number Block_Success 0
-- @field #number Spellcast_Success 0
-- @field #number Weapon_SuccessfulHit 0
-- @field #number Alchemy_CreatePotion 0
-- @field #number Alchemy_UseIngredient 1
-- @field #number Enchant_Recharge 0
-- @field #number Enchant_UseMagicItem 1
-- @field #number Enchant_CreateMagicItem 2
-- @field #number Enchant_CastOnStrike 3
-- @field #number Acrobatics_Jump 0
-- @field #number Acrobatics_Fall 1
-- @field #number Mercantile_Success 0
-- @field #number Mercantile_Bribe 1
-- @field #number Security_DisarmTrap 0
-- @field #number Security_PickLock 1
-- @field #number Sneak_AvoidNotice 0
-- @field #number Sneak_PickPocket 1
-- @field #number Speechcraft_Success 0
-- @field #number Speechcraft_Fail 1
-- @field #number Armorer_Repair 0
-- @field #number Athletics_RunOneSecond 0
-- @field #number Athletics_SwimOneSecond 0
---
-- Table of valid sources for skill increases
-- @type SkillLevelUpSource
-- @field #string Book book
-- @field #string Trainer trainer
-- @field #string Usage usage
---
-- Table of valid handler signatures
-- @type HandlerSignatures
--
--- Signature of the skillLevelUp handler
-- @function [parent=#HandlerSignatures] skillLevelUpHandler
-- @param #string skillid The ID of the skill being leveled up
-- @param #SkillLevelUpSource source The source of the skill level up
-- @param #table params Modifiable skill level up values as a table. These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values:
--
-- * `skillIncreaseValue` - The numeric amount of skill levels gained.
-- * `levelUpProgress` - The numeric amount of level up progress gained.
-- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up.
-- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up.
-- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up.
-- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up.
--- Signature of the skillUsed handler
-- @function [parent=#HandlerSignatures] skillUsedHandler
-- @param #string skillid The ID of the skill being progressed
-- @param #SkillUseType useType The use type the skill progression
-- @param #table params Modifiable skill progression value. By default contains the single value
--
-- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level.
local skillUsedHandlers = {}
local skillLevelUpHandlers = {}
local function tableHasValue(table, value)
for _, v in pairs(table) do
if v == value then return true end
end
return false
end
local function getSkillGainValue(skillid, useType, scale)
local skillRecord = Skill.record(skillid)
local skillGain = 0
if useType == 0 then
skillGain = skillRecord.skillGain1
elseif useType == 1 then
skillGain = skillRecord.skillGain2
elseif useType == 2 then
skillGain = skillRecord.skillGain3
elseif useType == 3 then
skillGain = skillRecord.skillGain4
end
if scale ~= nil then skillGain = skillGain * scale end
return skillGain
end
local function getSkillProgressRequirementUnorm(npc, skillid)
local npcRecord = NPC.record(npc)
local class = NPC.classes.record(npcRecord.class)
local skillStat = NPC.stats.skills[skillid](npc)
local skillRecord = Skill.record(skillid)
local factor = core.getGMST('fMiscSkillBonus')
if tableHasValue(class.majorSkills, skillid) then
factor = core.getGMST('fMajorSkillBonus')
elseif tableHasValue(class.minorSkills, skillid) then
factor = core.getGMST('fMinorSkillBonus')
end
if skillRecord.specialization == class.specialization then
factor = factor * core.getGMST('fSpecialSkillBonus')
end
return (skillStat.base + 1) * factor
end
local function skillUsed(skillid, useType, scale)
if #skillUsedHandlers == 0 then
-- If there are no handlers, then there won't be any effect, so skip calculations
return
end
if useType > 3 or useType < 0 then
print('Error: Unknown useType: '..tostring(useType))
return
end
-- Compute skill gain
local skillStat = NPC.stats.skills[skillid](self)
local skillGainUnorm = getSkillGainValue(skillid, useType, scale)
local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid)
local skillGain = skillGainUnorm / skillProgressRequirementUnorm
-- Put skill gain in a table so that handlers can modify it
local parameters = {
skillGain = skillGain,
}
for i = #skillUsedHandlers, 1, -1 do
if skillUsedHandlers[i](skillid, useType, parameters) == false then
return
end
end
end
local function skillLevelUp(skillid, source)
if #skillLevelUpHandlers == 0 then
-- If there are no handlers, then there won't be any effect, so skip calculations
return
end
local skillRecord = Skill.record(skillid)
local npcRecord = NPC.record(self)
local class = NPC.classes.record(npcRecord.class)
local levelUpProgress = 0
local levelUpAttributeIncreaseValue = core.getGMST('iLevelupMiscMultAttriubte')
if tableHasValue(class.minorSkills, skillid) then
levelUpProgress = core.getGMST('iLevelUpMinorMult')
levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMinorMultAttribute')
elseif tableHasValue(class.majorSkills, skillid) then
levelUpProgress = core.getGMST('iLevelUpMajorMult')
levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute')
end
local parameters =
{
skillIncreaseValue = 1,
levelUpProgress = levelUpProgress,
levelUpAttribute = skillRecord.attribute,
levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue,
levelUpSpecialization = skillRecord.specialization,
levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'),
}
for i = #skillLevelUpHandlers, 1, -1 do
if skillLevelUpHandlers[i](skillid, source, parameters) == false then
return
end
end
end
return {
interfaceName = 'SkillProgression',
---
-- Allows to extend or override built-in skill progression mechanics.
-- @module SkillProgression
-- @usage local I = require('openmw.interfaces')
--
-- -- Forbid increasing destruction skill past 50
-- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options)
-- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then
-- return false
-- end
-- end)
--
-- -- Scale sneak skill progression based on active invisibility effects
-- I.SkillProgression.addSkillUsedHandler(function(skillid, useType, params)
-- if skillid == 'sneak' and useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then
-- local activeEffects = Actor.activeEffects(self)
-- local visibility = activeEffects:getEffect(core.magic.EFFECT_TYPE.Chameleon).magnitude / 100
-- visibility = visibility + activeEffects:getEffect(core.magic.EFFECT_TYPE.Invisibility).magnitude
-- visibility = 1 - math.min(1, math.max(0, visibility))
-- local oldSkillGain = params.skillGain
-- params.skillGain = oldSkillGain * visibility
-- end
-- end
--
interface = {
--- Interface version
-- @field [parent=#SkillProgression] #number version
version = 0,
--- Add new skill level up handler for this actor
-- @function [parent=#SkillProgression] addSkillLevelUpHandler
-- @param #function handler The handler, see #skillLevelUpHandler.
addSkillLevelUpHandler = function(handler)
skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler
end,
--- Add new skillUsed handler for this actor
-- @function [parent=#SkillProgression] addSkillUsedHandler
-- @param #function handler The handler.
addSkillUsedHandler = function(handler)
skillUsedHandlers[#skillUsedHandlers + 1] = handler
end,
--- Register a skill use, activating relevant handlers
-- @function [parent=#SkillProgression] skillUsed
-- @param #string skillid The if of the skill that was used
-- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType}
-- @param #number scale A number that linearly scales the skill progress received from this use. Defaults to 1.
skillUsed = skillUsed,
--- @{#SkillUseType}
-- @field [parent=#SkillProgression] #SkillUseType SKILL_USE_TYPES Available skill usage types
SKILL_USE_TYPES = {
-- These are shared by multiple skills
Armor_HitByOpponent = 0,
Block_Success = 0,
Spellcast_Success = 0,
Weapon_SuccessfulHit = 0,
-- Skill-specific use types
Alchemy_CreatePotion = 0,
Alchemy_UseIngredient = 1,
Enchant_Recharge = 0,
Enchant_UseMagicItem = 1,
Enchant_CreateMagicItem = 2,
Enchant_CastOnStrike = 3,
Acrobatics_Jump = 0,
Acrobatics_Fall = 1,
Mercantile_Success = 0,
Mercantile_Bribe = 1, -- Note: This is bugged in vanilla and is not actually in use.
Security_DisarmTrap = 0,
Security_PickLock = 1,
Sneak_AvoidNotice = 0,
Sneak_PickPocket = 1,
Speechcraft_Success = 0,
Speechcraft_Fail = 1,
Armorer_Repair = 0,
Athletics_RunOneSecond = 0,
Athletics_SwimOneSecond = 0,
},
--- Register a skill increase, activating relevant handlers
-- @function [parent=#SkillProgression] skillUsed
-- @param #string skillid The id of the skill to be increased.
-- @param #SkillLevelUpSource source The source of the skill increase.
skillLevelUp = skillLevelUp,
--- @{#SkillLevelUpSource}
-- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES
SKILL_INCREASE_SOURCES = {
Book = 'book',
Usage = 'usage',
Trainer = 'trainer',
},
},
engineHandlers = { _onSkillUse = skillUsed },
}

@ -875,6 +875,10 @@
-- @field #string specialization Skill specialization. Either combat, magic, or stealth.
-- @field #MagicSchoolData school Optional magic school
-- @field #string attribute The id of the skill's governing attribute
-- @field #string skillGain1 1 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType)
-- @field #string skillGain2 2 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType)
-- @field #string skillGain3 3 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType)
-- @field #string skillGain4 4 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType)
---
-- @type MagicSchoolData

@ -26,6 +26,9 @@
---
-- @field [parent=#interfaces] scripts.omw.usehandlers#scripts.omw.usehandlers ItemUsage
---
-- @field [parent=#interfaces] scripts.omw.skillhandlers#scripts.omw.skillhandlers SkillProgression
---
-- @function [parent=#interfaces] __index
-- @param #interfaces self

@ -352,10 +352,29 @@
-- @function [parent=#ActorSpells] clear
-- @param self
--- Values affect how much each attribute can be increased at level up, and are all reset to 0 upon level up.
-- @type SkillIncreasesForAttributeStats
-- @field #number agility Number of contributions to agility for the next level up.
-- @field #number endurance Number of contributions to endurance for the next level up.
-- @field #number intelligence Number of contributions to intelligence for the next level up.
-- @field #number luck Number of contributions to luck for the next level up.
-- @field #number personality Number of contributions to personality for the next level up.
-- @field #number speed Number of contributions to speed for the next level up.
-- @field #number strength Number of contributions to strength for the next level up.
-- @field #number willpower Number of contributions to willpower for the next level up.
--- Values affect the graphic used on the level up screen, and are all reset to 0 upon level up.
-- @type SkillIncreasesForSpecializationStats
-- @field #number combat Number of contributions to combat specialization for the next level up.
-- @field #number magic Number of contributions to magic specialization for the next level up.
-- @field #number stealth Number of contributions to stealth specialization for the next level up.
---
-- @type LevelStat
-- @field #number current The actor's current level.
-- @field #number progress The NPC's level progress (read-only.)
-- @field #number progress The NPC's level progress.
-- @field #SkillIncreasesForAttributeStats skillIncreasesForAttribute The NPC's attribute contributions towards the next level up. Values affect how much each attribute can be increased at level up.
-- @field #SkillIncreasesForSpecializationStats skillIncreasesForSpecialization The NPC's attribute contributions towards the next level up. Values affect the graphic used on the level up screen.
---
-- @type DynamicStat

Loading…
Cancel
Save