diff --git a/CHANGELOG.md b/CHANGELOG.md index b37fed31fd..58d111dd01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) + Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a9b1f461ca..4ca5b37cf8 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -112,7 +112,10 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data (new CreatureCustomData); + auto tempData = std::make_unique(); + CreatureCustomData* data = tempData.get(); + MWMechanics::CreatureCustomDataResetter resetter(ptr); + ptr.getRefData().setCustomData(std::move(tempData)); MWWorld::LiveCellRef *ref = ptr.get(); @@ -156,10 +159,7 @@ namespace MWClass data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); - data->mCreatureStats.setNeedRecalcDynamicStats(false); - - // store - ptr.getRefData().setCustomData(std::move(data)); + resetter.mPtr = {}; getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index becfb8719f..b8df6747b3 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -303,7 +303,11 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data(new NpcCustomData); + bool recalculate = false; + auto tempData = std::make_unique(); + NpcCustomData* data = tempData.get(); + MWMechanics::CreatureCustomDataResetter resetter(ptr); + ptr.getRefData().setCustomData(std::move(tempData)); MWWorld::LiveCellRef *ref = ptr.get(); @@ -334,8 +338,6 @@ namespace MWClass data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); - - data->mNpcStats.setNeedRecalcDynamicStats(false); } else { @@ -351,7 +353,7 @@ namespace MWClass autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised); - data->mNpcStats.setNeedRecalcDynamicStats(true); + recalculate = true; } // Persistent actors with 0 health do not play death animation @@ -387,7 +389,9 @@ namespace MWClass data->mNpcStats.setGoldPool(gold); // store - ptr.getRefData().setCustomData(std::move(data)); + resetter.mPtr = {}; + if(recalculate) + data->mNpcStats.recalculateMagicka(); // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d85946dbd5..0af65844f9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -240,10 +240,7 @@ namespace MWMechanics { // magic effects adjustMagicEffects (ptr, duration); - if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) - calculateDynamicStats (ptr); - calculateCreatureStatModifiers (ptr, duration); // fatigue restoration calculateRestoration(ptr, duration); } @@ -654,29 +651,6 @@ namespace MWMechanics updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f); } - void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) - { - CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - - float intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); - - float base = 1.f; - if (ptr == getPlayer()) - base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); - else - base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); - - double magickaFactor = base + - creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; - - DynamicStat magicka = creatureStats.getMagicka(); - float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); - float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; - magicka.setModified(magicka.getModified() + diff, 0); - magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); - creatureStats.setMagicka(magicka); - } - void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); @@ -771,14 +745,6 @@ namespace MWMechanics stats.setFatigue (fatigue); } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) - { - CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); - - if (creatureStats.needToRecalcDynamicStats()) - calculateDynamicStats(ptr); - } - bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); @@ -1711,10 +1677,6 @@ namespace MWMechanics if (iter->first.getType() == ESM::Creature::sRecordId) soulTrap(iter->first); - // Magic effects will be reset later, and the magic effect that could kill the actor - // needs to be determined now - calculateCreatureStatModifiers(iter->first, 0); - if (cls.isEssential(iter->first)) MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } @@ -1730,8 +1692,6 @@ namespace MWMechanics // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); - // Reset dynamic stats, attributes and skills - calculateCreatureStatModifiers(iter->first, 0); stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism); if (isPlayer) @@ -1816,10 +1776,6 @@ namespace MWMechanics continue; adjustMagicEffects (iter->first, duration); - if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats()) - calculateDynamicStats (iter->first); - - calculateCreatureStatModifiers (iter->first, duration); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); if (animation) @@ -2209,7 +2165,6 @@ namespace MWMechanics void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) { adjustMagicEffects(ptr, 0.f); - calculateCreatureStatModifiers(ptr, 0.f); } bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 9950a591ab..7d44fd06cd 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -43,10 +43,6 @@ namespace MWMechanics void adjustMagicEffects (const MWWorld::Ptr& creature, float duration); - void calculateDynamicStats (const MWWorld::Ptr& ptr); - - void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); - void calculateRestoration (const MWWorld::Ptr& ptr, float duration); void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer); diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index 04cbb8e9f5..4a587dd662 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -29,4 +29,12 @@ namespace MWMechanics const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; } + + CreatureCustomDataResetter::CreatureCustomDataResetter(const MWWorld::Ptr& ptr) : mPtr(ptr) {} + + CreatureCustomDataResetter::~CreatureCustomDataResetter() + { + if(!mPtr.isEmpty()) + mPtr.getRefData().setCustomData({}); + } } diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index a226fc9cb6..a66d8866bf 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -86,6 +86,14 @@ namespace MWMechanics template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); template void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount); + + struct CreatureCustomDataResetter + { + MWWorld::Ptr mPtr; + + CreatureCustomDataResetter(const MWWorld::Ptr& ptr); + ~CreatureCustomDataResetter(); + }; } #endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 8ca6e8b766..6710dcd734 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -7,6 +7,7 @@ #include #include +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" @@ -22,7 +23,7 @@ namespace MWMechanics mTalkedTo (false), mAlarmed (false), mAttacked (false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), - mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), + mFallHeight(0), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) { for (int i=0; i<4; ++i) @@ -146,7 +147,7 @@ namespace MWMechanics mAttributes[index] = value; if (index == ESM::Attribute::Intelligence) - mRecalcMagicka = true; + recalculateMagicka(); else if (index == ESM::Attribute::Strength || index == ESM::Attribute::Willpower || index == ESM::Attribute::Agility || @@ -208,11 +209,10 @@ namespace MWMechanics void CreatureStats::modifyMagicEffects(const MagicEffects &effects) { - if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() - != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()) - mRecalcMagicka = true; - + bool recalc = effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier(); mMagicEffects.setModifiers(effects); + if(recalc) + recalculateMagicka(); } void CreatureStats::setAiSetting (AiSetting index, Stat value) @@ -400,19 +400,26 @@ namespace MWMechanics return height; } - bool CreatureStats::needToRecalcDynamicStats() + void CreatureStats::recalculateMagicka() { - if (mRecalcMagicka) - { - mRecalcMagicka = false; - return true; - } - return false; - } + auto world = MWBase::Environment::get().getWorld(); + float intelligence = getAttribute(ESM::Attribute::Intelligence).getModified(); - void CreatureStats::setNeedRecalcDynamicStats(bool val) - { - mRecalcMagicka = val; + float base = 1.f; + const auto& player = world->getPlayerPtr(); + if (this == &player.getClass().getCreatureStats(player)) + base = world->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); + else + base = world->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); + + double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; + + DynamicStat magicka = getMagicka(); + float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); + float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; + magicka.setModified(magicka.getModified() + diff, 0); + magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); + setMagicka(magicka); } void CreatureStats::setKnockedDown(bool value) @@ -532,7 +539,6 @@ namespace MWMechanics state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; state.mLastHitAttemptObject = mLastHitAttemptObject; - state.mRecalcDynamicStats = mRecalcMagicka; state.mDrawState = mDrawState; state.mLevel = mLevel; state.mActorId = mActorId; @@ -586,7 +592,6 @@ namespace MWMechanics mFallHeight = state.mFallHeight; mLastHitObject = state.mLastHitObject; mLastHitAttemptObject = state.mLastHitAttemptObject; - mRecalcMagicka = state.mRecalcDynamicStats; mDrawState = DrawState_(state.mDrawState); mLevel = state.mLevel; mActorId = state.mActorId; @@ -627,6 +632,8 @@ namespace MWMechanics if (state.mHasAiSettings) for (int i=0; i<4; ++i) mAiSettings[i].readState(state.mAiSettings[i]); + if(state.mRecalcDynamicStats) + recalculateMagicka(); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index d234d14486..e5c4f6fa41 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -65,8 +65,6 @@ namespace MWMechanics std::string mLastHitObject; // The last object to hit this actor std::string mLastHitAttemptObject; // The last object to attempt to hit this actor - bool mRecalcMagicka; - // For merchants: the last time items were restocked and gold pool refilled. MWWorld::TimeStamp mLastRestock; @@ -103,8 +101,7 @@ namespace MWMechanics DrawState_ getDrawState() const; void setDrawState(DrawState_ state); - bool needToRecalcDynamicStats(); - void setNeedRecalcDynamicStats(bool val); + void recalculateMagicka(); float getFallHeight() const; void addToFallHeight(float height); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 149056e605..e01ed3c425 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -77,7 +77,7 @@ namespace MWMechanics MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); - npcStats.setNeedRecalcDynamicStats(true); + npcStats.recalculateMagicka(); const ESM::NPC *player = ptr.get()->mBase; @@ -222,7 +222,6 @@ namespace MWMechanics // forced update and current value adjustments mActors.updateActor (ptr, 0); - mActors.updateActor (ptr, 0); for (int i=0; i<3; ++i) { diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 0e67f06ba2..d9e32b8b9f 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -279,7 +279,7 @@ namespace namespace MWMechanics { -void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage) +void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage, bool& recalculateMagicka) { const auto world = MWBase::Environment::get().getWorld(); bool godmode = target == getPlayer() && world->getGodModeState(); @@ -609,7 +609,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co fortifySkill(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::FortifyMaximumMagicka: - target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + recalculateMagicka = true; break; case ESM::MagicEffect::AbsorbHealth: case ESM::MagicEffect::AbsorbMagicka: @@ -687,13 +687,14 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac const auto world = MWBase::Environment::get().getWorld(); bool invalid = false; bool receivedMagicDamage = false; + bool recalculateMagicka = false; if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen()) { spellParams.worsen(); for(auto& otherEffect : spellParams.getEffects()) { if(isCorprusEffect(otherEffect)) - applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage); + applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, recalculateMagicka); } if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); @@ -815,7 +816,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac if(effect.mEffectId == ESM::MagicEffect::Corprus) spellParams.worsen(); else - applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage); + applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, recalculateMagicka); effect.mMagnitude = magnitude; magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); } @@ -832,6 +833,8 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; if (receivedMagicDamage && target == getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + if(recalculateMagicka) + target.getClass().getCreatureStats(target).recalculateMagicka(); return false; } @@ -981,7 +984,7 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara fortifySkill(target, effect, -effect.mMagnitude); break; case ESM::MagicEffect::FortifyMaximumMagicka: - target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + target.getClass().getCreatureStats(target).recalculateMagicka(); break; case ESM::MagicEffect::AbsorbAttribute: {