mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-06 09:45:33 +00:00
Merge branch 'dehardcode-skillprogression' into 'master'
Lua: Dehardcode skill and level progression See merge request OpenMW/openmw!3779
This commit is contained in:
commit
a060a7297c
40 changed files with 703 additions and 151 deletions
|
@ -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,95 +209,16 @@ 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 +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,
|
||||
},
|
||||
}
|
||||
|
|
269
files/data/scripts/omw/skillhandlers.lua
Normal file
269
files/data/scripts/omw/skillhandlers.lua
Normal file
|
@ -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…
Reference in a new issue