1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 17:29:55 +00:00

Overhaul magic effects to work with onApply and onEnd events

This commit is contained in:
Evil Eye 2021-08-27 20:07:50 +02:00
parent 5ce2eff3bd
commit dc1fe62dde
65 changed files with 2360 additions and 2734 deletions

View file

@ -1,6 +1,7 @@
0.48.0
------
Bug #1751: Birthsign abilities increase modified attribute values instead of base ones
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)
@ -16,13 +17,18 @@
Bug #5453: Magic effect VFX are offset for creatures
Bug #5483: AutoCalc flag is not used to calculate spells cost
Bug #5508: Engine binary links to Qt without using it
Bug #5596: Effects in constant spells should not be merged
Bug #5621: Drained stats cannot be restored
Bug #5755: Active grid object paging - disappearing textures
Bug #5788: Texture editing parses the selected indexes wrongly
Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention
Bug #5842: GetDisposition adds temporary disposition change from different actors
Bug #5863: GetEffect should return true after the player has teleported
Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher
Bug #6051: NaN water height in ESM file is not handled gracefully
Bug #6066: addtopic "return" does not work from within script. No errors thrown
Bug #6067: esp loader fails in for certain subrecord orders
Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends
Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime
Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
Bug #6115: Showmap overzealous matching
@ -36,6 +42,7 @@
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
Bug #6197: Infinite Casting Loop
Bug #6223: Some Constant Effect Bound Items inconsistencies
Bug #6273: Respawning NPCs rotation is inconsistent
Bug #6282: Laura craft doesn't follow the player character
Bug #6283: Avis Dorsey follows you after her death
@ -45,8 +52,10 @@
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
Feature #2780: A way to see current OpenMW version in the console
Feature #3616: Allow Zoom levels on the World Map
Feature #4297: Implement APPLIED_ONCE flag for magic effects
Feature #4595: Unique object identifier
Feature #4737: Handle instance move from one cell to another
Feature #5198: Implement "Magic effect expired" event
Feature #5489: MCP: Telekinesis fix for activators
Feature #5996: Support Lua scripts in OpenMW
Feature #6017: Separate persistent and temporary cell references when saving

View file

@ -124,11 +124,9 @@ public:
{
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
mContext->mPlayerBase = npc;
ESM::SpellState::SpellParams empty;
// FIXME: player start spells and birthsign spells aren't listed here,
// need to fix openmw to account for this
for (const auto & spell : npc.mSpells.mList)
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty;
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList;
// Clear the list now that we've written it, this prevents issues cropping up with
// ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal.

View file

@ -92,8 +92,8 @@ add_openmw_dir (mwmechanics
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil tickableeffects
spellabsorption linkedeffects
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
spellabsorption spelleffects
)
add_openmw_dir (mwstate

View file

@ -277,8 +277,6 @@ 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;
};
}

View file

@ -539,7 +539,7 @@ namespace MWBase
virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0;
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) = 0;
virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0;
virtual void updateProjectilesCasters() = 0;
@ -591,7 +591,7 @@ namespace MWBase
virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster,
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
const std::string& sourceName, const bool fromProjectile=false) = 0;
const std::string& sourceName, const bool fromProjectile=false, int slot = 0) = 0;
virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;

View file

@ -72,13 +72,6 @@ namespace
return {question, {r2, r1, r0}, sound};
}
}
void updatePlayerHealth()
{
MWWorld::Ptr player = MWMechanics::getPlayer();
MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);
npcStats.updateHealth();
}
}
namespace MWGui
@ -372,8 +365,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
mPickClassDialog = nullptr;
}
updatePlayerHealth();
}
void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow)
@ -448,8 +439,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
mRaceDialog = nullptr;
}
updatePlayerHealth();
}
void CharacterCreation::onRaceDialogBack()
@ -477,8 +466,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
mBirthSignDialog = nullptr;
}
updatePlayerHealth();
}
void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow)
@ -527,7 +514,6 @@ namespace MWGui
// Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later
mCreateClassDialog->setVisible(false);
}
updatePlayerHealth();
}
void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow)
@ -719,8 +705,6 @@ namespace MWGui
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(mGenerateClass);
mPlayerClass = *klass;
updatePlayerHealth();
}
void CharacterCreation::onGenerateClassBack()

View file

@ -262,7 +262,7 @@ namespace MWGui
}
// Clean up summoned creatures as well
std::map<ESM::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
auto& creatureMap = creatureStats.getSummonedCreatureMap();
for (const auto& creature : creatureMap)
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second);
creatureMap.clear();

View file

@ -82,10 +82,7 @@ namespace MWGui
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;
}
player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24);
std::set<int> skills;
for (int day=0; day<mDays; ++day)

View file

@ -99,10 +99,8 @@ namespace MWGui
std::vector<const ESM::Spell*> spellsToSort;
for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter)
for (const ESM::Spell* spell : merchantSpells)
{
const ESM::Spell* spell = iter->first;
if (spell->mData.mType!=ESM::Spell::ST_Spell)
continue; // don't try to sell diseases, curses or powers
@ -115,10 +113,10 @@ namespace MWGui
continue;
}
if (playerHasSpell(iter->first->mId))
if (playerHasSpell(spell->mId))
continue;
spellsToSort.push_back(iter->first);
spellsToSort.push_back(spell);
}
std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells);

View file

@ -520,10 +520,8 @@ namespace MWGui
std::vector<short> knownEffects;
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
for (const ESM::Spell* spell : spells)
{
const ESM::Spell* spell = it->first;
// only normal spells count
if (spell->mData.mType != ESM::Spell::ST_Spell)
continue;

View file

@ -24,50 +24,33 @@
namespace MWGui
{
void EffectSourceVisitor::visit (MWMechanics::EffectKey key, int effectIndex,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime, float totalTime)
{
MagicEffectInfo newEffectSource;
newEffectSource.mKey = key;
newEffectSource.mMagnitude = static_cast<int>(magnitude);
newEffectSource.mPermanent = mIsPermanent;
newEffectSource.mRemainingTime = remainingTime;
newEffectSource.mSource = sourceName;
newEffectSource.mTotalTime = totalTime;
mEffectSources[key.mId].push_back(newEffectSource);
}
void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize)
{
// TODO: Tracking add/remove/expire would be better than force updating every frame
MWWorld::Ptr player = MWMechanics::getPlayer();
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
EffectSourceVisitor visitor;
// permanent item enchantments & permanent spells
visitor.mIsPermanent = true;
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
store.visitEffectSources(visitor);
stats.getSpells().visitEffectSources(visitor);
// now add lasting effects
visitor.mIsPermanent = false;
stats.getActiveSpells().visitEffectSources(visitor);
std::map <int, std::vector<MagicEffectInfo> >& effects = visitor.mEffectSources;
std::map<int, std::vector<MagicEffectInfo>> effects;
for(const auto& params : stats.getActiveSpells())
{
for(const auto& effect : params.getEffects())
{
if(!effect.mMagnitude)
continue;
MagicEffectInfo newEffectSource;
newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg);
newEffectSource.mMagnitude = static_cast<int>(effect.mMagnitude);
newEffectSource.mPermanent = effect.mDuration == -1.f;
newEffectSource.mRemainingTime = effect.mTimeLeft;
newEffectSource.mSource = params.getDisplayName();
newEffectSource.mTotalTime = effect.mDuration;
effects[effect.mEffectId].push_back(newEffectSource);
}
}
int w=2;
for (auto& effectInfoPair : effects)
for (const auto& [effectId, effectInfos] : effects)
{
const int effectId = effectInfoPair.first;
const ESM::MagicEffect* effect =
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectId);
@ -78,7 +61,6 @@ namespace MWGui
static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMagicStartIconBlink")->mValue.getFloat();
std::vector<MagicEffectInfo>& effectInfos = effectInfoPair.second;
bool addNewLine = false;
for (const MagicEffectInfo& effectInfo : effectInfos)
{

View file

@ -37,20 +37,6 @@ namespace MWGui
bool mPermanent; // the effect is permanent
};
class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor
{
public:
bool mIsPermanent;
std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
virtual ~EffectSourceVisitor() {}
void visit (MWMechanics::EffectKey key, int effectIndex,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1) override;
};
class SpellIcons
{
public:

View file

@ -92,9 +92,8 @@ namespace MWGui
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
for (const ESM::Spell* spell : spells)
{
const ESM::Spell* spell = it->first;
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
continue;

View file

@ -5,97 +5,279 @@
#include <components/esm/loadmgef.hpp>
#include "creaturestats.hpp"
#include "spellcasting.hpp"
#include "spelleffects.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
namespace
{
bool merge(std::vector<ESM::ActiveEffect>& present, const std::vector<ESM::ActiveEffect>& queued)
{
// Can't merge if we already have an effect with the same effect index
auto problem = std::find_if(queued.begin(), queued.end(), [&] (const auto& qEffect)
{
return std::find_if(present.begin(), present.end(), [&] (const auto& pEffect) { return pEffect.mEffectIndex == qEffect.mEffectIndex; }) != present.end();
});
if(problem != queued.end())
return false;
present.insert(present.end(), queued.begin(), queued.end());
return true;
}
void addEffects(std::vector<ESM::ActiveEffect>& effects, const ESM::EffectList& list, bool ignoreResistances = false)
{
int currentEffectIndex = 0;
for(const auto& enam : list.mList)
{
ESM::ActiveEffect effect;
effect.mEffectId = enam.mEffectID;
effect.mArg = MWMechanics::EffectKey(enam).mArg;
effect.mMagnitude = 0.f;
effect.mMinMagnitude = enam.mMagnMin;
effect.mMaxMagnitude = enam.mMagnMax;
effect.mEffectIndex = currentEffectIndex++;
effect.mFlags = ESM::ActiveEffect::Flag_None;
if(ignoreResistances)
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances;
effect.mDuration = -1;
effect.mTimeLeft = -1;
effects.emplace_back(effect);
}
}
}
namespace MWMechanics
{
void ActiveSpells::update(float duration) const
ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells)
{
bool rebuild = false;
mActiveSpells.mIterating = true;
}
// Erase no longer active spells and effects
if (duration > 0)
ActiveSpells::IterationGuard::~IterationGuard()
{
mActiveSpells.mIterating = false;
}
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster)
: mId(cast.mId), mDisplayName(cast.mSourceName), mCasterActorId(-1), mSlot(cast.mSlot), mType(cast.mType), mWorsenings(-1)
{
if(!caster.isEmpty() && caster.getClass().isActor())
mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId();
}
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances)
: mId(spell->mId), mDisplayName(spell->mName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()), mSlot(0)
, mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent), mWorsenings(-1)
{
assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power);
addEffects(mEffects, spell->mEffects, ignoreResistances);
}
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor)
: mId(item.getCellRef().getRefId()), mDisplayName(item.getClass().getName(item)), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mSlot(slotIndex), mType(ESM::ActiveSpells::Type_Enchantment), mWorsenings(-1)
{
assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect);
addEffects(mEffects, enchantment->mEffects);
}
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params)
: mId(params.mId), mEffects(params.mEffects), mDisplayName(params.mDisplayName), mCasterActorId(params.mCasterActorId)
, mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0)
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
{}
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
{
ESM::ActiveSpells::ActiveSpellParams params;
params.mId = mId;
params.mEffects = mEffects;
params.mDisplayName = mDisplayName;
params.mCasterActorId = mCasterActorId;
params.mItem.unset();
if(mSlot)
{
TContainer::iterator iter (mSpells.begin());
while (iter!=mSpells.end())
// Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing
// mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148)
params.mItem = { static_cast<unsigned int>(mSlot), 0 };
}
params.mType = mType;
params.mWorsenings = mWorsenings;
params.mNextWorsening = mNextWorsening.toEsm();
return params;
}
void ActiveSpells::ActiveSpellParams::worsen()
{
++mWorsenings;
if(!mWorsenings)
mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp();
mNextWorsening += CorprusStats::sWorseningPeriod;
}
bool ActiveSpells::ActiveSpellParams::shouldWorsen() const
{
return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening;
}
void ActiveSpells::ActiveSpellParams::resetWorsenings()
{
mWorsenings = -1;
}
void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
{
const auto& creatureStats = ptr.getClass().getCreatureStats(ptr);
assert(&creatureStats.getActiveSpells() == this);
IterationGuard guard{*this};
// Erase no longer active spells and effects
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{
if(spellIt->mType != ESM::ActiveSpells::Type_Temporary && spellIt->mType != ESM::ActiveSpells::Type_Consumable)
{
if (!timeToExpire (iter))
++spellIt;
continue;
}
bool removedSpell = false;
for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();)
{
if(effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f)
{
mSpells.erase (iter++);
rebuild = true;
auto effect = *effectIt;
effectIt = spellIt->mEffects.erase(effectIt);
onMagicEffectRemoved(ptr, *spellIt, effect);
removedSpell = applyPurges(ptr, &spellIt, &effectIt);
if(removedSpell)
break;
}
else
{
bool interrupt = false;
std::vector<ActiveEffect>& effects = iter->second.mEffects;
for (std::vector<ActiveEffect>::iterator effectIt = effects.begin(); effectIt != effects.end();)
++effectIt;
}
}
if(removedSpell)
continue;
if(spellIt->mEffects.empty())
spellIt = mSpells.erase(spellIt);
else
++spellIt;
}
for(const auto& spell : mQueue)
addToSpells(ptr, spell);
mQueue.clear();
// Vanilla only does this on cell change I think
const auto& spells = creatureStats.getSpells();
for(const ESM::Spell* spell : spells)
{
if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId))
mSpells.emplace_back(ActiveSpellParams{spell, ptr});
}
if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
{
auto& store = ptr.getClass().getInventoryStore(ptr);
if(store.getInvListener() != nullptr)
{
bool playNonLooping = !store.isFirstEquip();
const auto world = MWBase::Environment::get().getWorld();
for(int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
{
auto slot = store.getSlot(slotIndex);
if(slot == store.end())
continue;
const auto& enchantmentId = slot->getClass().getEnchantment(*slot);
if(enchantmentId.empty())
continue;
const ESM::Enchantment* enchantment = world->getStore().get<ESM::Enchantment>().find(enchantmentId);
if(enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
continue;
if(std::find_if(mSpells.begin(), mSpells.end(), [&] (const ActiveSpellParams& params)
{
if (effectIt->mTimeLeft <= 0)
{
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
{
effectIt->mTimeLeft -= duration;
++effectIt;
}
}
if (!interrupt)
++iter;
return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId();
}) != mSpells.end())
continue;
ActiveSpellParams params(*slot, enchantment, slotIndex, ptr);
mSpells.emplace_back(params);
for(const auto& effect : params.mEffects)
MWMechanics::playEffects(ptr, *world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId), playNonLooping);
}
}
}
if (mSpellsChanged)
// Update effects
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{
mSpellsChanged = false;
rebuild = true;
}
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
bool removedSpell = false;
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
{
bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
if(remove)
it = spellIt->mEffects.erase(it);
else
++it;
removedSpell = applyPurges(ptr, &spellIt, &it);
if(removedSpell)
break;
}
if(removedSpell)
continue;
if (rebuild)
rebuildEffects();
bool remove = false;
if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent)
remove = !spells.hasSpell(spellIt->mId);
else if(spellIt->mType == ESM::ActiveSpells::Type_Enchantment)
{
const auto& store = ptr.getClass().getInventoryStore(ptr);
auto slot = store.getSlot(spellIt->mSlot);
remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId;
}
if(remove)
{
auto params = *spellIt;
spellIt = mSpells.erase(spellIt);
for(const auto& effect : params.mEffects)
onMagicEffectRemoved(ptr, params, effect);
applyPurges(ptr, &spellIt);
continue;
}
++spellIt;
}
}
void ActiveSpells::rebuildEffects() const
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
{
mEffects = MagicEffects();
for (TIterator iter (begin()); iter!=end(); ++iter)
if(spell.mType != ESM::ActiveSpells::Type_Consumable)
{
const std::vector<ActiveEffect>& effects = iter->second.mEffects;
for (std::vector<ActiveEffect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing)
{
if (effectIt->mTimeLeft > 0)
mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude));
return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot;
});
if(found != mSpells.end())
{
if(merge(found->mEffects, spell.mEffects))
return;
auto params = *found;
mSpells.erase(found);
for(const auto& effect : params.mEffects)
onMagicEffectRemoved(ptr, params, effect);
}
}
mSpells.emplace_back(spell);
}
ActiveSpells::ActiveSpells()
: mSpellsChanged (false)
ActiveSpells::ActiveSpells() : mIterating(false)
{}
const MagicEffects& ActiveSpells::getMagicEffects() const
{
update(0.f);
return mEffects;
}
ActiveSpells::TIterator ActiveSpells::begin() const
{
return mSpells.begin();
@ -106,246 +288,159 @@ namespace MWMechanics
return mSpells.end();
}
double ActiveSpells::timeToExpire (const TIterator& iterator) const
{
const std::vector<ActiveEffect>& effects = iterator->second.mEffects;
float duration = 0;
for (std::vector<ActiveEffect>::const_iterator iter (effects.begin());
iter!=effects.end(); ++iter)
{
if (iter->mTimeLeft > duration)
duration = iter->mTimeLeft;
}
if (duration < 0)
return 0;
return duration;
}
bool ActiveSpells::isSpellActive(const std::string& id) const
{
for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell)
{
if (Misc::StringUtils::ciEqual(iter->first, id))
return true;
}
return false;
return Misc::StringUtils::ciEqual(spell.mId, id);
}) != mSpells.end();
}
const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const
void ActiveSpells::addSpell(const ActiveSpellParams& params)
{
return mSpells;
mQueue.emplace_back(params);
}
void ActiveSpells::addSpell(const std::string &id, bool stack, const std::vector<ActiveEffect>& effects,
const std::string &displayName, int casterActorId)
void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor)
{
TContainer::iterator it(mSpells.find(id));
ActiveSpellParams params;
params.mEffects = effects;
params.mDisplayName = displayName;
params.mCasterActorId = casterActorId;
if (it == end() || stack)
{
mSpells.insert(std::make_pair(id, params));
}
else
{
// addSpell() is called with effects for a range.
// but a spell may have effects with different ranges (e.g. Touch & Target)
// so, if we see new effects for same spell assume additional
// spell effects and add to existing effects of spell
mergeEffects(params.mEffects, it->second.mEffects);
it->second = params;
}
mSpellsChanged = true;
mQueue.emplace_back(ActiveSpellParams{spell, actor, true});
}
void ActiveSpells::mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from)
void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr)
{
for (std::vector<ActiveEffect>::const_iterator effect(from.begin()); effect != from.end(); ++effect)
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
mPurges.emplace(predicate);
if(!mIterating)
{
// if effect is not in addTo, add it
bool missing = true;
for (std::vector<ActiveEffect>::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter)
IterationGuard guard{*this};
applyPurges(ptr);
}
}
void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr)
{
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
mPurges.emplace(predicate);
if(!mIterating)
{
IterationGuard guard{*this};
applyPurges(ptr);
}
}
bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell, std::vector<ActiveEffect>::iterator* currentEffect)
{
bool removedCurrentSpell = false;
while(!mPurges.empty())
{
auto predicate = mPurges.front();
mPurges.pop();
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{
if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg))
bool isCurrentSpell = currentSpell && *currentSpell == spellIt;
std::visit([&] (auto&& variant)
{
missing = false;
break;
}
}
if (missing)
{
addTo.push_back(*effect);
using T = std::decay_t<decltype(variant)>;
if constexpr (std::is_same_v<T, ParamsPredicate>)
{
if(variant(*spellIt))
{
auto params = *spellIt;
spellIt = mSpells.erase(spellIt);
if(isCurrentSpell)
{
*currentSpell = spellIt;
removedCurrentSpell = true;
}
for(const auto& effect : params.mEffects)
onMagicEffectRemoved(ptr, params, effect);
}
else
++spellIt;
}
else
{
static_assert(std::is_same_v<T, EffectPredicate>, "Non-exhaustive visitor");
for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();)
{
if(variant(*spellIt, *effectIt))
{
auto effect = *effectIt;
if(isCurrentSpell && currentEffect)
{
auto distance = std::distance(spellIt->mEffects.begin(), *currentEffect);
if(effectIt <= *currentEffect)
distance--;
effectIt = spellIt->mEffects.erase(effectIt);
*currentEffect = spellIt->mEffects.begin() + distance;
}
else
effectIt = spellIt->mEffects.erase(effectIt);
onMagicEffectRemoved(ptr, *spellIt, effect);
}
else
++effectIt;
}
++spellIt;
}
}, predicate);
}
}
return removedCurrentSpell;
}
void ActiveSpells::removeEffects(const std::string &id)
void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const std::string &id)
{
for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell)
purge([=] (const ActiveSpellParams& params)
{
if (spell->first == id)
{
spell->second.mEffects.clear();
mSpellsChanged = true;
}
}
return params.mId == id;
}, ptr);
}
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId)
{
for (TContainer::const_iterator it = begin(); it != end(); ++it)
purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect)
{
for (std::vector<ActiveEffect>::const_iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end(); ++effectIt)
{
std::string name = it->second.mDisplayName;
float magnitude = effectIt->mMagnitude;
if (magnitude)
visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration);
}
}
return effect.mEffectId == effectId;
}, ptr);
}
void ActiveSpells::purgeAll(float chance, bool spellOnly)
void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); )
purge([=] (const ActiveSpellParams& params)
{
const std::string spellId = it->first;
// if spellOnly is true, dispell only spells. Leave potions, enchanted items etc.
if (spellOnly)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
if (!spell || spell->mData.mType != ESM::Spell::ST_Spell)
{
++it;
continue;
}
}
if (Misc::Rng::roll0to99() < chance)
mSpells.erase(it++);
else
++it;
}
mSpellsChanged = true;
return params.mCasterActorId == casterActorId;
}, ptr);
}
void ActiveSpells::purgeEffect(short effectId)
void ActiveSpells::clear(const MWWorld::Ptr& ptr)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
mQueue.clear();
purge([] (const ActiveSpellParams& params) { return true; }, ptr);
}
void ActiveSpells::skipWorsenings(double hours)
{
for(auto& spell : mSpells)
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
{
if (effectIt->mEffectId == effectId)
effectIt = it->second.mEffects.erase(effectIt);
else
++effectIt;
}
if(spell.mWorsenings >= 0)
spell.mNextWorsening += hours;
}
mSpellsChanged = true;
}
void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
{
if (effectIt->mEffectId == effectId && it->first == sourceId && (effectIndex < 0 || effectIndex == effectIt->mEffectIndex))
effectIt = it->second.mEffects.erase(effectIt);
else
++effectIt;
}
}
mSpellsChanged = true;
}
void ActiveSpells::purge(int casterActorId)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
{
if (it->second.mCasterActorId == casterActorId)
effectIt = it->second.mEffects.erase(effectIt);
else
++effectIt;
}
}
mSpellsChanged = true;
}
void ActiveSpells::purgeCorprusDisease()
{
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
{
bool hasCorprusEffect = false;
for (std::vector<ActiveEffect>::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();
mSpellsChanged = true;
}
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
{
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it)
{
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
ESM::ActiveSpells::ActiveSpellParams params;
params.mEffects = it->second.mEffects;
params.mCasterActorId = it->second.mCasterActorId;
params.mDisplayName = it->second.mDisplayName;
state.mSpells.insert (std::make_pair(it->first, params));
}
for(const auto& spell : mSpells)
state.mSpells.emplace_back(spell.toEsm());
for(const auto& spell : mQueue)
state.mQueue.emplace_back(spell.toEsm());
}
void ActiveSpells::readState(const ESM::ActiveSpells &state)
{
for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
{
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
ActiveSpellParams params;
params.mEffects = it->second.mEffects;
params.mCasterActorId = it->second.mCasterActorId;
params.mDisplayName = it->second.mDisplayName;
mSpells.insert (std::make_pair(it->first, params));
mSpellsChanged = true;
}
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells)
mSpells.emplace_back(ActiveSpellParams{spell});
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
mQueue.emplace_back(ActiveSpellParams{spell});
}
}

View file

@ -1,40 +1,83 @@
#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H
#define GAME_MWMECHANICS_ACTIVESPELLS_H
#include <map>
#include <vector>
#include <functional>
#include <list>
#include <queue>
#include <string>
#include <variant>
#include <vector>
#include <components/esm/activespells.hpp>
#include "../mwworld/timestamp.hpp"
#include "../mwworld/ptr.hpp"
#include "magiceffects.hpp"
#include "spellcasting.hpp"
namespace ESM
{
struct Enchantment;
struct Spell;
}
namespace MWMechanics
{
/// \brief Lasting spell effects
///
/// \note The name of this class is slightly misleading, since it also handels lasting potion
/// \note The name of this class is slightly misleading, since it also handles lasting potion
/// effects.
class ActiveSpells
{
public:
typedef ESM::ActiveEffect ActiveEffect;
struct ActiveSpellParams
using ActiveEffect = ESM::ActiveEffect;
class ActiveSpellParams
{
std::vector<ActiveEffect> mEffects;
MWWorld::TimeStamp mTimeStamp;
std::string mDisplayName;
std::string mId;
std::vector<ActiveEffect> mEffects;
std::string mDisplayName;
int mCasterActorId;
int mSlot;
ESM::ActiveSpells::EffectType mType;
int mWorsenings;
MWWorld::TimeStamp mNextWorsening;
// The caster that inflicted this spell on us
int mCasterActorId;
ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params);
ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false);
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
ESM::ActiveSpells::ActiveSpellParams toEsm() const;
friend class ActiveSpells;
public:
ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster);
const std::string& getId() const { return mId; }
const std::vector<ActiveEffect>& getEffects() const { return mEffects; }
std::vector<ActiveEffect>& getEffects() { return mEffects; }
ESM::ActiveSpells::EffectType getType() const { return mType; }
int getCasterActorId() const { return mCasterActorId; }
int getWorsenings() const { return mWorsenings; }
const std::string& getDisplayName() const { return mDisplayName; }
// Increments worsenings count and sets the next timestamp
void worsen();
bool shouldWorsen() const;
void resetWorsenings();
};
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
typedef TContainer::const_iterator TIterator;
typedef std::list<ActiveSpellParams>::const_iterator TIterator;
void readState (const ESM::ActiveSpells& state);
void writeState (ESM::ActiveSpells& state) const;
@ -43,24 +86,29 @@ namespace MWMechanics
TIterator end() const;
void update(float duration) const;
void update(const MWWorld::Ptr& ptr, float duration);
private:
using ParamsPredicate = std::function<bool(const ActiveSpellParams&)>;
using EffectPredicate = std::function<bool(const ActiveSpellParams&, const ESM::ActiveEffect&)>;
using Predicate = std::variant<ParamsPredicate, EffectPredicate>;
mutable TContainer mSpells;
mutable MagicEffects mEffects;
mutable bool mSpellsChanged;
struct IterationGuard
{
ActiveSpells& mActiveSpells;
void rebuildEffects() const;
IterationGuard(ActiveSpells& spells);
~IterationGuard();
};
/// Add any effects that are in "from" and not in "addTo" to "addTo"
void mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from);
std::list<ActiveSpellParams> mSpells;
std::vector<ActiveSpellParams> mQueue;
std::queue<Predicate> mPurges;
bool mIterating;
double timeToExpire (const TIterator& iterator) const;
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
/// expires.
void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell);
const TContainer& getActiveSpells() const;
bool applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell = nullptr, std::vector<ActiveEffect>::iterator* currentEffect = nullptr);
public:
@ -70,40 +118,31 @@ namespace MWMechanics
///
/// \brief addSpell
/// \param id ID for stacking purposes.
/// \param stack If false, the spell is not added if one with the same ID exists already.
/// \param effects
/// \param displayName Name for display in magic menu.
///
void addSpell (const std::string& id, bool stack, const std::vector<ActiveEffect>& effects,
const std::string& displayName, int casterActorId);
void addSpell (const ActiveSpellParams& params);
/// Bypasses resistances
void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor);
/// Removes the active effects from this spell/potion/.. with \a id
void removeEffects (const std::string& id);
void removeEffects (const MWWorld::Ptr& ptr, const std::string& id);
/// Remove all active effects with this effect id
void purgeEffect (short effectId);
void purgeEffect (const MWWorld::Ptr& ptr, short effectId);
/// Remove all active effects with this effect id and source id
void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1);
void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);
/// Remove all active effects, if roll succeeds (for each effect)
void purgeAll(float chance, bool spellOnly = false);
/// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId
void purge (int casterActorId);
/// Remove all effects that were cast by \a casterActorId
void purge (const MWWorld::Ptr& ptr, int casterActorId);
/// Remove all spells
void clear();
void clear(const MWWorld::Ptr& ptr);
bool isSpellActive (const std::string& id) const;
///< case insensitive
void purgeCorprusDisease();
const MagicEffects& getMagicEffects() const;
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
void skipWorsenings(double hours);
};
}

File diff suppressed because it is too large Load diff

View file

@ -41,15 +41,11 @@ namespace MWMechanics
{
std::map<std::string, int> mDeathCount;
void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
void adjustMagicEffects (const MWWorld::Ptr& creature);
void adjustMagicEffects (const MWWorld::Ptr& creature, float duration);
void calculateDynamicStats (const MWWorld::Ptr& ptr);
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
void calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration);
void calculateRestoration (const MWWorld::Ptr& ptr, float duration);
@ -208,7 +204,6 @@ namespace MWMechanics
private:
void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl);
void applyCureEffects (const MWWorld::Ptr& actor);
PtrActorMap mActors;
float mTimerDisposeSummonsCorpses;

View file

@ -208,14 +208,14 @@ namespace MWMechanics
}
}
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
for (const ESM::Spell* spell : spells)
{
float rating = rateSpell(it->first, actor, enemy);
float rating = rateSpell(spell, actor, enemy);
if (rating > bestActionRating)
{
bestActionRating = rating;
bestAction.reset(new ActionSpell(it->first->mId));
antiFleeRating = vanillaRateSpell(it->first, actor, enemy);
bestAction.reset(new ActionSpell(spell->mId));
antiFleeRating = vanillaRateSpell(spell, actor, enemy);
}
}
@ -266,9 +266,9 @@ namespace MWMechanics
}
}
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
for (const ESM::Spell* spell : spells)
{
float rating = rateSpell(it->first, actor, enemy);
float rating = rateSpell(spell, actor, enemy);
if (rating > bestActionRating)
{
bestActionRating = rating;

View file

@ -545,20 +545,12 @@ namespace MWMechanics
mAiSequence.writeState(state.mAiSequence);
mMagicEffects.writeState(state.mMagicEffects);
state.mSummonedCreatureMap = mSummonedCreatures;
state.mSummonedCreatures = mSummonedCreatures;
state.mSummonGraveyard = mSummonGraveyard;
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; i<ESM::Attribute::Length; ++i)
state.mCorprusSpells[it->first].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)
@ -618,7 +610,7 @@ namespace MWMechanics
// We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects
auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell)
{
const auto& effects = spell.second.mEffects;
const auto& effects = spell.getEffects();
return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect)
{
return effect.mEffectId == effectId;
@ -629,21 +621,12 @@ namespace MWMechanics
}
}
mSummonedCreatures = state.mSummonedCreatureMap;
mSummonedCreatures = state.mSummonedCreatures;
mSummonGraveyard = state.mSummonGraveyard;
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; i<ESM::Attribute::Length; ++i)
mCorprusSpells[it->first].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)
@ -710,7 +693,7 @@ namespace MWMechanics
return mTimeOfDeath;
}
std::map<ESM::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
std::multimap<int, int>& CreatureStats::getSummonedCreatureMap()
{
return mSummonedCreatures;
}
@ -719,23 +702,4 @@ namespace MWMechanics
{
return mSummonGraveyard;
}
std::map<std::string, CorprusStats> &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);
}
}
}

View file

@ -1,6 +1,7 @@
#ifndef GAME_MWMECHANICS_CREATURESTATS_H
#define GAME_MWMECHANICS_CREATURESTATS_H
#include <map>
#include <set>
#include <string>
#include <stdexcept>
@ -87,14 +88,12 @@ namespace MWMechanics
float mSideMovementAngle;
private:
std::map<ESM::SummonKey, int> mSummonedCreatures; // <SummonKey, ActorId>
std::multimap<int, int> mSummonedCreatures; // <Effect, ActorId>
// Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet.
// This may be necessary when the creature is in an inactive cell.
std::vector<int> mSummonGraveyard;
std::map<std::string, CorprusStats> mCorprusSpells;
protected:
int mLevel;
@ -236,7 +235,7 @@ namespace MWMechanics
void setBlock(bool value);
bool getBlock() const;
std::map<ESM::SummonKey, int>& getSummonedCreatureMap(); // <SummonKey, ActorId of summoned creature>
std::multimap<int, int>& getSummonedCreatureMap(); // <Effect, ActorId of summoned creature>
std::vector<int>& getSummonedCreatureGraveyard(); // ActorIds
enum Flag
@ -297,12 +296,6 @@ namespace MWMechanics
static void cleanup();
std::map<std::string, CorprusStats> & getCorprusSpells();
void addCorprusSpell(const std::string& sourceId, CorprusStats& stats);
void removeCorprusSpell(const std::string& sourceId);
float getSideMovementAngle() const { return mSideMovementAngle; }
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
};

View file

@ -30,12 +30,11 @@ namespace MWMechanics
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fDiseaseXferChance")->mValue.getFloat();
MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects();
const MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects();
Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells();
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
for (const ESM::Spell* spell : spells)
{
const ESM::Spell* spell = it->first;
if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId))
continue;
@ -56,7 +55,7 @@ namespace MWMechanics
if (Misc::Rng::rollDice(10000) < x)
{
// Contracted disease!
actor.getClass().getCreatureStats(actor).getSpells().add(it->first);
actor.getClass().getCreatureStats(actor).getSpells().add(spell);
MWBase::Environment::get().getWorld()->applyLoopingParticles(actor);
std::string msg = "sMagicContractDisease";

View file

@ -1,75 +0,0 @@
#include "linkedeffects.hpp"
#include <components/misc/rng.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/animation.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "creaturestats.hpp"
namespace MWMechanics
{
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects)
{
if (caster.isEmpty() || caster == target || !target.getClass().isActor())
return false;
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable;
if (!isHarmful || isUnreflectable)
return false;
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
if (Misc::Rng::roll0to99() >= reflect)
return false;
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation && !reflectStatic->mModel.empty())
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
reflectedEffects.mList.emplace_back(effect);
return true;
}
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source)
{
if (caster.isEmpty() || caster == target)
return;
if (!target.getClass().isActor() || !caster.getClass().isActor())
return;
// Make sure callers don't do something weird
if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill)
throw std::runtime_error("invalid absorb stat effect");
if (appliedEffect.mMagnitude == 0)
return;
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
ActiveSpells::ActiveEffect absorbEffect = appliedEffect;
absorbEffect.mMagnitude *= -1;
absorbEffect.mEffectIndex = appliedEffect.mEffectIndex;
absorbEffects.emplace_back(absorbEffect);
// Morrowind negates reflected Absorb spells so the original caster won't be harmed.
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
{
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true,
absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId());
return;
}
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true,
absorbEffects, source, target.getClass().getCreatureStats(target).getActorId());
}
}

View file

@ -1,32 +0,0 @@
#ifndef MWMECHANICS_LINKEDEFFECTS_H
#define MWMECHANICS_LINKEDEFFECTS_H
#include <string>
namespace ESM
{
struct ActiveEffect;
struct EffectList;
struct ENAMstruct;
struct MagicEffect;
struct Spell;
}
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
// Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list.
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects);
// Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster.
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source);
}
#endif

View file

@ -193,22 +193,22 @@ namespace MWMechanics
void MagicEffects::writeState(ESM::MagicEffects &state) const
{
// Don't need to save Modifiers, they are recalculated every frame anyway.
for (Collection::const_iterator iter (begin()); iter!=end(); ++iter)
for (const auto& [key, params] : mCollection)
{
if (iter->second.getBase() != 0)
if (params.getBase() != 0 || params.getModifier() != 0.f)
{
// Don't worry about mArg, never used by magic effect script instructions
state.mEffects.insert(std::make_pair(iter->first.mId, iter->second.getBase()));
state.mEffects[key.mId] = {params.getBase(), params.getModifier()};
}
}
}
void MagicEffects::readState(const ESM::MagicEffects &state)
{
for (std::map<int, int>::const_iterator it = state.mEffects.begin(); it != state.mEffects.end(); ++it)
for (const auto& [key, params] : state.mEffects)
{
mCollection[EffectKey(it->first)].setBase(it->second);
mCollection[EffectKey(key)].setBase(params.first);
mCollection[EffectKey(key)].setModifier(params.second);
}
}
}

View file

@ -69,16 +69,6 @@ namespace MWMechanics
return param -= right;
}
// Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
struct EffectSourceVisitor
{
virtual ~EffectSourceVisitor() { }
virtual void visit (EffectKey key, int effectIndex,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1) = 0;
};
/// \brief Effects currently affecting a NPC or creature
class MagicEffects
{

View file

@ -84,7 +84,7 @@ namespace MWMechanics
// reset
creatureStats.setLevel(player->mNpdt.mLevel);
creatureStats.getSpells().clear(true);
creatureStats.modifyMagicEffects(MagicEffects());
creatureStats.getActiveSpells().clear(ptr);
for (int i=0; i<27; ++i)
npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]);
@ -213,6 +213,7 @@ namespace MWMechanics
int attributes[ESM::Attribute::Length];
for (int i=0; i<ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase();
npcStats.updateHealth();
std::vector<std::string> selectedSpells = autoCalcPlayerSpells(skills, attributes, race);
@ -221,6 +222,7 @@ namespace MWMechanics
// forced update and current value adjustments
mActors.updateActor (ptr, 0);
mActors.updateActor (ptr, 0);
for (int i=0; i<3; ++i)
{
@ -282,24 +284,6 @@ namespace MWMechanics
mObjects.dropObjects(cellStore);
}
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)
{
// Note: we should do it here since game mechanics and world updates use these values

View file

@ -230,8 +230,6 @@ namespace MWMechanics
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override;
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
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<MWWorld::Ptr> &playerFollowers);

View file

@ -16,33 +16,30 @@
namespace MWMechanics
{
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
float getProbability(const MWMechanics::ActiveSpells& activeSpells)
{
public:
float mProbability{0.f};
GetAbsorptionProbability() = default;
void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
float magnitude, float /*remainingTime*/, float /*totalTime*/) override
float probability = 0.f;
for(const auto& params : activeSpells)
{
if (key.mId == ESM::MagicEffect::SpellAbsorption)
for(const auto& effect : params.getEffects())
{
if (mProbability == 0.f)
mProbability = magnitude / 100;
else
if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption)
{
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
// Real absorption probability will be the (1 - total fail chance) in this case.
float failProbability = 1.f - mProbability;
failProbability *= 1.f - magnitude / 100;
mProbability = 1.f - failProbability;
if(probability == 0.f)
probability = effect.mMagnitude / 100;
else
{
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
// Real absorption probability will be the (1 - total fail chance) in this case.
float failProbability = 1.f - probability;
failProbability *= 1.f - effect.mMagnitude / 100;
probability = 1.f - failProbability;
}
}
}
}
};
return static_cast<int>(probability * 100);
}
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
@ -53,13 +50,7 @@ namespace MWMechanics
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
return false;
GetAbsorptionProbability check;
stats.getActiveSpells().visitEffectSources(check);
stats.getSpells().visitEffectSources(check);
if (target.getClass().hasInventoryStore(target))
target.getClass().getInventoryStore(target).visitEffectSources(check);
int chance = check.mProbability * 100;
int chance = getProbability(stats.getActiveSpells());
if (Misc::Rng::roll0to99() >= chance)
return false;

View file

@ -22,14 +22,38 @@
#include "actorutil.hpp"
#include "aifollow.hpp"
#include "creaturestats.hpp"
#include "linkedeffects.hpp"
#include "spellabsorption.hpp"
#include "spellresistance.hpp"
#include "spelleffects.hpp"
#include "spellutil.hpp"
#include "summoning.hpp"
#include "tickableeffects.hpp"
#include "weapontype.hpp"
namespace
{
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects)
{
if (caster.isEmpty() || caster == target || !target.getClass().isActor())
return false;
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable;
if (!isHarmful || isUnreflectable)
return false;
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
if (Misc::Rng::roll0to99() >= reflect)
return false;
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation && !reflectStatic->mModel.empty())
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
reflectedEffects.mList.emplace_back(effect);
return true;
}
}
namespace MWMechanics
{
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
@ -54,7 +78,7 @@ namespace MWMechanics
(mTarget.getRefData().getPosition().asVec3() + offset) -
(mCaster.getRefData().getPosition().asVec3());
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection);
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot);
}
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
@ -100,20 +124,13 @@ namespace MWMechanics
}
ESM::EffectList reflectedEffects;
std::vector<ActiveSpells::ActiveEffect> appliedLastingEffects;
// HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance.
// This is required for Weakness effects in a spell to apply to any subsequent effects in the spell.
// Otherwise, they'd only apply after the whole spell was added.
MagicEffects targetEffects;
if (targetIsActor)
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
ActiveSpells::ActiveSpellParams params(*this, caster);
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
ActiveSpells targetSpells;
const ActiveSpells* targetSpells = nullptr;
if (targetIsActor)
targetSpells = target.getClass().getCreatureStats(target).getActiveSpells();
targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells();
bool canCastAnEffect = false; // For bound equipment.If this remains false
// throughout the iteration of this spell's
@ -134,7 +151,7 @@ namespace MWMechanics
effectIt->mEffectID);
// Re-casting a bound equipment effect has no effect if the spell is still active
if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells.isSpellActive(mId))
if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells && targetSpells->isSpellActive(mId))
{
if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}");
@ -163,307 +180,70 @@ namespace MWMechanics
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
continue;
// Try resisting.
float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
if (magnitudeMult == 0)
ActiveSpells::ActiveEffect effect;
effect.mEffectId = effectIt->mEffectID;
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
effect.mMagnitude = 0.f;
effect.mMinMagnitude = effectIt->mMagnMin;
effect.mMaxMagnitude = effectIt->mMagnMax;
effect.mTimeLeft = 0.f;
effect.mEffectIndex = currentEffectIndex;
effect.mFlags = ESM::ActiveEffect::Flag_None;
// Avoid applying harmful effects to the player in god mode
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
{
// Fully resisted, show message
if (target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
else if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
effect.mMinMagnitude = 0;
effect.mMaxMagnitude = 0;
}
else
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
if (!appliedOnce)
effect.mDuration = std::max(1.f, effect.mDuration);
effect.mTimeLeft = effect.mDuration;
// add to list of active effects, to apply in next frame
params.getEffects().emplace_back(effect);
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
{
float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1);
magnitude *= magnitudeMult;
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}
if (!target.getClass().isActor())
{
// non-actor objects have no list of active magic effects, so have to apply instantly
if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude))
continue;
}
else // target.getClass().isActor() == true
{
ActiveSpells::ActiveEffect effect;
effect.mEffectId = effectIt->mEffectID;
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
effect.mMagnitude = magnitude;
effect.mTimeLeft = 0.f;
effect.mEffectIndex = currentEffectIndex;
// Avoid applying absorb effects if the caster is the target
// We still need the spell to be added
if (caster == target
&& effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute
&& effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
{
effect.mMagnitude = 0;
}
// Avoid applying harmful effects to the player in god mode
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
{
effect.mMagnitude = 0;
}
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
if (castByPlayer && target != caster && !target.getClass().getCreatureStats(target).isDead() && effectAffectsHealth)
{
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
if (!appliedOnce)
effect.mDuration = std::max(1.f, effect.mDuration);
if (effect.mDuration == 0)
{
// We still should add effect to list to allow GetSpellEffects to detect this spell
appliedLastingEffects.push_back(effect);
// duration 0 means apply full magnitude instantly
bool wasDead = target.getClass().getCreatureStats(target).isDead();
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude);
bool isDead = target.getClass().getCreatureStats(target).isDead();
if (!wasDead && isDead)
MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster);
}
else
{
effect.mTimeLeft = effect.mDuration;
targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude));
// add to list of active effects, to apply in next frame
appliedLastingEffects.push_back(effect);
// Unequip all items, if a spell with the ExtraSpell effect was casted
if (effectIt->mEffectID == ESM::MagicEffect::ExtraSpell && target.getClass().hasInventoryStore(target))
{
MWWorld::InventoryStore& store = target.getClass().getInventoryStore(target);
store.unequipAll(target);
}
// Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel())
{
MWMechanics::AiFollow package(caster, true);
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
}
// For absorb effects, also apply the effect to the caster - but with a negative
// magnitude, since we're transferring stats from the target to the caster
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
absorbStat(*effectIt, effect, caster, target, reflected, mSourceName);
}
}
// Re-casting a summon effect will remove the creature from previous castings of that effect.
if (isSummoningEffect(effectIt->mEffectID) && targetIsActor)
{
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
auto findCreature = targetStats.getSummonedCreatureMap().find(key);
if (findCreature != targetStats.getSummonedCreatureMap().end())
{
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, findCreature->second);
targetStats.getSummonedCreatureMap().erase(findCreature);
}
}
if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
{
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!magicEffect->mHitSound.empty())
sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
// Add VFX
const ESM::Static* castStatic;
if (!magicEffect->mHit.empty())
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
else
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
// Note: in case of non actor, a free effect should be fine as well
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
if (anim && !castStatic->mModel.empty())
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle);
}
if (targetIsActor || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
{
playEffects(target, *magicEffect);
}
}
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile, mSlot);
if (!target.isEmpty())
{
if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true, exploded);
if (!appliedLastingEffects.empty())
if (!params.getEffects().empty())
{
int casterActorId = -1;
if (!caster.isEmpty() && caster.getClass().isActor())
casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
mSourceName, casterActorId);
}
}
}
bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
{
short effectId = effect.mId;
if (target.getClass().canLock(target))
{
if (effectId == ESM::MagicEffect::Lock)
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const ESM::MagicEffect *magiceffect = store.get<ESM::MagicEffect>().find(effectId);
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation)
animation->addSpellCastGlow(magiceffect);
if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude
{
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}");
target.getCellRef().lock(static_cast<int>(magnitude));
}
return true;
}
else if (effectId == ESM::MagicEffect::Open)
{
if (!caster.isEmpty())
{
MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target);
// Use the player instead of the caster for vanilla crime compatibility
}
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const ESM::MagicEffect *magiceffect = store.get<ESM::MagicEffect>().find(effectId);
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation)
animation->addSpellCastGlow(magiceffect);
if (target.getCellRef().getLockLevel() <= magnitude)
{
if (target.getCellRef().getLockLevel() > 0)
{
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}");
}
target.getCellRef().unlock();
}
if(targetIsActor)
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params);
else
{
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
for(auto& effect : params.getEffects())
applyMagicEffect(target, caster, params, effect, 0.f);
}
return true;
}
}
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel)
{
target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true);
return true;
}
else if(target.getClass().isActor() && effectId >= ESM::MagicEffect::CalmHumanoid && effectId <= ESM::MagicEffect::RallyCreature)
{
// Treat X Humanoid spells on creatures and X Creature spells on NPCs as instant effects and remove their VFX
bool affectsCreatures = (effectId - ESM::MagicEffect::CalmHumanoid) & 1;
if(affectsCreatures == target.getClass().isNpc())
{
MWBase::Environment::get().getWorld()->getAnimation(target)->removeEffect(effectId);
return true;
}
}
else if(target.getClass().isActor() && effectId == ESM::MagicEffect::TurnUndead)
{
// Diverge from vanilla by giving scripts a chance to detect Turn Undead on non-undead, but still remove the effect and VFX
if(target.getClass().isNpc() || target.get<ESM::Creature>()->mBase->mData.mType != ESM::Creature::Undead)
{
MWBase::Environment::get().getWorld()->getAnimation(target)->removeEffect(effectId);
return true;
}
}
else if (target.getClass().isActor() && target == getPlayer())
{
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
bool teleportingEnabled = MWBase::Environment::get().getWorld()->isTeleportingEnabled();
if (effectId == ESM::MagicEffect::DivineIntervention || effectId == ESM::MagicEffect::AlmsiviIntervention)
{
if (!teleportingEnabled)
{
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
return true;
}
std::string marker = (effectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker";
MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, marker);
anim->removeEffect(effectId);
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_end");
if (fx)
anim->addEffect("meshes\\" + fx->mModel, -1);
return true;
}
else if (effectId == ESM::MagicEffect::Mark)
{
if (teleportingEnabled)
{
MWBase::Environment::get().getWorld()->getPlayer().markPosition(
target.getCell(), target.getRefData().getPosition());
}
else if (caster == getPlayer())
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
}
return true;
}
else if (effectId == ESM::MagicEffect::Recall)
{
if (!teleportingEnabled)
{
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
return true;
}
MWWorld::CellStore* markedCell = nullptr;
ESM::Position markedPosition;
MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition);
if (markedCell)
{
MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName,
markedPosition, false);
action.execute(target);
anim->removeEffect(effectId);
}
return true;
}
}
return false;
}
bool CastSpell::cast(const std::string &id)
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
@ -479,7 +259,7 @@ namespace MWMechanics
throw std::runtime_error("ID type cannot be casted");
}
bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile)
bool CastSpell::cast(const MWWorld::Ptr &item, int slot, bool launchProjectile)
{
std::string enchantmentName = item.getClass().getEnchantment(item);
if (enchantmentName.empty())
@ -490,7 +270,7 @@ namespace MWMechanics
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
mStack = false;
mSlot = slot;
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
bool isProjectile = false;
@ -570,7 +350,7 @@ namespace MWMechanics
{
mSourceName = potion->mName;
mId = potion->mId;
mStack = true;
mType = ESM::ActiveSpells::Type_Consumable;
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
@ -581,7 +361,6 @@ namespace MWMechanics
{
mSourceName = spell->mName;
mId = spell->mId;
mStack = false;
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
@ -656,7 +435,7 @@ namespace MWMechanics
bool CastSpell::cast (const ESM::Ingredient* ingredient)
{
mId = ingredient->mId;
mStack = true;
mType = ESM::ActiveSpells::Type_Consumable;
mSourceName = ingredient->mName;
ESM::ENAMstruct effect;
@ -799,4 +578,36 @@ namespace MWMechanics
sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
}
}
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping)
{
if (playNonLooping)
{
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!magicEffect.mHitSound.empty())
sndMgr->playSound3D(target, magicEffect.mHitSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(target, schools[magicEffect.mData.mSchool]+" hit", 1.0f, 1.0f);
}
// Add VFX
const ESM::Static* castStatic;
if (!magicEffect.mHit.empty())
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect.mHit);
else
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
bool loop = (magicEffect.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
if(anim && !castStatic->mModel.empty())
{
// Don't play particle VFX unless the effect is new or it should be looping.
if (playNonLooping || loop)
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect.mIndex, loop, "", magicEffect.mParticle);
}
}
}

View file

@ -1,6 +1,7 @@
#ifndef MWMECHANICS_SPELLCASTING_H
#define MWMECHANICS_SPELLCASTING_H
#include <components/esm/activespells.hpp>
#include <components/esm/effectlist.hpp>
#include "../mwworld/ptr.hpp"
@ -11,6 +12,7 @@ namespace ESM
struct Ingredient;
struct Potion;
struct EffectList;
struct MagicEffect;
}
namespace MWMechanics
@ -26,13 +28,14 @@ namespace MWMechanics
void playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects);
public:
bool mStack{false};
std::string mId; // ID of spell, potion, item etc
std::string mSourceName; // Display name for spell, potion, etc
osg::Vec3f mHitPosition{0,0,0}; // Used for spawning area orb
bool mAlwaysSucceed{false}; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon)
bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.)
int mSlot{0};
ESM::ActiveSpells::EffectType mType{ESM::ActiveSpells::Type_Temporary};
public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false);
@ -41,7 +44,7 @@ namespace MWMechanics
/// @note mCaster must be an actor
/// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster.
bool cast (const MWWorld::Ptr& item, bool launchProjectile=true);
bool cast (const MWWorld::Ptr& item, int slot, bool launchProjectile=true);
/// @note mCaster must be an NPC
bool cast (const ESM::Ingredient* ingredient);
@ -60,11 +63,9 @@ namespace MWMechanics
/// @note \a caster can be any type of object, or even an empty object.
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false);
/// @note \a caster can be any type of object, or even an empty object.
/// @return was the target suitable for the effect?
bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
};
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
#ifndef GAME_MWMECHANICS_SPELLEFFECTS_H
#define GAME_MWMECHANICS_SPELLEFFECTS_H
#include "activespells.hpp"
#include "../mwworld/ptr.hpp"
// These functions should probably be split up into separate Lua functions for each magic effect when magic is dehardcoded.
// That way ESM::MGEF could point to two Lua scripts for each effect. Needs discussion.
namespace MWMechanics
{
// Applies a tick of a single effect. Returns true if the effect should be removed immediately
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);
}
#endif

View file

@ -16,12 +16,6 @@ namespace ESM
namespace MWMechanics
{
struct SpellParams
{
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
std::set<int> mPurgedEffects; // indices of purged effects
};
class Spells;
/// Multiple instances of the same actor share the same spell list in Morrowind.

View file

@ -31,21 +31,20 @@ namespace
// if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted items etc.
if (effectFilter == -1)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(it->first);
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(it->getId());
if (!spell || spell->mData.mType != ESM::Spell::ST_Spell)
continue;
}
const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second;
for (std::vector<MWMechanics::ActiveSpells::ActiveEffect>::const_iterator effectIt = params.mEffects.begin();
effectIt != params.mEffects.end(); ++effectIt)
const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it;
for (const auto& effect : params.getEffects())
{
int effectId = effectIt->mEffectId;
int effectId = effect.mEffectId;
if (effectFilter != -1 && effectId != effectFilter)
continue;
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectId);
if (effectIt->mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway
if (effect.mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway
continue;
if (negative && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
@ -64,15 +63,14 @@ namespace
const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells();
for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it)
{
if (it->first != spellId)
if (it->getId() != spellId)
continue;
const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second;
for (std::vector<MWMechanics::ActiveSpells::ActiveEffect>::const_iterator effectIt = params.mEffects.begin();
effectIt != params.mEffects.end(); ++effectIt)
const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it;
for (const auto& effect : params.getEffects())
{
if (effectIt->mDuration > duration)
duration = effectIt->mDuration;
if (effect.mDuration > duration)
duration = effect.mDuration;
}
}
return duration;

View file

@ -20,72 +20,33 @@
namespace MWMechanics
{
Spells::Spells()
: mSpellsChanged(false)
{
}
Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells),
mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers),
mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects)
mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers)
{
if(mSpellList)
mSpellList->addListener(this);
}
Spells::Spells(Spells&& spells) : mSpellList(std::move(spells.mSpellList)), mSpells(std::move(spells.mSpells)),
mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)),
mSpellsChanged(std::move(spells.mSpellsChanged)), mEffects(std::move(spells.mEffects)),
mSourcedEffects(std::move(spells.mSourcedEffects))
mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers))
{
if (mSpellList)
mSpellList->updateListener(&spells, this);
}
std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const
std::vector<const ESM::Spell*>::const_iterator Spells::begin() const
{
return mSpells.begin();
}
std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::end() const
std::vector<const ESM::Spell*>::const_iterator Spells::end() const
{
return mSpells.end();
}
void Spells::rebuildEffects() const
{
mEffects = MagicEffects();
mSourcedEffects.clear();
for (const auto& iter : mSpells)
{
const ESM::Spell *spell = iter.first;
if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight ||
spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse)
{
int i=0;
for (const auto& effect : spell->mEffects.mList)
{
if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end())
{
++i;
continue; // effect was purged
}
float random = 1.f;
if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end())
random = iter.second.mEffectRands.at(i);
float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random;
mEffects.add (effect, magnitude);
mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude);
++i;
}
}
}
}
bool Spells::hasSpell(const std::string &spell) const
{
return hasSpell(SpellList::getSpell(spell));
@ -93,7 +54,7 @@ namespace MWMechanics
bool Spells::hasSpell(const ESM::Spell *spell) const
{
return mSpells.find(spell) != mSpells.end();
return std::find(mSpells.begin(), mSpells.end(), spell) != mSpells.end();
}
void Spells::add (const ESM::Spell* spell)
@ -108,29 +69,8 @@ namespace MWMechanics
void Spells::addSpell(const ESM::Spell* spell)
{
if (mSpells.find (spell)==mSpells.end())
{
std::map<int, float> random;
// Determine the random magnitudes (unless this is a castable spell, in which case
// they will be determined when the spell is cast)
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
{
for (unsigned int i=0; i<spell->mEffects.mList.size();++i)
{
if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax)
{
int delta = spell->mEffects.mList[i].mMagnMax - spell->mEffects.mList[i].mMagnMin;
random[i] = Misc::Rng::rollDice(delta + 1) / static_cast<float>(delta);
}
}
}
SpellParams params;
params.mEffectRands = random;
mSpells.emplace(spell, params);
mSpellsChanged = true;
}
if (!hasSpell(spell))
mSpells.emplace_back(spell);
}
void Spells::remove (const std::string& spellId)
@ -145,27 +85,14 @@ namespace MWMechanics
void Spells::removeSpell(const ESM::Spell* spell)
{
const auto it = mSpells.find(spell);
const auto it = std::find(mSpells.begin(), mSpells.end(), spell);
if(it != mSpells.end())
{
mSpells.erase(it);
mSpellsChanged = true;
}
}
MagicEffects Spells::getMagicEffects() const
{
if (mSpellsChanged) {
rebuildEffects();
mSpellsChanged = false;
}
return mEffects;
}
void Spells::removeAllSpells()
{
mSpells.clear();
mSpellsChanged = true;
}
void Spells::clear(bool modifyBase)
@ -185,26 +112,10 @@ namespace MWMechanics
return mSelectedSpell;
}
bool Spells::isSpellActive(const std::string &id) const
{
if (id.empty())
return false;
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id);
if (spell && hasSpell(spell))
{
auto type = spell->mData.mType;
return (type==ESM::Spell::ST_Ability || type==ESM::Spell::ST_Blight || type==ESM::Spell::ST_Disease || type==ESM::Spell::ST_Curse);
}
return false;
}
bool Spells::hasDisease(const ESM::Spell::SpellType type) const
{
for (const auto& iter : mSpells)
for (const auto spell : mSpells)
{
const ESM::Spell *spell = iter.first;
if (spell->mData.mType == type)
return true;
}
@ -227,12 +138,11 @@ namespace MWMechanics
std::vector<std::string> purged;
for (auto iter = mSpells.begin(); iter!=mSpells.end();)
{
const ESM::Spell *spell = iter->first;
const ESM::Spell *spell = *iter;
if (filter(spell))
{
mSpells.erase(iter++);
purged.push_back(spell->mId);
mSpellsChanged = true;
}
else
++iter;
@ -261,43 +171,6 @@ namespace MWMechanics
purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; });
}
void Spells::removeEffects(const std::string &id)
{
if (isSpellActive(id))
{
for (auto& spell : mSpells)
{
if (spell.first == SpellList::getSpell(id))
{
for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++)
{
spell.second.mPurgedEffects.insert(i);
}
}
}
mSpellsChanged = true;
}
}
void Spells::visitEffectSources(EffectSourceVisitor &visitor) const
{
if (mSpellsChanged) {
rebuildEffects();
mSpellsChanged = false;
}
for (const auto& it : mSourcedEffects)
{
const ESM::Spell * spell = it.first;
for (const auto& effectIt : it.second)
{
// FIXME: since Spells merges effects with the same ID, there is no sense to use multiple effects with same ID here
visitor.visit(effectIt.first, -1, spell->mName, spell->mId, -1, effectIt.second.getMagnitude());
}
}
}
bool Spells::hasCorprusEffect(const ESM::Spell *spell)
{
for (const auto& effectIt : spell->mEffects.mList)
@ -310,46 +183,6 @@ namespace MWMechanics
return false;
}
void Spells::purgeEffect(int effectId)
{
for (auto& spellIt : mSpells)
{
int i = 0;
for (auto& effectIt : spellIt.first->mEffects.mList)
{
if (effectIt.mEffectID == effectId)
{
spellIt.second.mPurgedEffects.insert(i);
mSpellsChanged = true;
}
++i;
}
}
}
void Spells::purgeEffect(int effectId, const std::string & sourceId)
{
// Effect source may be not a spell
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(sourceId);
if (spell == nullptr)
return;
auto spellIt = mSpells.find(spell);
if (spellIt == mSpells.end())
return;
int index = 0;
for (auto& effectIt : spellIt->first->mEffects.mList)
{
if (effectIt.mEffectID == effectId)
{
spellIt->second.mPurgedEffects.insert(index);
mSpellsChanged = true;
}
++index;
}
}
bool Spells::canUsePower(const ESM::Spell* spell) const
{
const auto it = mUsedPowers.find(spell);
@ -365,17 +198,16 @@ namespace MWMechanics
{
const auto& baseSpells = mSpellList->getSpells();
for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
for (const std::string& id : state.mSpells)
{
// Discard spells that are no longer available due to changed content files
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(it->first);
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id);
if (spell)
{
mSpells[spell].mEffectRands = it->second.mEffectRands;
mSpells[spell].mPurgedEffects = it->second.mPurgedEffects;
mSpells.emplace_back(spell);
if (it->first == state.mSelectedSpell)
mSelectedSpell = it->first;
if (id == state.mSelectedSpell)
mSelectedSpell = id;
}
}
// Add spells from the base record
@ -394,31 +226,6 @@ namespace MWMechanics
mUsedPowers[spell] = MWWorld::TimeStamp(it->second);
}
for (std::map<std::string, ESM::SpellState::CorprusStats>::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it)
{
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(it->first);
if (!spell)
continue;
CorprusStats stats;
int worsening = state.mCorprusSpells.at(it->first).mWorsenings;
for (int i=0; i<ESM::Attribute::Length; ++i)
stats.mWorsenings[i] = 0;
for (auto& effect : spell->mEffects.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<std::string, std::vector<ESM::SpellState::PermanentSpellEffectInfo> >::const_iterator it =
state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it)
@ -457,16 +264,13 @@ namespace MWMechanics
void Spells::writeState(ESM::SpellState &state) const
{
const auto& baseSpells = mSpellList->getSpells();
for (const auto& it : mSpells)
for (const auto spell : mSpells)
{
// Don't save spells and powers stored in the base record
if((it.first->mData.mType != ESM::Spell::ST_Spell && it.first->mData.mType != ESM::Spell::ST_Power) ||
std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end())
if((spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) ||
std::find(baseSpells.begin(), baseSpells.end(), spell->mId) == baseSpells.end())
{
ESM::SpellState::SpellParams params;
params.mEffectRands = it.second.mEffectRands;
params.mPurgedEffects = it.second.mPurgedEffects;
state.mSpells.emplace(it.first->mId, params);
state.mSpells.emplace_back(spell->mId);
}
}

View file

@ -29,18 +29,13 @@ namespace MWMechanics
class Spells
{
std::shared_ptr<SpellList> mSpellList;
std::map<const ESM::Spell*, SpellParams> mSpells;
std::vector<const ESM::Spell*> mSpells;
// 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<const ESM::Spell*, MWWorld::TimeStamp> mUsedPowers;
mutable bool mSpellsChanged;
mutable MagicEffects mEffects;
mutable std::map<const ESM::Spell*, MagicEffects> mSourcedEffects;
void rebuildEffects() const;
bool hasDisease(const ESM::Spell::SpellType type) const;
using SpellFilter = bool (*)(const ESM::Spell*);
@ -52,8 +47,6 @@ namespace MWMechanics
friend class SpellList;
public:
using TIterator = std::map<const ESM::Spell*, SpellParams>::const_iterator;
Spells();
Spells(const Spells&);
@ -64,9 +57,6 @@ namespace MWMechanics
static bool hasCorprusEffect(const ESM::Spell *spell);
void purgeEffect(int effectId);
void purgeEffect(int effectId, const std::string & sourceId);
bool canUsePower (const ESM::Spell* spell) const;
void usePower (const ESM::Spell* spell);
@ -75,9 +65,9 @@ namespace MWMechanics
void purgeCorprusDisease();
void purgeCurses();
TIterator begin() const;
std::vector<const ESM::Spell*>::const_iterator begin() const;
TIterator end() const;
std::vector<const ESM::Spell*>::const_iterator end() const;
bool hasSpell(const std::string& spell) const;
bool hasSpell(const ESM::Spell* spell) const;
@ -92,9 +82,6 @@ namespace MWMechanics
///< If the spell to be removed is the selected spell, the selected spell will be changed to
/// no spell (empty string).
MagicEffects getMagicEffects() const;
///< Return sum of magic effects resulting from abilities, blights, deseases and curses.
void clear(bool modifyBase = false);
///< Remove all spells of al types.
@ -104,17 +91,10 @@ namespace MWMechanics
const std::string getSelectedSpell() const;
///< May return an empty string.
bool isSpellActive(const std::string& id) const;
///< Are we under the effects of the given spell ID?
bool hasCommonDisease() const;
bool hasBlightDisease() const;
void removeEffects(const std::string& id);
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
void readState (const ESM::SpellState& state, CreatureStats* creatureStats);
void writeState (ESM::SpellState& state) const;

View file

@ -60,90 +60,60 @@ namespace MWMechanics
return std::string();
}
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
: mActor(actor)
int summonCreature(int effectId, const MWWorld::Ptr& summoner)
{
}
void UpdateSummonedCreatures::visit(EffectKey key, int effectIndex, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime)
{
if (isSummoningEffect(key.mId) && magnitude > 0)
std::string creatureID = getSummonedCreature(effectId);
int creatureActorId = -1;
if (!creatureID.empty())
{
mActiveEffects.insert(ESM::SummonKey(key.mId, sourceId, effectIndex));
}
}
void UpdateSummonedCreatures::process(bool cleanup)
{
MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor);
std::map<ESM::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
for (std::set<ESM::SummonKey>::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it)
{
bool found = creatureMap.find(*it) != creatureMap.end();
if (!found)
try
{
std::string creatureID = getSummonedCreature(it->mEffectId);
if (!creatureID.empty())
auto world = MWBase::Environment::get().getWorld();
MWWorld::ManualRef ref(world->getStore(), creatureID, 1);
MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr());
// Make the summoned creature follow its master and help in fights
AiFollow package(summoner);
summonedCreatureStats.getAiSequence().stack(package, ref.getPtr());
creatureActorId = summonedCreatureStats.getActorId();
MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f);
MWRender::Animation* anim = world->getAnimation(placed);
if (anim)
{
int creatureActorId = -1;
try
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1);
MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr());
// Make the summoned creature follow its master and help in fights
AiFollow package(mActor);
summonedCreatureStats.getAiSequence().stack(package, ref.getPtr());
creatureActorId = summonedCreatureStats.getActorId();
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), mActor, mActor.getCell(), 0, 120.f);
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed);
if (anim)
{
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_Start");
if (fx)
anim->addEffect("meshes\\" + fx->mModel, -1, false);
}
}
catch (std::exception& e)
{
Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what();
// still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log
}
creatureMap.emplace(*it, creatureActorId);
const ESM::Static* fx = world->getStore().get<ESM::Static>().search("VFX_Summon_Start");
if (fx)
anim->addEffect("meshes\\" + fx->mModel, -1, false);
}
}
}
// Update summon effects
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
bool found = mActiveEffects.find(it->first) != mActiveEffects.end();
if (!found)
catch (std::exception& e)
{
// Effect has ended
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second);
creatureMap.erase(it++);
continue;
Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what();
// still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log
}
++it;
summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creatureActorId);
}
return creatureActorId;
}
void updateSummons(const MWWorld::Ptr& summoner, bool cleanup)
{
MWMechanics::CreatureStats& creatureStats = summoner.getClass().getCreatureStats(summoner);
auto& creatureMap = creatureStats.getSummonedCreatureMap();
std::vector<int> graveyard = creatureStats.getSummonedCreatureGraveyard();
creatureStats.getSummonedCreatureGraveyard().clear();
for (const int creature : graveyard)
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, creature);
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, creature);
if (!cleanup)
return;
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
for (auto it = creatureMap.begin(); it != creatureMap.end(); )
{
if(it->second == -1)
{
@ -155,7 +125,7 @@ namespace MWMechanics
if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())
{
// Purge the magic effect so a new creature can be summoned if desired
purgeSummonEffect(mActor, *it);
purgeSummonEffect(summoner, *it);
creatureMap.erase(it++);
}
else
@ -163,13 +133,13 @@ namespace MWMechanics
}
}
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<const ESM::SummonKey, int>& summon)
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, int>& summon)
{
auto& creatureStats = summoner.getClass().getCreatureStats(summoner);
creatureStats.getActiveSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId, summon.first.mEffectIndex);
creatureStats.getSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId);
if (summoner.getClass().hasInventoryStore(summoner))
summoner.getClass().getInventoryStore(summoner).purgeEffect(summon.first.mEffectId, summon.first.mSourceId, false, summon.first.mEffectIndex);
creatureStats.getActiveSpells().purge([summon] (const auto& spell, const auto& effect)
{
return effect.mEffectId == summon.first && effect.mArg == summon.second;
}, summoner);
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second);
}

View file

@ -11,32 +11,15 @@
namespace MWMechanics
{
class CreatureStats;
bool isSummoningEffect(int effectId);
std::string getSummonedCreature(int effectId);
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<const ESM::SummonKey, int>& summon);
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, int>& summon);
struct UpdateSummonedCreatures : public EffectSourceVisitor
{
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
virtual ~UpdateSummonedCreatures() = default;
void visit (MWMechanics::EffectKey key, int effectIndex,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1) override;
/// To call after all effect sources have been visited
void process(bool cleanup);
private:
MWWorld::Ptr mActor;
std::set<ESM::SummonKey> mActiveEffects;
};
int summonCreature(int effectId, const MWWorld::Ptr& summoner);
void updateSummons(const MWWorld::Ptr& summoner, bool cleanup);
}
#endif

View file

@ -1,216 +0,0 @@
#include "tickableeffects.hpp"
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "actorutil.hpp"
#include "npcstats.hpp"
namespace MWMechanics
{
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
{
DynamicStat<float> stat = creatureStats.getDynamic(index);
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
creatureStats.setDynamic(index, stat);
}
bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate)
{
if (!ptr.getClass().hasInventoryStore(ptr))
return false;
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator item = inv.getSlot(slot);
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
{
if (!item->getClass().hasItemHealth(*item))
return false;
int charge = item->getClass().getItemHealth(*item);
if (charge == 0)
return false;
// Store remainder of disintegrate amount (automatically subtracted if > 1)
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
charge = item->getClass().getItemHealth(*item);
charge -= std::min(static_cast<int>(disintegrate), charge);
item->getCellRef().setCharge(charge);
if (charge == 0)
{
// Will unequip the broken item and try to find a replacement
if (ptr != getPlayer())
inv.autoEquip(ptr);
else
inv.unequipItem(*item, ptr);
}
return true;
}
return false;
}
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
{
if (magnitude == 0.f)
return false;
bool receivedMagicDamage = false;
bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
switch (effectKey.mId)
{
case ESM::MagicEffect::DamageAttribute:
{
if (godmode)
break;
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.damage(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreAttribute:
{
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.restore(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreHealth:
case ESM::MagicEffect::RestoreMagicka:
case ESM::MagicEffect::RestoreFatigue:
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
break;
case ESM::MagicEffect::DamageHealth:
if (godmode)
break;
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
break;
case ESM::MagicEffect::DamageMagicka:
case ESM::MagicEffect::DamageFatigue:
{
if (godmode)
break;
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
break;
}
case ESM::MagicEffect::AbsorbHealth:
if (!godmode || magnitude <= 0)
{
if (magnitude > 0.f)
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
}
break;
case ESM::MagicEffect::AbsorbMagicka:
case ESM::MagicEffect::AbsorbFatigue:
if (!godmode || magnitude <= 0)
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
break;
case ESM::MagicEffect::DisintegrateArmor:
{
if (godmode)
break;
static const std::array<int, 9> priorities
{
MWWorld::InventoryStore::Slot_CarriedLeft,
MWWorld::InventoryStore::Slot_Cuirass,
MWWorld::InventoryStore::Slot_LeftPauldron,
MWWorld::InventoryStore::Slot_RightPauldron,
MWWorld::InventoryStore::Slot_LeftGauntlet,
MWWorld::InventoryStore::Slot_RightGauntlet,
MWWorld::InventoryStore::Slot_Helmet,
MWWorld::InventoryStore::Slot_Greaves,
MWWorld::InventoryStore::Slot_Boots
};
for (const int priority : priorities)
{
if (disintegrateSlot(actor, priority, magnitude))
break;
}
break;
}
case ESM::MagicEffect::DisintegrateWeapon:
if (!godmode)
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
break;
case ESM::MagicEffect::SunDamage:
{
// isInCell shouldn't be needed, but updateActor called during game start
if (!actor.isInCell() || !actor.getCell()->isExterior() || godmode)
break;
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
float damageScale = 1.f - timeDiff / 7.f;
// When cloudy, the sun damage effect is halved
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fMagicSunBlockedMult")->mValue.getFloat();
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
if (weather > 1)
damageScale *= fMagicSunBlockedMult;
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
if (magnitude * damageScale > 0.f)
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::FireDamage:
case ESM::MagicEffect::ShockDamage:
case ESM::MagicEffect::FrostDamage:
case ESM::MagicEffect::Poison:
{
if (godmode)
break;
adjustDynamicStat(creatureStats, 0, -magnitude);
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::DamageSkill:
case ESM::MagicEffect::RestoreSkill:
{
if (!actor.getClass().isNpc())
break;
if (godmode && effectKey.mId == ESM::MagicEffect::DamageSkill)
break;
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
skill.restore(magnitude);
else
skill.damage(magnitude);
break;
}
default:
return false;
}
if (receivedMagicDamage && actor == getPlayer())
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
return true;
}
}

View file

@ -1,20 +0,0 @@
#ifndef MWMECHANICS_TICKABLEEFFECTS_H
#define MWMECHANICS_TICKABLEEFFECTS_H
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
class CreatureStats;
struct EffectKey;
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
/// Note: this function works in loop, so magic effects should not be removed here to avoid iterator invalidation.
/// @return Was the effect a tickable effect with a magnitude?
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude);
}
#endif

View file

@ -1101,34 +1101,6 @@ Resource::ResourceSystem* NpcAnimation::getResourceSystem()
return mResourceSystem;
}
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew)
{
// During first auto equip, we don't play any sounds.
// Basically we don't want sounds when the actor is first loaded,
// the items should appear as if they'd always been equipped.
if (isNew)
{
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!magicEffect->mHitSound.empty())
sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
}
if (!magicEffect->mHit.empty())
{
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
// Don't play particle VFX unless the effect is new or it should be looping.
if (isNew || loop)
addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle);
}
}
void NpcAnimation::enableHeadAnimation(bool enable)
{
mHeadAnimationTime->setEnabled(enable);

View file

@ -31,7 +31,6 @@ class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWor
{
public:
void equipmentChanged() override;
void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) override;
public:
typedef std::map<ESM::PartReferenceType,std::string> PartBoneMap;

View file

@ -563,13 +563,7 @@ namespace MWScript
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects();
effects += stats.getActiveSpells().getMagicEffects();
if (ptr.getClass().hasInventoryStore(ptr) && !stats.isDeathAnimationFinished())
{
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
effects += store.getMagicEffects();
}
const MWMechanics::MagicEffects& effects = stats.getMagicEffects();
for (const auto& activeEffect : effects)
{
@ -821,7 +815,7 @@ namespace MWScript
}
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id));
runtime.push(stats.getActiveSpells().isSpellActive(id));
}
};

View file

@ -473,6 +473,8 @@ namespace MWScript
ESM::Spell::SpellType type = static_cast<ESM::Spell::SpellType>(spell->mData.mType);
if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Power)
{
// Add spell effect to *this actor's* queue immediately
creatureStats.getActiveSpells().addSpell(spell, ptr);
// Apply looping particles immediately for constant effects
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
}
@ -492,17 +494,6 @@ namespace MWScript
runtime.pop();
MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
// The spell may have an instant effect which must be handled before the spell's removal.
for (const auto& effect : creatureStats.getSpells().getMagicEffects())
{
if (effect.second.getMagnitude() <= 0)
continue;
MWMechanics::CastSpell cast(ptr, ptr);
if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude()))
creatureStats.getSpells().purgeEffect(effect.first.mId);
}
MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, id);
creatureStats.getSpells().remove (id);
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
@ -527,8 +518,7 @@ namespace MWScript
std::string spellid = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(spellid);
ptr.getClass().getCreatureStats (ptr).getSpells().removeEffects(spellid);
ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(ptr, spellid);
}
};
@ -544,7 +534,7 @@ namespace MWScript
Interpreter::Type_Integer effectId = runtime[0].mInteger;
runtime.pop();
ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId);
ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(ptr, effectId);
}
};

View file

@ -26,6 +26,7 @@
#include "../mwmechanics/recharge.hpp"
#include "ptr.hpp"
#include "esmloader.hpp"
#include "esmstore.hpp"
#include "class.hpp"
#include "containerstore.hpp"
@ -176,6 +177,13 @@ namespace
if (state.mVersion < 15)
fixRestocking(record, state);
if (state.mVersion < 17)
{
if constexpr (std::is_same_v<T, ESM::Creature>)
MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory);
else if constexpr (std::is_same_v<T, ESM::NPC>)
MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats);
}
if (state.mRef.mRefNum.hasContentFile())
{

View file

@ -2,6 +2,26 @@
#include "esmstore.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm/npcstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwmechanics/magiceffects.hpp"
namespace
{
template<class T>
void getEnchantedItem(const std::string& id, std::string& enchantment, std::string& itemName)
{
const T* item = MWBase::Environment::get().getWorld()->getStore().get<T>().search(id);
if(item)
{
enchantment = item->mEnchant;
itemName = item->mName;
}
}
}
namespace MWWorld
{
@ -28,4 +48,187 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index)
mStore.load(mEsm[index], &mListener);
}
void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats)
{
const auto& store = MWBase::Environment::get().getWorld()->getStore();
// Convert corprus to format 10
for (const auto& [id, oldStats] : creatureStats.mSpells.mCorprusSpells)
{
const ESM::Spell* spell = store.get<ESM::Spell>().search(id);
if (!spell)
continue;
ESM::CreatureStats::CorprusStats stats;
stats.mNextWorsening = oldStats.mNextWorsening;
for (int i=0; i<ESM::Attribute::Length; ++i)
stats.mWorsenings[i] = 0;
for (auto& effect : spell->mEffects.mList)
{
if (effect.mEffectID == ESM::MagicEffect::DrainAttribute)
stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings;
}
creatureStats.mCorprusSpells[id] = stats;
}
// Convert to format 17
for(const auto& [id, oldParams] : creatureStats.mSpells.mSpellParams)
{
const ESM::Spell* spell = store.get<ESM::Spell>().search(id);
if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power)
continue;
ESM::ActiveSpells::ActiveSpellParams params;
params.mId = id;
params.mDisplayName = spell->mName;
params.mItem.unset();
params.mCasterActorId = creatureStats.mActorId;
if(spell->mData.mType == ESM::Spell::ST_Ability)
params.mType = ESM::ActiveSpells::Type_Ability;
else
params.mType = ESM::ActiveSpells::Type_Permanent;
params.mWorsenings = -1;
int effectIndex = 0;
for(const auto& enam : spell->mEffects.mList)
{
if(oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end())
{
ESM::ActiveEffect effect;
effect.mEffectId = enam.mEffectID;
effect.mArg = MWMechanics::EffectKey(enam).mArg;
effect.mDuration = -1;
effect.mTimeLeft = -1;
effect.mEffectIndex = effectIndex;
auto rand = oldParams.mEffectRands.find(effectIndex);
if(rand != oldParams.mEffectRands.end())
{
float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin;
effect.mMagnitude = magnitude;
effect.mMinMagnitude = magnitude;
effect.mMaxMagnitude = magnitude;
// Prevent recalculation of resistances
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances;
}
else
{
effect.mMagnitude = 0.f;
effect.mMinMagnitude = enam.mMagnMin;
effect.mMaxMagnitude = enam.mMagnMax;
effect.mFlags = ESM::ActiveEffect::Flag_None;
}
params.mEffects.emplace_back(effect);
}
effectIndex++;
}
creatureStats.mActiveSpells.mSpells.emplace_back(params);
}
std::multimap<std::string, int> equippedItems;
for(std::size_t i = 0; i < inventory.mItems.size(); ++i)
{
const ESM::ObjectState& item = inventory.mItems[i];
auto slot = inventory.mEquipmentSlots.find(i);
if(slot != inventory.mEquipmentSlots.end())
equippedItems.emplace(item.mRef.mRefID, slot->second);
}
for(const auto& [id, oldMagnitudes] : inventory.mPermanentMagicEffectMagnitudes)
{
std::string eId;
std::string name;
switch(store.find(id))
{
case ESM::REC_ARMO:
getEnchantedItem<ESM::Armor>(id, eId, name);
break;
case ESM::REC_CLOT:
getEnchantedItem<ESM::Clothing>(id, eId, name);
break;
case ESM::REC_WEAP:
getEnchantedItem<ESM::Weapon>(id, eId, name);
break;
}
if(eId.empty())
continue;
const ESM::Enchantment* enchantment = store.get<ESM::Enchantment>().search(eId);
if(!enchantment)
continue;
ESM::ActiveSpells::ActiveSpellParams params;
params.mId = id;
params.mDisplayName = name;
params.mCasterActorId = creatureStats.mActorId;
params.mType = ESM::ActiveSpells::Type_Enchantment;
params.mWorsenings = -1;
for(std::size_t effectIndex = 0; effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex)
{
const auto& enam = enchantment->mEffects.mList[effectIndex];
auto [random, multiplier] = oldMagnitudes[effectIndex];
float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin;
magnitude *= multiplier;
if(magnitude <= 0)
continue;
ESM::ActiveEffect effect;
effect.mEffectId = enam.mEffectID;
effect.mMagnitude = magnitude;
effect.mMinMagnitude = magnitude;
effect.mMaxMagnitude = magnitude;
effect.mArg = MWMechanics::EffectKey(enam).mArg;
effect.mDuration = -1;
effect.mTimeLeft = -1;
effect.mEffectIndex = static_cast<int>(effectIndex);
// Prevent recalculation of resistances
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances;
params.mEffects.emplace_back(effect);
}
auto [begin, end] = equippedItems.equal_range(id);
for(auto it = begin; it != end; ++it)
{
params.mItem = { static_cast<unsigned int>(it->second), 0 };
creatureStats.mActiveSpells.mSpells.emplace_back(params);
}
}
for(const auto& spell : creatureStats.mCorprusSpells)
{
auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), [&] (const auto& params) { return params.mId == spell.first; });
if(it != creatureStats.mActiveSpells.mSpells.end())
{
it->mNextWorsening = spell.second.mNextWorsening;
int worsenings = 0;
for(int i = 0; i < ESM::Attribute::Length; ++i)
worsenings = std::max(spell.second.mWorsenings[i], worsenings);
it->mWorsenings = worsenings;
}
}
for(const auto& [key, actorId] : creatureStats.mSummonedCreatureMap)
{
if(actorId == -1)
continue;
for(auto& params : creatureStats.mActiveSpells.mSpells)
{
if(params.mId == key.mSourceId)
{
bool found = false;
for(auto& effect : params.mEffects)
{
if(effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex)
{
effect.mArg = actorId;
found = true;
break;
}
}
if(found)
break;
}
}
}
// Reset modifiers that were previously recalculated each frame
for(std::size_t i = 0; i < ESM::Attribute::Length; ++i)
creatureStats.mAttributes[i].mMod = 0.f;
for(std::size_t i = 0; i < 3; ++i)
creatureStats.mDynamic[i].mMod = 0.f;
for(std::size_t i = 0; i < 4; ++i)
creatureStats.mAiSettings[i].mMod = 0.f;
if(npcStats)
{
for(std::size_t i = 0; i < ESM::Skill::Length; ++i)
npcStats->mSkills[i].mMod = 0.f;
}
}
} /* namespace MWWorld */

View file

@ -13,6 +13,9 @@ namespace ToUTF8
namespace ESM
{
class ESMReader;
struct CreatureStats;
struct InventoryState;
struct NpcStats;
}
namespace MWWorld
@ -33,6 +36,8 @@ struct EsmLoader : public ContentLoader
ToUTF8::Utf8Encoder* mEncoder;
};
void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr);
} /* namespace MWWorld */
#endif // ESMLOADER_HPP

View file

@ -108,11 +108,9 @@ MWWorld::InventoryStore::InventoryStore()
MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
: ContainerStore (store)
, mMagicEffects(store.mMagicEffects)
, mInventoryListener(store.mInventoryListener)
, mUpdatesEnabled(store.mUpdatesEnabled)
, mFirstAutoEquip(store.mFirstAutoEquip)
, mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes)
, mSelectedEnchantItem(end())
{
copySlots (store);
@ -125,9 +123,7 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor
mListener = store.mListener;
mInventoryListener = store.mInventoryListener;
mMagicEffects = store.mMagicEffects;
mFirstAutoEquip = store.mFirstAutoEquip;
mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes;
mRechargingItemsUpToDate = false;
ContainerStore::operator= (store);
mSlots.clear();
@ -186,8 +182,6 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite
flagAsModified();
fireEquipmentChangedEvent(actor);
updateMagicEffects(actor);
}
void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor)
@ -199,7 +193,6 @@ void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor)
mUpdatesEnabled = true;
fireEquipmentChangedEvent(actor);
updateMagicEffects(actor);
}
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
@ -554,7 +547,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
{
mSlots.swap (slots_);
fireEquipmentChangedEvent(actor);
updateMagicEffects(actor);
flagAsModified();
}
}
@ -567,129 +559,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield(cons
return slots[Slot_CarriedLeft];
}
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const
{
return mMagicEffects;
}
void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
{
// To avoid excessive updates during auto-equip
if (!mUpdatesEnabled)
return;
// Delay update until the listener is set up
if (!mInventoryListener)
return;
mMagicEffects = MWMechanics::MagicEffects();
const auto& stats = actor.getClass().getCreatureStats(actor);
if (stats.isDead() && stats.isDeathAnimationFinished())
return;
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
{
if (*iter==end())
continue;
std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter);
if (!enchantmentId.empty())
{
const ESM::Enchantment& enchantment =
*MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
continue;
std::vector<EffectParams> params;
bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) != mPermanentMagicEffectMagnitudes.end());
if (!existed)
{
params.resize(enchantment.mEffects.mList.size());
int i=0;
for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList)
{
int delta = effect.mMagnMax - effect.mMagnMin;
// Roll some dice, one for each effect
if (delta)
params[i].mRandom = Misc::Rng::rollDice(delta + 1) / static_cast<float>(delta);
// Try resisting each effect
params[i].mMultiplier = MWMechanics::getEffectMultiplier(effect.mEffectID, actor, actor);
++i;
}
// Note that using the RefID as a key here is not entirely correct.
// Consider equipping the same item twice (e.g. a ring)
// However, permanent enchantments with a random magnitude are kind of an exploit anyway,
// so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case.
mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()] = params;
}
else
params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()];
int i=0;
for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList)
{
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effect.mEffectID);
// Fully resisted or can't be applied to target?
if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effect.mEffectID, actor, actor, actor == MWMechanics::getPlayer()))
{
i++;
continue;
}
float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params[i].mRandom;
magnitude *= params[i].mMultiplier;
if (!existed)
{
// During first auto equip, we don't play any sounds.
// Basically we don't want sounds when the actor is first loaded,
// the items should appear as if they'd always been equipped.
mInventoryListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip);
}
if (magnitude)
mMagicEffects.add (effect, magnitude);
i++;
}
}
}
// Now drop expired effects
for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin();
it != mPermanentMagicEffectMagnitudes.end();)
{
bool found = false;
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
{
if (*iter == end())
continue;
if ((**iter).getCellRef().getRefId() == it->first)
{
found = true;
}
}
if (!found)
mPermanentMagicEffectMagnitudes.erase(it++);
else
++it;
}
// Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor);
mFirstAutoEquip = false;
}
bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const
{
bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2);
@ -800,7 +669,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c
if (applyUpdates)
{
fireEquipmentChangedEvent(actor);
updateMagicEffects(actor);
}
return retval;
@ -848,7 +716,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con
return unstack(item, actor, item.getRefData().getCount() - count);
}
MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener()
MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const
{
return mInventoryListener;
}
@ -856,7 +724,6 @@ MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener()
void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor)
{
mInventoryListener = listener;
updateMagicEffects(actor);
}
void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor)
@ -875,105 +742,6 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor)
*/
}
void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor)
{
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
{
if (*iter==end())
continue;
std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter);
if (enchantmentId.empty())
continue;
const ESM::Enchantment& enchantment =
*MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
continue;
if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) == mPermanentMagicEffectMagnitudes.end())
continue;
int i=0;
for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList)
{
i++;
// Don't get spell icon display information for enchantments that weren't actually applied
if (mMagicEffects.get(MWMechanics::EffectKey(effect)).getMagnitude() == 0)
continue;
const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i-1];
float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom;
magnitude *= params.mMultiplier;
if (magnitude > 0)
visitor.visit(MWMechanics::EffectKey(effect), i-1, (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude);
}
}
}
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(), wholeSpell);
}
}
void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell, int effectIndex)
{
TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId);
if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end())
return;
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
{
if (*iter==end())
continue;
if ((*iter)->getCellRef().getRefId() != sourceId)
continue;
std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter);
if (!enchantmentId.empty())
{
const ESM::Enchantment& enchantment =
*MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
continue;
std::vector<EffectParams>& params = effectMagnitudeIt->second;
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i)
{
if (effectIt->mEffectID != effectId)
continue;
if (effectIndex >= 0 && effectIndex != i)
continue;
if (wholeSpell)
{
mPermanentMagicEffectMagnitudes.erase(sourceId);
return;
}
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom;
magnitude *= params[i].mMultiplier;
if (magnitude)
mMagicEffects.add (*effectIt, -magnitude);
params[i].mMultiplier = 0;
}
}
}
}
void MWWorld::InventoryStore::clear()
{
mSlots.clear();
@ -991,38 +759,9 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item)
return false;
}
void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const
bool MWWorld::InventoryStore::isFirstEquip()
{
MWWorld::ContainerStore::writeState(state);
for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it)
{
std::vector<std::pair<float, float> > params;
for (std::vector<EffectParams>::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt)
{
params.emplace_back(pIt->mRandom, pIt->mMultiplier);
}
state.mPermanentMagicEffectMagnitudes[it->first] = params;
}
}
void MWWorld::InventoryStore::readState(const ESM::InventoryState &state)
{
MWWorld::ContainerStore::readState(state);
for (ESM::InventoryState::TEffectMagnitudes::const_iterator it = state.mPermanentMagicEffectMagnitudes.begin();
it != state.mPermanentMagicEffectMagnitudes.end(); ++it)
{
std::vector<EffectParams> params;
for (std::vector<std::pair<float, float> >::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt)
{
EffectParams p;
p.mRandom = pIt->first;
p.mMultiplier = pIt->second;
params.push_back(p);
}
mPermanentMagicEffectMagnitudes[it->first] = params;
}
bool first = mFirstAutoEquip;
mFirstAutoEquip = false;
return first;
}

View file

@ -25,15 +25,6 @@ namespace MWWorld
*/
virtual void equipmentChanged () {}
/**
* @param effect
* @param isNew Is this effect new (e.g. the item for it was just now manually equipped)
* or was it loaded from a savegame / initial game state? \n
* If it isn't new, non-looping VFX should not be played.
* @param playSound Play effect sound?
*/
virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew) {}
virtual ~InventoryStoreListener() = default;
};
@ -68,8 +59,6 @@ namespace MWWorld
private:
MWMechanics::MagicEffects mMagicEffects;
InventoryStoreListener* mInventoryListener;
// Enables updates of magic effects and actor model whenever items are equipped or unequipped.
@ -78,19 +67,6 @@ namespace MWWorld
bool mFirstAutoEquip;
// Vanilla allows permanent effects with a random magnitude, so it needs to be stored here.
// We also need this to only play sounds and particle effects when the item is equipped, rather than on every update.
struct EffectParams
{
// Modifier to scale between min and max magnitude
float mRandom;
// Multiplier for when an effect was fully or partially resisted
float mMultiplier;
};
typedef std::map<std::string, std::vector<EffectParams> > TEffectMagnitudes;
TEffectMagnitudes mPermanentMagicEffectMagnitudes;
typedef std::vector<ContainerStoreIterator> TSlots;
TSlots mSlots;
@ -106,8 +82,6 @@ namespace MWWorld
void initSlots (TSlots& slots_);
void updateMagicEffects(const Ptr& actor);
void fireEquipmentChangedEvent(const Ptr& actor);
void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override;
@ -161,9 +135,6 @@ namespace MWWorld
void autoEquip (const MWWorld::Ptr& actor);
///< Auto equip items according to stats and item value.
const MWMechanics::MagicEffects& getMagicEffects() const;
///< Return magic effects from worn items.
bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const override;
///< @return true if the two specified objects can stack with each other
@ -198,22 +169,12 @@ namespace MWWorld
void setInvListener (InventoryStoreListener* listener, const Ptr& actor);
///< Set a listener for various events, see \a InventoryStoreListener
InventoryStoreListener* getInvListener();
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor);
void purgeEffect (short effectId, bool wholeSpell = false);
///< Remove a magic effect
void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false, int effectIndex=-1);
///< Remove a magic effect
InventoryStoreListener* getInvListener() const;
void clear() override;
///< Empty container.
void writeState (ESM::InventoryState& state) const override;
void readState (const ESM::InventoryState& state) override;
bool isFirstEquip();
};
}

View file

@ -22,9 +22,10 @@
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "class.hpp"
#include "ptr.hpp"
#include "cellstore.hpp"
#include "class.hpp"
#include "esmloader.hpp"
#include "ptr.hpp"
namespace MWWorld
{
@ -381,6 +382,14 @@ namespace MWWorld
// this is the one object we can not silently drop.
throw std::runtime_error ("invalid player state record (object state)");
}
if (reader.getFormat() < 17)
{
convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats);
for(std::size_t i = 0; i < ESM::Attribute::Length; ++i)
player.mSaveAttributes[i].mMod = 0.f;
for(std::size_t i = 0; i < ESM::Skill::Length; ++i)
player.mSaveSkills[i].mMod = 0.f;
}
if (!player.mObject.mEnabled)
{

View file

@ -256,7 +256,7 @@ namespace MWWorld
state.mEffectAnimationTime->addTime(duration);
}
void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection)
void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection, int slot)
{
osg::Vec3f pos = caster.getRefData().getPosition().asVec3();
if (caster.getClass().isActor())
@ -278,6 +278,7 @@ namespace MWWorld
MagicBoltState state;
state.mSpellId = spellId;
state.mCasterHandle = caster;
state.mSlot = slot;
if (caster.getClass().isActor())
state.mActorId = caster.getClass().getCreatureStats(caster).getActorId();
else
@ -545,10 +546,10 @@ namespace MWWorld
cast.mHitPosition = pos;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false;
cast.mSlot = magicBoltState.mSlot;
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot);
magicBoltState.mToDelete = true;
}
@ -628,7 +629,7 @@ namespace MWWorld
state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition()));
state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude()));
state.mActorId = it->mActorId;
state.mSlot = it->mSlot;
state.mSpellId = it->mSpellId;
state.mSpeed = it->mSpeed;
@ -684,6 +685,7 @@ namespace MWWorld
state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId;
state.mToDelete = false;
state.mSlot = esm.mSlot;
std::string texture;
try

View file

@ -49,7 +49,7 @@ namespace MWWorld
MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics);
/// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used.
void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection);
void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot);
void launchProjectile (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& projectile,
const osg::Vec3f& pos, const osg::Quat& orient, const MWWorld::Ptr& bow, float speed, float attackStrength);
@ -107,6 +107,7 @@ namespace MWWorld
ESM::EffectList mEffects;
float mSpeed;
int mSlot;
std::vector<MWBase::Sound*> mSounds;
std::set<std::string> mSoundIds;

View file

@ -1862,7 +1862,7 @@ namespace MWWorld
mRendering->getCamera()->setSneakOffset(0.f);
int blind = 0;
auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects();
const auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects();
if (!mGodMode)
blind = static_cast<int>(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude());
MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind)));
@ -3109,7 +3109,20 @@ namespace MWWorld
{
MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
if (inv.getSelectedEnchantItem() != inv.end())
cast.cast(*inv.getSelectedEnchantItem());
{
const auto& itemPtr = *inv.getSelectedEnchantItem();
auto [slots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
int slot = 0;
for(std::size_t i = 0; i < slots.size(); ++i)
{
if(inv.getSlot(slots[i]) == inv.getSelectedEnchantItem())
{
slot = slots[i];
break;
}
}
cast.cast(itemPtr, slot);
}
}
}
@ -3144,9 +3157,9 @@ namespace MWWorld
mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength);
}
void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection)
void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot)
{
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection, slot);
}
void World::updateProjectilesCasters()
@ -3154,46 +3167,24 @@ namespace MWWorld
mProjectileManager->updateCasters();
}
class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor
{
private:
MWWorld::Ptr mActor;
public:
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
: mActor(actor)
{
}
void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1) override
{
const ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const auto magicEffect = store.get<ESM::MagicEffect>().find(key.mId);
if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
return;
const ESM::Static* castStatic;
if (!magicEffect->mHit.empty())
castStatic = store.get<ESM::Static>().find (magicEffect->mHit);
else
castStatic = store.get<ESM::Static>().find ("VFX_DefaultHit");
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
if (anim && !castStatic->mModel.empty())
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
}
};
void World::applyLoopingParticles(const MWWorld::Ptr& ptr)
{
const MWWorld::Class &cls = ptr.getClass();
if (cls.isActor())
{
ApplyLoopingParticlesVisitor visitor(ptr);
cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
if (cls.hasInventoryStore(ptr))
cls.getInventoryStore(ptr).visitEffectSources(visitor);
std::set<int> playing;
for(const auto& params : cls.getCreatureStats(ptr).getActiveSpells())
{
for(const auto& effect : params.getEffects())
{
if(playing.insert(effect.mEffectId).second)
{
const auto magicEffect = getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
if(magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx)
MWMechanics::playEffects(ptr, *magicEffect, false);
}
}
}
}
}
@ -3204,10 +3195,7 @@ namespace MWWorld
void World::breakInvisibility(const Ptr &actor)
{
actor.getClass().getCreatureStats(actor).getSpells().purgeEffect(ESM::MagicEffect::Invisibility);
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(actor, ESM::MagicEffect::Invisibility);
// Normally updated once per frame, but here it is kinda important to do it right away.
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor);
@ -3700,7 +3688,7 @@ namespace MWWorld
}
void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType,
const std::string& id, const std::string& sourceName, const bool fromProjectile)
const std::string& id, const std::string& sourceName, const bool fromProjectile, int slot)
{
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
for (const ESM::ENAMstruct& effectInfo : effects.mList)
@ -3778,7 +3766,7 @@ namespace MWWorld
cast.mHitPosition = origin;
cast.mId = id;
cast.mSourceName = sourceName;
cast.mStack = false;
cast.mSlot = slot;
ESM::EffectList effectsToApply;
effectsToApply.mList = applyPair.second;
cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true);

View file

@ -636,7 +636,7 @@ namespace MWWorld
*/
void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override;
void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override;
void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) override;
void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override;
void updateProjectilesCasters() override;
@ -681,7 +681,7 @@ namespace MWWorld
void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore,
ESM::RangeType rangeType, const std::string& id, const std::string& sourceName,
const bool fromProjectile=false) override;
const bool fromProjectile=false, int slot = 0) override;
void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override;

View file

@ -3,44 +3,67 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
namespace ESM
namespace
{
void ActiveSpells::save(ESMWriter &esm) const
void save(ESM::ESMWriter& esm, const std::vector<ESM::ActiveSpells::ActiveSpellParams>& spells, const std::string& tag)
{
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it)
for (const auto& params : spells)
{
esm.writeHNString ("ID__", it->first);
const ActiveSpellParams& params = it->second;
esm.writeHNString (tag, params.mId);
esm.writeHNT ("CAST", params.mCasterActorId);
esm.writeHNString ("DISP", params.mDisplayName);
for (std::vector<ActiveEffect>::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt)
esm.writeHNT ("TYPE", params.mType);
if(params.mItem.isSet())
params.mItem.save(esm, true, "ITEM");
if(params.mWorsenings >= 0)
{
esm.writeHNT ("MGEF", effectIt->mEffectId);
if (effectIt->mArg != -1)
esm.writeHNT ("ARG_", effectIt->mArg);
esm.writeHNT ("MAGN", effectIt->mMagnitude);
esm.writeHNT ("DURA", effectIt->mDuration);
esm.writeHNT ("EIND", effectIt->mEffectIndex);
esm.writeHNT ("LEFT", effectIt->mTimeLeft);
esm.writeHNT ("WORS", params.mWorsenings);
esm.writeHNT ("TIME", params.mNextWorsening);
}
for (auto effect : params.mEffects)
{
esm.writeHNT ("MGEF", effect.mEffectId);
if (effect.mArg != -1)
esm.writeHNT ("ARG_", effect.mArg);
esm.writeHNT ("MAGN", effect.mMagnitude);
esm.writeHNT ("MAGN", effect.mMinMagnitude);
esm.writeHNT ("MAGN", effect.mMaxMagnitude);
esm.writeHNT ("DURA", effect.mDuration);
esm.writeHNT ("EIND", effect.mEffectIndex);
esm.writeHNT ("LEFT", effect.mTimeLeft);
esm.writeHNT ("FLAG", effect.mFlags);
}
}
}
void ActiveSpells::load(ESMReader &esm)
void load(ESM::ESMReader& esm, std::vector<ESM::ActiveSpells::ActiveSpellParams>& spells, const char* tag)
{
int format = esm.getFormat();
while (esm.isNextSub("ID__"))
while (esm.isNextSub(tag))
{
std::string spellId = esm.getHString();
ActiveSpellParams params;
ESM::ActiveSpells::ActiveSpellParams params;
params.mId = esm.getHString();
esm.getHNT (params.mCasterActorId, "CAST");
params.mDisplayName = esm.getHNString ("DISP");
params.mItem.unset();
if (format < 17)
params.mType = ESM::ActiveSpells::Type_Temporary;
else
{
esm.getHNT (params.mType, "TYPE");
if(esm.peekNextSub("ITEM"))
params.mItem.load(esm, true, "ITEM");
}
if(esm.isNextSub("WORS"))
{
esm.getHT(params.mWorsenings);
esm.getHNT(params.mNextWorsening, "TIME");
}
else
params.mWorsenings = -1;
// spell casting timestamp, no longer used
if (esm.isNextSub("TIME"))
@ -48,11 +71,21 @@ namespace ESM
while (esm.isNextSub("MGEF"))
{
ActiveEffect effect;
ESM::ActiveEffect effect;
esm.getHT(effect.mEffectId);
effect.mArg = -1;
esm.getHNOT(effect.mArg, "ARG_");
esm.getHNT (effect.mMagnitude, "MAGN");
if (format < 17)
{
effect.mMinMagnitude = effect.mMagnitude;
effect.mMaxMagnitude = effect.mMagnitude;
}
else
{
esm.getHNT (effect.mMinMagnitude, "MAGN");
esm.getHNT (effect.mMaxMagnitude, "MAGN");
}
esm.getHNT (effect.mDuration, "DURA");
effect.mEffectIndex = -1;
esm.getHNOT (effect.mEffectIndex, "EIND");
@ -60,10 +93,30 @@ namespace ESM
effect.mTimeLeft = effect.mDuration;
else
esm.getHNT (effect.mTimeLeft, "LEFT");
if (format < 17)
effect.mFlags = ESM::ActiveEffect::Flag_None;
else
esm.getHNT (effect.mFlags, "FLAG");
params.mEffects.push_back(effect);
}
mSpells.insert(std::make_pair(spellId, params));
spells.emplace_back(params);
}
}
}
namespace ESM
{
void ActiveSpells::save(ESMWriter &esm) const
{
::save(esm, mSpells, "ID__");
::save(esm, mQueue, "QID_");
}
void ActiveSpells::load(ESMReader &esm)
{
::load(esm, mSpells, "ID__");
::load(esm, mQueue, "QID_");
}
}

View file

@ -1,11 +1,12 @@
#ifndef OPENMW_ESM_ACTIVESPELLS_H
#define OPENMW_ESM_ACTIVESPELLS_H
#include "effectlist.hpp"
#include "cellref.hpp"
#include "defs.hpp"
#include "effectlist.hpp"
#include <string>
#include <map>
#include <vector>
namespace ESM
{
@ -14,29 +15,53 @@ namespace ESM
// Parameters of an effect concerning lasting effects.
// Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc.
// It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster.
struct ActiveEffect
{
enum Flags
{
Flag_None = 0,
Flag_Applied = 1 << 0,
Flag_Remove = 1 << 1,
Flag_Ignore_Resistances = 1 << 2
};
int mEffectId;
float mMagnitude;
float mMinMagnitude;
float mMaxMagnitude;
int mArg; // skill or attribute
float mDuration;
float mTimeLeft;
int mEffectIndex;
int mFlags;
};
// format 0, saved games only
struct ActiveSpells
{
enum EffectType
{
Type_Temporary,
Type_Ability,
Type_Enchantment,
Type_Permanent,
Type_Consumable
};
struct ActiveSpellParams
{
std::string mId;
std::vector<ActiveEffect> mEffects;
std::string mDisplayName;
int mCasterActorId;
RefNum mItem;
EffectType mType;
int mWorsenings;
TimeStamp mNextWorsening;
};
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
TContainer mSpells;
std::vector<ActiveSpellParams> mSpells;
std::vector<ActiveSpellParams> mQueue;
void load (ESMReader &esm);
void save (ESMWriter &esm) const;

View file

@ -110,16 +110,31 @@ void ESM::CreatureStats::load (ESMReader &esm)
mAiSequence.load(esm);
mMagicEffects.load(esm);
while (esm.isNextSub("SUMM"))
if (esm.getFormat() < 17)
{
int magicEffect;
esm.getHT(magicEffect);
std::string source = esm.getHNOString("SOUR");
int effectIndex = -1;
esm.getHNOT (effectIndex, "EIND");
int actorId;
esm.getHNT (actorId, "ACID");
mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId;
while (esm.isNextSub("SUMM"))
{
int magicEffect;
esm.getHT(magicEffect);
std::string source = esm.getHNOString("SOUR");
int effectIndex = -1;
esm.getHNOT (effectIndex, "EIND");
int actorId;
esm.getHNT (actorId, "ACID");
mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId;
mSummonedCreatures.emplace(magicEffect, actorId);
}
}
else
{
while (esm.isNextSub("SUMM"))
{
int magicEffect;
esm.getHT(magicEffect);
int actorId;
esm.getHNT (actorId, "ACID");
mSummonedCreatures.emplace(magicEffect, actorId);
}
}
while (esm.isNextSub("GRAV"))
@ -214,14 +229,10 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
mAiSequence.save(esm);
mMagicEffects.save(esm);
for (const auto& summon : mSummonedCreatureMap)
for (const auto& [effectId, actorId] : mSummonedCreatures)
{
esm.writeHNT ("SUMM", summon.first.mEffectId);
esm.writeHNString ("SOUR", summon.first.mSourceId);
int effectIndex = summon.first.mEffectIndex;
if (effectIndex != -1)
esm.writeHNT ("EIND", effectIndex);
esm.writeHNT ("ACID", summon.second);
esm.writeHNT ("SUMM", effectId);
esm.writeHNT ("ACID", actorId);
}
for (int key : mSummonGraveyard)
@ -235,15 +246,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
for (int i=0; i<4; ++i)
mAiSettings[i].save(esm);
}
for (const auto& corprusSpell : mCorprusSpells)
{
esm.writeHNString("CORP", corprusSpell.first);
const CorprusStats & stats = corprusSpell.second;
esm.writeHNT("WORS", stats.mWorsenings);
esm.writeHNT("TIME", stats.mNextWorsening);
}
}
void ESM::CreatureStats::blank()

View file

@ -40,6 +40,7 @@ namespace ESM
StatState<int> mAiSettings[4];
std::map<SummonKey, int> mSummonedCreatureMap;
std::multimap<int, int> mSummonedCreatures;
std::vector<int> mSummonGraveyard;
ESM::TimeStamp mTradeTime;

View file

@ -8,10 +8,11 @@ namespace ESM
void MagicEffects::save(ESMWriter &esm) const
{
for (std::map<int, int>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
for (const auto& [key, params] : mEffects)
{
esm.writeHNT("EFID", it->first);
esm.writeHNT("BASE", it->second);
esm.writeHNT("EFID", key);
esm.writeHNT("BASE", params.first);
esm.writeHNT("MODI", params.second);
}
}
@ -19,10 +20,15 @@ void MagicEffects::load(ESMReader &esm)
{
while (esm.isNextSub("EFID"))
{
int id, base;
int id;
std::pair<int, float> params;
esm.getHT(id);
esm.getHNT(base, "BASE");
mEffects.insert(std::make_pair(id, base));
esm.getHNT(params.first, "BASE");
if(esm.getFormat() < 17)
params.second = 0.f;
else
esm.getHNT(params.second, "MODI");
mEffects.emplace(id, params);
}
}

View file

@ -12,8 +12,8 @@ namespace ESM
// format 0, saved games only
struct MagicEffects
{
// <Effect Id, Base value>
std::map<int, int> mEffects;
// <Effect Id, Base value, Modifier>
std::map<int, std::pair<int, float>> mEffects;
void load (ESMReader &esm);
void save (ESMWriter &esm) const;

View file

@ -28,6 +28,7 @@ namespace ESM
esm.writeHNString ("SPEL", mSpellId);
esm.writeHNT ("SPED", mSpeed);
esm.writeHNT ("SLOT", mSlot);
}
void MagicBoltState::load(ESMReader &esm)
@ -39,6 +40,10 @@ namespace ESM
esm.skipHSub();
ESM::EffectList().load(esm); // for backwards compatibility
esm.getHNT (mSpeed, "SPED");
if(esm.getFormat() < 17)
mSlot = 0;
else
esm.getHNT(mSlot, "SLOT");
if (esm.isNextSub("STCK")) // for backwards compatibility
esm.skipHSub();
if (esm.isNextSub("SOUN")) // for backwards compatibility

View file

@ -32,6 +32,7 @@ namespace ESM
{
std::string mSpellId;
float mSpeed;
int mSlot;
void load (ESMReader &esm);
void save (ESMWriter &esm) const;

View file

@ -4,7 +4,7 @@
#include "esmwriter.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 16;
int ESM::SavedGame::sCurrentFormat = 17;
void ESM::SavedGame::load (ESMReader &esm)
{

View file

@ -8,29 +8,38 @@ namespace ESM
void SpellState::load(ESMReader &esm)
{
while (esm.isNextSub("SPEL"))
if(esm.getFormat() < 17)
{
std::string id = esm.getHString();
SpellParams state;
while (esm.isNextSub("INDX"))
while (esm.isNextSub("SPEL"))
{
int index;
esm.getHT(index);
std::string id = esm.getHString();
float magnitude;
esm.getHNT(magnitude, "RAND");
SpellParams state;
while (esm.isNextSub("INDX"))
{
int index;
esm.getHT(index);
state.mEffectRands[index] = magnitude;
float magnitude;
esm.getHNT(magnitude, "RAND");
state.mEffectRands[index] = magnitude;
}
while (esm.isNextSub("PURG")) {
int index;
esm.getHT(index);
state.mPurgedEffects.insert(index);
}
mSpellParams[id] = state;
mSpells.emplace_back(id);
}
while (esm.isNextSub("PURG")) {
int index;
esm.getHT(index);
state.mPurgedEffects.insert(index);
}
mSpells[id] = state;
}
else
{
while (esm.isNextSub("SPEL"))
mSpells.emplace_back(esm.getHString());
}
// Obsolete
@ -88,30 +97,8 @@ namespace ESM
void SpellState::save(ESMWriter &esm) const
{
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it)
{
esm.writeHNString("SPEL", it->first);
const std::map<int, float>& random = it->second.mEffectRands;
for (std::map<int, float>::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt)
{
esm.writeHNT("INDX", rIt->first);
esm.writeHNT("RAND", rIt->second);
}
const std::set<int>& purges = it->second.mPurgedEffects;
for (std::set<int>::const_iterator pIt = purges.begin(); pIt != purges.end(); ++pIt)
esm.writeHNT("PURG", *pIt);
}
for (std::map<std::string, CorprusStats>::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);
}
for (const std::string& spell : mSpells)
esm.writeHNString("SPEL", spell);
for (std::map<std::string, TimeStamp>::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it)
{

View file

@ -31,13 +31,13 @@ namespace ESM
struct SpellParams
{
std::map<int, float> mEffectRands;
std::set<int> mPurgedEffects;
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
std::set<int> mPurgedEffects; // indices of purged effects
};
typedef std::map<std::string, SpellParams> TContainer;
TContainer mSpells;
std::vector<std::string> mSpells;
// FIXME: obsolete, used only for old saves
std::map<std::string, SpellParams> mSpellParams;
std::map<std::string, std::vector<PermanentSpellEffectInfo> > mPermanentSpellEffects;
std::map<std::string, CorprusStats> mCorprusSpells;