From 2dbe30ed5c790b0aed862a3dd90dca1e3d076c80 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 23 May 2024 17:15:56 +0200 Subject: [PATCH 01/22] Update effects upon applying them --- apps/openmw/mwmechanics/activespells.cpp | 258 +++++++++++++---------- apps/openmw/mwmechanics/activespells.hpp | 8 +- apps/openmw/mwmechanics/spellcasting.cpp | 4 - apps/openmw/mwmechanics/spelleffects.cpp | 6 +- 4 files changed, 156 insertions(+), 120 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 270faf8598..f9b7ec57ea 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -73,6 +73,20 @@ namespace namespace MWMechanics { + struct ActiveSpells::UpdateContext + { + bool mUpdatedEnemy = false; + bool mUpdatedHitOverlay = false; + bool mUpdateSpellWindow = false; + bool mPlayNonLooping = false; + bool mUpdate; + + UpdateContext(bool update) + : mUpdate(update) + { + } + }; + ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells) { @@ -256,8 +270,9 @@ namespace MWMechanics ++spellIt; } + UpdateContext context(duration > 0.f); for (const auto& spell : mQueue) - addToSpells(ptr, spell); + addToSpells(ptr, spell, context); mQueue.clear(); // Vanilla only does this on cell change I think @@ -267,20 +282,17 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId)) { - mSpells.emplace_back(ActiveSpellParams{ spell, ptr, true }); - mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + initParams(ptr, ActiveSpellParams{ spell, ptr, true }, context); } } - bool updateSpellWindow = false; - bool playNonLooping = false; if (ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) { auto& store = ptr.getClass().getInventoryStore(ptr); if (store.getInvListener() != nullptr) { - playNonLooping = !store.isFirstEquip(); + context.mPlayNonLooping = !store.isFirstEquip(); const auto world = MWBase::Environment::get().getWorld(); for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) { @@ -306,117 +318,18 @@ namespace MWMechanics // invisibility manually purgeEffect(ptr, ESM::MagicEffect::Invisibility); applyPurges(ptr); - ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); - params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); - updateSpellWindow = true; + ActiveSpellParams* params = initParams(ptr, ActiveSpellParams{ *slot, enchantment, ptr }, context); + if (params) + context.mUpdateSpellWindow = true; } } } const MWWorld::Ptr player = MWMechanics::getPlayer(); - bool updatedHitOverlay = false; - bool updatedEnemy = false; // Update effects for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId( - spellIt->mCasterActorId); // Maybe make this search outside active grid? - bool removedSpell = false; - std::optional reflected; - for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) - { - auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration, playNonLooping); - if (result.mType == MagicApplicationResult::Type::REFLECTED) - { - if (!reflected) - { - if (Settings::game().mClassicReflectedAbsorbSpellsBehavior) - reflected = { *spellIt, caster }; - else - reflected = { *spellIt, ptr }; - } - auto& reflectedEffect = reflected->mEffects.emplace_back(*it); - reflectedEffect.mFlags - = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; - it = spellIt->mEffects.erase(it); - } - else if (result.mType == MagicApplicationResult::Type::REMOVED) - it = spellIt->mEffects.erase(it); - else - { - ++it; - if (!updatedEnemy && result.mShowHealth && caster == player && ptr != player) - { - MWBase::Environment::get().getWindowManager()->setEnemy(ptr); - updatedEnemy = true; - } - if (!updatedHitOverlay && result.mShowHit && ptr == player) - { - MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); - updatedHitOverlay = true; - } - } - removedSpell = applyPurges(ptr, &spellIt, &it); - if (removedSpell) - break; - } - if (reflected) - { - const ESM::Static* reflectStatic = MWBase::Environment::get().getESMStore()->get().find( - ESM::RefId::stringRefId("VFX_Reflect")); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if (animation && !reflectStatic->mModel.empty()) - { - const VFS::Path::Normalized reflectStaticModel - = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(reflectStatic->mModel)); - animation->addEffect( - reflectStaticModel, ESM::MagicEffect::indexToName(ESM::MagicEffect::Reflect), false); - } - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); - } - if (removedSpell) - continue; - - bool remove = false; - if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore)) - { - try - { - remove = !spells.hasSpell(spellIt->mSourceSpellId); - } - catch (const std::runtime_error& e) - { - remove = true; - Log(Debug::Error) << "Removing active effect: " << e.what(); - } - } - else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment)) - { - // Remove effects tied to equipment that has been unequipped - const auto& store = ptr.getClass().getInventoryStore(ptr); - 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) - { - auto params = *spellIt; - spellIt = mSpells.erase(spellIt); - for (const auto& effect : params.mEffects) - onMagicEffectRemoved(ptr, params, effect); - applyPurges(ptr, &spellIt); - updateSpellWindow = true; - continue; - } - ++spellIt; + updateActiveSpell(ptr, duration, spellIt, context); } if (Settings::game().mClassicCalmSpellsBehavior) @@ -427,7 +340,7 @@ namespace MWMechanics creatureStats.getAiSequence().stopCombat(); } - if (ptr == player && updateSpellWindow) + if (ptr == player && context.mUpdateSpellWindow) { // Something happened with the spell list -- possibly while the game is paused, // so we want to make the spell window get the memo. @@ -436,7 +349,125 @@ namespace MWMechanics } } - void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) + 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? + bool removedSpell = false; + std::optional reflected; + for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) + { + auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration, context.mPlayNonLooping); + if (result.mType == MagicApplicationResult::Type::REFLECTED) + { + if (!reflected) + { + if (Settings::game().mClassicReflectedAbsorbSpellsBehavior) + reflected = { *spellIt, caster }; + else + reflected = { *spellIt, ptr }; + } + auto& reflectedEffect = reflected->mEffects.emplace_back(*it); + reflectedEffect.mFlags + = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + it = spellIt->mEffects.erase(it); + } + else if (result.mType == MagicApplicationResult::Type::REMOVED) + it = spellIt->mEffects.erase(it); + else + { + const MWWorld::Ptr player = MWMechanics::getPlayer(); + ++it; + if (!context.mUpdatedEnemy && result.mShowHealth && caster == player && ptr != player) + { + MWBase::Environment::get().getWindowManager()->setEnemy(ptr); + context.mUpdatedEnemy = true; + } + if (!context.mUpdatedHitOverlay && result.mShowHit && ptr == player) + { + MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + context.mUpdatedHitOverlay = true; + } + } + removedSpell = applyPurges(ptr, &spellIt, &it); + if (removedSpell) + break; + } + if (reflected) + { + const ESM::Static* reflectStatic = MWBase::Environment::get().getESMStore()->get().find( + ESM::RefId::stringRefId("VFX_Reflect")); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if (animation && !reflectStatic->mModel.empty()) + { + const VFS::Path::Normalized reflectStaticModel + = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(reflectStatic->mModel)); + animation->addEffect( + reflectStaticModel, ESM::MagicEffect::indexToName(ESM::MagicEffect::Reflect), false); + } + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); + } + if (removedSpell) + return true; + + bool remove = false; + if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore)) + { + try + { + auto& spells = ptr.getClass().getCreatureStats(ptr).getSpells(); + remove = !spells.hasSpell(spellIt->mSourceSpellId); + } + catch (const std::runtime_error& e) + { + remove = true; + Log(Debug::Error) << "Removing active effect: " << e.what(); + } + } + else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment)) + { + // Remove effects tied to equipment that has been unequipped + const auto& store = ptr.getClass().getInventoryStore(ptr); + 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) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + for (const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + applyPurges(ptr, &spellIt); + context.mUpdateSpellWindow = true; + return true; + } + ++spellIt; + return false; + } + + ActiveSpells::ActiveSpellParams* ActiveSpells::initParams( + const MWWorld::Ptr& ptr, const ActiveSpellParams& params, UpdateContext& context) + { + mSpells.emplace_back(params).setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + auto it = mSpells.end(); + --it; + // We instantly apply the effect with a duration of 0 so continuous effects can be purged before truly applying + if (context.mUpdate && updateActiveSpell(ptr, 0.f, it, context)) + return nullptr; + return &*it; + } + + void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell, UpdateContext& context) { if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable)) { @@ -454,8 +485,7 @@ namespace MWMechanics onMagicEffectRemoved(ptr, params, effect); } } - mSpells.emplace_back(spell); - mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + initParams(ptr, spell, context); } ActiveSpells::ActiveSpells() @@ -608,6 +638,8 @@ namespace MWMechanics { purge( [=](const ActiveSpellParams&, const ESM::ActiveEffect& effect) { + if (!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) + return false; if (effectArg.empty()) return effect.mEffectId == effectId; return effect.mEffectId == effectId && effect.getSkillOrAttribute() == effectArg; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 3e4dafdb26..465e5aa456 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -116,17 +116,23 @@ namespace MWMechanics IterationGuard(ActiveSpells& spells); ~IterationGuard(); }; + struct UpdateContext; std::list mSpells; std::vector mQueue; std::queue mPurges; bool mIterating; - void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell); + void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell, UpdateContext& context); bool applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell = nullptr, std::vector::iterator* currentEffect = nullptr); + bool updateActiveSpell( + const MWWorld::Ptr& ptr, float duration, Collection::iterator& spellIt, UpdateContext& context); + + ActiveSpellParams* initParams(const MWWorld::Ptr& ptr, const ActiveSpellParams& params, UpdateContext& context); + public: ActiveSpells(); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 59e7e29a38..bf9d6aa025 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -215,10 +215,6 @@ namespace MWMechanics bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); effect.mDuration = hasDuration ? static_cast(enam.mData.mDuration) : 1.f; - bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; - if (!appliedOnce) - effect.mDuration = std::max(1.f, effect.mDuration); - effect.mTimeLeft = effect.mDuration; // add to list of active effects, to apply in next frame diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 99e5a09481..822c394352 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -1011,11 +1011,13 @@ namespace MWMechanics else { // Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats - // updated instantly. We don't want to teleport instantly though + // updated instantly. We don't want to teleport instantly though. Nor do we want to force players to drink + // invisibility potions in the "right" order if (!dt && (effect.mEffectId == ESM::MagicEffect::Recall || effect.mEffectId == ESM::MagicEffect::DivineIntervention - || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention)) + || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention + || effect.mEffectId == ESM::MagicEffect::Invisibility)) return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); From c80b3d26b9cb0b8198b6d12364e0fb632b37019c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 13 Jul 2025 11:18:15 +0200 Subject: [PATCH 02/22] Only check for spelllist/equipment changes in the update loop --- apps/openmw/mwmechanics/activespells.cpp | 73 +++++++++++++----------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index f9b7ec57ea..7a4369a464 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -79,6 +79,7 @@ namespace MWMechanics bool mUpdatedHitOverlay = false; bool mUpdateSpellWindow = false; bool mPlayNonLooping = false; + bool mEraseRemoved = false; bool mUpdate; UpdateContext(bool update) @@ -327,6 +328,7 @@ namespace MWMechanics const MWWorld::Ptr player = MWMechanics::getPlayer(); // Update effects + context.mEraseRemoved = true; for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { updateActiveSpell(ptr, duration, spellIt, context); @@ -411,45 +413,48 @@ namespace MWMechanics if (removedSpell) return true; - bool remove = false; - if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore)) + if (context.mEraseRemoved) { - try + bool remove = false; + if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore)) { - auto& spells = ptr.getClass().getCreatureStats(ptr).getSpells(); - remove = !spells.hasSpell(spellIt->mSourceSpellId); - } - catch (const std::runtime_error& e) - { - remove = true; - Log(Debug::Error) << "Removing active effect: " << e.what(); - } - } - else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment)) - { - // Remove effects tied to equipment that has been unequipped - const auto& store = ptr.getClass().getInventoryStore(ptr); - 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) + try { - remove = false; - break; + auto& spells = ptr.getClass().getCreatureStats(ptr).getSpells(); + remove = !spells.hasSpell(spellIt->mSourceSpellId); + } + catch (const std::runtime_error& e) + { + remove = true; + Log(Debug::Error) << "Removing active effect: " << e.what(); } } - } - if (remove) - { - auto params = *spellIt; - spellIt = mSpells.erase(spellIt); - for (const auto& effect : params.mEffects) - onMagicEffectRemoved(ptr, params, effect); - applyPurges(ptr, &spellIt); - context.mUpdateSpellWindow = true; - return true; + else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment)) + { + // Remove effects tied to equipment that has been unequipped + const auto& store = ptr.getClass().getInventoryStore(ptr); + 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) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + for (const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + applyPurges(ptr, &spellIt); + context.mUpdateSpellWindow = true; + return true; + } } ++spellIt; return false; From d899454f3618b85608cba374e5f50373c4fd10ff Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 11 Apr 2025 06:08:42 +0300 Subject: [PATCH 03/22] Remove completion threshold-based turning for the player (#8447) --- apps/openmw/mwmechanics/character.cpp | 48 ++++++++------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1aac063ce3..3f4f6c6956 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2310,17 +2310,13 @@ namespace MWMechanics } else { - // Do not play turning animation for player if rotation speed is very slow. - // Actual threshold should take framerate in account. - float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration; - // It seems only bipedal actors use turning animations. // Also do not use turning animations in the first-person view and when sneaking. if (!sneak && !isFirstPersonPlayer && isBiped) { - if (effectiveRotation > rotationThreshold) + if (effectiveRotation > 0.f) movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - else if (effectiveRotation < -rotationThreshold) + else if (effectiveRotation < 0.f) movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; } } @@ -2346,34 +2342,19 @@ namespace MWMechanics vec.y() *= std::sqrt(1.0f - swimUpwardCoef * swimUpwardCoef); } - // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering - if (isPlayer) + if (isBiped) { - float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f; - float complete; - bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete); - if (movestate == CharState_None && jumpstate == JumpState_None && isTurning()) - { - if (animPlaying && complete < threshold) - movestate = mMovementState; - } - } - else - { - if (isBiped) - { - if (mTurnAnimationThreshold > 0) - mTurnAnimationThreshold -= duration; + if (mTurnAnimationThreshold > 0) + mTurnAnimationThreshold -= duration; - if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft - || movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) - { - mTurnAnimationThreshold = 0.05f; - } - else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0) - { - movestate = mMovementState; - } + if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft + || movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) + { + mTurnAnimationThreshold = 0.05f; + } + else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0) + { + movestate = mMovementState; } } @@ -2402,11 +2383,10 @@ namespace MWMechanics if (isTurning()) { - // Adjust animation speed from 1.0 to 1.5 multiplier if (duration > 0) { float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI)); - mAnimation->adjustSpeedMult(mCurrentMovement, std::max(turnSpeed, 1.0f)); + mAnimation->adjustSpeedMult(mCurrentMovement, turnSpeed); } } else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) From 896d6fd01ef1b305f76e1e1fdbad7716b112c332 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 22 Mar 2025 21:11:33 +0300 Subject: [PATCH 04/22] Put combat actions on hold when the actor is incapacitated (#7979) --- apps/openmw/mwmechanics/aicombat.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index a6f9935194..7b7148f1de 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -178,11 +178,16 @@ namespace MWMechanics currentCell = actor.getCell(); } + const MWWorld::Class& actorClass = actor.getClass(); + MWMechanics::CreatureStats& stats = actorClass.getCreatureStats(actor); + if (stats.isParalyzed() || stats.getKnockedDown()) + return false; + bool forceFlee = false; if (!canFight(actor, target)) { storage.stopAttack(); - actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); + stats.setAttackingOrSpell(false); storage.mActionCooldown = 0.f; // Continue combat if target is player or player follower/escorter and an attack has been attempted const auto& playerFollowersAndEscorters @@ -191,18 +196,14 @@ namespace MWMechanics = (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target) != playerFollowersAndEscorters.end()); if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer) - && ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId() - == target.getClass().getCreatureStats(target).getActorId()) - || (target.getClass().getCreatureStats(target).getHitAttemptActorId() - == actor.getClass().getCreatureStats(actor).getActorId()))) + && ((stats.getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId()) + || (target.getClass().getCreatureStats(target).getHitAttemptActorId() == stats.getActorId()))) forceFlee = true; else // Otherwise end combat return true; } - const MWWorld::Class& actorClass = actor.getClass(); - actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - + stats.setMovementFlag(CreatureStats::Flag_Run, true); float& actionCooldown = storage.mActionCooldown; std::unique_ptr& currentAction = storage.mCurrentAction; @@ -330,7 +331,7 @@ namespace MWMechanics { storage.mUseCustomDestination = false; storage.stopAttack(); - actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); + stats.setAttackingOrSpell(false); currentAction = std::make_unique(); actionCooldown = currentAction->getActionCooldown(); storage.startFleeing(); From 362c1a7ebe63792e2799e042414ab8441174d061 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 23 Jul 2025 17:35:35 +0200 Subject: [PATCH 05/22] Update sol --- extern/sol3/README.md | 7 +- extern/sol3/sol/abort.hpp | 47 + extern/sol3/sol/as_args.hpp | 2 +- extern/sol3/sol/as_returns.hpp | 2 +- extern/sol3/sol/assert.hpp | 198 +- extern/sol3/sol/base_traits.hpp | 279 +-- extern/sol3/sol/bind_traits.hpp | 10 +- extern/sol3/sol/bytecode.hpp | 242 +-- extern/sol3/sol/call.hpp | 32 +- extern/sol3/sol/compatibility.hpp | 26 +- extern/sol3/sol/compatibility/compat-5.3.c.h | 1800 ++++++++--------- extern/sol3/sol/compatibility/compat-5.3.h | 854 ++++---- extern/sol3/sol/compatibility/compat-5.4.h | 54 +- extern/sol3/sol/compatibility/lua_version.hpp | 99 +- extern/sol3/sol/config.hpp | 50 + extern/sol3/sol/coroutine.hpp | 12 +- extern/sol3/sol/debug.hpp | 115 +- extern/sol3/sol/demangle.hpp | 8 +- extern/sol3/sol/deprecate.hpp | 88 +- extern/sol3/sol/detail/build_version.hpp | 464 ++--- extern/sol3/sol/dump_handler.hpp | 154 +- extern/sol3/sol/ebco.hpp | 321 +-- extern/sol3/sol/environment.hpp | 23 +- extern/sol3/sol/epilogue.hpp | 78 +- extern/sol3/sol/error.hpp | 191 +- extern/sol3/sol/error_handler.hpp | 27 +- extern/sol3/sol/forward.hpp | 14 +- extern/sol3/sol/forward_as.hpp | 44 + extern/sol3/sol/forward_detail.hpp | 4 +- extern/sol3/sol/function.hpp | 284 +-- extern/sol3/sol/function_result.hpp | 2 +- extern/sol3/sol/function_types.hpp | 14 +- extern/sol3/sol/function_types_core.hpp | 2 +- extern/sol3/sol/function_types_overloaded.hpp | 4 +- extern/sol3/sol/function_types_stateful.hpp | 6 +- extern/sol3/sol/function_types_stateless.hpp | 44 +- extern/sol3/sol/function_types_templated.hpp | 4 +- extern/sol3/sol/in_place.hpp | 2 +- extern/sol3/sol/inheritance.hpp | 390 ++-- extern/sol3/sol/load_result.hpp | 16 +- extern/sol3/sol/lua_table.hpp | 190 +- extern/sol3/sol/lua_value.hpp | 324 +-- extern/sol3/sol/make_reference.hpp | 2 +- extern/sol3/sol/metatable.hpp | 10 +- extern/sol3/sol/object.hpp | 2 +- extern/sol3/sol/object_base.hpp | 2 +- extern/sol3/sol/optional.hpp | 12 +- extern/sol3/sol/optional_implementation.hpp | 21 +- extern/sol3/sol/overload.hpp | 4 +- extern/sol3/sol/packaged_coroutine.hpp | 524 ++--- extern/sol3/sol/pairs_iterator.hpp | 550 ++--- extern/sol3/sol/pointer_like.hpp | 205 +- extern/sol3/sol/policies.hpp | 2 +- extern/sol3/sol/prologue.hpp | 94 +- extern/sol3/sol/property.hpp | 2 +- extern/sol3/sol/protect.hpp | 2 +- extern/sol3/sol/protected_function.hpp | 20 +- extern/sol3/sol/protected_function_result.hpp | 27 +- extern/sol3/sol/protected_handler.hpp | 4 +- extern/sol3/sol/proxy_base.hpp | 2 +- extern/sol3/sol/raii.hpp | 2 +- extern/sol3/sol/reference.hpp | 26 +- extern/sol3/sol/resolve.hpp | 2 +- extern/sol3/sol/sol.hpp | 18 +- extern/sol3/sol/stack.hpp | 735 +++---- extern/sol3/sol/stack/detail/pairs.hpp | 196 +- extern/sol3/sol/stack_check.hpp | 2 +- extern/sol3/sol/stack_check_get.hpp | 2 +- extern/sol3/sol/stack_check_get_qualified.hpp | 21 +- .../sol3/sol/stack_check_get_unqualified.hpp | 8 +- extern/sol3/sol/stack_check_qualified.hpp | 4 +- extern/sol3/sol/stack_check_unqualified.hpp | 135 +- extern/sol3/sol/stack_core.hpp | 51 +- extern/sol3/sol/stack_field.hpp | 14 +- extern/sol3/sol/stack_get.hpp | 2 +- extern/sol3/sol/stack_get_qualified.hpp | 2 +- extern/sol3/sol/stack_get_unqualified.hpp | 99 +- extern/sol3/sol/stack_guard.hpp | 4 +- extern/sol3/sol/stack_iterator.hpp | 2 +- extern/sol3/sol/stack_pop.hpp | 2 +- extern/sol3/sol/stack_probe.hpp | 4 +- extern/sol3/sol/stack_proxy.hpp | 2 +- extern/sol3/sol/stack_proxy_base.hpp | 2 +- extern/sol3/sol/stack_push.hpp | 107 +- extern/sol3/sol/stack_reference.hpp | 6 +- extern/sol3/sol/state.hpp | 2 +- extern/sol3/sol/state_handling.hpp | 18 +- extern/sol3/sol/state_view.hpp | 18 +- extern/sol3/sol/string_view.hpp | 2 +- extern/sol3/sol/table.hpp | 232 +-- extern/sol3/sol/table_core.hpp | 12 +- extern/sol3/sol/table_iterator.hpp | 2 +- extern/sol3/sol/table_proxy.hpp | 22 +- extern/sol3/sol/thread.hpp | 12 +- extern/sol3/sol/tie.hpp | 2 +- extern/sol3/sol/traits.hpp | 39 +- extern/sol3/sol/trampoline.hpp | 25 +- extern/sol3/sol/tuple.hpp | 186 +- extern/sol3/sol/types.hpp | 97 +- extern/sol3/sol/unicode.hpp | 616 +++--- extern/sol3/sol/unique_usertype_traits.hpp | 480 ++--- extern/sol3/sol/unreachable.hpp | 37 + extern/sol3/sol/unsafe_function.hpp | 10 +- extern/sol3/sol/unsafe_function_result.hpp | 8 +- extern/sol3/sol/userdata.hpp | 18 +- extern/sol3/sol/usertype.hpp | 2 +- extern/sol3/sol/usertype_container.hpp | 90 +- extern/sol3/sol/usertype_container_launch.hpp | 2 +- extern/sol3/sol/usertype_core.hpp | 4 +- extern/sol3/sol/usertype_proxy.hpp | 2 +- extern/sol3/sol/usertype_storage.hpp | 14 +- extern/sol3/sol/usertype_traits.hpp | 122 +- extern/sol3/sol/utility/is_integer.hpp | 43 + extern/sol3/sol/utility/to_string.hpp | 59 + extern/sol3/sol/variadic_args.hpp | 2 +- extern/sol3/sol/variadic_results.hpp | 4 +- extern/sol3/sol/version.hpp | 202 +- extern/sol3/sol/wrapper.hpp | 4 +- 118 files changed, 6291 insertions(+), 5557 deletions(-) create mode 100644 extern/sol3/sol/abort.hpp create mode 100644 extern/sol3/sol/config.hpp create mode 100644 extern/sol3/sol/forward_as.hpp create mode 100644 extern/sol3/sol/unreachable.hpp create mode 100644 extern/sol3/sol/utility/is_integer.hpp create mode 100644 extern/sol3/sol/utility/to_string.hpp diff --git a/extern/sol3/README.md b/extern/sol3/README.md index 202b2ca08b..fe249704c4 100644 --- a/extern/sol3/README.md +++ b/extern/sol3/README.md @@ -1,5 +1,8 @@ -The code in this directory is copied from https://github.com/ThePhD/sol2.git (64096348465b980e2f1d0e5ba9cbeea8782e8f27) +The code in this directory is copied from https://github.com/ThePhD/sol2.git (c1f95a773c6f8f4fde8ca3efe872e7286afe4444) and has been patched to include -Additional changes include cherry-picking upstream commit d805d027e0a0a7222e936926139f06e23828ce9f to fix compilation under Clang 19. +https://github.com/ThePhD/sol2/pull/1674 (71d85143ad69164f5f52c3bdab91fb503c676eb4) +https://github.com/ThePhD/sol2/pull/1676 (a6872ef46b08704b9069ebf83161f4637459ce63) +https://github.com/ThePhD/sol2/pull/1716 (5b6881ed94c795298eae72b6848308e9a37e42c5) +https://github.com/ThePhD/sol2/pull/1722 (ab874eb0e8ef8aea4c10074a89efa25f62a29d9a) License: MIT diff --git a/extern/sol3/sol/abort.hpp b/extern/sol3/sol/abort.hpp new file mode 100644 index 0000000000..692244daa7 --- /dev/null +++ b/extern/sol3/sol/abort.hpp @@ -0,0 +1,47 @@ +// sol2 + +// The MIT License (MIT) + +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SOL_ABORT_HPP +#define SOL_ABORT_HPP + +#include + +#include + +#include + +// clang-format off +#if SOL_IS_ON(SOL_DEBUG_BUILD) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #define SOL_DEBUG_ABORT() \ + if (true) { ::std::abort(); } \ + static_assert(true, "") + #else + #define SOL_DEBUG_ABORT() ::std::abort() + #endif +#else + #define SOL_DEBUG_ABORT() static_assert(true, "") +#endif +// clang-format on + +#endif // SOL_ABORT_HPP diff --git a/extern/sol3/sol/as_args.hpp b/extern/sol3/sol/as_args.hpp index 5afe78b0e4..719a3cdc99 100644 --- a/extern/sol3/sol/as_args.hpp +++ b/extern/sol3/sol/as_args.hpp @@ -2,7 +2,7 @@ // The MIT License (MIT) -// Copyright (c) 2013-2021 Rapptz, ThePhD and contributors +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in diff --git a/extern/sol3/sol/as_returns.hpp b/extern/sol3/sol/as_returns.hpp index 0ac499e67d..982f408b71 100644 --- a/extern/sol3/sol/as_returns.hpp +++ b/extern/sol3/sol/as_returns.hpp @@ -2,7 +2,7 @@ // The MIT License (MIT) -// Copyright (c) 2013-2021 Rapptz, ThePhD and contributors +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in diff --git a/extern/sol3/sol/assert.hpp b/extern/sol3/sol/assert.hpp index e46b9f122a..7f27905dc4 100644 --- a/extern/sol3/sol/assert.hpp +++ b/extern/sol3/sol/assert.hpp @@ -1,99 +1,99 @@ -// sol2 - -// The MIT License (MIT) - -// Copyright (c) 2013-2021 Rapptz, ThePhD and contributors - -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#pragma once - -#ifndef SOL_ASSERT_HPP -#define SOL_ASSERT_HPP - -#include - -#if SOL_IS_ON(SOL2_CI_I_) - -struct pre_main { - pre_main() { -#ifdef _MSC_VER - _set_abort_behavior(0, _WRITE_ABORT_MSG); -#endif - } -} inline sol2_ci_dont_lock_ci_please = {}; - -#endif // Prevent lockup when doing Continuous Integration - - -// clang-format off - -#if SOL_IS_ON(SOL_USER_C_ASSERT_I_) - #define sol_c_assert(...) SOL_C_ASSERT(__VA_ARGS__) -#else - #if SOL_IS_ON(SOL_DEBUG_BUILD_I_) - #include - #include - #include - - #define sol_c_assert(...) \ - do { \ - if (!(__VA_ARGS__)) { \ - std::cerr << "Assertion `" #__VA_ARGS__ "` failed in " << __FILE__ << " line " << __LINE__ << std::endl; \ - std::terminate(); \ - } \ - } while (false) - #else - #define sol_c_assert(...) \ - do { \ - if (false) { \ - (void)(__VA_ARGS__); \ - } \ - } while (false) - #endif -#endif - -#if SOL_IS_ON(SOL_USER_M_ASSERT_I_) - #define sol_m_assert(message, ...) SOL_M_ASSERT(message, __VA_ARGS__) -#else - #if SOL_IS_ON(SOL_DEBUG_BUILD_I_) - #include - #include - #include - - #define sol_m_assert(message, ...) \ - do { \ - if (!(__VA_ARGS__)) { \ - std::cerr << "Assertion `" #__VA_ARGS__ "` failed in " << __FILE__ << " line " << __LINE__ << ": " << message << std::endl; \ - std::terminate(); \ - } \ - } while (false) - #else - #define sol_m_assert(message, ...) \ - do { \ - if (false) { \ - (void)(__VA_ARGS__); \ - (void)sizeof(message); \ - } \ - } while (false) - #endif -#endif - -// clang-format on - -#endif // SOL_ASSERT_HPP +// sol2 + +// The MIT License (MIT) + +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#pragma once + +#ifndef SOL_ASSERT_HPP +#define SOL_ASSERT_HPP + +#include + +#if SOL_IS_ON(SOL2_CI) + +struct pre_main { + pre_main() { +#ifdef _MSC_VER + _set_abort_behavior(0, _WRITE_ABORT_MSG); +#endif + } +} inline sol2_ci_dont_lock_ci_please = {}; + +#endif // Prevent lockup when doing Continuous Integration + + +// clang-format off + +#if SOL_IS_ON(SOL_USER_ASSERT) + #define SOL_ASSERT(...) SOL_C_ASSERT(__VA_ARGS__) +#else + #if SOL_IS_ON(SOL_DEBUG_BUILD) + #include + #include + #include + + #define SOL_ASSERT(...) \ + do { \ + if (!(__VA_ARGS__)) { \ + std::cerr << "Assertion `" #__VA_ARGS__ "` failed in " << __FILE__ << " line " << __LINE__ << std::endl; \ + std::terminate(); \ + } \ + } while (false) + #else + #define SOL_ASSERT(...) \ + do { \ + if (false) { \ + (void)(__VA_ARGS__); \ + } \ + } while (false) + #endif +#endif + +#if SOL_IS_ON(SOL_USER_ASSERT_MSG) + #define SOL_ASSERT_MSG(message, ...) SOL_ASSERT_MSG(message, __VA_ARGS__) +#else + #if SOL_IS_ON(SOL_DEBUG_BUILD) + #include + #include + #include + + #define SOL_ASSERT_MSG(message, ...) \ + do { \ + if (!(__VA_ARGS__)) { \ + std::cerr << "Assertion `" #__VA_ARGS__ "` failed in " << __FILE__ << " line " << __LINE__ << ": " << message << std::endl; \ + std::terminate(); \ + } \ + } while (false) + #else + #define SOL_ASSERT_MSG(message, ...) \ + do { \ + if (false) { \ + (void)(__VA_ARGS__); \ + (void)sizeof(message); \ + } \ + } while (false) + #endif +#endif + +// clang-format on + +#endif // SOL_ASSERT_HPP diff --git a/extern/sol3/sol/base_traits.hpp b/extern/sol3/sol/base_traits.hpp index a28f23a74c..204afc276f 100644 --- a/extern/sol3/sol/base_traits.hpp +++ b/extern/sol3/sol/base_traits.hpp @@ -1,123 +1,156 @@ -// sol2 - -// The MIT License (MIT) - -// Copyright (c) 2013-2021 Rapptz, ThePhD and contributors - -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef SOL_BASE_TRAITS_HPP -#define SOL_BASE_TRAITS_HPP - -#include - -namespace sol { - namespace detail { - struct unchecked_t { }; - const unchecked_t unchecked = unchecked_t {}; - } // namespace detail - - namespace meta { - using sfinae_yes_t = std::true_type; - using sfinae_no_t = std::false_type; - - template - using void_t = void; - - template - using unqualified = std::remove_cv>; - - template - using unqualified_t = typename unqualified::type; - - namespace meta_detail { - template - struct unqualified_non_alias : unqualified { }; - - template