Merge branch 'dehardcode-skillprogression' into 'master'

Lua: Dehardcode skill and level progression

See merge request OpenMW/openmw!3779
pull/3234/head
psi29a 3 months ago
commit a060a7297c

@ -68,6 +68,8 @@ namespace MWBase
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)
= 0;
virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0;
virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0;
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void actorDied(const MWWorld::Ptr& actor) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;

@ -23,6 +23,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"
@ -662,7 +663,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();
@ -852,7 +853,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);
@ -862,7 +863,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);
}
}
@ -1142,16 +1143,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)
{

@ -164,8 +164,10 @@ namespace MWGui
const MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
setClassImage(mClassImage,
ESM::RefId::stringRefId(getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0),
pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2))));
ESM::RefId::stringRefId(
getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Combat),
pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Magic),
pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Stealth))));
int level = creatureStats.getLevel() + 1;
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level));

@ -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;
}

@ -5,6 +5,7 @@
#include <MyGUI_TextIterator.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
@ -174,10 +175,7 @@ namespace MWGui
}
// increase skill
MWWorld::LiveCellRef<ESM::NPC>* playerRef = player.get<ESM::NPC>();
const ESM::Class* class_ = store.get<ESM::Class>().find(playerRef->mBase->mClass);
pcStats.increaseSkill(skill->mId, *class_, true);
MWBase::Environment::get().getLuaManager()->skillLevelUp(player, skill->mId, "trainer");
// remove gold
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);

@ -95,6 +95,24 @@ namespace MWLua
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);
}
void operator()(const OnSkillLevelUp& event) const
{
MWWorld::Ptr actor = getPtr(event.mActor);
if (actor.isEmpty())
return;
if (auto* scripts = getLocalScripts(actor))
scripts->onSkillLevelUp(event.mSkill, event.mSource);
}
private:
MWWorld::Ptr getPtr(ESM::RefNum id) const
{

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

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

@ -79,6 +79,14 @@ namespace MWLua
{
callEngineHandlers(mOnPlayAnimationHandlers, groupname, options);
}
void onSkillUse(std::string_view skillId, int useType, float scale)
{
callEngineHandlers(mOnSkillUse, skillId, useType, scale);
}
void onSkillLevelUp(std::string_view skillId, std::string_view source)
{
callEngineHandlers(mOnSkillLevelUp, skillId, source);
}
void applyStatsCache();
@ -93,6 +101,8 @@ namespace MWLua
EngineHandlerList mOnTeleportedHandlers{ "onTeleported" };
EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" };
EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" };
EngineHandlerList mOnSkillUse{ "_onSkillUse" };
EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" };
};
}

@ -456,6 +456,17 @@ 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::skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source)
{
mEngineEvents.addToQueue(
EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) });
}
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.

@ -88,6 +88,8 @@ 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 skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override;
void exteriorCreated(MWWorld::CellStore& cell) override
{
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });

@ -73,6 +73,96 @@ 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(
static_cast<ESM::Class::Specialization>(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(
static_cast<ESM::Class::Specialization>(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 +175,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 +183,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 +191,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 +223,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 +429,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 +474,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 +604,13 @@ namespace MWLua
skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string {
return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText();
});
skillT["skillGain"] = sol::readonly_property([lua](const ESM::Skill& rec) -> sol::table {
sol::table res(lua, sol::create);
int index = 1;
for (auto skillGain : rec.mData.mUseValue)
res[index++] = skillGain;
return res;
});
auto schoolT = context.mLua->sol().new_usertype<ESM::MagicSchool>("MagicSchool");
schoolT[sol::meta_function::to_string]

@ -1956,7 +1956,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;

@ -209,93 +209,14 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(ESM::RefId id, const ES
return progressRequirement;
}
void MWMechanics::NpcStats::useSkill(ESM::RefId id, const ESM::Class& class_, int usageType, float extraFactor)
{
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(id);
float skillGain = 1;
if (usageType >= 4)
throw std::runtime_error("skill usage type out of range");
if (usageType >= 0)
{
skillGain = skill->mData.mUseValue[usageType];
if (skillGain < 0)
throw std::runtime_error("invalid skill gain factor");
}
skillGain *= extraFactor;
MWMechanics::SkillValue& value = getSkill(skill->mId);
value.setProgress(value.getProgress() + skillGain);
if (int(value.getProgress()) >= int(getSkillProgressRequirement(skill->mId, class_)))
{
// skill levelled up
increaseSkill(skill->mId, class_, false);
}
}
void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook)
int MWMechanics::NpcStats::getLevelProgress() const
{
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(id);
float base = getSkill(skill->mId).getBase();
if (base >= 100.f)
return;
base += 1;
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
// is this a minor or major skill?
int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo
int index = ESM::Skill::refIdToIndex(skill->mId);
for (const auto& skills : class_.mData.mSkills)
{
if (skills[0] == index)
{
mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger();
increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger();
break;
}
else if (skills[1] == index)
{
mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger();
increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger();
break;
}
}
mSkillIncreases[ESM::Attribute::indexToRefId(skill->mData.mAttribute)] += increase;
mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger();
// Play sound & skill progress notification
/// \todo check if character is the player, if levelling is ever implemented for NPCs
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("skillraise"));
std::string message{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sNotifyMessage39", {}) };
message = Misc::StringUtils::format(
message, MyGUI::TextIterator::toTagsString(skill->mName).asUTF8(), static_cast<int>(base));
if (readBook)
message = "#{sBookSkillMessage}\n" + message;
MWBase::Environment::get().getWindowManager()->messageBox(message, MWGui::ShowInDialogueMode_Never);
if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger())
{
// levelup is possible now
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never);
}
getSkill(skill->mId).setBase(base);
if (!preserveProgress)
getSkill(skill->mId).setProgress(0);
return mLevelProgress;
}
int MWMechanics::NpcStats::getLevelProgress() const
void MWMechanics::NpcStats::setLevelProgress(int progress)
{
return mLevelProgress;
mLevelProgress = progress;
}
void MWMechanics::NpcStats::levelUp()
@ -344,11 +265,33 @@ int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::Attribute::Attribu
return MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>().find(gmst.str())->mValue.getInteger();
}
int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const
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(ESM::Class::Specialization spec) const
{
return mSpecIncreases[spec];
}
void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases)
{
assert(spec >= 0 && spec < 3);
mSpecIncreases[spec] = increases;
}
void MWMechanics::NpcStats::flagAsUsed(const ESM::RefId& id)
{
mUsedIds.insert(id);

@ -3,6 +3,7 @@
#include "creaturestats.hpp"
#include <components/esm/refid.hpp>
#include <components/esm3/loadclas.hpp>
#include <components/esm3/loadskil.hpp>
#include <map>
#include <set>
@ -86,16 +87,15 @@ namespace MWMechanics
float getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const;
void useSkill(ESM::RefId id, const ESM::Class& class_, int usageType = -1, float extraFactor = 1.f);
///< Increase skill by usage.
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;
int getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const;
void setSkillIncreasesForSpecialization(ESM::Class::Specialization 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)

@ -5,6 +5,7 @@
#include <components/esm3/loadskil.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
@ -49,12 +50,7 @@ namespace MWWorld
ESM::RefId skill = ESM::Skill::indexToRefId(ref->mBase->mData.mSkillId);
if (!skill.empty() && !npcStats.hasBeenUsed(ref->mBase->mId))
{
MWWorld::LiveCellRef<ESM::NPC>* playerRef = actor.get<ESM::NPC>();
const ESM::Class* class_
= MWBase::Environment::get().getESMStore()->get<ESM::Class>().find(playerRef->mBase->mClass);
npcStats.increaseSkill(skill, *class_, true, true);
MWBase::Environment::get().getLuaManager()->skillLevelUp(actor, skill, "book");
npcStats.flagAsUsed(ref->mBase->mId);
}

@ -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[@]}"

@ -41,6 +41,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

@ -19,6 +19,7 @@ PLAYER: scripts/omw/input/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,67 @@ 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 = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,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 +106,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,269 @@
local self = require('openmw.self')
local I = require('openmw.interfaces')
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
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 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 skillRecord = Skill.record(skillid)
local skillGainUnorm = skillRecord.skillGain[useType + 1]
if scale then skillGainUnorm = skillGainUnorm * scale end
local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid)
local skillGain = skillGainUnorm / skillProgressRequirementUnorm
-- Put skill gain in a table so that handlers can modify it
local options = {
skillGain = skillGain,
}
for i = #skillUsedHandlers, 1, -1 do
if skillUsedHandlers[i](skillid, useType, options) == 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 options =
{
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, options) == 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
-- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler)
-- will be skipped. Where skillid and source are the parameters passed to @{SkillProgression#skillLevelUp}, and options is
-- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers.
-- 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.
--
-- @function [parent=#SkillProgression] addSkillLevelUpHandler
-- @param #function handler The handler.
addSkillLevelUpHandler = function(handler)
skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler
end,
--- Add new skillUsed handler for this actor
-- If `handler(skillid, useType, options)` returns false, other handlers (including the default skill progress handler)
-- will be skipped. Where skillid and useType are the parameters passed to @{SkillProgression#skillUsed},
-- and options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers.
-- 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.
--
-- @function [parent=#SkillProgression] addSkillUsedHandler
-- @param #function handler The handler.
addSkillUsedHandler = function(handler)
skillUsedHandlers[#skillUsedHandlers + 1] = handler
end,
--- Trigger 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,
},
--- Trigger a skill level up, activating relevant handlers
-- @function [parent=#SkillProgression] skillLevelUp
-- @param #string skillid The id of the skill to level up.
-- @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 = {
-- Use the interface in these handlers so any overrides will receive the calls.
_onSkillUse = function (skillid, useType, scale)
I.SkillProgression.skillUsed(skillid, useType, scale)
end,
_onSkillLevelUp = function (skillid, source)
I.SkillProgression.skillLevelUp(skillid, source)
end,
},
}

@ -881,6 +881,7 @@
-- @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 #table skillGain Table of the 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