diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 4cf528c832..195b299450 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -491,7 +491,7 @@ namespace ESSImport out.mSpellId = ESM::RefId::stringRefId(it->mSPDT.mId.toString()); out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from - out.mSlot = 0; + out.mItem = ESM::RefNum(); esm.startRecord(ESM::REC_MPRJ); out.save(esm); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index a57bb70134..a4f714973c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -483,8 +483,8 @@ namespace MWBase virtual void castSpell(const MWWorld::Ptr& actor, bool manualSpell = false) = 0; - virtual void launchMagicBolt( - const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) + virtual void launchMagicBolt(const ESM::RefId& spellId, const MWWorld::Ptr& caster, + const osg::Vec3f& fallbackDirection, ESM::RefNum item) = 0; virtual void launchProjectile(MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index ef5c9992be..55477bef0c 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -85,7 +85,7 @@ namespace MWMechanics : mId(cast.mId) , mDisplayName(cast.mSourceName) , mCasterActorId(-1) - , mSlot(cast.mSlot) + , mItem(cast.mItem) , mType(cast.mType) , mWorsenings(-1) { @@ -98,7 +98,6 @@ namespace MWMechanics : mId(spell->mId) , mDisplayName(spell->mName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) - , mSlot(0) , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent) , mWorsenings(-1) @@ -108,11 +107,11 @@ namespace MWMechanics } ActiveSpells::ActiveSpellParams::ActiveSpellParams( - const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor) + const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor) : mId(item.getCellRef().getRefId()) , mDisplayName(item.getClass().getName(item)) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) - , mSlot(slotIndex) + , mItem(item.getCellRef().getRefNum()) , mType(ESM::ActiveSpells::Type_Enchantment) , mWorsenings(-1) { @@ -125,7 +124,7 @@ namespace MWMechanics , mEffects(params.mEffects) , mDisplayName(params.mDisplayName) , mCasterActorId(params.mCasterActorId) - , mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0) + , mItem(params.mItem) , mType(params.mType) , mWorsenings(params.mWorsenings) , mNextWorsening({ params.mNextWorsening }) @@ -136,7 +135,7 @@ namespace MWMechanics : mId(params.mId) , mDisplayName(params.mDisplayName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) - , mSlot(params.mSlot) + , mItem(params.mItem) , mType(params.mType) , mWorsenings(-1) { @@ -149,12 +148,7 @@ namespace MWMechanics params.mEffects = mEffects; params.mDisplayName = mDisplayName; params.mCasterActorId = mCasterActorId; - if (mSlot) - { - // Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing - // mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148) - params.mItem = { static_cast(mSlot), 0 }; - } + params.mItem = mItem; params.mType = mType; params.mWorsenings = mWorsenings; params.mNextWorsening = mNextWorsening.toEsm(); @@ -254,7 +248,8 @@ namespace MWMechanics continue; if (std::find_if(mSpells.begin(), mSpells.end(), [&](const ActiveSpellParams& params) { - return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment + return params.mItem == slot->getCellRef().getRefNum() + && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId(); }) != mSpells.end()) @@ -264,7 +259,7 @@ namespace MWMechanics purgeEffect(ptr, ESM::MagicEffect::Invisibility); applyPurges(ptr); const ActiveSpellParams& params - = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, slotIndex, ptr }); + = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); for (const auto& effect : params.mEffects) MWMechanics::playEffects( ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); @@ -351,9 +346,19 @@ namespace MWMechanics } else if (spellIt->mType == ESM::ActiveSpells::Type_Enchantment) { + // Remove constant effect enchantments that have been unequipped const auto& store = ptr.getClass().getInventoryStore(ptr); - auto slot = store.getSlot(spellIt->mSlot); - remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId; + remove = true; + for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) + { + auto slot = store.getSlot(slotIndex); + if (slot != store.end() && slot->getCellRef().getRefNum().isSet() + && slot->getCellRef().getRefNum() == spellIt->mItem) + { + remove = false; + break; + } + } } if (remove) { @@ -382,7 +387,7 @@ namespace MWMechanics { auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) { return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId - && spell.mSlot == existing.mSlot; + && spell.mItem == existing.mItem; }); if (found != mSpells.end()) { diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 6b71d93d64..8184567a5a 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -37,17 +37,18 @@ namespace MWMechanics std::vector mEffects; std::string mDisplayName; int mCasterActorId; - int mSlot; + ESM::RefNum mItem; ESM::ActiveSpells::EffectType mType; int mWorsenings; MWWorld::TimeStamp mNextWorsening; + MWWorld::Ptr mSource; ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params); ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false); - ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, - const MWWorld::Ptr& actor); + ActiveSpellParams( + const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor); ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index ef870b1edf..1c9c0c1dee 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -54,7 +54,7 @@ namespace MWMechanics { MWMechanics::CastSpell cast(attacker, victim, fromProjectile); cast.mHitPosition = hitPosition; - cast.cast(object, 0, false); + cast.cast(object, false); // Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills if (!victim.isEmpty() && victim.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d7aa0d6b71..52e371b6e9 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -146,7 +146,7 @@ namespace MWMechanics fallbackDirection = (mTarget.getRefData().getPosition().asVec3() + offset) - (mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mItem); } void CastSpell::inflict( @@ -290,7 +290,7 @@ namespace MWMechanics throw std::runtime_error("ID type cannot be casted"); } - bool CastSpell::cast(const MWWorld::Ptr& item, int slot, bool launchProjectile) + bool CastSpell::cast(const MWWorld::Ptr& item, bool launchProjectile) { const ESM::RefId& enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) @@ -302,7 +302,10 @@ namespace MWMechanics const auto& store = MWBase::Environment::get().getESMStore(); const ESM::Enchantment* enchantment = store->get().find(enchantmentName); - mSlot = slot; + // CastOnce enchantments (i.e. scrolls) never stack and the item is immediately destroyed, + // so don't track the source item. + if (enchantment->mData.mType != ESM::Enchantment::CastOnce) + mItem = item.getCellRef().getRefNum(); bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool isProjectile = false; diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index adad2de0e3..8f10066e04 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -43,7 +43,7 @@ namespace MWMechanics bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, // etc.) - int mSlot{ 0 }; + ESM::RefNum mItem; ESM::ActiveSpells::EffectType mType{ ESM::ActiveSpells::Type_Temporary }; CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile = false, @@ -54,7 +54,7 @@ namespace MWMechanics /// @note mCaster must be an actor /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched /// as projectile originating from the caster. - bool cast(const MWWorld::Ptr& item, int slot, bool launchProjectile = true); + bool cast(const MWWorld::Ptr& item, bool launchProjectile = true); /// @note mCaster must be an NPC bool cast(const ESM::Ingredient* ingredient); diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index b3ea654404..f5e45415ae 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -104,8 +104,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( LiveCellRef ref(record); ref.load(state); collection.mList.push_back(ref); + auto it = ContainerStoreIterator(this, --collection.mList.end()); + MWBase::Environment::get().getWorldModel()->registerPtr(*it); - return ContainerStoreIterator(this, --collection.mList.end()); + return it; } void MWWorld::ContainerStore::storeEquipmentState( @@ -155,7 +157,12 @@ MWWorld::ContainerStore::ContainerStore() { } -MWWorld::ContainerStore::~ContainerStore() {} +MWWorld::ContainerStore::~ContainerStore() +{ + MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); + for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) + worldModel->deregisterPtr(*iter); +} MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin(int mask) const { @@ -196,10 +203,14 @@ int MWWorld::ContainerStore::count(const ESM::RefId& id) const return total; } -void MWWorld::ContainerStore::clearRefNums() +void MWWorld::ContainerStore::updateRefNums() { for (const auto& iter : *this) + { iter.getCellRef().unsetRefNum(); + iter.getRefData().setLuaScripts(nullptr); + MWBase::Environment::get().getWorldModel()->registerPtr(iter); + } } MWWorld::ContainerStoreListener* MWWorld::ContainerStore::getContListener() const @@ -656,7 +667,8 @@ void MWWorld::ContainerStore::addInitialItemImp( else { ptr.getCellRef().setOwner(owner); - addImp(ptr, count, false); + MWWorld::ContainerStoreIterator it = addImp(ptr, count, false); + MWBase::Environment::get().getWorldModel()->registerPtr(*it); } } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 1948c07ae5..c7ad14013b 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -117,7 +117,7 @@ namespace MWWorld // Used in clone() to unset refnums of copies. // (RefNum should be unique, copy can not have the same RefNum). - void clearRefNums(); + void updateRefNums(); // (item, max charge) typedef std::vector> TRechargingItems; @@ -185,7 +185,7 @@ namespace MWWorld virtual std::unique_ptr clone() { auto res = std::make_unique(*this); - res->clearRefNums(); + res->updateRefNums(); return res; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 78d7e35275..c0723d319c 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -100,7 +100,7 @@ namespace MWWorld std::unique_ptr clone() override { auto res = std::make_unique(*this); - res->clearRefNums(); + res->updateRefNums(); return res; } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 4fdcb4de31..aef821fb1f 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -263,7 +263,7 @@ namespace MWWorld } void ProjectileManager::launchMagicBolt( - const ESM::RefId& spellId, const Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) + const ESM::RefId& spellId, const Ptr& caster, const osg::Vec3f& fallbackDirection, ESM::RefNum item) { osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) @@ -287,7 +287,7 @@ namespace MWWorld MagicBoltState state; state.mSpellId = spellId; state.mCasterHandle = caster; - state.mSlot = slot; + state.mItem = item; if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else @@ -575,7 +575,7 @@ namespace MWWorld cast.mHitPosition = Misc::Convert::toOsg(projectile->getHitPosition()); cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; - cast.mSlot = magicBoltState.mSlot; + cast.mItem = magicBoltState.mItem; // Grab original effect list so the indices are correct const ESM::EffectList* effects; if (const ESM::Spell* spell = esmStore.get().search(magicBoltState.mSpellId)) @@ -669,7 +669,7 @@ namespace MWWorld state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; - state.mSlot = it->mSlot; + state.mItem = it->mItem; state.mSpellId = it->mSpellId; state.mSpeed = it->mSpeed; @@ -727,7 +727,7 @@ namespace MWWorld state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; state.mToDelete = false; - state.mSlot = esm.mSlot; + state.mItem = esm.mItem; std::string texture; try diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 3e9d70893a..65254a9110 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -49,8 +49,8 @@ namespace MWWorld MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. - void launchMagicBolt( - const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot); + void launchMagicBolt(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, + ESM::RefNum item); void launchProjectile(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& projectile, const osg::Vec3f& pos, const osg::Quat& orient, const MWWorld::Ptr& bow, float speed, float attackStrength); @@ -108,7 +108,8 @@ namespace MWWorld ESM::EffectList mEffects; float mSpeed; - int mSlot; + // Refnum of the casting item + ESM::RefNum mItem; std::vector mSounds; std::set mSoundIds; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6897d663e1..b78e7f4124 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3136,9 +3136,9 @@ namespace MWWorld } void World::launchMagicBolt( - const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) + const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, ESM::RefNum item) { - mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection, slot); + mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection, item); } void World::updateProjectilesCasters() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 615172ef96..89754ae796 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -578,7 +578,7 @@ namespace MWWorld void castSpell(const MWWorld::Ptr& actor, bool manualSpell = false) override; void launchMagicBolt(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, - int slot) override; + ESM::RefNum item) override; void launchProjectile(MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; void updateProjectilesCasters() override; diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 97d56d1a2b..e2906f2399 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -56,7 +56,14 @@ namespace ESM { esm.getHNT(params.mType, "TYPE"); if (esm.peekNextSub("ITEM")) - params.mItem = esm.getFormId(true, "ITEM"); + { + if (format <= MaxActiveSpellSlotIndexFormatVersion) + // Previous versions saved slot index in this record. + // Ignore these values as we can't use them + esm.getFormId(true, "ITEM"); + else + params.mItem = esm.getFormId(true, "ITEM"); + } } if (esm.isNextSub("WORS")) { diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index aa0de9b0b7..644298b00d 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -24,7 +24,8 @@ namespace ESM inline constexpr FormatVersion MaxSavedGameCellNameAsRefIdFormatVersion = 24; inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25; inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 27; + inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 28; } #endif diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index 2b9eccc3e8..e3003f2fd0 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -28,7 +28,8 @@ namespace ESM esm.writeHNRefId("SPEL", mSpellId); esm.writeHNT("SPED", mSpeed); - esm.writeHNT("SLOT", mSlot); + if (mItem.isSet()) + esm.writeFormId(mItem, true, "ITEM"); } void MagicBoltState::load(ESMReader& esm) @@ -40,10 +41,10 @@ namespace ESM esm.skipHSub(); EffectList().load(esm); // for backwards compatibility esm.getHNT(mSpeed, "SPED"); - if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion) - mSlot = 0; - else - esm.getHNT(mSlot, "SLOT"); + if (esm.peekNextSub("ITEM")) + mItem = esm.getFormId(true, "ITEM"); + if (esm.isNextSub("SLOT")) // for backwards compatibility + esm.skipHSub(); if (esm.isNextSub("STCK")) // for backwards compatibility esm.skipHSub(); if (esm.isNextSub("SOUN")) // for backwards compatibility diff --git a/components/esm3/projectilestate.hpp b/components/esm3/projectilestate.hpp index afb8047da9..7a7651f364 100644 --- a/components/esm3/projectilestate.hpp +++ b/components/esm3/projectilestate.hpp @@ -33,7 +33,7 @@ namespace ESM { RefId mSpellId; float mSpeed; - int mSlot; + RefNum mItem; void load(ESMReader& esm); void save(ESMWriter& esm) const;