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:
parent
5ce2eff3bd
commit
dc1fe62dde
65 changed files with 2360 additions and 2734 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
1032
apps/openmw/mwmechanics/spelleffects.cpp
Normal file
1032
apps/openmw/mwmechanics/spelleffects.cpp
Normal file
File diff suppressed because it is too large
Load diff
20
apps/openmw/mwmechanics/spelleffects.hpp
Normal file
20
apps/openmw/mwmechanics/spelleffects.hpp
Normal 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
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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_");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,6 +32,7 @@ namespace ESM
|
|||
{
|
||||
std::string mSpellId;
|
||||
float mSpeed;
|
||||
int mSlot;
|
||||
|
||||
void load (ESMReader &esm);
|
||||
void save (ESMWriter &esm) const;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue