From b3d984a9b1bd855e2f048f193e51730fe462bfcb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 18 Dec 2024 23:46:00 +0100 Subject: [PATCH 01/12] Replace HitAttemptActorId with a RefNum --- apps/openmw/mwclass/creature.cpp | 8 +++--- apps/openmw/mwclass/npc.cpp | 8 +++--- apps/openmw/mwmechanics/actors.cpp | 26 +++++++++++-------- apps/openmw/mwmechanics/aicombat.cpp | 9 +++++-- apps/openmw/mwmechanics/creaturestats.cpp | 8 +++--- apps/openmw/mwmechanics/creaturestats.hpp | 6 ++--- .../mwmechanics/mechanicsmanagerimp.cpp | 5 ++-- apps/openmw/mwworld/player.cpp | 1 + 8 files changed, 41 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c2c930ce1b..7effead29f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -376,14 +376,14 @@ namespace MWClass { MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); // First handle the attacked actor - if ((stats.getHitAttemptActorId() == -1) + if (!stats.getHitAttemptActor().isSet() && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) - stats.setHitAttemptActorId(statsAttacker.getActorId()); + stats.setHitAttemptActor(attacker.getCellRef().getRefNum()); // Next handle the attacking actor - if ((statsAttacker.getHitAttemptActorId() == -1) + if (!statsAttacker.getHitAttemptActor().isSet() && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) - statsAttacker.setHitAttemptActorId(stats.getActorId()); + statsAttacker.setHitAttemptActor(ptr.getCellRef().getRefNum()); } if (!object.empty()) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index fd62765e61..7af84d1a48 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -728,14 +728,14 @@ namespace MWClass { MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); // First handle the attacked actor - if ((stats.getHitAttemptActorId() == -1) + if (!stats.getHitAttemptActor().isSet() && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) - stats.setHitAttemptActorId(statsAttacker.getActorId()); + stats.setHitAttemptActor(attacker.getCellRef().getRefNum()); // Next handle the attacking actor - if ((statsAttacker.getHitAttemptActorId() == -1) + if (!statsAttacker.getHitAttemptActor().isSet() && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) - statsAttacker.setHitAttemptActorId(stats.getActorId()); + statsAttacker.setHitAttemptActor(ptr.getCellRef().getRefNum()); } if (!object.empty()) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 786539e585..9e0279e8b9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -25,6 +25,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" #include "../mwworld/scene.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" @@ -641,12 +642,13 @@ namespace MWMechanics if (creatureStats1.getAiSequence().isInCombat(ally)) continue; - if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) + ESM::RefNum allyHitNum = ally.getClass().getCreatureStats(ally).getHitAttemptActor(); + if (allyHitNum.isSet() && actor2.getCellRef().getRefNum() == allyHitNum) { mechanicsManager->startCombat(actor1, actor2, &cachedAllies.getActorsSidingWith(actor2)); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player - creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); + creatureStats1.setHitAttemptActor(allyHitNum); return; } @@ -1155,9 +1157,10 @@ namespace MWMechanics = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { + ESM::RefNum playerNum = player.getCellRef().getRefNum(); mechanicsManager->startCombat(ptr, player, &cachedAllies.getActorsSidingWith(player)); // Stops the guard from quitting combat if player is unreachable - creatureStats.setHitAttemptActorId(playerClass.getCreatureStats(player).getActorId()); + creatureStats.setHitAttemptActor(playerNum); } else creatureStats.getAiSequence().stack(AiPursue(player), ptr); @@ -1517,13 +1520,14 @@ namespace MWMechanics SidingCache cachedAllies{ *this, true }; // will be filled as engageCombat iterates const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); - const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); - if (attackedByPlayerId != -1) + const ESM::RefNum attackedByPlayerNum = player.getClass().getCreatureStats(player).getHitAttemptActor(); + if (attackedByPlayerNum.isSet()) { - const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId); + const MWWorld::Ptr playerHitAttemptActor + = MWBase::Environment::get().getWorldModel()->getPtr(attackedByPlayerNum); if (!playerHitAttemptActor.isInCell()) - player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); + player.getClass().getCreatureStats(player).setHitAttemptActor({}); } const int actorsProcessingRange = Settings::game().mActorsProcessingRange; @@ -1548,10 +1552,10 @@ namespace MWMechanics || !actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getAiSequence().isInCombat() || !inProcessingRange)) { - actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActorId(-1); - if (player.getClass().getCreatureStats(player).getHitAttemptActorId() - == actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActorId()) - player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); + actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActor({}); + ESM::RefNum playerHitNum = player.getClass().getCreatureStats(player).getHitAttemptActor(); + if (playerHitNum.isSet() && playerHitNum == actor.getPtr().getCellRef().getRefNum()) + player.getClass().getCreatureStats(player).setHitAttemptActor({}); } const Misc::TimerStatus engageCombatTimerStatus = actor.updateEngageCombatTimer(duration); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0ddcf409b6..cd92e4cc96 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -36,6 +36,12 @@ namespace osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); + + bool hitAttemptMatchesTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + { + ESM::RefNum hitNum = actor.getClass().getCreatureStats(actor).getHitAttemptActor(); + return hitNum.isSet() && target.getCellRef().getRefNum() == hitNum; + } } namespace MWMechanics @@ -194,8 +200,7 @@ namespace MWMechanics = (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target) != playerFollowersAndEscorters.end()); if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer) - && ((stats.getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId()) - || (target.getClass().getCreatureStats(target).getHitAttemptActorId() == stats.getActorId()))) + && (hitAttemptMatchesTarget(actor, target) || hitAttemptMatchesTarget(target, actor))) forceFlee = true; else // Otherwise end combat return true; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 24b5891c1c..3329d87cbf 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -365,14 +365,14 @@ namespace MWMechanics return mLastHitAttemptObject; } - void CreatureStats::setHitAttemptActorId(int actorId) + void CreatureStats::setHitAttemptActor(ESM::RefNum actor) { - mHitAttemptActorId = actorId; + mHitAttemptActor = actor; } - int CreatureStats::getHitAttemptActorId() const + ESM::RefNum CreatureStats::getHitAttemptActor() const { - return mHitAttemptActorId; + return mHitAttemptActor; } void CreatureStats::addToFallHeight(float height) diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index aa46e9504f..ab8e03a294 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -76,7 +76,7 @@ namespace MWMechanics int mActorId = -1; // Stores an actor that attacked this actor. Only one is stored at a time, and it is not changed if a different // actor attacks. It is cleared when combat ends. - int mHitAttemptActorId = -1; + ESM::RefNum mHitAttemptActor; // The difference between view direction and lower body direction. float mSideMovementAngle = 0; @@ -266,8 +266,8 @@ namespace MWMechanics void setLastHitAttemptObject(const ESM::RefId& objectid); void clearLastHitAttemptObject(); const ESM::RefId& getLastHitAttemptObject() const; - void setHitAttemptActorId(const int actorId); - int getHitAttemptActorId() const; + void setHitAttemptActor(ESM::RefNum actorId); + ESM::RefNum getHitAttemptActor() const; void writeState(ESM::CreatureStats& state) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index a494153543..3db7015d63 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1734,8 +1734,9 @@ namespace MWMechanics // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) { + const ESM::RefNum playerNum = target.getCellRef().getRefNum(); // Stops guard from ending combat if player is unreachable - stats.setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); + stats.setHitAttemptActor(playerNum); for (const Actor& actor : mActors) { if (actor.isInvalid()) @@ -1752,7 +1753,7 @@ namespace MWMechanics actor.getPtr() .getClass() .getCreatureStats(actor.getPtr()) - .setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); + .setHitAttemptActor(playerNum); } } } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 7bbc469e07..a8cbdb447b 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -343,6 +343,7 @@ namespace MWWorld MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(mPlayer); mPlayer.load(player.mObject); + MWBase::Environment::get().getWorldModel()->registerPtr(getPlayer()); for (size_t i = 0; i < mSaveAttributes.size(); ++i) mSaveAttributes[i] = player.mSaveAttributes[i]; From 1bcc40354454cf2c1eb6edb5271e10fc29cbcef1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 20 Dec 2024 14:55:24 +0100 Subject: [PATCH 02/12] Use a RefMum to track spawned levelled creatures --- apps/openmw/mwclass/creaturelevlist.cpp | 58 ++++++++++++------------ apps/openmw/mwstate/statemanagerimp.cpp | 7 ++- apps/openmw/mwworld/cellstore.cpp | 9 ++++ components/CMakeLists.txt | 2 +- components/esm3/actoridconverter.cpp | 32 +++++++++++++ components/esm3/actoridconverter.hpp | 29 ++++++++++++ components/esm3/creaturelevliststate.cpp | 13 ++++-- components/esm3/creaturelevliststate.hpp | 2 +- components/esm3/esmreader.hpp | 7 ++- components/esm3/formatversion.hpp | 3 +- components/esm3/objectstate.cpp | 1 + components/esm3/objectstate.hpp | 24 ++++------ 12 files changed, 133 insertions(+), 54 deletions(-) create mode 100644 components/esm3/actoridconverter.cpp create mode 100644 components/esm3/actoridconverter.hpp diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index f16601531d..577bc33cf3 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -1,5 +1,6 @@ #include "creaturelevlist.hpp" +#include #include #include @@ -9,6 +10,7 @@ #include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -20,9 +22,15 @@ namespace MWClass class CreatureLevListCustomData : public MWWorld::TypedCustomData { public: - // actorId of the creature we spawned - int mSpawnActorId; - bool mSpawn; // Should a new creature be spawned? + ESM::RefNum mSpawnedActor; + bool mSpawn = true; // Should a new creature be spawned? + + MWWorld::Ptr getSpawnedPtr() const + { + if (mSpawnedActor.isSet()) + return MWBase::Environment::get().getWorldModel()->getPtr(mSpawnedActor); + return {}; + } CreatureLevListCustomData& asCreatureLevListCustomData() override { return *this; } const CreatureLevListCustomData& asCreatureLevListCustomData() const override { return *this; } @@ -46,9 +54,7 @@ namespace MWClass return; CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); - MWWorld::Ptr creature = (customData.mSpawnActorId == -1) - ? MWWorld::Ptr() - : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + MWWorld::Ptr creature = customData.getSpawnedPtr(); if (!creature.isEmpty()) MWBase::Environment::get().getWorld()->adjustPosition(creature, force); } @@ -71,13 +77,7 @@ namespace MWClass if (customData.mSpawn) return; - MWWorld::Ptr creature; - if (customData.mSpawnActorId != -1) - { - creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); - if (creature.isEmpty()) - creature = ptr.getCell()->getMovedActor(customData.mSpawnActorId); - } + MWWorld::Ptr creature = customData.getSpawnedPtr(); if (!creature.isEmpty()) { const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); @@ -116,21 +116,17 @@ namespace MWClass if (!id.empty()) { // Delete the previous creature - if (customData.mSpawnActorId != -1) - { - MWWorld::Ptr creature - = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); - if (!creature.isEmpty()) - MWBase::Environment::get().getWorld()->deleteObject(creature); - customData.mSpawnActorId = -1; - } + MWWorld::Ptr previous = customData.getSpawnedPtr(); + if (!previous.isEmpty()) + MWBase::Environment::get().getWorld()->deleteObject(previous); MWWorld::ManualRef manualRef(store, id); manualRef.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); manualRef.getPtr().getCellRef().setScale(ptr.getCellRef().getScale()); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject( manualRef.getPtr(), ptr.getCell(), ptr.getRefData().getPosition()); - customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); + MWBase::Environment::get().getWorldModel()->registerPtr(placed); + customData.mSpawnedActor = placed.getCellRef().getRefNum(); customData.mSpawn = false; } else @@ -141,11 +137,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data = std::make_unique(); - data->mSpawnActorId = -1; - data->mSpawn = true; - - ptr.getRefData().setCustomData(std::move(data)); + ptr.getRefData().setCustomData(std::make_unique()); } } @@ -157,8 +149,16 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); const ESM::CreatureLevListState& levListState = state.asCreatureLevListState(); - customData.mSpawnActorId = levListState.mSpawnActorId; + customData.mSpawnedActor = levListState.mSpawnedActor; customData.mSpawn = levListState.mSpawn; + if (state.mActorIdConverter) + { + if (state.mActorIdConverter->convert(customData.mSpawnedActor, customData.mSpawnedActor.mIndex)) + return; + state.mActorIdConverter->addConverter([converter = state.mActorIdConverter, &customData]() { + customData.mSpawnedActor = converter->convert(customData.mSpawnedActor.mIndex); + }); + } } void CreatureLevList::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const @@ -171,7 +171,7 @@ namespace MWClass const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); ESM::CreatureLevListState& levListState = state.asCreatureLevListState(); - levListState.mSpawnActorId = customData.mSpawnActorId; + levListState.mSpawnedActor = customData.mSpawnedActor; levListState.mSpawn = customData.mSpawn; } } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index dbb4c46ee3..7c5444fd4b 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -469,6 +470,10 @@ void MWState::StateManager::loadGame(const Character* character, const std::file reader.setContentFileMapping(&contentFileMap); MWBase::Environment::get().getLuaManager()->setContentFileMapping(contentFileMap); + ESM::ActorIdConverter actorIdConverter; + if (version <= ESM::MaxActorIdSaveGameFormatVersion) + reader.setActorIdConverter(&actorIdConverter); + Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener.setProgressRange(100); @@ -589,7 +594,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file currentPercent = progressPercent; } } - + actorIdConverter.apply(); mCharacterManager.setCurrentCharacter(character); mState = State_Running; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 75ee14f627..4fb38ff456 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -275,6 +276,14 @@ namespace if constexpr (std::is_same_v || std::is_same_v) MWWorld::convertEnchantmentSlots(state.mCreatureStats, state.mInventory); } + if (reader.getActorIdConverter()) + { + if constexpr (std::is_same_v || std::is_same_v) + { + MWBase::Environment::get().getWorldModel()->assignSaveFileRefNum(state.mRef); + reader.getActorIdConverter()->mMappings.emplace(state.mCreatureStats.mActorId, state.mRef.mRefNum); + } + } if (state.mRef.mRefNum.hasContentFile()) { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 76d4722614..76a94a95db 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -189,7 +189,7 @@ add_component_dir (esm3 weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache infoorder timestamp formatversion landrecorddata selectiongroup dialoguecondition - refnum + refnum actoridconverter ) add_component_dir (esmterrain diff --git a/components/esm3/actoridconverter.cpp b/components/esm3/actoridconverter.cpp new file mode 100644 index 0000000000..3d415c53c5 --- /dev/null +++ b/components/esm3/actoridconverter.cpp @@ -0,0 +1,32 @@ +#include "actoridconverter.hpp" + +namespace ESM +{ + void ActorIdConverter::apply() + { + for (auto& converter : mConverters) + converter(); + } + + ESM::RefNum ActorIdConverter::convert(int actorId) const + { + auto it = mMappings.find(actorId); + if (it == mMappings.end()) + return {}; + return it->second; + } + + bool ActorIdConverter::convert(ESM::RefNum& refNum, int actorId) const + { + if (actorId == -1) + { + refNum = {}; + return true; + } + auto it = mMappings.find(actorId); + if (it == mMappings.end()) + return false; + refNum = it->second; + return true; + } +} diff --git a/components/esm3/actoridconverter.hpp b/components/esm3/actoridconverter.hpp new file mode 100644 index 0000000000..5e1c71aa48 --- /dev/null +++ b/components/esm3/actoridconverter.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H +#define OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H + +#include +#include +#include + +#include "refnum.hpp" + +namespace ESM +{ + class ActorIdConverter + { + std::vector> mConverters; + + public: + std::map mMappings; + + void apply(); + + ESM::RefNum convert(int actorId) const; + + bool convert(ESM::RefNum& refNum, int actorId) const; + + void addConverter(std::function&& converter) { mConverters.emplace_back(converter); } + }; +} + +#endif diff --git a/components/esm3/creaturelevliststate.cpp b/components/esm3/creaturelevliststate.cpp index 6deb7d68b7..0aba958114 100644 --- a/components/esm3/creaturelevliststate.cpp +++ b/components/esm3/creaturelevliststate.cpp @@ -10,8 +10,13 @@ namespace ESM { ObjectState::load(esm); - mSpawnActorId = -1; - esm.getHNOT(mSpawnActorId, "SPAW"); + if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion) + { + mSpawnedActor.mIndex = -1; + esm.getHNOT(mSpawnedActor.mIndex, "SPAW"); + } + else if (esm.peekNextSub("SPAW")) + esm.getFormId(true, "SPAW"); mSpawn = false; esm.getHNOT(mSpawn, "RESP"); @@ -21,8 +26,8 @@ namespace ESM { ObjectState::save(esm, inInventory); - if (mSpawnActorId != -1) - esm.writeHNT("SPAW", mSpawnActorId); + if (mSpawnedActor.isSet()) + esm.writeFormId(mSpawnedActor, true, "SPAW"); if (mSpawn) esm.writeHNT("RESP", mSpawn); diff --git a/components/esm3/creaturelevliststate.hpp b/components/esm3/creaturelevliststate.hpp index e7121cf8ac..7b7de481a1 100644 --- a/components/esm3/creaturelevliststate.hpp +++ b/components/esm3/creaturelevliststate.hpp @@ -9,7 +9,7 @@ namespace ESM struct CreatureLevListState final : public ObjectState { - int32_t mSpawnActorId; + ESM::RefNum mSpawnedActor; // actor id in older saves bool mSpawn; void load(ESMReader& esm) override; diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 9bae5f217e..938275c69e 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -43,6 +43,7 @@ namespace ESM inline constexpr bool IsReadable = false; class ReadersCache; + class ActorIdConverter; // For old save games class ESMReader { @@ -118,11 +119,13 @@ namespace ESM // Used only when loading saves to adjust FormIds if load order was changes. void setContentFileMapping(const std::map* mapping) { mContentFileMapping = mapping; } - const std::map* getContentFileMapping(); // Returns false if content file not found. bool applyContentFileMapping(FormId& id); + void setActorIdConverter(ActorIdConverter* converter) { mActorIdConverter = converter; } + ActorIdConverter* getActorIdConverter() const { return mActorIdConverter; } + /************************************************************************* * * Medium-level reading shortcuts @@ -386,6 +389,8 @@ namespace ESM size_t mFileSize; const std::map* mContentFileMapping = nullptr; + + ActorIdConverter* mActorIdConverter = nullptr; }; } #endif diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index f25cdca4bf..f0cb36465d 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -28,7 +28,8 @@ namespace ESM inline constexpr FormatVersion MaxOldCountFormatVersion = 30; inline constexpr FormatVersion MaxActiveSpellTypeVersion = 31; inline constexpr FormatVersion MaxPlayerBeforeCellDataFormatVersion = 32; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 34; + inline constexpr FormatVersion MaxActorIdSaveGameFormatVersion = 34; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 35; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5; inline constexpr FormatVersion OpenMW0_49MinSaveGameFormatVersion = 5; diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index f3017e2d0d..6f83e6a648 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -14,6 +14,7 @@ namespace ESM void ObjectState::load(ESMReader& esm) { mVersion = esm.getFormatVersion(); + mActorIdConverter = esm.getActorIdConverter(); bool isDeleted; mRef.loadData(esm, isDeleted); diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index c947adcd97..fc8760d333 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -14,6 +14,7 @@ namespace ESM { + class ActorIdConverter; class ESMReader; class ESMWriter; struct ContainerState; @@ -29,27 +30,18 @@ namespace ESM { CellRef mRef; - unsigned char mHasLocals; Locals mLocals; LuaScripts mLuaScripts; - unsigned char mEnabled; Position mPosition; - uint32_t mFlags; + AnimationState mAnimationState; + ActorIdConverter* mActorIdConverter = nullptr; + FormatVersion mVersion = DefaultFormatVersion; + uint32_t mFlags = 0; + unsigned char mHasLocals = 0; + unsigned char mEnabled = 0; // Is there any class-specific state following the ObjectState - bool mHasCustomState; - - FormatVersion mVersion = DefaultFormatVersion; - - AnimationState mAnimationState; - - ObjectState() - : mHasLocals(0) - , mEnabled(0) - , mFlags(0) - , mHasCustomState(true) - { - } + bool mHasCustomState = true; /// @note Does not load the CellRef ID, it should already be loaded before calling this method virtual void load(ESMReader& esm); From 1d24ad54d9b74c389ce253da3a58d52992d931d2 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 20 Dec 2024 17:34:22 +0100 Subject: [PATCH 03/12] Replace actor ids with RefNums in ai packages --- apps/openmw/mwclass/creaturelevlist.cpp | 8 +-- apps/openmw/mwlua/localscripts.cpp | 31 ++++------ apps/openmw/mwmechanics/aicombat.cpp | 34 +++++------ apps/openmw/mwmechanics/aicombat.hpp | 3 - apps/openmw/mwmechanics/aiescort.cpp | 8 +-- apps/openmw/mwmechanics/aiescort.hpp | 9 +-- apps/openmw/mwmechanics/aifollow.cpp | 13 ++-- apps/openmw/mwmechanics/aifollow.hpp | 4 +- apps/openmw/mwmechanics/aipackage.cpp | 75 +++++++----------------- apps/openmw/mwmechanics/aipackage.hpp | 14 ++--- apps/openmw/mwmechanics/aipursue.cpp | 34 +++++------ apps/openmw/mwmechanics/aipursue.hpp | 2 - apps/openmw/mwmechanics/aisequence.cpp | 8 +++ apps/openmw/mwscript/aiextensions.cpp | 4 +- components/esm3/actoridconverter.cpp | 28 ++++----- components/esm3/actoridconverter.hpp | 9 +-- components/esm3/aisequence.cpp | 35 +++++++---- components/esm3/aisequence.hpp | 26 ++++---- components/esm3/creaturelevliststate.cpp | 2 +- 19 files changed, 150 insertions(+), 197 deletions(-) diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 577bc33cf3..15a73fbf1d 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -152,13 +152,7 @@ namespace MWClass customData.mSpawnedActor = levListState.mSpawnedActor; customData.mSpawn = levListState.mSpawn; if (state.mActorIdConverter) - { - if (state.mActorIdConverter->convert(customData.mSpawnedActor, customData.mSpawnedActor.mIndex)) - return; - state.mActorIdConverter->addConverter([converter = state.mActorIdConverter, &customData]() { - customData.mSpawnedActor = converter->convert(customData.mSpawnedActor.mIndex); - }); - } + state.mActorIdConverter->convert(customData.mSpawnedActor, customData.mSpawnedActor.mIndex); } void CreatureLevList::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 99c994304b..d9834abd91 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -170,34 +170,25 @@ namespace MWLua float duration, const osg::Vec3f& dest, bool repeat, bool cancelOther) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + std::string_view cellNameId; if (cell) - { - ai.stack(MWMechanics::AiFollow(target.ptr().getCellRef().getRefId(), - cell->mStore->getCell()->getNameId(), duration, dest.x(), dest.y(), dest.z(), repeat), - ptr, cancelOther); - } - else - { - ai.stack(MWMechanics::AiFollow( - target.ptr().getCellRef().getRefId(), duration, dest.x(), dest.y(), dest.z(), repeat), - ptr, cancelOther); - } + cellNameId = cell->mStore->getCell()->getNameId(); + ai.stack( + MWMechanics::AiFollow(getId(target.ptr()), cellNameId, duration, dest.x(), dest.y(), dest.z(), repeat), + ptr, cancelOther); }; selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, float duration, const osg::Vec3f& dest, bool repeat, bool cancelOther) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - // TODO: change AiEscort implementation to accept ptr instead of a non-unique refId. - const ESM::RefId& refId = target.ptr().getCellRef().getRefId(); int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); auto* esmCell = cell.mStore->getCell(); - if (esmCell->isExterior()) - ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat), ptr, - cancelOther); - else - ai.stack(MWMechanics::AiEscort( - refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat), - ptr, cancelOther); + std::string_view cellNameId; + if (!esmCell->isExterior()) + cellNameId = esmCell->getNameId(); + ai.stack(MWMechanics::AiEscort( + getId(target.ptr()), cellNameId, gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat), + ptr, cancelOther); }; selfAPI["_startAiWander"] = [](SelfObject& self, int distance, int duration, sol::table luaIdle, bool repeat, bool cancelOther) { diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index cd92e4cc96..1ccc4455ed 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -18,6 +18,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwlua/localscripts.hpp" + #include "actorutil.hpp" #include "aicombataction.hpp" #include "character.hpp" @@ -48,12 +50,12 @@ namespace MWMechanics { AiCombat::AiCombat(const MWWorld::Ptr& actor) { - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); + mTargetActor = actor.getCellRef().getRefNum(); } AiCombat::AiCombat(const ESM::AiSequence::AiCombat* combat) { - mTargetActorId = combat->mTargetActorId; + mTargetActor = combat->mTargetActor; } void AiCombat::init() {} @@ -115,16 +117,21 @@ namespace MWMechanics if (actor.getClass().getCreatureStats(actor).isDead()) return true; - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); - if (target.isEmpty()) - return true; + const MWWorld::Ptr target = getTarget(); // The target to follow - if (!target.getCellRef().getCount() - || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently - // registered with the MechanicsManager + // Stop if the target doesn't exist + if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled() || target.getClass().getCreatureStats(target).isDead()) return true; + // 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()) + return true; + } + if (actor == target) // This should never happen. return true; @@ -496,19 +503,10 @@ namespace MWMechanics storage.mRotateMove = !smoothTurn(actor, targetAngleRadians, axis, eps); } - MWWorld::Ptr AiCombat::getTarget() const - { - if (mCachedTarget.isEmpty() || mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) - { - mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); - } - return mCachedTarget; - } - void AiCombat::writeState(ESM::AiSequence::AiSequence& sequence) const { auto combat = std::make_unique(); - combat->mTargetActorId = mTargetActorId; + combat->mTargetActor = mTargetActor; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Combat; diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 42baaf6349..4102a6218a 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -100,9 +100,6 @@ namespace MWMechanics return options; } - /// Returns target ID - MWWorld::Ptr getTarget() const override; - void writeState(ESM::AiSequence::AiSequence& sequence) const override; private: diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index a375aea33c..1781512eb8 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -24,7 +24,7 @@ namespace MWMechanics { - AiEscort::AiEscort(const ESM::RefId& actorId, int duration, float x, float y, float z, bool repeat) + AiEscort::AiEscort(ESM::RefNum actor, std::string_view cellId, int duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat) , mX(x) , mY(y) @@ -32,7 +32,7 @@ namespace MWMechanics , mDuration(static_cast(duration)) , mRemainingDuration(static_cast(duration)) { - mTargetActorRefId = actorId; + mTargetActor = actor; } AiEscort::AiEscort( @@ -58,7 +58,7 @@ namespace MWMechanics , mRemainingDuration(escort->mRemainingDuration) { mTargetActorRefId = escort->mTargetId; - mTargetActorId = escort->mTargetActorId; + mTargetActor = escort->mTargetActor; } bool AiEscort::execute( @@ -131,7 +131,7 @@ namespace MWMechanics escort->mData.mZ = mZ; escort->mData.mDuration = static_cast(mDuration); escort->mTargetId = mTargetActorRefId; - escort->mTargetActorId = mTargetActorId; + escort->mTargetActor = mTargetActor; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; escort->mRepeat = getRepeat(); diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index d88ecac6a5..e37196e134 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -20,13 +20,10 @@ namespace MWMechanics class AiEscort final : public TypedAiPackage { public: - /// Implementation of AiEscort - /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or + /** The Actor will escort the specified actor to the position x, y, z until they reach their position, or they run out of time \implement AiEscort **/ - AiEscort(const ESM::RefId& actorId, int duration, float x, float y, float z, bool repeat); - /// Implementation of AiEscortCell - /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or - they run out of time \implement AiEscortCell **/ + AiEscort(ESM::RefNum actor, std::string_view cellId, int duration, float x, float y, float z, bool repeat); + /// Implementation of AiEscort/AiEscortCell AiEscort( const ESM::RefId& actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index ec128ae2a9..3702c981ae 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -30,7 +30,8 @@ namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; - AiFollow::AiFollow(const ESM::RefId& actorId, float duration, float x, float y, float z, bool repeat) + AiFollow::AiFollow( + ESM::RefNum actor, std::string_view cellId, float duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat) , mAlwaysFollow(false) , mDuration(duration) @@ -41,7 +42,7 @@ namespace MWMechanics , mActive(false) , mFollowIndex(mFollowIndexCounter++) { - mTargetActorRefId = actorId; + mTargetActor = actor; } AiFollow::AiFollow( @@ -72,12 +73,12 @@ namespace MWMechanics , mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); + mTargetActor = actor.getCellRef().getRefNum(); } AiFollow::AiFollow(const ESM::AiSequence::AiFollow* follow) : TypedAiPackage( - makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) + makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) , mAlwaysFollow(follow->mAlwaysFollow) , mDuration(follow->mData.mDuration) , mRemainingDuration(follow->mRemainingDuration) @@ -89,7 +90,7 @@ namespace MWMechanics , mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = follow->mTargetId; - mTargetActorId = follow->mTargetActorId; + mTargetActor = follow->mTargetActor; } bool AiFollow::execute( @@ -246,7 +247,7 @@ namespace MWMechanics follow->mData.mZ = mZ; follow->mData.mDuration = static_cast(mDuration); follow->mTargetId = mTargetActorRefId; - follow->mTargetActorId = mTargetActorId; + follow->mTargetActor = mTargetActor; follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 85d11977ca..94c8a35efc 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -41,9 +41,7 @@ namespace MWMechanics class AiFollow final : public TypedAiPackage { public: - /// Follow Actor for duration or until you arrive at a world position - AiFollow(const ESM::RefId& actorId, float duration, float x, float y, float z, bool repeat); - /// Follow Actor for duration or until you arrive at a position in a cell + AiFollow(ESM::RefNum actor, std::string_view cellId, float duration, float x, float y, float z, bool repeat); AiFollow( const ESM::RefId& actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat); /// Follow Actor indefinitely diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 146124fcbf..d7daae0e98 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -16,6 +16,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwphysics/raycasting.hpp" @@ -51,71 +52,35 @@ MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options : mTypeId(typeId) , mOptions(options) , mReaction(MWBase::Environment::get().getWorld()->getPrng()) - , mTargetActorId(-1) - , mCachedTarget() - , mRotateOnTheRunChecks(0) - , mIsShortcutting(false) - , mShortcutProhibited(false) - , mShortcutFailPos() { } MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { - if (!mCachedTarget.isEmpty()) - { - if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) - mCachedTarget = MWWorld::Ptr(); - else - return mCachedTarget; - } - - if (mTargetActorId == -2) - return MWWorld::Ptr(); - - if (mTargetActorId == -1) - { - if (mTargetActorRefId.empty()) - { - mTargetActorId = -2; - return MWWorld::Ptr(); - } - mCachedTarget = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); - if (mCachedTarget.isEmpty()) - { - mTargetActorId = -2; - return mCachedTarget; - } - else - mTargetActorId = mCachedTarget.getClass().getCreatureStats(mCachedTarget).getActorId(); - } - - if (mTargetActorId != -1) - mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (mTargetActor.isSet()) + return MWBase::Environment::get().getWorldModel()->getPtr(mTargetActor); + if (mTargetActorRefId.empty() || mTargetNotFound) + return {}; + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); + if (ptr.isEmpty()) + mTargetNotFound = true; else - return MWWorld::Ptr(); - - return mCachedTarget; + { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + mTargetActor = ptr.getCellRef().getRefNum(); + } + return ptr; } bool MWMechanics::AiPackage::targetIs(const MWWorld::Ptr& ptr) const { - if (mTargetActorId == -2) - return ptr.isEmpty(); - else if (mTargetActorId == -1) - { - if (mTargetActorRefId.empty()) - { - mTargetActorId = -2; - return ptr.isEmpty(); - } - if (!ptr.isEmpty() && ptr.getCellRef().getRefId() == mTargetActorRefId) - return getTarget() == ptr; + if (ptr.isEmpty()) + return getTarget() == ptr; + if (mTargetActor.isSet()) + return ptr.getCellRef().getRefNum() == mTargetActor; + if (ptr.getCellRef().getRefId() != mTargetActorRefId) return false; - } - if (ptr.isEmpty() || !ptr.getClass().isActor()) - return false; - return ptr.getClass().getCreatureStats(ptr).getActorId() == mTargetActorId; + return getTarget() == ptr; } void MWMechanics::AiPackage::reset() @@ -125,7 +90,7 @@ void MWMechanics::AiPackage::reset() mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); - mCachedTarget = MWWorld::Ptr(); + mTargetNotFound = false; mPathFinder.clearPath(); mObstacleCheck.clear(); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 42aa62ffe3..30b840dd22 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -26,6 +26,7 @@ namespace ESM namespace MWMechanics { + class AiSequence; class CharacterController; class PathgridGraph; @@ -171,16 +172,15 @@ namespace MWMechanics AiReactionTimer mReaction; ESM::RefId mTargetActorRefId; - mutable int mTargetActorId; - mutable MWWorld::Ptr mCachedTarget; - - short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility - - bool mIsShortcutting; // if shortcutting at the moment - bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt + mutable ESM::RefNum mTargetActor; osg::Vec3f mShortcutFailPos; // position of last shortcut fail float mLastDestinationTolerance = 0; + short mRotateOnTheRunChecks = 0; // attempts to check rotation to the pathpoint on the run possibility + mutable bool mTargetNotFound = false; + bool mIsShortcutting = false; // if shortcutting at the moment + bool mShortcutProhibited = false; // shortcutting may be prohibited after unsuccessful attempt + friend class AiSequence; private: bool isNearInactiveCell(osg::Vec3f position); }; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 1dbbddd218..8be31dbd66 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -7,6 +7,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwlua/localscripts.hpp" + #include "../mwworld/class.hpp" #include "actorutil.hpp" @@ -19,12 +21,12 @@ namespace MWMechanics AiPursue::AiPursue(const MWWorld::Ptr& actor) { - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); + mTargetActor = actor.getCellRef().getRefNum(); } AiPursue::AiPursue(const ESM::AiSequence::AiPursue* pursue) { - mTargetActorId = pursue->mTargetActorId; + mTargetActor = pursue->mTargetActor; } bool AiPursue::execute( @@ -33,14 +35,19 @@ namespace MWMechanics if (actor.getClass().getCreatureStats(actor).isDead()) return true; - const MWWorld::Ptr target - = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); // The target to follow + const MWWorld::Ptr target = getTarget(); // The target to follow // Stop if the target doesn't exist - // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) + 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 + if (const MWLua::LocalScripts* scripts = target.getRefData().getLuaScripts()) + { + if (!scripts->isActive()) + return true; + } + if (isTargetMagicallyHidden(target) && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(target, actor, false)) return false; @@ -79,23 +86,10 @@ namespace MWMechanics return false; } - MWWorld::Ptr AiPursue::getTarget() const - { - if (!mCachedTarget.isEmpty()) - { - if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) - mCachedTarget = MWWorld::Ptr(); - else - return mCachedTarget; - } - mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); - return mCachedTarget; - } - void AiPursue::writeState(ESM::AiSequence::AiSequence& sequence) const { auto pursue = std::make_unique(); - pursue->mTargetActorId = mTargetActorId; + pursue->mTargetActor = mTargetActor; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Pursue; diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index d9cf4a40c3..c7ed7e04f5 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -39,8 +39,6 @@ namespace MWMechanics return options; } - MWWorld::Ptr getTarget() const override; - void writeState(ESM::AiSequence::AiSequence& sequence) const override; }; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 70b94b1eba..a532f55c1a 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -538,6 +539,7 @@ namespace MWMechanics for (auto& container : sequence.mPackages) { std::unique_ptr package; + bool hasTarget = false; switch (container.mType) { case ESM::AiSequence::Ai_Wander: @@ -560,12 +562,14 @@ namespace MWMechanics { package = std::make_unique( &static_cast(*container.mPackage)); + hasTarget = true; break; } case ESM::AiSequence::Ai_Follow: { package = std::make_unique( &static_cast(*container.mPackage)); + hasTarget = true; break; } case ESM::AiSequence::Ai_Activate: @@ -578,12 +582,14 @@ namespace MWMechanics { package = std::make_unique( &static_cast(*container.mPackage)); + hasTarget = true; break; } case ESM::AiSequence::Ai_Pursue: { package = std::make_unique( &static_cast(*container.mPackage)); + hasTarget = true; break; } default: @@ -592,6 +598,8 @@ namespace MWMechanics if (!package.get()) continue; + if (hasTarget && sequence.mActorIdConverter) + sequence.mActorIdConverter->convert(package->mTargetActor, package->mTargetActor.mIndex); onPackageAdded(*package); mPackages.push_back(std::move(package)); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 55717a4a5d..7daece407e 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -124,7 +124,7 @@ namespace MWScript if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; - MWMechanics::AiEscort escortPackage(actorID, static_cast(duration), x, y, z, repeat); + MWMechanics::AiEscort escortPackage(actorID, {}, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; @@ -353,7 +353,7 @@ namespace MWScript if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; - MWMechanics::AiFollow followPackage(actorID, duration, x, y, z, repeat); + MWMechanics::AiFollow followPackage(actorID, {}, duration, x, y, z, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(followPackage, ptr); Log(Debug::Info) << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration; diff --git a/components/esm3/actoridconverter.cpp b/components/esm3/actoridconverter.cpp index 3d415c53c5..dba9cb82ea 100644 --- a/components/esm3/actoridconverter.cpp +++ b/components/esm3/actoridconverter.cpp @@ -4,29 +4,29 @@ namespace ESM { void ActorIdConverter::apply() { - for (auto& converter : mConverters) - converter(); + for (auto& [refNum, actorId] : mToConvert) + { + auto it = mMappings.find(actorId); + if (it == mMappings.end()) + refNum = {}; + else + refNum = it->second; + } } - ESM::RefNum ActorIdConverter::convert(int actorId) const - { - auto it = mMappings.find(actorId); - if (it == mMappings.end()) - return {}; - return it->second; - } - - bool ActorIdConverter::convert(ESM::RefNum& refNum, int actorId) const + void ActorIdConverter::convert(ESM::RefNum& refNum, int actorId) { if (actorId == -1) { refNum = {}; - return true; + return; } auto it = mMappings.find(actorId); if (it == mMappings.end()) - return false; + { + mToConvert.emplace_back(refNum, actorId); + return; + } refNum = it->second; - return true; } } diff --git a/components/esm3/actoridconverter.hpp b/components/esm3/actoridconverter.hpp index 5e1c71aa48..07a78bc2de 100644 --- a/components/esm3/actoridconverter.hpp +++ b/components/esm3/actoridconverter.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H #define OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H -#include #include #include @@ -11,18 +10,14 @@ namespace ESM { class ActorIdConverter { - std::vector> mConverters; + std::vector> mToConvert; public: std::map mMappings; void apply(); - ESM::RefNum convert(int actorId) const; - - bool convert(ESM::RefNum& refNum, int actorId) const; - - void addConverter(std::function&& converter) { mConverters.emplace_back(converter); } + void convert(ESM::RefNum& refNum, int actorId); }; } diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index ba28ca3e92..cbfc027b15 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -35,6 +35,22 @@ namespace ESM f(v.mX, v.mY, v.mZ, v.mDuration); } + namespace + { + void loadActorId(ESMReader& esm, ESM::NAME name, RefNum& refNum) + { + if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion) + { + refNum.mIndex = -1; + esm.getHNOT(refNum.mIndex, name); + } + else if (esm.peekNextSub(name)) + refNum = esm.getFormId(true, name); + else + refNum = {}; + } + } + namespace AiSequence { void AiWander::load(ESMReader& esm) @@ -72,8 +88,7 @@ namespace ESM { esm.getNamedComposite("DATA", mData); mTargetId = esm.getHNRefId("TARG"); - mTargetActorId = -1; - esm.getHNOT(mTargetActorId, "TAID"); + loadActorId(esm, "TAID", mTargetActor); esm.getHNT(mRemainingDuration, "DURA"); mCellId = esm.getHNOString("CELL"); mRepeat = false; @@ -92,7 +107,7 @@ namespace ESM { esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); - esm.writeHNT("TAID", mTargetActorId); + esm.writeFormId(mTargetActor, true, "TAID"); esm.writeHNT("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString("CELL", mCellId); @@ -104,8 +119,7 @@ namespace ESM { esm.getNamedComposite("DATA", mData); mTargetId = esm.getHNRefId("TARG"); - mTargetActorId = -1; - esm.getHNOT(mTargetActorId, "TAID"); + loadActorId(esm, "TAID", mTargetActor); esm.getHNT(mRemainingDuration, "DURA"); mCellId = esm.getHNOString("CELL"); esm.getHNT(mAlwaysFollow, "ALWY"); @@ -129,7 +143,7 @@ namespace ESM { esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); - esm.writeHNT("TAID", mTargetActorId); + esm.writeFormId(mTargetActor, true, "TAID"); esm.writeHNT("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString("CELL", mCellId); @@ -157,22 +171,22 @@ namespace ESM void AiCombat::load(ESMReader& esm) { - esm.getHNT(mTargetActorId, "TARG"); + loadActorId(esm, "TARG", mTargetActor); } void AiCombat::save(ESMWriter& esm) const { - esm.writeHNT("TARG", mTargetActorId); + esm.writeFormId(mTargetActor, true, "TARG"); } void AiPursue::load(ESMReader& esm) { - esm.getHNT(mTargetActorId, "TARG"); + loadActorId(esm, "TARG", mTargetActor); } void AiPursue::save(ESMWriter& esm) const { - esm.writeHNT("TARG", mTargetActorId); + esm.writeFormId(mTargetActor, true, "TARG"); } void AiSequence::save(ESMWriter& esm) const @@ -214,6 +228,7 @@ namespace ESM void AiSequence::load(ESMReader& esm) { + mActorIdConverter = esm.getActorIdConverter(); int count = 0; while (esm.isNextSub("AIPK")) { diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index 729a914544..342d4161e9 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -9,8 +9,11 @@ #include #include +#include "refnum.hpp" + namespace ESM { + class ActorIdConverter; class ESMReader; class ESMWriter; @@ -33,7 +36,7 @@ namespace ESM struct AiPackage { - virtual ~AiPackage() {} + virtual ~AiPackage() = default; }; struct AiWanderData @@ -89,7 +92,7 @@ namespace ESM { AiEscortData mData; - int32_t mTargetActorId; + ESM::RefNum mTargetActor; ESM::RefId mTargetId; std::string mCellId; float mRemainingDuration; @@ -103,7 +106,7 @@ namespace ESM { AiEscortData mData; - int32_t mTargetActorId; + ESM::RefNum mTargetActor; ESM::RefId mTargetId; std::string mCellId; float mRemainingDuration; @@ -129,7 +132,7 @@ namespace ESM struct AiCombat : AiPackage { - int32_t mTargetActorId; + ESM::RefNum mTargetActor; void load(ESMReader& esm); void save(ESMWriter& esm) const; @@ -137,7 +140,7 @@ namespace ESM struct AiPursue : AiPackage { - int32_t mTargetActorId; + ESM::RefNum mTargetActor; void load(ESMReader& esm); void save(ESMWriter& esm) const; @@ -152,17 +155,16 @@ namespace ESM struct AiSequence { - AiSequence() { mLastAiPackage = -1; } - std::vector mPackages; - int32_t mLastAiPackage; + ActorIdConverter* mActorIdConverter = nullptr; + int32_t mLastAiPackage = -1; + + AiSequence() {} + AiSequence(const AiSequence&) = delete; + AiSequence& operator=(const AiSequence&) = delete; void load(ESMReader& esm); void save(ESMWriter& esm) const; - - private: - AiSequence(const AiSequence&); - AiSequence& operator=(const AiSequence&); }; } diff --git a/components/esm3/creaturelevliststate.cpp b/components/esm3/creaturelevliststate.cpp index 0aba958114..2fde1e9276 100644 --- a/components/esm3/creaturelevliststate.cpp +++ b/components/esm3/creaturelevliststate.cpp @@ -16,7 +16,7 @@ namespace ESM esm.getHNOT(mSpawnedActor.mIndex, "SPAW"); } else if (esm.peekNextSub("SPAW")) - esm.getFormId(true, "SPAW"); + mSpawnedActor = esm.getFormId(true, "SPAW"); mSpawn = false; esm.getHNOT(mSpawn, "RESP"); From 42b9f1ec2bf62090fcdf0774f9a40ed89a2bbbc8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 20 Dec 2024 23:05:12 +0100 Subject: [PATCH 04/12] Optimize HUD memory layout and replace actor id with a RefNum --- apps/openmw/mwgui/hud.cpp | 42 +++++--------------------- apps/openmw/mwgui/hud.hpp | 63 ++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 69 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index fbd4dff33b..53cfd89f5b 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -19,6 +19,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/npcstats.hpp" @@ -34,36 +35,7 @@ namespace MWGui HUD::HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) : WindowBase("openmw_hud.layout") , LocalMapBase(customMarkers, localMapRender, Settings::map().mLocalMapHudFogOfWar) - , mHealth(nullptr) - , mMagicka(nullptr) - , mStamina(nullptr) - , mDrowning(nullptr) - , mWeapImage(nullptr) - , mSpellImage(nullptr) - , mWeapStatus(nullptr) - , mSpellStatus(nullptr) - , mEffectBox(nullptr) - , mMinimap(nullptr) - , mCrosshair(nullptr) - , mCellNameBox(nullptr) - , mDrowningBar(nullptr) - , mDrowningFlash(nullptr) - , mHealthManaStaminaBaseLeft(0) - , mWeapBoxBaseLeft(0) - , mSpellBoxBaseLeft(0) - , mMinimapBoxBaseRight(0) - , mEffectBoxBaseRight(0) , mDragAndDrop(dragAndDrop) - , mCellNameTimer(0.0f) - , mWeaponSpellTimer(0.f) - , mMapVisible(true) - , mWeaponVisible(true) - , mSpellVisible(true) - , mWorldMouseOver(false) - , mEnemyActorId(-1) - , mEnemyHealthTimer(-1) - , mIsDrowning(false) - , mDrowningFlashTheta(0.f) { // Energy bars getWidget(mHealthFrame, "HealthFrame"); @@ -338,7 +310,7 @@ namespace MWGui mSpellIcons->updateWidgets(mEffectBox, true); - if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) + if (mEnemyActor.isSet() && mEnemyHealth->getVisible()) { updateEnemyHealthBar(); } @@ -579,7 +551,7 @@ namespace MWGui void HUD::updateEnemyHealthBar() { - MWWorld::Ptr enemy = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mEnemyActorId); + MWWorld::Ptr enemy = MWBase::Environment::get().getWorldModel()->getPtr(mEnemyActor); if (enemy.isEmpty()) return; MWMechanics::CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); @@ -599,7 +571,7 @@ namespace MWGui void HUD::setEnemy(const MWWorld::Ptr& enemy) { - mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); + mEnemyActor = enemy.getCellRef().getRefNum(); mEnemyHealthTimer = MWBase::Environment::get() .getESMStore() ->get() @@ -613,12 +585,12 @@ namespace MWGui void HUD::clear() { - mEnemyActorId = -1; + mEnemyActor = {}; mEnemyHealthTimer = -1; mWeaponSpellTimer = 0.f; - mWeaponName = std::string(); - mSpellName = std::string(); + mWeaponName.clear(); + mSpellName.clear(); mWeaponSpellBox->setVisible(false); mWeapStatus->setProgressRange(100); diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 9e9fa4e597..ef71551965 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -64,47 +64,42 @@ namespace MWGui void dropDraggedItem(float mouseX, float mouseY); private: - MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning; - MyGUI::Widget* mHealthFrame; - MyGUI::Widget *mWeapBox, *mSpellBox, *mSneakBox; - ItemWidget* mWeapImage; - SpellWidget* mSpellImage; - MyGUI::ProgressBar *mWeapStatus, *mSpellStatus; - MyGUI::Widget *mEffectBox, *mMinimapBox; - MyGUI::Button* mMinimapButton; - MyGUI::ScrollView* mMinimap; - MyGUI::ImageBox* mCrosshair; - MyGUI::TextBox* mCellNameBox; - MyGUI::TextBox* mWeaponSpellBox; - MyGUI::Widget *mDrowningBar, *mDrowningFrame, *mDrowningFlash; + MyGUI::ProgressBar *mHealth = nullptr, *mMagicka = nullptr, *mStamina = nullptr, *mEnemyHealth = nullptr, + *mDrowning = nullptr; + MyGUI::Widget* mHealthFrame = nullptr; + MyGUI::Widget *mWeapBox = nullptr, *mSpellBox = nullptr, *mSneakBox = nullptr; + ItemWidget* mWeapImage = nullptr; + SpellWidget* mSpellImage = nullptr; + MyGUI::ProgressBar *mWeapStatus = nullptr, *mSpellStatus = nullptr; + MyGUI::Widget *mEffectBox = nullptr, *mMinimapBox = nullptr; + MyGUI::Button* mMinimapButton = nullptr; + MyGUI::ScrollView* mMinimap = nullptr; + MyGUI::ImageBox* mCrosshair = nullptr; + MyGUI::TextBox* mCellNameBox = nullptr; + MyGUI::TextBox* mWeaponSpellBox = nullptr; + MyGUI::Widget *mDrowningBar = nullptr, *mDrowningFrame = nullptr, *mDrowningFlash = nullptr; + DragAndDrop* mDragAndDrop; + std::string mCellName; + std::string mWeaponName; + std::string mSpellName; + std::unique_ptr mSpellIcons; + ESM::RefNum mEnemyActor; // bottom left elements int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft; // bottom right elements int mMinimapBoxBaseRight, mEffectBoxBaseRight; - DragAndDrop* mDragAndDrop; + float mCellNameTimer = 0.f; + float mWeaponSpellTimer = 0.f; + float mEnemyHealthTimer = -1; + float mDrowningFlashTheta = 0.f; - std::string mCellName; - float mCellNameTimer; - - std::string mWeaponName; - std::string mSpellName; - float mWeaponSpellTimer; - - bool mMapVisible; - bool mWeaponVisible; - bool mSpellVisible; - - bool mWorldMouseOver; - - std::unique_ptr mSpellIcons; - - int mEnemyActorId; - float mEnemyHealthTimer; - - bool mIsDrowning; - float mDrowningFlashTheta; + bool mMapVisible = true; + bool mWeaponVisible = true; + bool mSpellVisible = true; + bool mWorldMouseOver = false; + bool mIsDrowning = false; void onWorldClicked(MyGUI::Widget* sender); void onWorldMouseOver(MyGUI::Widget* sender, int x, int y); From 8020bfcafddc39fd4ed7a6af2ea912a9c41ec246 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 20 Dec 2024 23:59:15 +0100 Subject: [PATCH 05/12] Track projectile casters using RefNum --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 4 +- apps/openmw/mwworld/projectilemanager.cpp | 79 +++++++++++++---------- apps/openmw/mwworld/projectilemanager.hpp | 8 +-- apps/openmw/mwworld/worldimp.cpp | 3 +- apps/openmw/mwworld/worldimp.hpp | 2 +- components/esm3/projectilestate.cpp | 10 ++- components/esm3/projectilestate.hpp | 2 +- 8 files changed, 64 insertions(+), 46 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 23fb98063f..c60d575c7e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -375,7 +375,7 @@ namespace MWBase virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void disableDeferredPreviewRotation() = 0; - virtual void saveLoaded() = 0; + virtual void saveLoaded(const ESM::ESMReader& reader) = 0; virtual void setupPlayer() = 0; virtual void renderPlayer() = 0; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 7c5444fd4b..05a4540afe 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -594,7 +594,6 @@ void MWState::StateManager::loadGame(const Character* character, const std::file currentPercent = progressPercent; } } - actorIdConverter.apply(); mCharacterManager.setCurrentCharacter(character); mState = State_Running; @@ -604,7 +603,8 @@ void MWState::StateManager::loadGame(const Character* character, const std::file mLastSavegame = filepath; MWBase::Environment::get().getWindowManager()->setNewGame(false); - MWBase::Environment::get().getWorld()->saveLoaded(); + MWBase::Environment::get().getWorld()->saveLoaded(reader); + actorIdConverter.apply(); MWBase::Environment::get().getWorld()->setupPlayer(); MWBase::Environment::get().getWorld()->renderPlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer(); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 203444fa61..c79c265e38 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -9,6 +9,8 @@ #include +#include +#include #include #include #include @@ -36,6 +38,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -289,10 +292,8 @@ namespace MWWorld state.mSpellId = spellId; state.mCasterHandle = caster; state.mItem = item; - if (caster.getClass().isActor()) - state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); - else - state.mActorId = -1; + MWBase::Environment::get().getWorldModel()->registerPtr(caster); + state.mCaster = caster.getCellRef().getRefNum(); std::string texture; @@ -342,7 +343,7 @@ namespace MWWorld const osg::Quat& orient, const Ptr& bow, float speed, float attackStrength) { ProjectileState state; - state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); + state.mCaster = actor.getCellRef().getRefNum(); state.mBowId = bow.getCellRef().getRefId(); state.mVelocity = orient * osg::Vec3f(0, 1, 0) * speed; state.mIdArrow = projectile.getCellRef().getRefId(); @@ -367,24 +368,24 @@ namespace MWWorld void ProjectileManager::updateCasters() { for (auto& state : mProjectiles) - mPhysics->setCaster(state.mProjectileId, state.getCaster()); + { + state.mCasterHandle = state.getCaster(); + mPhysics->setCaster(state.mProjectileId, state.mCasterHandle); + } for (auto& state : mMagicBolts) { - // casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified - // back. - // TODO: should object-type caster be restored from savegame? - if (state.mActorId == -1) + if (!state.mCaster.isSet()) continue; - auto caster = state.getCaster(); - if (caster.isEmpty()) + state.mCasterHandle = state.getCaster(); + if (state.mCasterHandle.isEmpty()) { - Log(Debug::Error) << "Couldn't find caster with ID " << state.mActorId; + Log(Debug::Error) << "Couldn't find caster with ID " << state.mCaster; cleanupMagicBolt(state); continue; } - mPhysics->setCaster(state.mProjectileId, caster); + mPhysics->setCaster(state.mProjectileId, state.mCasterHandle); } } @@ -650,37 +651,37 @@ namespace MWWorld void ProjectileManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { - for (std::vector::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + for (const ProjectileState& projectile : mProjectiles) { writer.startRecord(ESM::REC_PROJ); ESM::ProjectileState state; - state.mId = it->mIdArrow; - state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); - state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); - state.mActorId = it->mActorId; + state.mId = projectile.mIdArrow; + state.mPosition = ESM::Vector3(osg::Vec3f(projectile.mNode->getPosition())); + state.mOrientation = ESM::Quaternion(osg::Quat(projectile.mNode->getAttitude())); + state.mCaster = projectile.mCaster; - state.mBowId = it->mBowId; - state.mVelocity = it->mVelocity; - state.mAttackStrength = it->mAttackStrength; + state.mBowId = projectile.mBowId; + state.mVelocity = projectile.mVelocity; + state.mAttackStrength = projectile.mAttackStrength; state.save(writer); writer.endRecord(ESM::REC_PROJ); } - for (std::vector::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) + for (const MagicBoltState& bolt : mMagicBolts) { writer.startRecord(ESM::REC_MPRJ); ESM::MagicBoltState state; - state.mId = it->mIdMagic.at(0); - state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); - state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); - state.mActorId = it->mActorId; - state.mItem = it->mItem; - state.mSpellId = it->mSpellId; - state.mSpeed = it->mSpeed; + state.mId = bolt.mIdMagic.at(0); + state.mPosition = ESM::Vector3(osg::Vec3f(bolt.mNode->getPosition())); + state.mOrientation = ESM::Quaternion(osg::Quat(bolt.mNode->getAttitude())); + state.mCaster = bolt.mCaster; + state.mItem = bolt.mItem; + state.mSpellId = bolt.mSpellId; + state.mSpeed = bolt.mSpeed; state.save(writer); @@ -696,7 +697,7 @@ namespace MWWorld esm.load(reader); ProjectileState state; - state.mActorId = esm.mActorId; + state.mCaster = esm.mCaster; state.mBowId = esm.mBowId; state.mVelocity = esm.mVelocity; state.mIdArrow = esm.mId; @@ -736,7 +737,7 @@ namespace MWWorld MagicBoltState state; state.mIdMagic.push_back(esm.mId); state.mSpellId = esm.mSpellId; - state.mActorId = esm.mActorId; + state.mCaster = esm.mCaster; state.mToDelete = false; state.mItem = esm.mItem; std::string texture; @@ -798,12 +799,24 @@ namespace MWWorld return mMagicBolts.size() + mProjectiles.size(); } + void ProjectileManager::saveLoaded(const ESM::ESMReader& reader) + { + // Can't do this in readRecord because the vectors might get reallocated as they grow + if (reader.getActorIdConverter()) + { + for (ProjectileState& projectile : mProjectiles) + reader.getActorIdConverter()->convert(projectile.mCaster, projectile.mCaster.mIndex); + for (MagicBoltState& bolt : mMagicBolts) + reader.getActorIdConverter()->convert(bolt.mCaster, bolt.mCaster.mIndex); + } + } + MWWorld::Ptr ProjectileManager::State::getCaster() { if (!mCasterHandle.isEmpty()) return mCasterHandle; - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); + return MWBase::Environment::get().getWorldModel()->getPtr(mCaster); } } diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 432f463dbd..dd26f1df91 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -68,6 +68,7 @@ namespace MWWorld void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord(ESM::ESMReader& reader, uint32_t type); size_t countSavedGameRecords() const; + void saveLoaded(const ESM::ESMReader& reader); private: osg::ref_ptr mParent; @@ -81,11 +82,7 @@ namespace MWWorld osg::ref_ptr mNode; std::shared_ptr mEffectAnimationTime; - int mActorId; - int mProjectileId; - - // TODO: this will break when the game is saved and reloaded, since there is currently - // no way to write identifiers for non-actors to a savegame. + ESM::RefNum mCaster; MWWorld::Ptr mCasterHandle; MWWorld::Ptr getCaster(); @@ -96,6 +93,7 @@ namespace MWWorld // MW-id of an arrow projectile ESM::RefId mIdArrow; + int mProjectileId; bool mToDelete; }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index fcb479db45..8681b12ab2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2307,11 +2307,12 @@ namespace MWWorld return true; } - void World::saveLoaded() + void World::saveLoaded(const ESM::ESMReader& reader) { mStore.rebuildIdsIndex(); mStore.validateDynamic(); mTimeManager->setup(mGlobalVariables); + mProjectileManager->saveLoaded(reader); } void World::setupPlayer() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4b8d5def1b..af757af63c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -467,7 +467,7 @@ namespace MWWorld void applyDeferredPreviewRotationToPlayer(float dt) override; void disableDeferredPreviewRotation() override; - void saveLoaded() override; + void saveLoaded(const ESM::ESMReader& reader) override; void setupPlayer() override; void renderPlayer() override; diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index e20cefa882..a8df70b776 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -11,7 +11,7 @@ namespace ESM esm.writeHNRefId("ID__", mId); esm.writeHNT("VEC3", mPosition); esm.writeHNT("QUAT", mOrientation); - esm.writeHNT("ACTO", mActorId); + esm.writeFormId(mCaster, true, "ACTO"); } void BaseProjectileState::load(ESMReader& esm) @@ -19,7 +19,13 @@ namespace ESM mId = esm.getHNRefId("ID__"); esm.getHNT("VEC3", mPosition.mValues); esm.getHNT("QUAT", mOrientation.mValues); - esm.getHNT(mActorId, "ACTO"); + if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion) + { + mCaster.mIndex = -1; + esm.getHNT(mCaster.mIndex, "ACTO"); + } + else + mCaster = esm.getFormId(true, "ACTO"); } void MagicBoltState::save(ESMWriter& esm) const diff --git a/components/esm3/projectilestate.hpp b/components/esm3/projectilestate.hpp index c89d55c0e5..4c34ce3ca1 100644 --- a/components/esm3/projectilestate.hpp +++ b/components/esm3/projectilestate.hpp @@ -24,7 +24,7 @@ namespace ESM Vector3 mPosition; Quaternion mOrientation; - int32_t mActorId; + RefNum mCaster; void load(ESMReader& esm); void save(ESMWriter& esm) const; From c4bc040b4b86f4ba639c81d6450bfbfcb6a1cf1d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 29 Dec 2024 13:55:58 +0100 Subject: [PATCH 06/12] Replace actor ids in magic effects with refnums --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwgui/container.cpp | 4 +- apps/openmw/mwlua/magicbindings.cpp | 5 +- apps/openmw/mwmechanics/activespells.cpp | 42 +++++++---- apps/openmw/mwmechanics/activespells.hpp | 8 +- apps/openmw/mwmechanics/actors.cpp | 33 +++----- apps/openmw/mwmechanics/actors.hpp | 5 +- apps/openmw/mwmechanics/aifollow.cpp | 2 +- apps/openmw/mwmechanics/aipackage.hpp | 1 + apps/openmw/mwmechanics/aipursue.cpp | 3 +- apps/openmw/mwmechanics/creaturestats.cpp | 19 ++--- apps/openmw/mwmechanics/creaturestats.hpp | 9 +-- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwmechanics/spelleffects.cpp | 14 ++-- apps/openmw/mwmechanics/spellpriority.cpp | 4 +- apps/openmw/mwmechanics/summoning.cpp | 32 ++++---- apps/openmw/mwmechanics/summoning.hpp | 7 +- apps/openmw/mwstate/statemanagerimp.cpp | 6 ++ apps/openmw/mwworld/magiceffects.cpp | 6 +- apps/openmw/mwworld/player.cpp | 3 + components/esm3/activespells.cpp | 75 +++++++++---------- components/esm3/activespells.hpp | 8 +- components/esm3/actoridconverter.hpp | 1 + components/esm3/creaturestats.cpp | 5 -- components/esm3/creaturestats.hpp | 2 +- 26 files changed, 155 insertions(+), 147 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 22eaad8dc1..c363c2b808 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -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) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 1b6dd4cbfb..bb8ccc5536 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -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; diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index d36b31a931..4f4437fbc0 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -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 diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 109b75e1d4..d2f4e7d2d4 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -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 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(&effect.mArg)) + converter->convert(*refNum, refNum->mIndex); + } + } + }; + convertSummons(mSpells); + convertSummons(mQueue); + } } void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 32046256fd..c465e28556 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -37,7 +37,7 @@ namespace MWMechanics ESM::RefId mSourceSpellId; std::vector 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& getEffects() const { return mEffects; } std::vector& 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); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 9e0279e8b9..0f1fe34701 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -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& 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); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 3e34ed0d67..a941aa0395 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -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; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 3702c981ae..fb6deaa40f 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -78,7 +78,7 @@ namespace MWMechanics AiFollow::AiFollow(const ESM::AiSequence::AiFollow* follow) : TypedAiPackage( - makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) + makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) , mAlwaysFollow(follow->mAlwaysFollow) , mDuration(follow->mData.mDuration) , mRemainingDuration(follow->mRemainingDuration) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 30b840dd22..fbae265cc6 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -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); }; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 8be31dbd66..2f15b35dd5 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -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()) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 3329d87cbf..f8f8685b42 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -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& CreatureStats::getSummonedCreatureMap() + std::multimap& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } - std::vector& CreatureStats::getSummonedCreatureGraveyard() - { - return mSummonGraveyard; - } - void CreatureStats::updateAwareness(float duration) { mAwarenessTimer += duration; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index ab8e03a294..5e13a86c11 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -84,11 +84,7 @@ namespace MWMechanics MWWorld::TimeStamp mTimeOfDeath; private: - std::multimap mSummonedCreatures; // - - // 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 mSummonGraveyard; + std::multimap mSummonedCreatures; // float mAwarenessTimer = 0.f; int mAwarenessRoll = -1; @@ -237,8 +233,7 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::multimap& getSummonedCreatureMap(); // - std::vector& getSummonedCreatureGraveyard(); // ActorIds + std::multimap& getSummonedCreatureMap(); // enum Flag { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 3db7015d63..ad6a9c7c45 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -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 diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 53eee5ed83..83da60d99c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -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; diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index a1beca14bb..13a84ba4a0 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -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); } diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 7041254c4e..1e811a0658 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -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(); } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 78d4976040..9f7b59b030 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -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 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& summon) + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& 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); } } diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index a341ee6e93..3992f8399f 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -3,6 +3,9 @@ #include #include + +#include + namespace ESM { class RefId; @@ -18,9 +21,9 @@ namespace MWMechanics ESM::RefId getSummonedCreature(int effectId); - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& 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); } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 05a4540afe..7e515bffe7 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -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) { diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 6323dcb772..ff38d909ae 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -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(actorId), .mContentFile = -1 }; effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; found = true; break; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index a8cbdb447b..7fa88b5784 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -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]; diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 7c84afe489..55ce37f95d 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -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& 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(&effect.mArg)) + { + if (!id->empty()) + esm.writeHNRefId("ARG_", *id); + } + else if (const ESM::RefNum* actor = std::get_if(&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(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(&mArg)) + if (const ESM::RefId* id = std::get_if(&mArg)) return *id; return {}; } - int ActiveEffect::getActorId() const + RefNum ActiveEffect::getActor() const { - if (const auto* id = std::get_if(&mArg)) - return *id; - return -1; + if (const ESM::RefNum* actor = std::get_if(&mArg)) + return *actor; + return {}; } } diff --git a/components/esm3/activespells.hpp b/components/esm3/activespells.hpp index 3058e8e4de..0db1b0e61b 100644 --- a/components/esm3/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -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 mArg; // skill, attribute, or summon + std::variant 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 mEffects; std::string mDisplayName; - int32_t mCasterActorId; + RefNum mCaster; RefNum mItem; Flags mFlags; int32_t mWorsenings; @@ -74,6 +75,7 @@ namespace ESM std::vector mSpells; std::vector mQueue; + ActorIdConverter* mActorIdConverter = nullptr; void load(ESMReader& esm); void save(ESMWriter& esm) const; diff --git a/components/esm3/actoridconverter.hpp b/components/esm3/actoridconverter.hpp index 07a78bc2de..09b7e71aa1 100644 --- a/components/esm3/actoridconverter.hpp +++ b/components/esm3/actoridconverter.hpp @@ -14,6 +14,7 @@ namespace ESM public: std::map mMappings; + std::vector mGraveyard; void apply(); diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index 44c3bd993b..77c6644638 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -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) { diff --git a/components/esm3/creaturestats.hpp b/components/esm3/creaturestats.hpp index 6e65a52354..cba74e065d 100644 --- a/components/esm3/creaturestats.hpp +++ b/components/esm3/creaturestats.hpp @@ -43,7 +43,7 @@ namespace ESM std::array, 4> mAiSettings; std::map mSummonedCreatureMap; - std::multimap mSummonedCreatures; + std::multimap mSummonedCreatures; std::vector mSummonGraveyard; TimeStamp mTradeTime; From 9cb437c9d3e418d205816b342e563943388ffbb3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 29 Dec 2024 15:00:41 +0100 Subject: [PATCH 07/12] Remove actor ids --- apps/openmw/mwbase/world.hpp | 3 -- apps/openmw/mwmechanics/creaturestats.cpp | 34 --------------- apps/openmw/mwmechanics/creaturestats.hpp | 18 +------- apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 1 - apps/openmw/mwworld/cellstore.cpp | 53 ----------------------- apps/openmw/mwworld/cellstore.hpp | 5 --- apps/openmw/mwworld/scene.cpp | 11 ----- apps/openmw/mwworld/scene.hpp | 2 - apps/openmw/mwworld/worldimp.cpp | 14 +----- apps/openmw/mwworld/worldimp.hpp | 3 -- components/esm3/creaturestats.cpp | 19 ++++---- 12 files changed, 14 insertions(+), 151 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c60d575c7e..819c4ac91d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -196,9 +196,6 @@ namespace MWBase ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. - virtual MWWorld::Ptr searchPtrViaActorId(int actorId) = 0; - ///< Search is limited to the active cells. - virtual MWWorld::Ptr findContainer(const MWWorld::ConstPtr& ptr) = 0; ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index f8f8685b42..e52abefed6 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -18,8 +18,6 @@ namespace MWMechanics { - int CreatureStats::sActorId = 0; - CreatureStats::CreatureStats() { for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) @@ -540,7 +538,6 @@ namespace MWMechanics state.mRecalcDynamicStats = false; state.mDrawState = static_cast(mDrawState); state.mLevel = mLevel; - state.mActorId = mActorId; state.mDeathAnimation = mDeathAnimation; state.mTimeOfDeath = mTimeOfDeath.toEsm(); // state.mHitAttemptActorId = mHitAttemptActorId; @@ -593,7 +590,6 @@ namespace MWMechanics mLastHitAttemptObject = state.mLastHitAttemptObject; mDrawState = DrawState(state.mDrawState); mLevel = state.mLevel; - mActorId = state.mActorId; mDeathAnimation = state.mDeathAnimation; mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); // mHitAttemptActor = state.mHitAttemptActor; @@ -638,36 +634,6 @@ namespace MWMechanics return mGoldPool; } - int CreatureStats::getActorId() - { - if (mActorId == -1) - mActorId = sActorId++; - - return mActorId; - } - - bool CreatureStats::matchesActorId(int id) const - { - return mActorId != -1 && id == mActorId; - } - - void CreatureStats::cleanup() - { - sActorId = 0; - } - - void CreatureStats::writeActorIdCounter(ESM::ESMWriter& esm) - { - esm.startRecord(ESM::REC_ACTC); - esm.writeHNT("COUN", sActorId); - esm.endRecord(ESM::REC_ACTC); - } - - void CreatureStats::readActorIdCounter(ESM::ESMReader& esm) - { - esm.getHNT(sActorId, "COUN"); - } - signed char CreatureStats::getDeathAnimation() const { return mDeathAnimation; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 5e13a86c11..5441fdc28f 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -38,7 +38,6 @@ namespace MWMechanics /// class CreatureStats { - static int sActorId; std::map mAttributes; DynamicStat mDynamic[3]; // health, magicka, fatigue DrawState mDrawState = DrawState::Nothing; @@ -73,7 +72,6 @@ namespace MWMechanics // The pool of merchant gold (not in inventory) int mGoldPool = 0; - int mActorId = -1; // Stores an actor that attacked this actor. Only one is stored at a time, and it is not changed if a different // actor attacks. It is cleared when combat ends. ESM::RefNum mHitAttemptActor; @@ -84,7 +82,7 @@ namespace MWMechanics MWWorld::TimeStamp mTimeOfDeath; private: - std::multimap mSummonedCreatures; // + std::multimap mSummonedCreatures; // float mAwarenessTimer = 0.f; int mAwarenessRoll = -1; @@ -233,7 +231,7 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::multimap& getSummonedCreatureMap(); // + std::multimap& getSummonedCreatureMap(); // enum Flag { @@ -268,9 +266,6 @@ namespace MWMechanics void readState(const ESM::CreatureStats& state); - static void writeActorIdCounter(ESM::ESMWriter& esm); - static void readActorIdCounter(ESM::ESMReader& esm); - void setLastRestockTime(MWWorld::TimeStamp tradeTime); MWWorld::TimeStamp getLastRestockTime() const; @@ -282,15 +277,6 @@ namespace MWMechanics MWWorld::TimeStamp getTimeOfDeath() const; - int getActorId(); - ///< Will generate an actor ID, if the actor does not have one yet. - - bool matchesActorId(int id) const; - ///< Check if \a id matches the actor ID of *this (if the actor does not have an ID - /// assigned this function will return false). - - static void cleanup(); - float getSideMovementAngle() const { return mSideMovementAngle; } void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index d2baedab0f..f336688c1a 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -250,7 +250,7 @@ namespace MWMechanics // Import data only for player, other actors should not suffer from corprus worsening. MWWorld::Ptr player = getPlayer(); - if (creatureStats->getActorId() != player.getClass().getCreatureStats(player).getActorId()) + if (creatureStats != &player.getClass().getCreatureStats(player)) return; // Note: if target actor has the Restore attribute effects, stats will be restored. diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 7e515bffe7..97a516f635 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -66,7 +66,6 @@ void MWState::StateManager::cleanup(bool force) mCharacterManager.setCurrentCharacter(nullptr); mTimePlayed = 0; mLastSavegame.clear(); - MWMechanics::CreatureStats::cleanup(); mState = State_NoGame; MWBase::Environment::get().getLuaManager()->noGame(); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 4fb38ff456..300af962dc 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -142,25 +142,6 @@ namespace return MWWorld::Ptr(); } - template - MWWorld::Ptr searchViaActorId(MWWorld::CellRefList& actorList, int actorId, MWWorld::CellStore* cell, - const std::map& toIgnore) - { - for (typename MWWorld::CellRefList::List::iterator iter(actorList.mList.begin()); - iter != actorList.mList.end(); ++iter) - { - MWWorld::Ptr actor(&*iter, cell); - - if (toIgnore.find(&*iter) != toIgnore.end()) - continue; - - if (actor.getClass().getCreatureStats(actor).matchesActorId(actorId) && actor.getCellRef().getCount() > 0) - return actor; - } - - return MWWorld::Ptr(); - } - template void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) { @@ -694,26 +675,6 @@ namespace MWWorld return searchVisitor.mFound; } - Ptr CellStore::searchViaActorId(int id) - { - if (Ptr ptr = ::searchViaActorId(get(), id, this, mMovedToAnotherCell); !ptr.isEmpty()) - return ptr; - - if (Ptr ptr = ::searchViaActorId(get(), id, this, mMovedToAnotherCell); !ptr.isEmpty()) - return ptr; - - for (const auto& [base, _] : mMovedHere) - { - MWWorld::Ptr actor(base, this); - if (!actor.getClass().isActor()) - continue; - if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getCellRef().getCount() > 0) - return actor; - } - - return Ptr(); - } - class RefNumSearchVisitor { ESM::RefNum mRefNum; @@ -1371,20 +1332,6 @@ namespace MWWorld ptr.getBase(), static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); } - Ptr MWWorld::CellStore::getMovedActor(int actorId) const - { - for (const auto& [cellRef, cell] : mMovedToAnotherCell) - { - if (cellRef->mClass->isActor() && cellRef->mData.getCustomData()) - { - Ptr actor(cellRef, cell); - if (actor.getClass().getCreatureStats(actor).getActorId() == actorId) - return actor; - } - } - return {}; - } - CellStore* MWWorld::CellStore::getOriginCell(const Ptr& object) const { MovedRefTracker::const_iterator found = mMovedHere.find(object.getBase()); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 59127d6186..bca2c137d7 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -183,9 +183,6 @@ namespace MWWorld /// containers. /// @note Does not trigger CellStore hasState flag. - Ptr searchViaActorId(int id); - ///< Will return an empty Ptr if cell is not loaded. - float getWaterLevel() const; bool movedHere(const MWWorld::Ptr& ptr) const; @@ -336,8 +333,6 @@ namespace MWWorld void respawn(); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. - Ptr getMovedActor(int actorId) const; - CellStore* getOriginCell(const Ptr& object) const; Ptr getPtr(ESM::RefId id); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index a7b9179839..d7b793ae9d 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1083,17 +1083,6 @@ namespace MWWorld return mActiveCells.contains(&cell); } - Ptr Scene::searchPtrViaActorId(int actorId) - { - for (CellStoreCollection::const_iterator iter(mActiveCells.begin()); iter != mActiveCells.end(); ++iter) - { - Ptr ptr = (*iter)->searchViaActorId(actorId); - if (!ptr.isEmpty()) - return ptr; - } - return Ptr(); - } - class PreloadMeshItem : public SceneUtil::WorkItem { public: diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 1fa7779e51..2e81325eab 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -197,8 +197,6 @@ namespace MWWorld bool isCellActive(const CellStore& cell); - Ptr searchPtrViaActorId(int actorId); - void preload(const std::string& mesh, bool useAnim = false); void testExteriorCells(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8681b12ab2..94bfaa3997 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -449,7 +449,6 @@ namespace MWWorld + mGlobalVariables.countSavedGameRecords() + mProjectileManager->countSavedGameRecords() + 1 // player record + 1 // weather record - + 1 // actorId counter + 1 // levitation/teleport enabled state + 1 // camera + 1; // random state. @@ -472,8 +471,6 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->writeFog(cellstore); } - MWMechanics::CreatureStats::writeActorIdCounter(writer); - mStore.write(writer, progress); // dynamic Store must be written (and read) before Cells, so that // references to custom made records will be recognized mWorldModel.write(writer, progress); // the player's cell needs to be loaded before the player @@ -497,7 +494,7 @@ namespace MWWorld switch (type) { case ESM::REC_ACTC: - MWMechanics::CreatureStats::readActorIdCounter(reader); + reader.skipRecord(); return; case ESM::REC_ENAB: reader.getHNT(mTeleportEnabled, "TELE"); @@ -750,15 +747,6 @@ namespace MWWorld throw std::runtime_error(error); } - Ptr World::searchPtrViaActorId(int actorId) - { - // The player is not registered in any CellStore so must be checked manually - if (actorId == getPlayerPtr().getClass().getCreatureStats(getPlayerPtr()).getActorId()) - return getPlayerPtr(); - // Now search cells - return mWorldScene->searchPtrViaActorId(actorId); - } - struct FindContainerVisitor { ConstPtr mContainedPtr; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index af757af63c..9962978485 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -288,9 +288,6 @@ namespace MWWorld ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do not search inactive cells. - Ptr searchPtrViaActorId(int actorId) override; - ///< Search is limited to the active cells. - MWWorld::Ptr findContainer(const MWWorld::ConstPtr& ptr) override; ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index 77c6644638..ba61a8c895 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -118,7 +118,8 @@ namespace ESM int32_t actorId; esm.getHNT(actorId, "ACID"); mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId; - mSummonedCreatures.emplace(magicEffect, actorId); + mSummonedCreatures.emplace( + magicEffect, RefNum{ .mIndex = static_cast(actorId), .mContentFile = -1 }); } } else @@ -127,9 +128,12 @@ namespace ESM { int32_t magicEffect; esm.getHT(magicEffect); - int32_t actorId; - esm.getHNT(actorId, "ACID"); - mSummonedCreatures.emplace(magicEffect, actorId); + RefNum actor; + if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion) + esm.getHNT(actor.mIndex, "ACID"); + else + actor = esm.getFormId(true, "ACID"); + mSummonedCreatures.emplace(magicEffect, actor); } } @@ -231,9 +235,6 @@ namespace ESM if (mLevel != 1) esm.writeHNT("LEVL", mLevel); - if (mActorId != -1) - esm.writeHNT("ACID", mActorId); - if (mDeathAnimation != -1) esm.writeHNT("DANM", mDeathAnimation); @@ -245,10 +246,10 @@ namespace ESM mAiSequence.save(esm); mMagicEffects.save(esm); - for (const auto& [effectId, actorId] : mSummonedCreatures) + for (const auto& [effectId, actor] : mSummonedCreatures) { esm.writeHNT("SUMM", effectId); - esm.writeHNT("ACID", actorId); + esm.writeFormId(actor, true, "ACID"); } esm.writeHNT("AISE", mHasAiSettings); From 20ddb848d42989e270080c743b365e6dfc321c60 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 30 Dec 2024 12:59:17 +0100 Subject: [PATCH 08/12] Stop using actor ids in the essimporter and tests --- apps/components_tests/esm3/testsaveload.cpp | 7 ++-- apps/essimporter/converter.cpp | 35 +++++++++---------- apps/essimporter/converter.hpp | 38 ++++++++++----------- apps/essimporter/importer.cpp | 35 ++++++++++--------- apps/essimporter/importercontext.hpp | 18 +++++++--- components/esm3/aisequence.cpp | 6 ++-- 6 files changed, 75 insertions(+), 64 deletions(-) diff --git a/apps/components_tests/esm3/testsaveload.cpp b/apps/components_tests/esm3/testsaveload.cpp index 7e5b73e4dd..0aac55d0fd 100644 --- a/apps/components_tests/esm3/testsaveload.cpp +++ b/apps/components_tests/esm3/testsaveload.cpp @@ -524,7 +524,7 @@ namespace ESM record.mData.mY = 2; record.mData.mZ = 3; record.mData.mDuration = 4; - record.mTargetActorId = 5; + record.mTargetActor = RefNum{ .mIndex = 5, .mContentFile = -1 }; record.mTargetId = generateRandomRefId(32); record.mCellId = generateRandomString(257); record.mRemainingDuration = 6; @@ -540,7 +540,10 @@ namespace ESM EXPECT_EQ(result.mData.mDuration, record.mRemainingDuration); else EXPECT_EQ(result.mData.mDuration, record.mData.mDuration); - EXPECT_EQ(result.mTargetActorId, record.mTargetActorId); + if (GetParam() > MaxActorIdSaveGameFormatVersion) + { + EXPECT_EQ(result.mTargetActor, record.mTargetActor); + } EXPECT_EQ(result.mTargetId, record.mTargetId); EXPECT_EQ(result.mCellId, record.mCellId); EXPECT_EQ(result.mRemainingDuration, record.mRemainingDuration); diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 3e6b293f2c..b6ab2aeeb1 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -64,7 +64,7 @@ namespace refId = indexedRefId.substr(0, indexedRefId.size() - 8); } - int convertActorId(const std::string& indexedRefId, ESSImport::Context& context) + ESM::RefNum convertActorId(const std::string& indexedRefId, ESSImport::Context& context) { if (isIndexedRefId(indexedRefId)) { @@ -73,16 +73,15 @@ namespace splitIndexedRefId(indexedRefId, refIndex, refId); auto it = context.mActorIdMap.find(std::make_pair(refIndex, ESM::RefId::stringRefId(refId))); - if (it == context.mActorIdMap.end()) - return -1; - return it->second; + if (it != context.mActorIdMap.end()) + return it->second; } else if (indexedRefId == "PlayerSaveGame") { - return context.mPlayer.mObject.mCreatureStats.mActorId; + return context.mPlayer.mObject.mRef.mRefNum; } - return -1; + return {}; } } @@ -114,7 +113,7 @@ namespace ESSImport mGlobalMapImage->scaleImage(maph.size * 2, maph.size * 2, 1, GL_UNSIGNED_BYTE); } - void ConvertFMAP::write(ESM::ESMWriter& esm) + void ConvertFMAP::write(ESM::ESMWriter& esm) const { int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly // with the 512x512 map the game has by default @@ -314,7 +313,7 @@ namespace ESSImport mIntCells[cell.mName] = std::move(newcell); } - void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm) + void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm) const { ESM::Cell esmcell = cell.mCell; esm.startRecord(ESM::REC_CSTA); @@ -378,9 +377,8 @@ namespace ESSImport convertNPCC(npccIt->second, objstate); convertCellRef(cellref, objstate); - objstate.mCreatureStats.mActorId = mContext->generateActorId(); - mContext->mActorIdMap.insert( - std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); + mContext->generateRefNum(objstate.mRef.mRefNum); + mContext->mActorIdMap.emplace(std::make_pair(refIndex, out.mRefID), objstate.mRef.mRefNum); esm.writeHNT("OBJE", ESM::REC_NPC_); objstate.save(esm); @@ -419,9 +417,8 @@ namespace ESSImport convertCREC(crecIt->second, objstate); convertCellRef(cellref, objstate); - objstate.mCreatureStats.mActorId = mContext->generateActorId(); - mContext->mActorIdMap.insert( - std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); + mContext->generateRefNum(objstate.mRef.mRefNum); + mContext->mActorIdMap.emplace(std::make_pair(refIndex, out.mRefID), objstate.mRef.mRefNum); esm.writeHNT("OBJE", ESM::REC_CREA); objstate.save(esm); @@ -437,7 +434,7 @@ namespace ESSImport esm.endRecord(ESM::REC_CSTA); } - void ConvertCell::write(ESM::ESMWriter& esm) + void ConvertCell::write(ESM::ESMWriter& esm) const { for (const auto& cell : mIntCells) writeCell(cell.second, esm); @@ -458,7 +455,7 @@ namespace ESSImport mProj.load(esm); } - void ConvertPROJ::write(ESM::ESMWriter& esm) + void ConvertPROJ::write(ESM::ESMWriter& esm) const { for (const PROJ::PNAM& pnam : mProj.mProjectiles) { @@ -501,7 +498,7 @@ namespace ESSImport } } - void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) + void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) const { base.mId = ESM::RefId::stringRefId(pnam.mArrowId.toString()); base.mPosition = pnam.mPosition; @@ -510,7 +507,7 @@ namespace ESSImport orient.makeRotate(osg::Vec3f(0, 1, 0), pnam.mVelocity); base.mOrientation = orient; - base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext); + base.mCaster = convertActorId(pnam.mActorId.toString(), *mContext); } void ConvertSPLM::read(ESM::ESMReader& esm) @@ -519,7 +516,7 @@ namespace ESSImport mContext->mActiveSpells = mSPLM.mActiveSpells; } - void ConvertSPLM::write(ESM::ESMWriter& esm) + void ConvertSPLM::write(ESM::ESMWriter& esm) const { std::cerr << "Warning: Skipped active spell conversion (not implemented)" << std::endl; } diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 9221adf83d..d819bf2374 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -54,7 +54,7 @@ namespace ESSImport { public: /// @return the order for writing this converter's records to the output file, in relation to other converters - virtual int getStage() { return 1; } + virtual int getStage() const { return 1; } virtual ~Converter() = default; @@ -66,7 +66,7 @@ namespace ESSImport /// Called after the input file has been read in completely, which may be necessary /// if the conversion process relies on information in other records - virtual void write(ESM::ESMWriter& esm) {} + virtual void write(ESM::ESMWriter& esm) const {} protected: Context* mContext; @@ -77,7 +77,7 @@ namespace ESSImport class DefaultConverter : public Converter { public: - int getStage() override { return 0; } + int getStage() const override { return 0; } void read(ESM::ESMReader& esm) override { @@ -88,7 +88,7 @@ namespace ESSImport mRecords[record.mId] = record; } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { for (auto it = mRecords.begin(); it != mRecords.end(); ++it) { @@ -257,7 +257,7 @@ namespace ESSImport } } } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { esm.startRecord(ESM::REC_ASPL); esm.writeHNRefId("ID__", mSelectedSpell); @@ -286,7 +286,7 @@ namespace ESSImport convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState); } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { esm.startRecord(ESM::REC_ENAB); esm.writeHNT("TELE", mTeleportingEnabled); @@ -331,7 +331,7 @@ namespace ESSImport { public: void read(ESM::ESMReader& esm) override; - void write(ESM::ESMWriter& esm) override; + void write(ESM::ESMWriter& esm) const override; private: osg::ref_ptr mGlobalMapImage; @@ -341,7 +341,7 @@ namespace ESSImport { public: void read(ESM::ESMReader& esm) override; - void write(ESM::ESMWriter& esm) override; + void write(ESM::ESMWriter& esm) const override; private: struct Cell @@ -356,7 +356,7 @@ namespace ESSImport std::vector mMarkers; - void writeCell(const Cell& cell, ESM::ESMWriter& esm); + void writeCell(const Cell& cell, ESM::ESMWriter& esm) const; }; class ConvertKLST : public Converter @@ -371,7 +371,7 @@ namespace ESSImport mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills; } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { esm.startRecord(ESM::REC_DCOU); for (const auto& [id, count] : mKillCounter) @@ -428,7 +428,7 @@ namespace ESSImport } } } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { ESM::StolenItems items; for (auto it = mStolenItems.begin(); it != mStolenItems.end(); ++it) @@ -486,7 +486,7 @@ namespace ESSImport if (dial.mIndex > 0) mDials[id] = dial; } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { for (auto it = mDials.begin(); it != mDials.end(); ++it) { @@ -539,7 +539,7 @@ namespace ESSImport mHasGame = true; } - int validateWeatherID(int weatherID) + int validateWeatherID(int weatherID) const { if (weatherID >= -1 && weatherID < 10) { @@ -551,7 +551,7 @@ namespace ESSImport } } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { if (!mHasGame) return; @@ -586,7 +586,7 @@ namespace ESSImport convertSCPT(script, out); mScripts.push_back(std::move(out)); } - void write(ESM::ESMWriter& esm) override + void write(ESM::ESMWriter& esm) const override { for (const auto& script : mScripts) { @@ -604,12 +604,12 @@ namespace ESSImport class ConvertPROJ : public Converter { public: - int getStage() override { return 2; } + int getStage() const override { return 2; } void read(ESM::ESMReader& esm) override; - void write(ESM::ESMWriter& esm) override; + void write(ESM::ESMWriter& esm) const override; private: - void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam); + void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) const; PROJ mProj; }; @@ -617,7 +617,7 @@ namespace ESSImport { public: void read(ESM::ESMReader& esm) override; - void write(ESM::ESMWriter& esm) override; + void write(ESM::ESMWriter& esm) const override; private: SPLM mSPLM; diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 84ddfc0463..9bcc4466f1 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -388,13 +388,22 @@ namespace ESSImport profile.save(writer); writer.endRecord(ESM::REC_SAVE); + writer.startRecord(ESM::REC_DIAS); + context.mDialogueState.save(writer); + writer.endRecord(ESM::REC_DIAS); + + writer.startRecord(ESM::REC_LUAM); + writer.writeHNT("LUAW", 0); + writer.writeFormId(context.mNextRefNum, true); + writer.endRecord(ESM::REC_LUAM); + // Writing order should be Dynamic Store -> Cells -> Player, // so that references to dynamic records can be recognized when loading - for (auto it = converters.begin(); it != converters.end(); ++it) + for (const auto& [_, converter] : converters) { - if (it->second->getStage() != 0) + if (converter->getStage() != 0) continue; - it->second->write(writer); + converter->write(writer); } writer.startRecord(ESM::REC_NPC_); @@ -402,11 +411,11 @@ namespace ESSImport context.mPlayerBase.save(writer); writer.endRecord(ESM::REC_NPC_); - for (auto it = converters.begin(); it != converters.end(); ++it) + for (const auto& [_, converter] : converters) { - if (it->second->getStage() != 1) + if (converter->getStage() != 1) continue; - it->second->write(writer); + converter->write(writer); } writer.startRecord(ESM::REC_PLAY); @@ -418,22 +427,14 @@ namespace ESSImport context.mPlayer.save(writer); writer.endRecord(ESM::REC_PLAY); - writer.startRecord(ESM::REC_ACTC); - writer.writeHNT("COUN", context.mNextActorId); - writer.endRecord(ESM::REC_ACTC); - // Stage 2 requires cell references to be written / actors IDs assigned - for (auto it = converters.begin(); it != converters.end(); ++it) + for (const auto& [_, converter] : converters) { - if (it->second->getStage() != 2) + if (converter->getStage() != 2) continue; - it->second->write(writer); + converter->write(writer); } - writer.startRecord(ESM::REC_DIAS); - context.mDialogueState.save(writer); - writer.endRecord(ESM::REC_DIAS); - writer.startRecord(ESM::REC_INPU); context.mControlsState.save(writer); writer.endRecord(ESM::REC_INPU); diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 03ea9d0943..ac54b7d5be 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -45,8 +45,8 @@ namespace ESSImport std::map, NPCC> mNpcChanges; std::map, CNTC> mContainerChanges; - std::map, int> mActorIdMap; - int mNextActorId; + std::map, ESM::RefNum> mActorIdMap; + ESM::RefNum mNextRefNum; std::map mCreatures; std::map mNpcs; @@ -58,7 +58,6 @@ namespace ESSImport , mMonth(0) , mYear(0) , mHour(0.f) - , mNextActorId(0) { mPlayer.mCellId = ESM::RefId::esm3ExteriorCell(0, 0); mPlayer.mLastKnownExteriorPosition[0] = mPlayer.mLastKnownExteriorPosition[1] @@ -69,7 +68,7 @@ namespace ESSImport mPlayer.mObject.blank(); mPlayer.mObject.mEnabled = true; mPlayer.mObject.mRef.mRefID = ESM::RefId::stringRefId("player"); // REFR.mRefID would be PlayerSaveGame - mPlayer.mObject.mCreatureStats.mActorId = generateActorId(); + generateRefNum(mPlayer.mObject.mRef.mRefNum); mGlobalMapState.mBounds.mMinX = 0; mGlobalMapState.mBounds.mMaxX = 0; @@ -79,7 +78,16 @@ namespace ESSImport mPlayerBase.blank(); } - int generateActorId() { return mNextActorId++; } + void generateRefNum(ESM::RefNum& refNum) + { + if (!refNum.isSet()) + { + mNextRefNum.mIndex++; + if (mNextRefNum.mIndex == 0) + mNextRefNum.mContentFile--; + refNum = mNextRefNum; + } + } }; } diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index cbfc027b15..0c3fae3c3d 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -107,7 +107,8 @@ namespace ESM { esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); - esm.writeFormId(mTargetActor, true, "TAID"); + if (esm.getFormatVersion() > MaxActorIdSaveGameFormatVersion) + esm.writeFormId(mTargetActor, true, "TAID"); esm.writeHNT("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString("CELL", mCellId); @@ -143,7 +144,8 @@ namespace ESM { esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); - esm.writeFormId(mTargetActor, true, "TAID"); + if (esm.getFormatVersion() > MaxActorIdSaveGameFormatVersion) + esm.writeFormId(mTargetActor, true, "TAID"); esm.writeHNT("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString("CELL", mCellId); From 63f5c9777e8e4377017339e0c7cd5b17edb672a6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 23 Nov 2025 15:15:41 +0100 Subject: [PATCH 09/12] Fix rebase induced issues --- apps/openmw/mwlua/magicbindings.cpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 7 ++----- components/esm3/aisequence.cpp | 2 +- components/esm3/creaturelevliststate.cpp | 2 +- components/esm3/projectilestate.cpp | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 4f4437fbc0..97cf4dfb89 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -545,7 +545,7 @@ namespace MWLua return sol::make_object(lua, LObject(itemPtr)); }); activeSpellT["caster"] - = sol::readonly_property([lua = lua.lua_state()](const ActiveSpell& activeSpell) -> sol::object { + = sol::readonly_property([lua = state.lua_state()](const ActiveSpell& activeSpell) -> sol::object { auto caster = MWBase::Environment::get().getWorldModel()->getPtr(activeSpell.mParams.getCaster()); if (caster.isEmpty()) return sol::nil; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index ad6a9c7c45..856f5ffcf9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1454,7 +1454,7 @@ namespace MWMechanics } startCombat(actor, player, &playerFollowers); - observerStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId()); + observerStats.setHitAttemptActor(player.getCellRef().getRefNum()); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off @@ -1750,10 +1750,7 @@ namespace MWMechanics aiSeq.stopPursuit(); aiSeq.stack(MWMechanics::AiCombat(target), ptr); // Stops guard from ending combat if player is unreachable - actor.getPtr() - .getClass() - .getCreatureStats(actor.getPtr()) - .setHitAttemptActor(playerNum); + actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActor(playerNum); } } } diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 0c3fae3c3d..0ecba5f7cc 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -41,7 +41,7 @@ namespace ESM { if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion) { - refNum.mIndex = -1; + refNum.mIndex = static_cast(-1); esm.getHNOT(refNum.mIndex, name); } else if (esm.peekNextSub(name)) diff --git a/components/esm3/creaturelevliststate.cpp b/components/esm3/creaturelevliststate.cpp index 2fde1e9276..7af2f1e2a1 100644 --- a/components/esm3/creaturelevliststate.cpp +++ b/components/esm3/creaturelevliststate.cpp @@ -12,7 +12,7 @@ namespace ESM if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion) { - mSpawnedActor.mIndex = -1; + mSpawnedActor.mIndex = static_cast(-1); esm.getHNOT(mSpawnedActor.mIndex, "SPAW"); } else if (esm.peekNextSub("SPAW")) diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index a8df70b776..3b23ff7813 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -21,7 +21,7 @@ namespace ESM esm.getHNT("QUAT", mOrientation.mValues); if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion) { - mCaster.mIndex = -1; + mCaster.mIndex = static_cast(-1); esm.getHNT(mCaster.mIndex, "ACTO"); } else From dbeaa25985ae955a39799908872c38b04ab51f9b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 26 Dec 2025 11:40:35 +0100 Subject: [PATCH 10/12] Fix mistakes --- apps/openmw/mwmechanics/aiescort.cpp | 1 + apps/openmw/mwmechanics/aifollow.cpp | 1 + components/esm3/activespells.cpp | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 1781512eb8..553e41bec6 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -26,6 +26,7 @@ namespace MWMechanics { AiEscort::AiEscort(ESM::RefNum actor, std::string_view cellId, int duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat) + , mCellId(cellId) , mX(x) , mY(y) , mZ(z) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index fb6deaa40f..7f2f3a0fef 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -39,6 +39,7 @@ namespace MWMechanics , mX(x) , mY(y) , mZ(z) + , mCellId(cellId) , mActive(false) , mFollowIndex(mFollowIndexCounter++) { diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 55ce37f95d..ea6c4fe8ea 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -125,7 +125,7 @@ namespace ESM if (format <= MaxActorIdSaveGameFormatVersion) esm.getHNT(params.mCaster.mIndex, "CAST"); else - params.mCaster = esm.getFormId("CAST"); + params.mCaster = esm.getFormId(true, "CAST"); params.mDisplayName = esm.getHNString("DISP"); if (format <= MaxClearModifiersFormatVersion) params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags; @@ -194,7 +194,7 @@ namespace ESM else if (esm.peekNextSub("ARG_")) effect.mArg = esm.getHNRefId("ARG_"); else if (esm.peekNextSub("SUM_")) - effect.mArg = esm.getFormId("SUM_"); + effect.mArg = esm.getFormId(true, "SUM_"); esm.getHNT(effect.mMagnitude, "MAGN"); if (format <= MaxClearModifiersFormatVersion) { From 72de0c06ba617f417c742ef0f6134ce76cd1a56a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 27 Dec 2025 22:29:32 +0100 Subject: [PATCH 11/12] Use getBaseNode to detect if the actor is active --- apps/openmw/mwmechanics/aicombat.cpp | 12 +----------- apps/openmw/mwmechanics/aipursue.cpp | 13 ++----------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 1ccc4455ed..2b71f2e8a5 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -18,8 +18,6 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwlua/localscripts.hpp" - #include "actorutil.hpp" #include "aicombataction.hpp" #include "character.hpp" @@ -121,17 +119,9 @@ namespace MWMechanics // Stop if the target doesn't exist if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled() - || target.getClass().getCreatureStats(target).isDead()) + || target.getClass().getCreatureStats(target).isDead() || !target.getRefData().getBaseNode()) return true; - // 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()) - return true; - } - if (actor == target) // This should never happen. return true; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 2f15b35dd5..b9bc3d9966 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -7,8 +7,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwlua/localscripts.hpp" - #include "../mwworld/class.hpp" #include "actorutil.hpp" @@ -38,17 +36,10 @@ namespace MWMechanics const MWWorld::Ptr target = getTarget(); // The target to follow // Stop if the target doesn't exist - if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) + if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled() + || !target.getRefData().getBaseNode()) return true; - // 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()) - return true; - } - if (isTargetMagicallyHidden(target) && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(target, actor, false)) return false; From d3b4ea0ba73d82f3d06879616d472e4d77e592e1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 28 Dec 2025 11:21:30 +0100 Subject: [PATCH 12/12] Only mark valid actor ids for conversion --- apps/openmw/mwworld/cellstore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 300af962dc..d2c7b4af97 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -257,9 +257,9 @@ namespace if constexpr (std::is_same_v || std::is_same_v) MWWorld::convertEnchantmentSlots(state.mCreatureStats, state.mInventory); } - if (reader.getActorIdConverter()) + if constexpr (std::is_same_v || std::is_same_v) { - if constexpr (std::is_same_v || std::is_same_v) + if (reader.getActorIdConverter() && state.mHasCustomState) { MWBase::Environment::get().getWorldModel()->assignSaveFileRefNum(state.mRef); reader.getActorIdConverter()->mMappings.emplace(state.mCreatureStats.mActorId, state.mRef.mRefNum);