1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-19 03:00:53 +00:00

Replace actor ids in magic effects with refnums

This commit is contained in:
Evil Eye 2024-12-29 13:55:58 +01:00
parent 8020bfcafd
commit c4bc040b4b
26 changed files with 155 additions and 147 deletions

View file

@ -294,7 +294,7 @@ namespace MWBase
/// It only applies to the current form the NPC is in.
virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0;
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0;
virtual void cleanupSummonedCreature(ESM::RefNum creature) = 0;
virtual void confiscateStolenItemToOwner(
const MWWorld::Ptr& player, const MWWorld::Ptr& item, const MWWorld::Ptr& victim, int count)

View file

@ -312,7 +312,7 @@ namespace MWGui
// Clean up summoned creatures as well
auto& creatureMap = creatureStats.getSummonedCreatureMap();
for (const auto& creature : creatureMap)
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second);
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(creature.second);
creatureMap.clear();
// Check if we are a summon and inform our master we've bit the dust
@ -323,7 +323,7 @@ namespace MWGui
const auto& summoner = package->getTarget();
auto& summons = summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap();
auto it = std::find_if(summons.begin(), summons.end(),
[&](const auto& entry) { return entry.second == creatureStats.getActorId(); });
[&](const auto& entry) { return entry.second == ptr.getCellRef().getRefNum(); });
if (it != summons.end())
{
auto summon = *it;

View file

@ -545,9 +545,8 @@ namespace MWLua
return sol::make_object(lua, LObject(itemPtr));
});
activeSpellT["caster"]
= sol::readonly_property([lua = state.lua_state()](const ActiveSpell& activeSpell) -> sol::object {
auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(
activeSpell.mParams.getCasterActorId());
= sol::readonly_property([lua = lua.lua_state()](const ActiveSpell& activeSpell) -> sol::object {
auto caster = MWBase::Environment::get().getWorldModel()->getPtr(activeSpell.mParams.getCaster());
if (caster.isEmpty())
return sol::nil;
else

View file

@ -9,6 +9,7 @@
#include <components/misc/strings/algorithm.hpp>
#include <components/esm/generatedrefid.hpp>
#include <components/esm3/actoridconverter.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadstat.hpp>
@ -30,6 +31,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/worldmodel.hpp"
namespace
{
@ -103,20 +105,19 @@ namespace MWMechanics
const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item)
: mSourceSpellId(id)
, mDisplayName(sourceName)
, mCasterActorId(-1)
, mItem(item)
, mFlags()
, mWorsenings(-1)
{
if (!caster.isEmpty() && caster.getClass().isActor())
mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId();
mCaster = caster.getCellRef().getRefNum();
}
ActiveSpells::ActiveSpellParams::ActiveSpellParams(
const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances)
: mSourceSpellId(spell->mId)
, mDisplayName(spell->mName)
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mCaster(actor.getCellRef().getRefNum())
, mFlags()
, mWorsenings(-1)
{
@ -131,7 +132,7 @@ namespace MWMechanics
const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor)
: mSourceSpellId(item.getCellRef().getRefId())
, mDisplayName(item.getClass().getName(item))
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mCaster(actor.getCellRef().getRefNum())
, mItem(item.getCellRef().getRefNum())
, mFlags()
, mWorsenings(-1)
@ -146,7 +147,7 @@ namespace MWMechanics
, mSourceSpellId(params.mSourceSpellId)
, mEffects(params.mEffects)
, mDisplayName(params.mDisplayName)
, mCasterActorId(params.mCasterActorId)
, mCaster(params.mCaster)
, mItem(params.mItem)
, mFlags(params.mFlags)
, mWorsenings(params.mWorsenings)
@ -157,7 +158,7 @@ namespace MWMechanics
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor)
: mSourceSpellId(params.mSourceSpellId)
, mDisplayName(params.mDisplayName)
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mCaster(actor.getCellRef().getRefNum())
, mItem(params.mItem)
, mFlags(params.mFlags)
, mWorsenings(-1)
@ -171,7 +172,7 @@ namespace MWMechanics
params.mSourceSpellId = mSourceSpellId;
params.mEffects = mEffects;
params.mDisplayName = mDisplayName;
params.mCasterActorId = mCasterActorId;
params.mCaster = mCaster;
params.mItem = mItem;
params.mFlags = mFlags;
params.mWorsenings = mWorsenings;
@ -375,8 +376,7 @@ namespace MWMechanics
bool ActiveSpells::updateActiveSpell(
const MWWorld::Ptr& ptr, float duration, Collection::iterator& spellIt, UpdateContext& context)
{
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(
spellIt->mCasterActorId); // Maybe make this search outside active grid?
const auto caster = MWBase::Environment::get().getWorldModel()->getPtr(spellIt->mCaster);
bool removedSpell = false;
std::optional<ActiveSpellParams> reflected;
for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
@ -484,8 +484,8 @@ namespace MWMechanics
if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable))
{
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) {
return spell.mSourceSpellId == existing.mSourceSpellId
&& spell.mCasterActorId == existing.mCasterActorId && spell.mItem == existing.mItem;
return spell.mSourceSpellId == existing.mSourceSpellId && spell.mCaster == existing.mCaster
&& spell.mItem == existing.mItem;
});
if (found != mSpells.end())
{
@ -659,9 +659,9 @@ namespace MWMechanics
ptr);
}
void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
void ActiveSpells::purge(const MWWorld::Ptr& ptr, ESM::RefNum actor)
{
purge([=](const ActiveSpellParams& params) { return params.mCasterActorId == casterActorId; }, ptr);
purge([=](const ActiveSpellParams& params) { return params.mCaster == actor; }, ptr);
}
void ActiveSpells::clear(const MWWorld::Ptr& ptr)
@ -698,6 +698,22 @@ namespace MWMechanics
}
for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
mQueue.emplace_back(ActiveSpellParams{ spell });
if (state.mActorIdConverter)
{
const auto convertSummons = [converter = state.mActorIdConverter](auto& collection) {
for (ActiveSpellParams& params : collection)
{
converter->convert(params.mCaster, params.mCaster.mIndex);
for (ESM::ActiveEffect& effect : params.mEffects)
{
if (ESM::RefNum* refNum = std::get_if<ESM::RefNum>(&effect.mArg))
converter->convert(*refNum, refNum->mIndex);
}
}
};
convertSummons(mSpells);
convertSummons(mQueue);
}
}
void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr)

View file

@ -37,7 +37,7 @@ namespace MWMechanics
ESM::RefId mSourceSpellId;
std::vector<ActiveEffect> mEffects;
std::string mDisplayName;
int mCasterActorId;
ESM::RefNum mCaster;
ESM::RefNum mItem;
ESM::ActiveSpells::Flags mFlags;
int mWorsenings;
@ -69,7 +69,7 @@ namespace MWMechanics
const std::vector<ActiveEffect>& getEffects() const { return mEffects; }
std::vector<ActiveEffect>& getEffects() { return mEffects; }
int getCasterActorId() const { return mCasterActorId; }
ESM::RefNum getCaster() const { return mCaster; }
int getWorsenings() const { return mWorsenings; }
@ -157,8 +157,8 @@ namespace MWMechanics
void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);
/// Remove all effects that were cast by \a casterActorId
void purge(const MWWorld::Ptr& ptr, int casterActorId);
/// Remove all effects that were cast by \a actor
void purge(const MWWorld::Ptr& ptr, ESM::RefNum actor);
/// Remove all spells
void clear(const MWWorld::Ptr& ptr);

View file

@ -179,7 +179,7 @@ namespace
{
if (effect.mEffectId != ESM::MagicEffect::Soultrap || effect.mMagnitude <= 0.f)
continue;
MWWorld::Ptr caster = world->searchPtrViaActorId(params.getCasterActorId());
MWWorld::Ptr caster = MWBase::Environment::get().getWorldModel()->getPtr(params.getCaster());
if (caster.isEmpty() || !caster.getClass().isActor())
continue;
@ -791,8 +791,7 @@ namespace MWMechanics
{
bool actorKilled = false;
MWWorld::Ptr caster
= MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId());
MWWorld::Ptr caster = MWBase::Environment::get().getWorldModel()->getPtr(spell.getCaster());
if (caster.isEmpty())
continue;
for (const auto& effect : spell.getEffects())
@ -833,7 +832,7 @@ namespace MWMechanics
if (!creature.isInCell())
return;
if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty())
if (!creatureStats.getSummonedCreatureMap().empty())
updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f);
}
@ -1816,7 +1815,7 @@ namespace MWMechanics
= stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Vampirism).getMagnitude();
stats.getActiveSpells().clear(actor.getPtr());
// Make sure spell effects are removed
purgeSpellEffects(stats.getActorId());
purgeSpellEffects(actor.getPtr().getCellRef().getRefNum());
stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism);
@ -1834,9 +1833,9 @@ namespace MWMechanics
}
}
void Actors::cleanupSummonedCreature(MWMechanics::CreatureStats& casterStats, int creatureActorId) const
void Actors::cleanupSummonedCreature(ESM::RefNum creature) const
{
const MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId);
const MWWorld::Ptr ptr = MWBase::Environment::get().getWorldModel()->getPtr(creature);
if (!ptr.isEmpty())
{
MWBase::Environment::get().getWorld()->deleteObject(ptr);
@ -1849,24 +1848,16 @@ namespace MWMechanics
ptr.getRefData().getPosition().asVec3());
// Remove the summoned creature's summoned creatures as well
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
auto& creatureMap = stats.getSummonedCreatureMap();
for (const auto& creature : creatureMap)
cleanupSummonedCreature(stats, creature.second);
auto& creatureMap = ptr.getClass().getCreatureStats(ptr).getSummonedCreatureMap();
for (const auto& [_, refNum] : creatureMap)
cleanupSummonedCreature(refNum);
creatureMap.clear();
}
else if (creatureActorId != -1)
{
// We didn't find the creature. It's probably in an inactive cell.
// Add to graveyard so we can delete it when the cell becomes active.
std::vector<int>& graveyard = casterStats.getSummonedCreatureGraveyard();
graveyard.push_back(creatureActorId);
}
purgeSpellEffects(creatureActorId);
purgeSpellEffects(creature);
}
void Actors::purgeSpellEffects(int casterActorId) const
void Actors::purgeSpellEffects(ESM::RefNum creature) const
{
for (const Actor& actor : mActors)
{
@ -1874,7 +1865,7 @@ namespace MWMechanics
continue;
MWMechanics::ActiveSpells& spells
= actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActiveSpells();
spells.purge(actor.getPtr(), casterActorId);
spells.purge(actor.getPtr(), creature);
}
}

View file

@ -35,7 +35,6 @@ namespace MWMechanics
{
class Actor;
class CharacterController;
class CreatureStats;
class SidingCache;
class Actors
@ -128,7 +127,7 @@ namespace MWMechanics
bool isAnyObjectInRange(const osg::Vec3f& position, float radius) const;
void cleanupSummonedCreature(CreatureStats& casterStats, int creatureActorId) const;
void cleanupSummonedCreature(ESM::RefNum creature) const;
/// Returns the list of actors which are siding with the given actor in fights
/**ie AiFollow or AiEscort is active and the target is the actor **/
@ -189,7 +188,7 @@ namespace MWMechanics
void killDeadActors();
void purgeSpellEffects(int casterActorId) const;
void purgeSpellEffects(ESM::RefNum creature) const;
void predictAndAvoidCollisions(float duration) const;

View file

@ -78,7 +78,7 @@ namespace MWMechanics
AiFollow::AiFollow(const ESM::AiSequence::AiFollow* follow)
: TypedAiPackage<AiFollow>(
makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat))
makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat))
, mAlwaysFollow(follow->mAlwaysFollow)
, mDuration(follow->mData.mDuration)
, mRemainingDuration(follow->mRemainingDuration)

View file

@ -181,6 +181,7 @@ namespace MWMechanics
bool mShortcutProhibited = false; // shortcutting may be prohibited after unsuccessful attempt
friend class AiSequence;
private:
bool isNearInactiveCell(osg::Vec3f position);
};

View file

@ -41,7 +41,8 @@ namespace MWMechanics
if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled())
return true;
// This is equivalent to checking if the actor is registered with the mechanics manager since every actor has a script
// This is equivalent to checking if the actor is registered with the mechanics manager since every actor has a
// script
if (const MWLua::LocalScripts* scripts = target.getRefData().getLuaScripts())
{
if (!scripts->isActive())

View file

@ -3,6 +3,7 @@
#include <algorithm>
#include <type_traits>
#include <components/esm3/actoridconverter.hpp>
#include <components/esm3/creaturestats.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
@ -550,7 +551,6 @@ namespace MWMechanics
mMagicEffects.writeState(state.mMagicEffects);
state.mSummonedCreatures = mSummonedCreatures;
state.mSummonGraveyard = mSummonGraveyard;
state.mHasAiSettings = true;
for (size_t i = 0; i < state.mAiSettings.size(); ++i)
@ -596,7 +596,7 @@ namespace MWMechanics
mActorId = state.mActorId;
mDeathAnimation = state.mDeathAnimation;
mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath);
// mHitAttemptActorId = state.mHitAttemptActorId;
// mHitAttemptActor = state.mHitAttemptActor;
mSpells.readState(state.mSpells, this);
mActiveSpells.readState(state.mActiveSpells);
@ -604,13 +604,19 @@ namespace MWMechanics
mMagicEffects.readState(state.mMagicEffects);
mSummonedCreatures = state.mSummonedCreatures;
mSummonGraveyard = state.mSummonGraveyard;
if (state.mHasAiSettings)
for (size_t i = 0; i < state.mAiSettings.size(); ++i)
mAiSettings[i].readState(state.mAiSettings[i]);
if (state.mRecalcDynamicStats)
recalculateMagicka();
if (state.mAiSequence.mActorIdConverter)
{
for (auto& [_, refNum] : mSummonedCreatures)
state.mAiSequence.mActorIdConverter->convert(refNum, refNum.mIndex);
auto& graveyard = state.mAiSequence.mActorIdConverter->mGraveyard;
graveyard.insert(graveyard.end(), state.mSummonGraveyard.begin(), state.mSummonGraveyard.end());
}
}
void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime)
@ -677,16 +683,11 @@ namespace MWMechanics
return mTimeOfDeath;
}
std::multimap<int, int>& CreatureStats::getSummonedCreatureMap()
std::multimap<int, ESM::RefNum>& CreatureStats::getSummonedCreatureMap()
{
return mSummonedCreatures;
}
std::vector<int>& CreatureStats::getSummonedCreatureGraveyard()
{
return mSummonGraveyard;
}
void CreatureStats::updateAwareness(float duration)
{
mAwarenessTimer += duration;

View file

@ -84,11 +84,7 @@ namespace MWMechanics
MWWorld::TimeStamp mTimeOfDeath;
private:
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::multimap<int, ESM::RefNum> mSummonedCreatures; // <Effect, ActorId>
float mAwarenessTimer = 0.f;
int mAwarenessRoll = -1;
@ -237,8 +233,7 @@ namespace MWMechanics
void setBlock(bool value);
bool getBlock() const;
std::multimap<int, int>& getSummonedCreatureMap(); // <Effect, ActorId of summoned creature>
std::vector<int>& getSummonedCreatureGraveyard(); // ActorIds
std::multimap<int, ESM::RefNum>& getSummonedCreatureMap(); // <Effect, ActorId of summoned creature>
enum Flag
{

View file

@ -2042,9 +2042,9 @@ namespace MWMechanics
skill.setModifier(acrobatics->mWerewolfValue - skill.getModified());
}
void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId)
void MechanicsManager::cleanupSummonedCreature(ESM::RefNum creature)
{
mActors.cleanupSummonedCreature(caster.getClass().getCreatureStats(caster), creatureActorId);
mActors.cleanupSummonedCreature(creature);
}
void MechanicsManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const

View file

@ -227,7 +227,7 @@ namespace MWMechanics
void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override;
void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) override;
void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) override;
void cleanupSummonedCreature(ESM::RefNum creature) override;
void confiscateStolenItemToOwner(
const MWWorld::Ptr& player, const MWWorld::Ptr& item, const MWWorld::Ptr& victim, int count) override;

View file

@ -33,6 +33,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/worldmodel.hpp"
namespace
{
@ -1106,6 +1107,7 @@ namespace MWMechanics
const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
{
const auto world = MWBase::Environment::get().getWorld();
const auto worldModel = MWBase::Environment::get().getWorldModel();
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
switch (effect.mEffectId)
{
@ -1192,14 +1194,14 @@ namespace MWMechanics
case ESM::MagicEffect::SummonCreature04:
case ESM::MagicEffect::SummonCreature05:
{
int actorId = effect.getActorId();
if (actorId != -1)
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, actorId);
ESM::RefNum actor = effect.getActor();
if (actor.isSet())
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(actor);
auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap();
auto [begin, end] = summons.equal_range(effect.mEffectId);
for (auto it = begin; it != end; ++it)
{
if (it->second == actorId)
if (it->second == actor)
{
summons.erase(it);
break;
@ -1284,7 +1286,7 @@ namespace MWMechanics
break;
case ESM::MagicEffect::AbsorbAttribute:
{
const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
const auto caster = worldModel->getPtr(spellParams.getCaster());
restoreAttribute(target, effect, effect.mMagnitude);
if (!caster.isEmpty())
fortifyAttribute(caster, effect, -effect.mMagnitude);
@ -1294,7 +1296,7 @@ namespace MWMechanics
{
if (target.getClass().isNpc())
restoreSkill(target, effect, effect.mMagnitude);
const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
const auto caster = worldModel->getPtr(spellParams.getCaster());
if (!caster.isEmpty() && caster.getClass().isNpc())
fortifySkill(caster, effect, -effect.mMagnitude);
}

View file

@ -82,10 +82,10 @@ namespace
bool isSpellActive(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const ESM::RefId& id)
{
int actorId = caster.getClass().getCreatureStats(caster).getActorId();
ESM::RefNum actor = caster.getCellRef().getRefNum();
const auto& active = target.getClass().getCreatureStats(target).getActiveSpells();
return std::find_if(active.begin(), active.end(), [&](const auto& spell) {
return spell.getCasterActorId() == actorId && spell.getSourceSpellId() == id;
return spell.getCaster() == actor && spell.getSourceSpellId() == id;
}) != active.end();
}

View file

@ -13,6 +13,7 @@
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/worldmodel.hpp"
#include "../mwrender/animation.hpp"
@ -80,10 +81,10 @@ namespace MWMechanics
return ESM::RefId();
}
int summonCreature(int effectId, const MWWorld::Ptr& summoner)
ESM::RefNum summonCreature(int effectId, const MWWorld::Ptr& summoner)
{
const ESM::RefId& creatureID = getSummonedCreature(effectId);
int creatureActorId = -1;
ESM::RefNum creature;
if (!creatureID.empty())
{
try
@ -91,13 +92,12 @@ namespace MWMechanics
auto world = MWBase::Environment::get().getWorld();
MWWorld::ManualRef ref(world->getStore(), creatureID, 1);
MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f);
MWMechanics::CreatureStats& summonedCreatureStats = placed.getClass().getCreatureStats(placed);
MWBase::Environment::get().getWorldModel()->registerPtr(placed);
creature = placed.getCellRef().getRefNum();
// Make the summoned creature follow its master and help in fights
AiFollow package(summoner);
summonedCreatureStats.getAiSequence().stack(package, placed);
creatureActorId = summonedCreatureStats.getActorId();
placed.getClass().getCreatureStats(placed).getAiSequence().stack(package, placed);
MWRender::Animation* anim = world->getAnimation(placed);
if (anim)
@ -117,9 +117,9 @@ namespace MWMechanics
// log
}
summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creatureActorId);
summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creature);
}
return creatureActorId;
return creature;
}
void updateSummons(const MWWorld::Ptr& summoner, bool cleanup)
@ -127,24 +127,18 @@ namespace MWMechanics
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(summoner, creature);
if (!cleanup)
return;
for (auto it = creatureMap.begin(); it != creatureMap.end();)
{
if (it->second == -1)
if (!it->second.isSet())
{
// Keep the spell effect active if we failed to spawn anything
it++;
continue;
}
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
MWWorld::Ptr ptr = MWBase::Environment::get().getWorldModel()->getPtr(it->second);
if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()
&& ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())
{
@ -158,15 +152,15 @@ namespace MWMechanics
}
}
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, int>& summon)
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, ESM::RefNum>& summon)
{
auto& creatureStats = summoner.getClass().getCreatureStats(summoner);
creatureStats.getActiveSpells().purge(
[summon](const auto& spell, const auto& effect) {
return effect.mEffectId == summon.first && effect.getActorId() == summon.second;
return effect.mEffectId == summon.first && effect.getActor() == summon.second;
},
summoner);
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second);
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summon.second);
}
}

View file

@ -3,6 +3,9 @@
#include <string_view>
#include <utility>
#include <components/esm3/refnum.hpp>
namespace ESM
{
class RefId;
@ -18,9 +21,9 @@ namespace MWMechanics
ESM::RefId getSummonedCreature(int effectId);
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, int>& summon);
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, ESM::RefNum>& summon);
int summonCreature(int effectId, const MWWorld::Ptr& summoner);
ESM::RefNum summonCreature(int effectId, const MWWorld::Ptr& summoner);
void updateSummons(const MWWorld::Ptr& summoner, bool cleanup);
}

View file

@ -651,6 +651,12 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
MWBase::Environment::get().getWorldScene()->markCellAsUnchanged();
MWBase::Environment::get().getLuaManager()->gameLoaded();
for (int actorId : actorIdConverter.mGraveyard)
{
auto mapped = actorIdConverter.mMappings.find(actorId);
if (mapped != actorIdConverter.mMappings.end())
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mapped->second);
}
}
catch (const SaveVersionTooNewError& e)
{

View file

@ -61,7 +61,7 @@ namespace MWWorld
ESM::ActiveSpells::ActiveSpellParams params;
params.mSourceSpellId = id;
params.mDisplayName = spell->mName;
params.mCasterActorId = creatureStats.mActorId;
params.mCaster.mIndex = creatureStats.mActorId;
if (spell->mData.mType == ESM::Spell::ST_Ability)
params.mFlags = ESM::Compatibility::ActiveSpells::Type_Ability_Flags;
else
@ -137,7 +137,7 @@ namespace MWWorld
ESM::ActiveSpells::ActiveSpellParams params;
params.mSourceSpellId = id;
params.mDisplayName = std::move(name);
params.mCasterActorId = creatureStats.mActorId;
params.mCaster.mIndex = creatureStats.mActorId;
params.mFlags = ESM::Compatibility::ActiveSpells::Type_Enchantment_Flags;
params.mWorsenings = -1;
params.mNextWorsening = ESM::TimeStamp();
@ -196,7 +196,7 @@ namespace MWWorld
{
if (effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex)
{
effect.mArg = actorId;
effect.mArg = ESM::RefNum{ .mIndex = static_cast<uint32_t>(actorId), .mContentFile = -1 };
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
found = true;
break;

View file

@ -5,6 +5,7 @@
#include <components/debug/debuglog.hpp>
#include <components/esm/defs.hpp>
#include <components/esm3/actoridconverter.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadbsgn.hpp>
@ -344,6 +345,8 @@ namespace MWWorld
MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(mPlayer);
mPlayer.load(player.mObject);
MWBase::Environment::get().getWorldModel()->registerPtr(getPlayer());
if (ESM::ActorIdConverter* converter = reader.getActorIdConverter())
converter->mMappings.emplace(player.mObject.mCreatureStats.mActorId, mPlayer.mRef.getRefNum());
for (size_t i = 0; i < mSaveAttributes.size(); ++i)
mSaveAttributes[i] = player.mSaveAttributes[i];

View file

@ -70,25 +70,6 @@ namespace ESM
return false;
}
struct ToInt
{
int effectId;
int operator()(const ESM::RefId& id) const
{
if (!id.empty())
{
if (affectsAttribute(effectId))
return ESM::Attribute::refIdToIndex(id);
else if (affectsSkill(effectId))
return ESM::Skill::refIdToIndex(id);
}
return -1;
}
int operator()(int actor) const { return actor; }
};
void saveImpl(ESMWriter& esm, const std::vector<ActiveSpells::ActiveSpellParams>& spells, NAME tag)
{
for (const auto& params : spells)
@ -96,7 +77,7 @@ namespace ESM
esm.writeHNRefId(tag, params.mSourceSpellId);
esm.writeHNRefId("SPID", params.mActiveSpellId);
esm.writeHNT("CAST", params.mCasterActorId);
esm.writeFormId(params.mCaster, true, "CAST");
esm.writeHNString("DISP", params.mDisplayName);
esm.writeHNT("FLAG", params.mFlags);
if (params.mItem.isSet())
@ -110,9 +91,16 @@ namespace ESM
for (auto& effect : params.mEffects)
{
esm.writeHNT("MGEF", effect.mEffectId);
int arg = std::visit(ToInt{ effect.mEffectId }, effect.mArg);
if (arg != -1)
esm.writeHNT("ARG_", arg);
if (const ESM::RefId* id = std::get_if<ESM::RefId>(&effect.mArg))
{
if (!id->empty())
esm.writeHNRefId("ARG_", *id);
}
else if (const ESM::RefNum* actor = std::get_if<ESM::RefNum>(&effect.mArg))
{
if (actor->isSet())
esm.writeFormId(*actor, true, "SUM_");
}
esm.writeHNT("MAGN", effect.mMagnitude);
esm.writeHNT("MAGN", effect.mMinMagnitude);
esm.writeHNT("MAGN", effect.mMaxMagnitude);
@ -134,7 +122,10 @@ namespace ESM
params.mSourceSpellId = esm.getRefId();
if (format > MaxActiveSpellTypeVersion)
params.mActiveSpellId = esm.getHNRefId("SPID");
esm.getHNT(params.mCasterActorId, "CAST");
if (format <= MaxActorIdSaveGameFormatVersion)
esm.getHNT(params.mCaster.mIndex, "CAST");
else
params.mCaster = esm.getFormId("CAST");
params.mDisplayName = esm.getHNString("DISP");
if (format <= MaxClearModifiersFormatVersion)
params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags;
@ -186,17 +177,24 @@ namespace ESM
{
ActiveEffect effect;
esm.getHT(effect.mEffectId);
int32_t arg = -1;
esm.getHNOT(arg, "ARG_");
if (arg >= 0)
if (format <= MaxActorIdSaveGameFormatVersion)
{
if (isSummon(effect.mEffectId))
effect.mArg = arg;
else if (affectsAttribute(effect.mEffectId))
effect.mArg = ESM::Attribute::indexToRefId(arg);
else if (affectsSkill(effect.mEffectId))
effect.mArg = ESM::Skill::indexToRefId(arg);
int32_t arg = -1;
esm.getHNOT(arg, "ARG_");
if (arg >= 0)
{
if (isSummon(effect.mEffectId))
effect.mArg = RefNum{ .mIndex = static_cast<uint32_t>(arg), .mContentFile = -1 };
else if (affectsAttribute(effect.mEffectId))
effect.mArg = ESM::Attribute::indexToRefId(arg);
else if (affectsSkill(effect.mEffectId))
effect.mArg = ESM::Skill::indexToRefId(arg);
}
}
else if (esm.peekNextSub("ARG_"))
effect.mArg = esm.getHNRefId("ARG_");
else if (esm.peekNextSub("SUM_"))
effect.mArg = esm.getFormId("SUM_");
esm.getHNT(effect.mMagnitude, "MAGN");
if (format <= MaxClearModifiersFormatVersion)
{
@ -235,21 +233,22 @@ namespace ESM
void ActiveSpells::load(ESMReader& esm)
{
mActorIdConverter = esm.getActorIdConverter();
loadImpl(esm, mSpells, "ID__");
loadImpl(esm, mQueue, "QID_");
}
RefId ActiveEffect::getSkillOrAttribute() const
{
if (const auto* id = std::get_if<ESM::RefId>(&mArg))
if (const ESM::RefId* id = std::get_if<ESM::RefId>(&mArg))
return *id;
return {};
}
int ActiveEffect::getActorId() const
RefNum ActiveEffect::getActor() const
{
if (const auto* id = std::get_if<int>(&mArg))
return *id;
return -1;
if (const ESM::RefNum* actor = std::get_if<ESM::RefNum>(&mArg))
return *actor;
return {};
}
}

View file

@ -14,6 +14,7 @@ namespace ESM
{
class ESMReader;
class ESMWriter;
class ActorIdConverter;
// Parameters of an effect concerning lasting effects.
// Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc.
@ -34,14 +35,14 @@ namespace ESM
float mMagnitude;
float mMinMagnitude;
float mMaxMagnitude;
std::variant<RefId, int> mArg; // skill, attribute, or summon
std::variant<RefId, RefNum> mArg; // skill, attribute, or summon
float mDuration;
float mTimeLeft;
int32_t mEffectIndex;
int32_t mFlags;
RefId getSkillOrAttribute() const;
int getActorId() const;
RefNum getActor() const;
};
// format 0, saved games only
@ -65,7 +66,7 @@ namespace ESM
RefId mSourceSpellId;
std::vector<ActiveEffect> mEffects;
std::string mDisplayName;
int32_t mCasterActorId;
RefNum mCaster;
RefNum mItem;
Flags mFlags;
int32_t mWorsenings;
@ -74,6 +75,7 @@ namespace ESM
std::vector<ActiveSpellParams> mSpells;
std::vector<ActiveSpellParams> mQueue;
ActorIdConverter* mActorIdConverter = nullptr;
void load(ESMReader& esm);
void save(ESMWriter& esm) const;

View file

@ -14,6 +14,7 @@ namespace ESM
public:
std::map<int, ESM::RefNum> mMappings;
std::vector<int> mGraveyard;
void apply();

View file

@ -251,11 +251,6 @@ namespace ESM
esm.writeHNT("ACID", actorId);
}
for (int32_t key : mSummonGraveyard)
{
esm.writeHNT("GRAV", key);
}
esm.writeHNT("AISE", mHasAiSettings);
if (mHasAiSettings)
{

View file

@ -43,7 +43,7 @@ namespace ESM
std::array<StatState<int>, 4> mAiSettings;
std::map<SummonKey, int> mSummonedCreatureMap;
std::multimap<int, int> mSummonedCreatures;
std::multimap<int, RefNum> mSummonedCreatures;
std::vector<int> mSummonGraveyard;
TimeStamp mTradeTime;