From 3dce225f28cae4a7e6ac1d5c992eb64c873677e3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 30 Sep 2019 20:27:42 +0400 Subject: [PATCH] Implement vanilla-style corprus handling (bug #3714, bug #4623) --- CHANGELOG.md | 2 + apps/openmw/mwbase/mechanicsmanager.hpp | 2 + apps/openmw/mwgui/jailscreen.cpp | 6 + apps/openmw/mwmechanics/activespells.cpp | 41 ++++- apps/openmw/mwmechanics/activespells.hpp | 2 + apps/openmw/mwmechanics/actors.cpp | 119 ++++++++++++-- apps/openmw/mwmechanics/creaturestats.cpp | 38 ++++- apps/openmw/mwmechanics/creaturestats.hpp | 20 ++- .../mwmechanics/mechanicsmanagerimp.cpp | 18 ++ .../mwmechanics/mechanicsmanagerimp.hpp | 2 + apps/openmw/mwmechanics/spells.cpp | 154 ++++++------------ apps/openmw/mwmechanics/spells.hpp | 22 +-- apps/openmw/mwmechanics/stat.cpp | 9 +- apps/openmw/mwscript/statsextensions.cpp | 1 + apps/openmw/mwworld/inventorystore.cpp | 12 +- apps/openmw/mwworld/inventorystore.hpp | 4 +- components/esm/attr.hpp | 2 +- components/esm/creaturestats.cpp | 21 +++ components/esm/creaturestats.hpp | 11 +- components/esm/savedgame.cpp | 2 +- components/esm/spellstate.cpp | 34 ++-- components/esm/spellstate.hpp | 5 +- 22 files changed, 364 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87796d0151..58fe39c1e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Bug #1952: Incorrect particle lighting Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #3676: NiParticleColorModifier isn't applied properly + Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects + Bug #4623: Corprus implementation is incorrect Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #5108: Savegame bloating due to inefficient fog textures format Bug #5165: Active spells should use real time intead of timestamps diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 3bde83e94b..c9ce43e013 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -280,6 +280,8 @@ namespace MWBase virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; + + virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) = 0; }; } diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 9c8fbfb17d..4a89304b3d 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -81,6 +81,12 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true); MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); + // We should not worsen corprus when in prison + for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells()) + { + spell.second.mNextWorsening += mDays * 24; + } + std::set skills; for (int day=0; day& effects = iter->second.mEffects; for (std::vector::iterator effectIt = effects.begin(); effectIt != effects.end();) { if (effectIt->mTimeLeft <= 0) { - effectIt = effects.erase(effectIt); rebuild = true; + + // Note: it we expire a Corprus effect, we should remove the whole spell. + if (effectIt->mEffectId == ESM::MagicEffect::Corprus) + { + iter = mSpells.erase (iter); + interrupt = true; + break; + } + + effectIt = effects.erase(effectIt); } else { @@ -43,7 +53,9 @@ namespace MWMechanics ++effectIt; } } - ++iter; + + if (!interrupt) + ++iter; } } } @@ -278,6 +290,31 @@ namespace MWMechanics mSpellsChanged = true; } + void ActiveSpells::purgeCorprusDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + bool hasCorprusEffect = false; + for (std::vector::iterator effectIt = iter->second.mEffects.begin(); + effectIt != iter->second.mEffects.end();++effectIt) + { + if (effectIt->mEffectId == ESM::MagicEffect::Corprus) + { + hasCorprusEffect = true; + break; + } + } + + if (hasCorprusEffect) + { + mSpells.erase(iter++); + mSpellsChanged = true; + } + else + ++iter; + } + } + void ActiveSpells::clear() { mSpells.clear(); diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index ddfa56ecf6..9a1783bc9e 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -99,6 +99,8 @@ namespace MWMechanics bool isSpellActive (const std::string& id) const; ///< case insensitive + void purgeCorprusDisease(); + const MagicEffects& getMagicEffects() const; void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4fdacfd05c..584a09492c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -176,6 +176,49 @@ namespace MWMechanics } }; + class GetCurrentMagnitudes : public MWMechanics::EffectSourceVisitor + { + std::string mSpellId; + + public: + GetCurrentMagnitudes(const std::string& spellId) + : mSpellId(spellId) + { + } + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) + { + if (magnitude <= 0) + return; + + if (sourceId != mSpellId) + return; + + mMagnitudes.push_back(std::make_pair(key, magnitude)); + } + + std::vector> mMagnitudes; + }; + + class GetCorprusSpells : public MWMechanics::EffectSourceVisitor + { + + public: + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) + { + if (key.mId != ESM::MagicEffect::Corprus) + return; + + mSpells.push_back(sourceId); + } + + std::vector mSpells; + }; + class SoulTrap : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mCreature; @@ -942,20 +985,74 @@ namespace MWMechanics if (creatureStats.needToRecalcDynamicStats()) calculateDynamicStats(ptr); + if (ptr == getPlayer()) { - Spells & spells = creatureStats.getSpells(); - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - if (spells.getCorprusSpells().find(it->first) != spells.getCorprusSpells().end()) - { - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= spells.getCorprusSpells().at(it->first).mNextWorsening) - { - spells.worsenCorprus(it->first); + GetCorprusSpells getCorprusSpellsVisitor; + creatureStats.getSpells().visitEffectSources(getCorprusSpellsVisitor); + creatureStats.getActiveSpells().visitEffectSources(getCorprusSpellsVisitor); + ptr.getClass().getInventoryStore(ptr).visitEffectSources(getCorprusSpellsVisitor); + std::vector corprusSpells = getCorprusSpellsVisitor.mSpells; + std::vector corprusSpellsToRemove; - if (ptr == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); - } + for (auto it = creatureStats.getCorprusSpells().begin(); it != creatureStats.getCorprusSpells().end(); ++it) + { + if(std::find(corprusSpells.begin(), corprusSpells.end(), it->first) == corprusSpells.end()) + { + // Corprus effect expired, remove entry and restore stats. + MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, it->first); + corprusSpellsToRemove.push_back(it->first); + corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); + continue; } + + corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); + + if (MWBase::Environment::get().getWorld()->getTimeStamp() >= it->second.mNextWorsening) + { + it->second.mNextWorsening += CorprusStats::sWorseningPeriod; + GetCurrentMagnitudes getMagnitudesVisitor (it->first); + creatureStats.getSpells().visitEffectSources(getMagnitudesVisitor); + creatureStats.getActiveSpells().visitEffectSources(getMagnitudesVisitor); + ptr.getClass().getInventoryStore(ptr).visitEffectSources(getMagnitudesVisitor); + for (auto& effectMagnitude : getMagnitudesVisitor.mMagnitudes) + { + if (effectMagnitude.first.mId == ESM::MagicEffect::FortifyAttribute) + { + AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); + attr.damage(-effectMagnitude.second); + creatureStats.setAttribute(effectMagnitude.first.mArg, attr); + it->second.mWorsenings[effectMagnitude.first.mArg] = 0; + } + else if (effectMagnitude.first.mId == ESM::MagicEffect::DrainAttribute) + { + AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); + int currentDamage = attr.getDamage(); + if (currentDamage >= 0) + it->second.mWorsenings[effectMagnitude.first.mArg] = std::min(it->second.mWorsenings[effectMagnitude.first.mArg], currentDamage); + + it->second.mWorsenings[effectMagnitude.first.mArg] += effectMagnitude.second; + attr.damage(effectMagnitude.second); + creatureStats.setAttribute(effectMagnitude.first.mArg, attr); + } + } + + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); + } + } + + for (std::string& oldCorprusSpell : corprusSpellsToRemove) + { + creatureStats.removeCorprusSpell(oldCorprusSpell); + } + + for (std::string& newCorprusSpell : corprusSpells) + { + CorprusStats corprus; + for (int i=0; igetTimeStamp() + CorprusStats::sWorseningPeriod; + + creatureStats.addCorprusSpell(newCorprusSpell, corprus); } } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 800b5c22f9..a64fb087ca 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -551,6 +551,14 @@ namespace MWMechanics state.mHasAiSettings = true; for (int i=0; i<4; ++i) mAiSettings[i].writeState (state.mAiSettings[i]); + + for (auto it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + { + for (int i=0; ifirst].mWorsenings[i] = mCorprusSpells.at(it->first).mWorsenings[i]; + + state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); + } } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -589,7 +597,7 @@ namespace MWMechanics mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); //mHitAttemptActorId = state.mHitAttemptActorId; - mSpells.readState(state.mSpells); + mSpells.readState(state.mSpells, this); mActiveSpells.readState(state.mActiveSpells); mAiSequence.readState(state.mAiSequence); mMagicEffects.readState(state.mMagicEffects); @@ -600,6 +608,15 @@ namespace MWMechanics if (state.mHasAiSettings) for (int i=0; i<4; ++i) mAiSettings[i].readState(state.mAiSettings[i]); + + mCorprusSpells.clear(); + for (auto it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) + { + for (int i=0; ifirst].mWorsenings[i] = state.mCorprusSpells.at(it->first).mWorsenings[i]; + + mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); + } } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -675,4 +692,23 @@ namespace MWMechanics { return mSummonGraveyard; } + + std::map &CreatureStats::getCorprusSpells() + { + return mCorprusSpells; + } + + void CreatureStats::addCorprusSpell(const std::string& sourceId, CorprusStats& stats) + { + mCorprusSpells[sourceId] = stats; + } + + void CreatureStats::removeCorprusSpell(const std::string& sourceId) + { + auto corprusIt = mCorprusSpells.find(sourceId); + if (corprusIt != mCorprusSpells.end()) + { + mCorprusSpells.erase(corprusIt); + } + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index a4ecb23b3a..5a5dd3f12f 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -12,6 +12,8 @@ #include "aisequence.hpp" #include "drawstate.hpp" +#include + namespace ESM { struct CreatureStats; @@ -19,6 +21,14 @@ namespace ESM namespace MWMechanics { + struct CorprusStats + { + static const int sWorseningPeriod = 24; + + int mWorsenings[ESM::Attribute::Length]; + MWWorld::TimeStamp mNextWorsening; + }; + /// \brief Common creature stats /// /// @@ -26,7 +36,7 @@ namespace MWMechanics { static int sActorId; DrawState_ mDrawState; - AttributeValue mAttributes[8]; + AttributeValue mAttributes[ESM::Attribute::Length]; DynamicStat mDynamic[3]; // health, magicka, fatigue Spells mSpells; ActiveSpells mActiveSpells; @@ -79,6 +89,8 @@ namespace MWMechanics // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; + std::map mCorprusSpells; + protected: int mLevel; @@ -280,6 +292,12 @@ namespace MWMechanics /// assigned this function will return false). static void cleanup(); + + std::map & getCorprusSpells(); + + void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); + + void removeCorprusSpell(const std::string& sourceId); }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 88045ec442..5376ec86e6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -289,6 +289,24 @@ namespace MWMechanics mWatched = ptr; } + void MechanicsManager::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) + { + auto& stats = actor.getClass().getCreatureStats (actor); + auto& corprusSpells = stats.getCorprusSpells(); + + auto corprusIt = corprusSpells.find(sourceId); + + if (corprusIt != corprusSpells.end()) + { + for (int i = 0; i < ESM::Attribute::Length; ++i) + { + MWMechanics::AttributeValue attr = stats.getAttribute(i); + attr.restore(corprusIt->second.mWorsenings[i]); + actor.getClass().getCreatureStats(actor).setAttribute(i, attr); + } + } + } + void MechanicsManager::update(float duration, bool paused) { if(!mWatched.isEmpty()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 6785f979f9..86bc4c720b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -247,6 +247,8 @@ namespace MWMechanics virtual GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; + virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) override; + private: bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 0eaa531e29..ae7454f194 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -7,9 +7,13 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "actorutil.hpp" +#include "creaturestats.hpp" #include "magiceffects.hpp" +#include "stat.hpp" namespace MWMechanics { @@ -63,12 +67,6 @@ namespace MWMechanics } } } - - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) - { - mEffects += it->second; - mSourcedEffects[it->first] += it->second; - } } bool Spells::hasSpell(const std::string &spell) const @@ -101,15 +99,6 @@ namespace MWMechanics } } - if (hasCorprusEffect(spell)) - { - CorprusStats corprus; - corprus.mWorsenings = 0; - corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - - mCorprusSpells[spell] = corprus; - } - SpellParams params; params.mEffectRands = random; mSpells.insert (std::make_pair (spell, params)); @@ -127,27 +116,6 @@ namespace MWMechanics const ESM::Spell* spell = getSpell(spellId); TContainer::iterator iter = mSpells.find (spell); - std::map::iterator corprusIt = mCorprusSpells.find(spell); - - // if it's corprus, remove negative and keep positive effects - if (corprusIt != mCorprusSpells.end()) - { - worsenCorprus(spell); - if (mPermanentSpellEffects.find(spell) != mPermanentSpellEffects.end()) - { - MagicEffects & effects = mPermanentSpellEffects[spell]; - for (MagicEffects::Collection::const_iterator effectIt = effects.begin(); effectIt != effects.end();) - { - const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->first.mId); - if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) - effects.remove((effectIt++)->first); - else - ++effectIt; - } - } - mCorprusSpells.erase(corprusIt); - } - if (iter!=mSpells.end()) { mSpells.erase (iter); @@ -320,31 +288,6 @@ namespace MWMechanics } } - void Spells::worsenCorprus(const ESM::Spell* spell) - { - mCorprusSpells[spell].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[spell].mWorsenings++; - - // update worsened effects - mPermanentSpellEffects[spell] = MagicEffects(); - int i=0; - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt, ++i) - { - const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); - if ((effectIt->mEffectID != ESM::MagicEffect::Corprus) && (magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) - { - float random = 1.f; - if (mSpells[spell].mEffectRands.find(i) != mSpells[spell].mEffectRands.end()) - random = mSpells[spell].mEffectRands.at(i); - - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - magnitude *= std::max(1, mCorprusSpells[spell].mWorsenings); - mPermanentSpellEffects[spell].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude)); - mSpellsChanged = true; - } - } - } - bool Spells::hasCorprusEffect(const ESM::Spell *spell) { for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) @@ -357,11 +300,6 @@ namespace MWMechanics return false; } - const std::map &Spells::getCorprusSpells() const - { - return mCorprusSpells; - } - void Spells::purgeEffect(int effectId) { for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt) @@ -412,7 +350,7 @@ namespace MWMechanics mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp(); } - void Spells::readState(const ESM::SpellState &state) + void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) { for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { @@ -436,6 +374,32 @@ namespace MWMechanics mUsedPowers[spell] = MWWorld::TimeStamp(it->second); } + for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) + { + const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell) + continue; + + CorprusStats stats; + + int worsening = state.mCorprusSpells.at(it->first).mWorsenings; + + for (int i=0; imEffects.mList) + { + if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mAttribute] = worsening; + } + stats.mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); + + creatureStats->addCorprusSpell(it->first, stats); + } + + mSpellsChanged = true; + + // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach. for (std::map >::const_iterator it = state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) { @@ -443,24 +407,31 @@ namespace MWMechanics if (!spell) continue; - mPermanentSpellEffects[spell] = MagicEffects(); + // Import data only for player, other actors should not suffer from corprus worsening. + MWWorld::Ptr player = getPlayer(); + if (creatureStats->getActorId() != player.getClass().getCreatureStats(player).getActorId()) + return; + + // Note: if target actor has the Restore attirbute effects, stats will be restored. for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) { - mPermanentSpellEffects[spell].add(EffectKey(effectIt->mId, effectIt->mArg), effectIt->mMagnitude); + // Applied corprus effects are already in loaded stats modifiers + if (effectIt->mId == ESM::MagicEffect::FortifyAttribute) + { + AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); + attr.setModifier(attr.getModifier() - effectIt->mMagnitude); + attr.damage(-effectIt->mMagnitude); + creatureStats->setAttribute(effectIt->mArg, attr); + } + else if (effectIt->mId == ESM::MagicEffect::DrainAttribute) + { + AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); + attr.setModifier(attr.getModifier() + effectIt->mMagnitude); + attr.damage(effectIt->mMagnitude); + creatureStats->setAttribute(effectIt->mArg, attr); + } } } - - mCorprusSpells.clear(); - for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) - { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); - if (!spell) // Discard unavailable corprus spells - continue; - mCorprusSpells[spell].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings; - mCorprusSpells[spell].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - } - - mSpellsChanged = true; } void Spells::writeState(ESM::SpellState &state) const @@ -477,26 +448,5 @@ namespace MWMechanics for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) state.mUsedPowers[it->first->mId] = it->second.toEsm(); - - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) - { - std::vector effectList; - for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) - { - ESM::SpellState::PermanentSpellEffectInfo info; - info.mId = effectIt->first.mId; - info.mArg = effectIt->first.mArg; - info.mMagnitude = effectIt->second.getModifier(); - - effectList.push_back(info); - } - state.mPermanentSpellEffects[it->first->mId] = effectList; - } - - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) - { - state.mCorprusSpells[it->first->mId].mWorsenings = mCorprusSpells.at(it->first).mWorsenings; - state.mCorprusSpells[it->first->mId].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); - } } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index e635f4f56d..a4a599f8b5 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -22,6 +22,8 @@ namespace ESM namespace MWMechanics { + class CreatureStats; + class MagicEffects; /// \brief Spell list @@ -33,7 +35,8 @@ namespace MWMechanics public: typedef const ESM::Spell* SpellKey; - struct SpellParams { + struct SpellParams + { std::map mEffectRands; // std::set mPurgedEffects; // indices of purged effects }; @@ -41,27 +44,14 @@ namespace MWMechanics typedef std::map TContainer; typedef TContainer::const_iterator TIterator; - struct CorprusStats - { - static const int sWorseningPeriod = 24; - - int mWorsenings; - MWWorld::TimeStamp mNextWorsening; - }; - private: TContainer mSpells; - // spell-tied effects that will be applied even after removing the spell (currently used to keep positive effects when corprus is removed) - std::map mPermanentSpellEffects; - // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; std::map mUsedPowers; - std::map mCorprusSpells; - mutable bool mSpellsChanged; mutable MagicEffects mEffects; mutable std::map mSourcedEffects; @@ -73,9 +63,7 @@ namespace MWMechanics public: Spells(); - void worsenCorprus(const ESM::Spell* spell); static bool hasCorprusEffect(const ESM::Spell *spell); - const std::map & getCorprusSpells() const; void purgeEffect(int effectId); void purgeEffect(int effectId, const std::string & sourceId); @@ -128,7 +116,7 @@ namespace MWMechanics void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - void readState (const ESM::SpellState& state); + void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void writeState (ESM::SpellState& state) const; }; } diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index f53052a286..6a559a3611 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -256,10 +256,17 @@ namespace MWMechanics void AttributeValue::damage(float damage) { - mDamage += std::min(damage, (float)getModified()); + float threshold = mBase + mModifier; + + if (mDamage + damage > threshold) + mDamage = threshold; + else + mDamage += damage; } void AttributeValue::restore(float amount) { + if (mDamage <= 0) return; + mDamage -= std::min(mDamage, amount); } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 45eafa9601..8ba0cdcf28 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -499,6 +499,7 @@ namespace MWScript creatureStats.getSpells().purgeEffect(effect.first.mId); } + MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, id); creatureStats.getSpells().remove (id); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d4358532c4..8d2dfe6a4b 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -921,16 +921,16 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito } } -void MWWorld::InventoryStore::purgeEffect(short effectId) +void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell) { for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it) { if (*it != end()) - purgeEffect(effectId, (*it)->getCellRef().getRefId()); + purgeEffect(effectId, (*it)->getCellRef().getRefId(), wholeSpell); } } -void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId) +void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell) { TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) @@ -963,6 +963,12 @@ void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sou if (effectIt->mEffectID != effectId) continue; + if (wholeSpell) + { + mPermanentMagicEffectMagnitudes.erase(sourceId); + return; + } + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; magnitude *= params[i].mMultiplier; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index e18132f4b8..d597e5f30b 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -203,10 +203,10 @@ namespace MWWorld void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); - void purgeEffect (short effectId); + void purgeEffect (short effectId, bool wholeSpell = false); ///< Remove a magic effect - void purgeEffect (short effectId, const std::string& sourceId); + void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false); ///< Remove a magic effect virtual void clear(); diff --git a/components/esm/attr.hpp b/components/esm/attr.hpp index c0a54c6593..dd891a96d7 100644 --- a/components/esm/attr.hpp +++ b/components/esm/attr.hpp @@ -21,7 +21,7 @@ struct Attribute Endurance = 5, Personality = 6, Luck = 7, - Length + Length = 8 }; AttributeID mId; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 2270bb6dd4..9c6ee838e6 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -134,6 +134,17 @@ void ESM::CreatureStats::load (ESMReader &esm) for (int i=0; i<4; ++i) mAiSettings[i].load(esm); } + + while (esm.isNextSub("CORP")) + { + std::string id = esm.getHString(); + + CorprusStats stats; + esm.getHNT(stats.mWorsenings, "WORS"); + esm.getHNT(stats.mNextWorsening, "TIME"); + + mCorprusSpells[id] = stats; + } } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -218,6 +229,15 @@ void ESM::CreatureStats::save (ESMWriter &esm) const for (int i=0; i<4; ++i) mAiSettings[i].save(esm); } + + for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + { + esm.writeHNString("CORP", it->first); + + const CorprusStats & stats = it->second; + esm.writeHNT("WORS", stats.mWorsenings); + esm.writeHNT("TIME", stats.mNextWorsening); + } } void ESM::CreatureStats::blank() @@ -245,4 +265,5 @@ void ESM::CreatureStats::blank() mDrawState = 0; mDeathAnimation = -1; mLevel = 1; + mCorprusSpells.clear(); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 8c69553a3d..17ab4bbe73 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -9,6 +9,7 @@ #include "defs.hpp" +#include "attr.hpp" #include "spellstate.hpp" #include "activespells.hpp" #include "magiceffects.hpp" @@ -22,7 +23,13 @@ namespace ESM // format 0, saved games only struct CreatureStats { - StatState mAttributes[8]; + struct CorprusStats + { + int mWorsenings[Attribute::Length]; + TimeStamp mNextWorsening; + }; + + StatState mAttributes[Attribute::Length]; StatState mDynamic[3]; MagicEffects mMagicEffects; @@ -76,9 +83,9 @@ namespace ESM int mDrawState; signed char mDeathAnimation; ESM::TimeStamp mTimeOfDeath; - int mLevel; + std::map mCorprusSpells; SpellState mSpells; ActiveSpells mActiveSpells; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 6696ed4786..cda4113140 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 9; +int ESM::SavedGame::sCurrentFormat = 10; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp index a21078e105..2eb1e78679 100644 --- a/components/esm/spellstate.cpp +++ b/components/esm/spellstate.cpp @@ -33,23 +33,36 @@ namespace ESM mSpells[id] = state; } + // Obsolete while (esm.isNextSub("PERM")) { std::string spellId = esm.getHString(); - std::vector permEffectList; - while (esm.isNextSub("EFID")) + + while (true) { + ESM_Context restorePoint = esm.getContext(); + + if (!esm.isNextSub("EFID")) + break; + PermanentSpellEffectInfo info; esm.getHT(info.mId); - esm.getHNT(info.mArg, "ARG_"); - esm.getHNT(info.mMagnitude, "MAGN"); + if (esm.isNextSub("BASE")) + { + esm.restoreContext(restorePoint); + return; + } + else + esm.getHNT(info.mArg, "ARG_"); + esm.getHNT(info.mMagnitude, "MAGN"); permEffectList.push_back(info); } mPermanentSpellEffects[spellId] = permEffectList; } + // Obsolete while (esm.isNextSub("CORP")) { std::string id = esm.getHString(); @@ -91,19 +104,6 @@ namespace ESM esm.writeHNT("PURG", *pIt); } - for (std::map >::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) - { - esm.writeHNString("PERM", it->first); - - const std::vector & effects = it->second; - for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) - { - esm.writeHNT("EFID", effectIt->mId); - esm.writeHNT("ARG_", effectIt->mArg); - esm.writeHNT("MAGN", effectIt->mMagnitude); - } - } - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) { esm.writeHNString("CORP", it->first); diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index ec613afabe..55c57611a2 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -29,15 +29,16 @@ namespace ESM float mMagnitude; }; - struct SpellParams { + struct SpellParams + { std::map mEffectRands; std::set mPurgedEffects; }; typedef std::map TContainer; TContainer mSpells; + // FIXME: obsolete, used only for old saves std::map > mPermanentSpellEffects; - std::map mCorprusSpells; std::map mUsedPowers;