It is long time I was active (as forum poster) here, recently I wanted to play Morrowind once more.
I still hate the leveling system and I wanted to see if it were possible to do a minimalist mod to fix it.
The main problem I have with the default level up system is that it pushes the player to seek for easy challenges, like to cast the same weak spell continuously.
As minimal change I'd just want to push the player to look for challenges of its level, not too easy, not too hard.
The base idea would be that being x the chance of success (so x is in [0, 1]) the skill gain is proportional to x on failure and on 1-x on success.
I am aware that some skills (like Acrobatics) have no concept of "failing," but I have an idea for those too.
To see if it were possible to create such a mod in OpenMW I checked the code a bit.
At the moment the engine (a part of Speechcraft) does not really consider failures to increase the skills, and this is a necessary feature to implement the mod.
The member function MWClass::Npc::skillUsageSucceeded is called each time the player succeeds, the function will call MWMechanics::NpcStats ::useSkill to update the skills. Besides, there is a parameter extraFactor that allows to scale how much skill gain the player gets.
The default values about how much successes increase are outside the engine and stored in the SKIL records.
The problem is then: I see no ways to make the mod I'd like without changing the engine.
Here is a possible solution. I am writing this message because I'd like to ask if it would acceptable, this solution would implies some changes in the engine. But of course, by default the behavior will be the same.
- Add a new function similar to skillUsageSucceeded called on failure.
- Look for a new, or unused, GMST (or a similar flag) that, if present, sets up the new weights that are passed to extraFactor; if the new GMST is not there the value stays as now: 1. for everything a part of Mercantile.
I am fairly sure I'd be able to do so, but it would be an acceptable patch? I remember logical deviations from the original engine were frowned upon, but it was years ago and now apparently the version is "not 1.0" because of the editor more than the game. So perhaps it has changed.
Of course, given the problem other solutions are welcome. If I am missing something and it is actually possible to do such a mod without altering the engine code by all means suggest how; however I do not intend to do dumb hacks as I was forced with the original engine.
Level up mod
Re: Level up mod
Checking the code better there is no reason do duplicate MWClass::Npc::skillUsageSucceeded or touch GMSTs.
It is better to simply call MWClass::Npc::skillUsageSucceeded with a different extraFactor and similarly to some MCP changes (e.g., can loot during death animation) add the option to the game settings.
It is better to simply call MWClass::Npc::skillUsageSucceeded with a different extraFactor and similarly to some MCP changes (e.g., can loot during death animation) add the option to the game settings.
Re: Level up mod
Here is how it will look like. For now only on one of easiest skill: Block. I don't expect the others to be much different, though.
Code: Select all
diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp
index 183845b..6ea956a 100644
--- a/apps/openmw/mwmechanics/combat.cpp
+++ b/apps/openmw/mwmechanics/combat.cpp
@@ -56,6 +56,8 @@ namespace MWMechanics
bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength)
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
+
if (!blocker.getClass().hasInventoryStore(blocker))
return false;
@@ -114,6 +116,7 @@ namespace MWMechanics
int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x));
+ float block_chance = x / 100.f;
if (Misc::Rng::roll0to99() < x)
{
@@ -139,11 +142,20 @@ namespace MWMechanics
blockerStats.setBlock(true);
- if (blocker == getPlayer())
- blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
+ if (blocker == getPlayer()) {
+ if (increaseOnFailure) {
+ blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0, 1.f - block_chance);
+ } else {
+ blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
+ }
+ }
return true;
}
+
+ if (increaseOnFailure && blocker == getPlayer())
+ blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0, block_chance);
+
return false;
}
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
index 0c00d3b..180966a 100644
--- a/apps/openmw/mwclass/npc.cpp
+++ b/apps/openmw/mwclass/npc.cpp
@@ -1110,6 +1110,8 @@ namespace MWClass
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const
{
+ if (extraFactor <= 0.0f) return;
+
MWMechanics::NpcStats& stats = getNpcStats (ptr);
if (stats.isWerewolf())
diff --git a/files/settings-default.cfg b/files/settings-default.cfg
index ac5433f..b01b884 100644
--- a/files/settings-default.cfg
+++ b/files/settings-default.cfg
@@ -337,6 +337,9 @@ trainers training skills based on base skill = false
# Make stealing items from NPCs that were knocked down possible during combat.
always allow stealing from knocked out actors = false
+# Increase skills also on failure weighting in the difficulty
+increase skill on failure = false
+
[General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
Re: Level up mod
You could do a MR on the OpenMW's GITLAB and call for discussion here.
Re: Level up mod
I am afraid it is out of scope, but perhaps I will.
Here is the version 0, it does not comprise all abilities, but includes the most important ones.
If anyone can try it out to see "how it feels", feedback would be greatly appreciated.
Here is the version 0, it does not comprise all abilities, but includes the most important ones.
If anyone can try it out to see "how it feels", feedback would be greatly appreciated.
Code: Select all
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
index 0c00d3b..7e41556 100644
--- a/apps/openmw/mwclass/npc.cpp
+++ b/apps/openmw/mwclass/npc.cpp
@@ -541,6 +541,8 @@ namespace MWClass
void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
+
MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
@@ -589,6 +591,10 @@ namespace MWClass
if (Misc::Rng::roll0to99() >= hitchance)
{
+ if(increaseOnFailure && ptr == MWMechanics::getPlayer()) {
+ skillUsageSucceeded(ptr, weapskill, 0, hitchance / 100.f);
+ }
+
othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false);
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
return;
@@ -621,7 +627,11 @@ namespace MWClass
}
if(ptr == MWMechanics::getPlayer())
{
- skillUsageSucceeded(ptr, weapskill, 0);
+ if (increaseOnFailure)
+ skillUsageSucceeded(ptr, weapskill, 0, 1.f - hitchance / 100.f);
+ else
+ skillUsageSucceeded(ptr, weapskill, 0);
+
const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence();
@@ -1110,6 +1120,8 @@ namespace MWClass
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const
{
+ if (extraFactor <= 0.0f) return;
+
MWMechanics::NpcStats& stats = getNpcStats (ptr);
if (stats.isWerewolf())
diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp
index 183845b..32781ab 100644
--- a/apps/openmw/mwmechanics/combat.cpp
+++ b/apps/openmw/mwmechanics/combat.cpp
@@ -56,6 +56,8 @@ namespace MWMechanics
bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength)
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
+
if (!blocker.getClass().hasInventoryStore(blocker))
return false;
@@ -114,6 +116,7 @@ namespace MWMechanics
int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x));
+ float block_chance = x / 100.f;
if (Misc::Rng::roll0to99() < x)
{
@@ -139,11 +142,20 @@ namespace MWMechanics
blockerStats.setBlock(true);
- if (blocker == getPlayer())
- blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
+ if (blocker == getPlayer()) {
+ if (increaseOnFailure) {
+ blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0, 1.f - block_chance);
+ } else {
+ blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
+ }
+ }
return true;
}
+
+ if (increaseOnFailure && blocker == getPlayer())
+ blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0, block_chance);
+
return false;
}
@@ -193,6 +205,8 @@ namespace MWMechanics
void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile,
const osg::Vec3f& hitPosition, float attackStrength)
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
+
MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
@@ -210,8 +224,13 @@ namespace MWMechanics
int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon));
- if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue))
+ float hitChance = getHitChance(attacker, victim, skillValue);
+
+ if (Misc::Rng::roll0to99() >= hitChance)
{
+ if (increaseOnFailure && attacker == getPlayer())
+ attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0, hitChance / 100.f);
+
victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false);
MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
return;
@@ -231,7 +250,12 @@ namespace MWMechanics
applyWerewolfDamageMult(victim, projectile, damage);
if (attacker == getPlayer())
- attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
+ {
+ if (increaseOnFailure)
+ attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0, 1.f - hitChance / 100.f);
+ else
+ attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
+ }
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
bool unaware = attacker == getPlayer() && !sequence.isInCombat()
diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp
index 389d00d..34bdd2e 100644
--- a/apps/openmw/mwmechanics/repair.cpp
+++ b/apps/openmw/mwmechanics/repair.cpp
@@ -1,6 +1,7 @@
#include "repair.hpp"
#include <components/misc/rng.hpp>
+#include <components/settings/settings.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@@ -18,6 +19,8 @@ namespace MWMechanics
void Repair::repair(const MWWorld::Ptr &itemToRepair)
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
+
MWWorld::Ptr player = getPlayer();
MWWorld::LiveCellRef<ESM::Repair> *ref =
mTool.get<ESM::Repair>();
@@ -41,10 +44,10 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
float toolQuality = ref->mBase->mData.mQuality;
- float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm;
+ float successChance = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm;
int roll = Misc::Rng::roll0to99();
- if (roll <= x)
+ if (roll <= successChance)
{
int y = static_cast<int>(fRepairAmountMult * toolQuality * roll);
y = std::max(1, y);
@@ -63,13 +66,19 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1);
// increase skill
- player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0);
+ if (increaseOnFailure)
+ player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0, 1.f - successChance / 100.f);
+ else
+ player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0);
MWBase::Environment::get().getWindowManager()->playSound("Repair");
MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}");
}
else
{
+ if (increaseOnFailure)
+ player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0, successChance / 100.f);
+
MWBase::Environment::get().getWindowManager()->playSound("Repair Fail");
MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairFailed}");
}
diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp
index 001375f..2724a40 100644
--- a/apps/openmw/mwmechanics/security.cpp
+++ b/apps/openmw/mwmechanics/security.cpp
@@ -1,5 +1,6 @@
#include "security.hpp"
+#include <components/settings/settings.hpp>
#include <components/misc/rng.hpp>
#include "../mwworld/class.hpp"
@@ -28,6 +29,7 @@ namespace MWMechanics
void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick,
std::string& resultMessage, std::string& resultSound)
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
if (lock.getCellRef().getLockLevel() <= 0 ||
lock.getCellRef().getLockLevel() == ESM::UnbreakableLock ||
!lock.getClass().hasToolTip(lock)) //If it's unlocked or can not be unlocked back out immediately
@@ -53,12 +55,21 @@ namespace MWMechanics
if (Misc::Rng::roll0to99() <= x)
{
lock.getCellRef().unlock();
+
resultMessage = "#{sLockSuccess}";
resultSound = "Open Lock";
- mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1);
+
+ if (increaseOnFailure)
+ mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1, 1.f - x / 100.f);
+ else
+ mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1);
}
- else
+ else {
+ if (increaseOnFailure)
+ mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1, x / 100.f);
+
resultMessage = "#{sLockFail}";
+ }
}
int uses = lockpick.getClass().getItemHealth(lockpick);
@@ -71,6 +82,7 @@ namespace MWMechanics
void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe,
std::string& resultMessage, std::string& resultSound)
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
if (trap.getCellRef().getTrap() == "")
return;
@@ -98,10 +110,16 @@ namespace MWMechanics
resultSound = "Disarm Trap";
resultMessage = "#{sTrapSuccess}";
- mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0);
+ if (increaseOnFailure)
+ mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0, 1.f - x / 100.f);
+ else
+ mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0);
}
- else
+ else {
+ if (increaseOnFailure)
+ mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0, x / 100.f);
resultMessage = "#{sTrapFail}";
+ }
}
int uses = probe.getClass().getItemHealth(probe);
diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp
index 9f71082..65cc37e 100644
--- a/apps/openmw/mwmechanics/spellcasting.cpp
+++ b/apps/openmw/mwmechanics/spellcasting.cpp
@@ -2,6 +2,7 @@
#include <components/misc/constants.hpp>
#include <components/misc/rng.hpp>
+#include <components/settings/settings.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
@@ -555,6 +556,8 @@ namespace MWMechanics
bool CastSpell::cast(const ESM::Spell* spell)
{
+ static bool increaseOnFailure = Settings::Manager::getBool("increase skill on failure", "Game");
+
mSourceName = spell->mName;
mId = spell->mId;
mStack = false;
@@ -564,6 +567,7 @@ namespace MWMechanics
int school = 0;
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
+ float successChance = 100.;
if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell)
{
@@ -583,19 +587,17 @@ namespace MWMechanics
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
stats.setFatigue(fatigue);
- bool fail = false;
-
// Check success
- float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false);
+ successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false);
if (Misc::Rng::roll0to99() >= successChance)
{
- if (mCaster == getPlayer())
+ if (mCaster == getPlayer()) {
+ if (increaseOnFailure && !mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
+ mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0, successChance / 100.f);
+
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
- fail = true;
- }
+ }
- if (fail)
- {
// Failure sound
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
@@ -612,8 +614,13 @@ namespace MWMechanics
stats.getSpells().usePower(spell);
}
- if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
- mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0);
+ if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) {
+ if (increaseOnFailure) {
+ mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0, 1. - successChance / 100.f);
+ } else {
+ mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0);
+ }
+ }
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
if (!mCaster.getClass().isActor())
diff --git a/files/settings-default.cfg b/files/settings-default.cfg
index ac5433f..b01b884 100644
--- a/files/settings-default.cfg
+++ b/files/settings-default.cfg
@@ -337,6 +337,9 @@ trainers training skills based on base skill = false
# Make stealing items from NPCs that were knocked down possible during combat.
always allow stealing from knocked out actors = false
+# Increase skills also on failure weighting the difficulty
+increase skill on failure = false
+
[General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
- Attachments
-
- plus_three_increase.zip
- Changes the SKILL settings to match
- (1.99 KiB) Downloaded 186 times
Re: Level up mod
I player for few hours a character with this new rules. I tried to play "naturally" not to push the game or maximize.
I have to say I am fairly happy, while it does not change too much for swordplay, it pushes to use more dangerous spells instead of safer ones and with security is really satisfying to see "Your security has increased" after breaking a couple of lockpicks while trying to open a difficult door.
It is not about the mod and it is not a big news, but OpenMW and OpenCS OpenCS-MW did their job really well. So thanks to everyone.
I have to say I am fairly happy, while it does not change too much for swordplay, it pushes to use more dangerous spells instead of safer ones and with security is really satisfying to see "Your security has increased" after breaking a couple of lockpicks while trying to open a difficult door.
It is not about the mod and it is not a big news, but OpenMW and OpenCS OpenCS-MW did their job really well. So thanks to everyone.
Re: Level up mod
Maybe you could get more reactions for your branch on the OpenMW Discord channel. I'm intrigued with this mod and I'd like to test it someday.
Re: Level up mod
This place seems kinda dead. I guess I'll check there...
Re: Level up mod
While I am quite happy of the results with weapons' related abilities. When trying to play a wizard character the results were disappointing.
Spells are expensive and their failure dangerous so one needs reliable spells to go on. But reliable means that you it high chance of success, so it become too difficult to increase.
I'd like to try to put a minimum of how much the spell ability increases based on how much magicka the spell cased uses. Something like: a spell that uses 1/5 of your magicka will give you the normal increase as minimum. The 1/5 is a minimum, it can be more using the previous formula.
In formula, being x the chance of success (so x is in [0, 1]) the skill gain is proportional to x on failure and on 1-x on success, but the minimum is how much magicka it uses.
Spells are expensive and their failure dangerous so one needs reliable spells to go on. But reliable means that you it high chance of success, so it become too difficult to increase.
I'd like to try to put a minimum of how much the spell ability increases based on how much magicka the spell cased uses. Something like: a spell that uses 1/5 of your magicka will give you the normal increase as minimum. The 1/5 is a minimum, it can be more using the previous formula.
In formula, being x the chance of success (so x is in [0, 1]) the skill gain is proportional to x on failure and on 1-x on success, but the minimum is how much magicka it uses.