Don't wait a frame to recalculate magicka

pull/3198/head
Evil Eye 3 years ago
parent 41318a585f
commit 289e1544d2

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

@ -112,7 +112,10 @@ namespace MWClass
{
if (!ptr.getRefData().getCustomData())
{
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
auto tempData = std::make_unique<CreatureCustomData>();
CreatureCustomData* data = tempData.get();
MWMechanics::CreatureCustomDataResetter resetter(ptr);
ptr.getRefData().setCustomData(std::move(tempData));
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
@ -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());

@ -303,7 +303,11 @@ namespace MWClass
{
if (!ptr.getRefData().getCustomData())
{
std::unique_ptr<NpcCustomData> data(new NpcCustomData);
bool recalculate = false;
auto tempData = std::make_unique<NpcCustomData>();
NpcCustomData* data = tempData.get();
MWMechanics::CreatureCustomDataResetter resetter(ptr);
ptr.getRefData().setCustomData(std::move(tempData));
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
@ -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

@ -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<ESM::GameSetting>().find("fPCbaseMagickaMult")->mValue.getFloat();
else
base = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCbaseMagickaMult")->mValue.getFloat();
double magickaFactor = base +
creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
DynamicStat<float> magicka = creatureStats.getMagicka();
float diff = (static_cast<int>(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

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

@ -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({});
}
}

@ -86,6 +86,14 @@ namespace MWMechanics
template void modifyBaseInventory<ESM::Creature>(const std::string& actorId, const std::string& itemId, int amount);
template void modifyBaseInventory<ESM::NPC>(const std::string& actorId, const std::string& itemId, int amount);
template void modifyBaseInventory<ESM::Container>(const std::string& containerId, const std::string& itemId, int amount);
struct CreatureCustomDataResetter
{
MWWorld::Ptr mPtr;
CreatureCustomDataResetter(const MWWorld::Ptr& ptr);
~CreatureCustomDataResetter();
};
}
#endif

@ -7,6 +7,7 @@
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#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<int> 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<ESM::GameSetting>().find("fPCbaseMagickaMult")->mValue.getFloat();
else
base = world->getStore().get<ESM::GameSetting>().find("fNPCbaseMagickaMult")->mValue.getFloat();
double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
DynamicStat<float> magicka = getMagicka();
float diff = (static_cast<int>(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)

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

@ -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<ESM::NPC>()->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)
{

@ -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:
{

Loading…
Cancel
Save