Merge branch 'refnum-for-items-in-spellcast' into 'master'

Use refnum instead of slot for items during spellcast

Closes #4508

See merge request OpenMW/openmw!3244
revert-6246b479
psi29a 1 year ago
commit 488657d9b4

@ -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);

@ -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)

@ -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<unsigned int>(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<ESM::MagicEffect>().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())
{

@ -37,17 +37,18 @@ namespace MWMechanics
std::vector<ActiveEffect> 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);

@ -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);

@ -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<ESM::Enchantment>().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;

@ -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);

@ -104,8 +104,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState(
LiveCellRef<T> 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);
}
}

@ -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<std::pair<ContainerStoreIterator, float>> TRechargingItems;
@ -185,7 +185,7 @@ namespace MWWorld
virtual std::unique_ptr<ContainerStore> clone()
{
auto res = std::make_unique<ContainerStore>(*this);
res->clearRefNums();
res->updateRefNums();
return res;
}

@ -100,7 +100,7 @@ namespace MWWorld
std::unique_ptr<ContainerStore> clone() override
{
auto res = std::make_unique<InventoryStore>(*this);
res->clearRefNums();
res->updateRefNums();
return res;
}

@ -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<ESM::Spell>().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

@ -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<MWBase::Sound*> mSounds;
std::set<ESM::RefId> mSoundIds;

@ -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()

@ -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;

@ -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"))
{

@ -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

@ -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

@ -33,7 +33,7 @@ namespace ESM
{
RefId mSpellId;
float mSpeed;
int mSlot;
RefNum mItem;
void load(ESMReader& esm);
void save(ESMWriter& esm) const;

Loading…
Cancel
Save