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/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/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 23fb98063f..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. @@ -375,7 +372,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/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/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index f16601531d..15a73fbf1d 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,10 @@ 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) + state.mActorIdConverter->convert(customData.mSpawnedActor, customData.mSpawnedActor.mIndex); } void CreatureLevList::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const @@ -171,7 +165,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/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/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/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); 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/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index d36b31a931..97cf4dfb89 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -546,8 +546,7 @@ namespace MWLua }); activeSpellT["caster"] = sol::readonly_property([lua = state.lua_state()](const ActiveSpell& activeSpell) -> sol::object { - auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId( - activeSpell.mParams.getCasterActorId()); + 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 786539e585..0f1fe34701 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" @@ -178,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; @@ -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; } @@ -789,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()) @@ -831,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); } @@ -1155,9 +1156,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 +1519,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 +1551,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); @@ -1812,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); @@ -1830,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); @@ -1845,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) { @@ -1870,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/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0ddcf409b6..2b71f2e8a5 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -36,18 +36,24 @@ 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 { 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() {} @@ -109,14 +115,11 @@ 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 - || target.getClass().getCreatureStats(target).isDead()) + // Stop if the target doesn't exist + if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled() + || target.getClass().getCreatureStats(target).isDead() || !target.getRefData().getBaseNode()) return true; if (actor == target) // This should never happen. @@ -194,8 +197,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; @@ -491,19 +493,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..553e41bec6 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -24,15 +24,16 @@ 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) + , mCellId(cellId) , mX(x) , mY(y) , mZ(z) , mDuration(static_cast(duration)) , mRemainingDuration(static_cast(duration)) { - mTargetActorRefId = actorId; + mTargetActor = actor; } AiEscort::AiEscort( @@ -58,7 +59,7 @@ namespace MWMechanics , mRemainingDuration(escort->mRemainingDuration) { mTargetActorRefId = escort->mTargetId; - mTargetActorId = escort->mTargetActorId; + mTargetActor = escort->mTargetActor; } bool AiEscort::execute( @@ -131,7 +132,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..7f2f3a0fef 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) @@ -38,10 +39,11 @@ namespace MWMechanics , mX(x) , mY(y) , mZ(z) + , mCellId(cellId) , mActive(false) , mFollowIndex(mFollowIndexCounter++) { - mTargetActorRefId = actorId; + mTargetActor = actor; } AiFollow::AiFollow( @@ -72,7 +74,7 @@ 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) @@ -89,7 +91,7 @@ namespace MWMechanics , mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = follow->mTargetId; - mTargetActorId = follow->mTargetActorId; + mTargetActor = follow->mTargetActor; } bool AiFollow::execute( @@ -246,7 +248,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..fbae265cc6 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,15 +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..b9bc3d9966 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -19,12 +19,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,12 +33,11 @@ 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() + || !target.getRefData().getBaseNode()) return true; if (isTargetMagicallyHidden(target) @@ -79,23 +78,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/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 24b5891c1c..e52abefed6 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -17,8 +18,6 @@ namespace MWMechanics { - int CreatureStats::sActorId = 0; - CreatureStats::CreatureStats() { for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) @@ -365,14 +364,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) @@ -539,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; @@ -550,7 +548,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) @@ -593,10 +590,9 @@ namespace MWMechanics mLastHitAttemptObject = state.mLastHitAttemptObject; mDrawState = DrawState(state.mDrawState); mLevel = state.mLevel; - 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 +600,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) @@ -632,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; @@ -677,16 +649,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 aa46e9504f..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,10 +72,9 @@ 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. - int mHitAttemptActorId = -1; + ESM::RefNum mHitAttemptActor; // The difference between view direction and lower body direction. float mSideMovementAngle = 0; @@ -84,11 +82,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 +231,7 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::multimap& getSummonedCreatureMap(); // - std::vector& getSummonedCreatureGraveyard(); // ActorIds + std::multimap& getSummonedCreatureMap(); // enum Flag { @@ -266,16 +259,13 @@ 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; 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; @@ -287,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/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index a494153543..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 @@ -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()) @@ -1749,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()) - .setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); + actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActor(playerNum); } } } @@ -2041,9 +2039,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/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/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/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/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index dbb4c46ee3..97a516f635 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -65,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(); @@ -469,6 +469,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 +593,6 @@ void MWState::StateManager::loadGame(const Character* character, const std::file currentPercent = progressPercent; } } - mCharacterManager.setCurrentCharacter(character); mState = State_Running; @@ -599,7 +602,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(); @@ -646,6 +650,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/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 75ee14f627..d2c7b4af97 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -141,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) { @@ -275,6 +257,14 @@ namespace if constexpr (std::is_same_v || std::is_same_v) MWWorld::convertEnchantmentSlots(state.mCreatureStats, state.mInventory); } + 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); + } + } if (state.mRef.mRefNum.hasContentFile()) { @@ -685,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; @@ -1362,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/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 7bbc469e07..7fa88b5784 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -343,6 +344,9 @@ 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/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/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 fcb479db45..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; @@ -2307,11 +2295,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..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. @@ -467,7 +464,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/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/activespells.cpp b/components/esm3/activespells.cpp index 7c84afe489..ea6c4fe8ea 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(true, "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(true, "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.cpp b/components/esm3/actoridconverter.cpp new file mode 100644 index 0000000000..dba9cb82ea --- /dev/null +++ b/components/esm3/actoridconverter.cpp @@ -0,0 +1,32 @@ +#include "actoridconverter.hpp" + +namespace ESM +{ + void ActorIdConverter::apply() + { + for (auto& [refNum, actorId] : mToConvert) + { + auto it = mMappings.find(actorId); + if (it == mMappings.end()) + refNum = {}; + else + refNum = it->second; + } + } + + void ActorIdConverter::convert(ESM::RefNum& refNum, int actorId) + { + if (actorId == -1) + { + refNum = {}; + return; + } + auto it = mMappings.find(actorId); + if (it == mMappings.end()) + { + mToConvert.emplace_back(refNum, actorId); + return; + } + refNum = it->second; + } +} diff --git a/components/esm3/actoridconverter.hpp b/components/esm3/actoridconverter.hpp new file mode 100644 index 0000000000..09b7e71aa1 --- /dev/null +++ b/components/esm3/actoridconverter.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H +#define OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H + +#include +#include + +#include "refnum.hpp" + +namespace ESM +{ + class ActorIdConverter + { + std::vector> mToConvert; + + public: + std::map mMappings; + std::vector mGraveyard; + + void apply(); + + void convert(ESM::RefNum& refNum, int actorId); + }; +} + +#endif diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index ba28ca3e92..0ecba5f7cc 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 = static_cast(-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,8 @@ namespace ESM { esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); - esm.writeHNT("TAID", mTargetActorId); + if (esm.getFormatVersion() > MaxActorIdSaveGameFormatVersion) + esm.writeFormId(mTargetActor, true, "TAID"); esm.writeHNT("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString("CELL", mCellId); @@ -104,8 +120,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 +144,8 @@ namespace ESM { esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); - esm.writeHNT("TAID", mTargetActorId); + if (esm.getFormatVersion() > MaxActorIdSaveGameFormatVersion) + esm.writeFormId(mTargetActor, true, "TAID"); esm.writeHNT("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString("CELL", mCellId); @@ -157,22 +173,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 +230,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 6deb7d68b7..7af2f1e2a1 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 = static_cast(-1); + esm.getHNOT(mSpawnedActor.mIndex, "SPAW"); + } + else if (esm.peekNextSub("SPAW")) + mSpawnedActor = 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/creaturestats.cpp b/components/esm3/creaturestats.cpp index 44c3bd993b..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,15 +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); - } - - for (int32_t key : mSummonGraveyard) - { - esm.writeHNT("GRAV", key); + esm.writeFormId(actor, true, "ACID"); } esm.writeHNT("AISE", 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; 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); diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index e20cefa882..3b23ff7813 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 = static_cast(-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;