diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 58a908672..51fcb92c1 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -146,19 +146,10 @@ namespace MWMechanics } storage.mActionCooldown -= duration; - float& timerReact = storage.mTimerReact; - if (timerReact < AI_REACTION_TIME) - { - timerReact += duration; - } - else - { - timerReact = 0; - if (attack(actor, target, storage, characterController)) - return true; - } + if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) + return false; - return false; + return attack(actor, target, storage, characterController); } bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 3a77aa8e8..0f42c6e2d 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -10,6 +10,7 @@ #include "pathfinding.hpp" #include "movement.hpp" #include "obstacle.hpp" +#include "aitimer.hpp" namespace ESM { @@ -27,7 +28,7 @@ namespace MWMechanics struct AiCombatStorage : AiTemporaryBase { float mAttackCooldown; - float mTimerReact; + AiReactionTimer mReaction; float mTimerCombatMove; bool mReadyToAttack; bool mAttack; @@ -60,7 +61,6 @@ namespace MWMechanics AiCombatStorage(): mAttackCooldown(0.0f), - mTimerReact(AI_REACTION_TIME), mTimerCombatMove(0.0f), mReadyToAttack(false), mAttack(false), diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 99132b711..00e1c9c3a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -28,7 +28,6 @@ MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : mTypeId(typeId), mOptions(options), - mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mTargetActorRefId(""), mTargetActorId(-1), mRotateOnTheRunChecks(0), @@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const void MWMechanics::AiPackage::reset() { // reset all members - mTimer = AI_REACTION_TIME + 1.0f; + mReaction.reset(); mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); @@ -75,7 +74,7 @@ void MWMechanics::AiPackage::reset() bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) { - mTimer += duration; //Update timer + const Misc::TimerStatus timerStatus = mReaction.update(duration); const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -98,7 +97,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool isDestReached = (distToTarget <= destTolerance); const bool actorCanMoveByZ = canActorMoveByZAxis(actor); - if (!isDestReached && mTimer > AI_REACTION_TIME) + if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed) { if (actor.getClass().isBipedal(actor)) openDoors(actor); @@ -142,8 +141,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } } - - mTimer = 0; } const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 4201de5c8..81b09c8b9 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -10,6 +10,7 @@ #include "obstacle.hpp" #include "aistate.hpp" #include "aipackagetypeid.hpp" +#include "aitimer.hpp" namespace MWWorld { @@ -28,8 +29,6 @@ namespace ESM namespace MWMechanics { - const float AI_REACTION_TIME = 0.25f; - class CharacterController; class PathgridGraph; @@ -158,7 +157,7 @@ namespace MWMechanics PathFinder mPathFinder; ObstacleCheck mObstacleCheck; - float mTimer; + AiReactionTimer mReaction; std::string mTargetActorRefId; mutable int mTargetActorId; diff --git a/apps/openmw/mwmechanics/aitimer.hpp b/apps/openmw/mwmechanics/aitimer.hpp new file mode 100644 index 000000000..804cda1bd --- /dev/null +++ b/apps/openmw/mwmechanics/aitimer.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_MECHANICS_AITIMER_H +#define OPENMW_MECHANICS_AITIMER_H + +#include +#include + +namespace MWMechanics +{ + constexpr float AI_REACTION_TIME = 0.25f; + + class AiReactionTimer + { + public: + static constexpr float sDeviation = 0.1f; + + Misc::TimerStatus update(float duration) { return mImpl.update(duration); } + + void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); } + + private: + Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation, + Misc::Rng::deviate(0, sDeviation)}; + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 375209a25..72b8757bf 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -223,15 +223,10 @@ namespace MWMechanics doPerFrameActionsForState(actor, duration, storage); - float& lastReaction = storage.mReaction; - lastReaction += duration; - if (AI_REACTION_TIME <= lastReaction) - { - lastReaction = 0; - return reactionTimeActions(actor, storage, pos); - } - else + if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) return false; + + return reactionTimeActions(actor, storage, pos); } bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 8e718061e..52a926d14 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -10,6 +10,7 @@ #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" +#include "aitimer.hpp" namespace ESM { @@ -25,7 +26,7 @@ namespace MWMechanics /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. struct AiWanderStorage : AiTemporaryBase { - float mReaction; // update some actions infrequently + AiReactionTimer mReaction; // AiWander states enum WanderState @@ -57,7 +58,6 @@ namespace MWMechanics int mStuckCount; AiWanderStorage(): - mReaction(0), mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index 23d820448..4805f0d91 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -47,4 +47,9 @@ namespace Misc { return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); } + + float Rng::deviate(float mean, float deviation, Seed& seed) + { + return std::uniform_real_distribution(mean - deviation, mean + deviation)(seed.mGenerator); + } } diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 8efca438d..998ac0d53 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -42,6 +42,8 @@ public: /// returns default seed for RNG static unsigned int generateDefaultSeed(); + + static float deviate(float mean, float deviation, Seed& seed = getSeed()); }; } diff --git a/components/misc/timer.hpp b/components/misc/timer.hpp new file mode 100644 index 000000000..81a2ca073 --- /dev/null +++ b/components/misc/timer.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_MISC_TIMER_H +#define OPENMW_COMPONENTS_MISC_TIMER_H + +#include "rng.hpp" + +namespace Misc +{ + enum class TimerStatus + { + Waiting, + Elapsed, + }; + + class DeviatingPeriodicTimer + { + public: + explicit DeviatingPeriodicTimer(float period, float deviation, float timeLeft) + : mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft) + {} + + TimerStatus update(float duration) + { + if (mTimeLeft > 0) + { + mTimeLeft -= duration; + return TimerStatus::Waiting; + } + + mTimeLeft = Rng::deviate(mPeriod, mDeviation); + return TimerStatus::Elapsed; + } + + void reset(float timeLeft) { mTimeLeft = timeLeft; } + + private: + const float mPeriod; + const float mDeviation; + float mTimeLeft; + }; +} + +#endif