1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-21 18:40:01 +00:00
florent.teppe 1ced0c912e partially revert "Store: moved all the template specialization to its own heaper file, included where it's needed"
This reverts commit 80a25bcd3021f7ebfaf2f864e34532009b9b8aeb.
It didn't really make sense to do all those changes in the same MR

partially Revert "Store refactoring: more forgotten storeSpecialization.hpp"

This reverts commit 9943a5bc96b9025f06cbaac5bb7f1bf51ebc746f.

removed remaining references to storeSpecialization  CMakeLists.txt,  and landmanager.cpp
2022-09-05 17:35:06 +02:00

528 lines
21 KiB
C++

#include "activespells.hpp"
#include <optional>
#include <components/debug/debuglog.hpp>
#include <components/misc/rng.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/settings/settings.hpp>
#include "actorutil.hpp"
#include "creaturestats.hpp"
#include "spellcasting.hpp"
#include "spelleffects.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/animation.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
{
ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells)
{
mActiveSpells.mIterating = true;
}
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})
{}
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor)
: mId(params.mId), mDisplayName(params.mDisplayName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mSlot(params.mSlot), mType(params.mType), mWorsenings(-1)
{}
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)
{
// 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)
{
++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)
{
auto effect = *effectIt;
effectIt = spellIt->mEffects.erase(effectIt);
onMagicEffectRemoved(ptr, *spellIt, effect);
removedSpell = applyPurges(ptr, &spellIt, &effectIt);
if(removedSpell)
break;
}
else
{
++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 std::string_view 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)
{
return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId();
}) != mSpells.end())
continue;
// world->breakInvisibility leads to a stack overflow as it calls this method so just break invisibility manually
purgeEffect(ptr, ESM::MagicEffect::Invisibility);
applyPurges(ptr);
const ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{*slot, enchantment, slotIndex, ptr});
for(const auto& effect : params.mEffects)
MWMechanics::playEffects(ptr, *world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId), playNonLooping);
}
}
}
const MWWorld::Ptr player = MWMechanics::getPlayer();
bool updatedHitOverlay = false;
bool updatedEnemy = false;
// Update effects
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
bool removedSpell = false;
std::optional<ActiveSpellParams> reflected;
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
{
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
if(result.mType == MagicApplicationResult::Type::REFLECTED)
{
if(!reflected)
{
static const bool keepOriginalCaster = Settings::Manager::getBool("classic reflected absorb spells behavior", "Game");
if(keepOriginalCaster)
reflected = {*spellIt, caster};
else
reflected = {*spellIt, ptr};
}
auto& reflectedEffect = reflected->mEffects.emplace_back(*it);
reflectedEffect.mFlags = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
it = spellIt->mEffects.erase(it);
}
else if(result.mType == MagicApplicationResult::Type::REMOVED)
it = spellIt->mEffects.erase(it);
else
{
++it;
if(!updatedEnemy && result.mShowHealth && caster == player && ptr != player)
{
MWBase::Environment::get().getWindowManager()->setEnemy(ptr);
updatedEnemy = true;
}
if(!updatedHitOverlay && result.mShowHit && ptr == player)
{
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
updatedHitOverlay = true;
}
}
removedSpell = applyPurges(ptr, &spellIt, &it);
if(removedSpell)
break;
}
if(reflected)
{
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Reflect");
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
if(animation && !reflectStatic->mModel.empty())
{
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
animation->addEffect(
Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel, vfs),
ESM::MagicEffect::Reflect, false);
}
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected);
}
if(removedSpell)
continue;
bool remove = false;
if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent)
{
try
{
remove = !spells.hasSpell(spellIt->mId);
}
catch(const std::runtime_error& e)
{
remove = true;
Log(Debug::Error) << "Removing active effect: " << e.what();
}
}
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::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
{
if(spell.mType != ESM::ActiveSpells::Type_Consumable)
{
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing)
{
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() : mIterating(false)
{}
ActiveSpells::TIterator ActiveSpells::begin() const
{
return mSpells.begin();
}
ActiveSpells::TIterator ActiveSpells::end() const
{
return mSpells.end();
}
bool ActiveSpells::isSpellActive(std::string_view id) const
{
return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell)
{
return Misc::StringUtils::ciEqual(spell.mId, id);
}) != mSpells.end();
}
void ActiveSpells::addSpell(const ActiveSpellParams& params)
{
mQueue.emplace_back(params);
}
void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor)
{
mQueue.emplace_back(ActiveSpellParams{spell, actor, true});
}
void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr)
{
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
mPurges.emplace(predicate);
if(!mIterating)
{
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();)
{
bool isCurrentSpell = currentSpell && *currentSpell == spellIt;
std::visit([&] (auto&& variant)
{
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 MWWorld::Ptr& ptr, std::string_view id)
{
purge([=] (const ActiveSpellParams& params)
{
return params.mId == id;
}, ptr);
}
void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId)
{
purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect)
{
return effect.mEffectId == effectId;
}, ptr);
}
void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
{
purge([=] (const ActiveSpellParams& params)
{
return params.mCasterActorId == casterActorId;
}, ptr);
}
void ActiveSpells::clear(const MWWorld::Ptr& ptr)
{
mQueue.clear();
purge([] (const ActiveSpellParams& params) { return true; }, ptr);
}
void ActiveSpells::skipWorsenings(double hours)
{
for(auto& spell : mSpells)
{
if(spell.mWorsenings >= 0)
spell.mNextWorsening += hours;
}
}
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
{
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(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});
}
void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr)
{
purge([] (const auto& spell)
{
return spell.getType() == ESM::ActiveSpells::Type_Consumable || spell.getType() == ESM::ActiveSpells::Type_Temporary;
}, ptr);
mQueue.clear();
}
}