From 1bcc40354454cf2c1eb6edb5271e10fc29cbcef1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 20 Dec 2024 14:55:24 +0100 Subject: [PATCH] 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);