diff --git a/CHANGELOG.md b/CHANGELOG.md index 99c2b8972..195cd900e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Bug #4768: Fallback numerical value recovery chokes on invalid arguments Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken + Bug #4797: Player sneaking and running stances are not accounted for when in air Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change Bug #4803: Stray special characters before begin statement break script compilation Bug #4804: Particle system with the "Has Sizes = false" causes an exception diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4a3a51119..e40f97dec 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1543,72 +1543,7 @@ namespace MWMechanics } killDeadActors(); - - static float sneakTimer = 0.f; // times update of sneak icon - - // if player is in sneak state see if anyone detects him - if (playerCharacter && playerCharacter->isSneaking()) - { - static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" - - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - const int radius = esmStore.get().find("fSneakUseDist")->mValue.getInteger(); - - static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->mValue.getFloat(); - - if (sneakTimer >= fSneakUseDelay) - sneakTimer = 0.f; - - if (sneakTimer == 0.f) - { - // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. - bool avoidedNotice = false; - - bool detected = false; - - for (PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) - { - MWWorld::Ptr observer = iter->first; - - if (iter->first == player) // not the player - continue; - - if (observer.getClass().getCreatureStats(observer).isDead()) - continue; - - // is the player in range and can they be detected - if ((observer.getRefData().getPosition().asVec3() - playerPos).length2() <= radius*radius - && MWBase::Environment::get().getWorld()->getLOS(player, observer)) - { - if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) - { - detected = true; - avoidedNotice = false; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - break; - } - else - avoidedNotice = true; - } - } - - if (sneakSkillTimer >= fSneakUseDelay) - sneakSkillTimer = 0.f; - - if (avoidedNotice && sneakSkillTimer == 0.f) - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); - - if (!detected) - MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); - } - sneakTimer += duration; - sneakSkillTimer += duration; - } - else - { - sneakTimer = 0.f; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - } + updateSneaking(playerCharacter, duration); } updateCombatMusic(); @@ -1757,6 +1692,86 @@ namespace MWMechanics fastForwardAi(); } + void Actors::updateSneaking(CharacterController* ctrl, float duration) + { + static float sneakTimer = 0.f; // Times update of sneak icon + + if (!ctrl) + { + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + return; + } + + MWWorld::Ptr player = getPlayer(); + + CreatureStats& stats = player.getClass().getCreatureStats(player); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool inair = !world->isOnGround(player) && !world->isSwimming(player) && !world->isFlying(player); + sneaking = sneaking && (ctrl->isSneaking() || inair); + + if (!sneaking) + { + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + return; + } + + static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice" + + const MWWorld::Store& gmst = world->getStore().get(); + static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat(); + static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat(); + + if (sneakTimer >= fSneakUseDelay) + sneakTimer = 0.f; + + if (sneakTimer == 0.f) + { + // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. + bool avoidedNotice = false; + bool detected = false; + + std::vector observers; + osg::Vec3f position(player.getRefData().getPosition().asVec3()); + float radius = std::min(fSneakUseDist, mActorsProcessingRange); + getObjectsInRange(position, radius, observers); + + for (const MWWorld::Ptr &observer : observers) + { + if (observer == player || observer.getClass().getCreatureStats(observer).isDead()) + continue; + + if (world->getLOS(player, observer)) + { + if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) + { + detected = true; + avoidedNotice = false; + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + break; + } + else + { + avoidedNotice = true; + } + } + } + + if (sneakSkillTimer >= fSneakUseDelay) + sneakSkillTimer = 0.f; + + if (avoidedNotice && sneakSkillTimer == 0.f) + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + + if (!detected) + MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + } + + sneakTimer += duration; + sneakSkillTimer += duration; + } + int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const { float healthPerHour, magickaPerHour; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index c7d8c94f6..76ebd76ba 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -122,6 +122,9 @@ namespace MWMechanics void rest(bool sleep); ///< Update actors while the player is waiting or sleeping. This should be called every hour. + void updateSneaking(CharacterController* ctrl, float duration); + ///< Update the sneaking indicator state according to the given player character controller. + void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); int getHoursToRest(const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 21b3b5587..0d5b1bf3b 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -16,7 +16,6 @@ #include "../mwworld/ptr.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/movement.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -168,12 +167,15 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - const MWWorld::Class &cls = ptr.getClass(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); - bool isRunning = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Run); + bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - runtime.push (isRunning && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)); + runtime.push(stanceOn && (running || inair)); } }; @@ -184,11 +186,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWWorld::Class &cls = ptr.getClass(); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); - bool isSneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - runtime.push (isSneaking && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)); + runtime.push(stanceOn && (sneaking || inair)); } };