mirror of
https://github.com/TES3MP/TES3MP.git
synced 2025-09-26 22:45:15 -04:00
Apply uniform random deviation to AI reaction timer
This allows to distribute AI reaction calls over time. Before this change actors appearing at the same frame will react in the same frame over and over because AI reaction period is constant. It creates a non-uniform CPU usage over frames. If a single frame has too many AI reactions it may cause stuttering when there are too many actors on a scene for current system. Another concern is a synchronization of actions between creatures and NPC. They start to go or hit at the same frame that is unnatural.
This commit is contained in:
parent
d13d90a50d
commit
675c0ab72f
@ -146,19 +146,10 @@ namespace MWMechanics
|
|||||||
}
|
}
|
||||||
storage.mActionCooldown -= duration;
|
storage.mActionCooldown -= duration;
|
||||||
|
|
||||||
float& timerReact = storage.mTimerReact;
|
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
|
||||||
if (timerReact < AI_REACTION_TIME)
|
return false;
|
||||||
{
|
|
||||||
timerReact += duration;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
timerReact = 0;
|
|
||||||
if (attack(actor, target, storage, characterController))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return attack(actor, target, storage, characterController);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController)
|
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController)
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
@ -27,7 +28,7 @@ namespace MWMechanics
|
|||||||
struct AiCombatStorage : AiTemporaryBase
|
struct AiCombatStorage : AiTemporaryBase
|
||||||
{
|
{
|
||||||
float mAttackCooldown;
|
float mAttackCooldown;
|
||||||
float mTimerReact;
|
AiReactionTimer mReaction;
|
||||||
float mTimerCombatMove;
|
float mTimerCombatMove;
|
||||||
bool mReadyToAttack;
|
bool mReadyToAttack;
|
||||||
bool mAttack;
|
bool mAttack;
|
||||||
@ -60,7 +61,6 @@ namespace MWMechanics
|
|||||||
|
|
||||||
AiCombatStorage():
|
AiCombatStorage():
|
||||||
mAttackCooldown(0.0f),
|
mAttackCooldown(0.0f),
|
||||||
mTimerReact(AI_REACTION_TIME),
|
|
||||||
mTimerCombatMove(0.0f),
|
mTimerCombatMove(0.0f),
|
||||||
mReadyToAttack(false),
|
mReadyToAttack(false),
|
||||||
mAttack(false),
|
mAttack(false),
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
|
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
|
||||||
mTypeId(typeId),
|
mTypeId(typeId),
|
||||||
mOptions(options),
|
mOptions(options),
|
||||||
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
|
||||||
mTargetActorRefId(""),
|
mTargetActorRefId(""),
|
||||||
mTargetActorId(-1),
|
mTargetActorId(-1),
|
||||||
mRotateOnTheRunChecks(0),
|
mRotateOnTheRunChecks(0),
|
||||||
@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
|
|||||||
void MWMechanics::AiPackage::reset()
|
void MWMechanics::AiPackage::reset()
|
||||||
{
|
{
|
||||||
// reset all members
|
// reset all members
|
||||||
mTimer = AI_REACTION_TIME + 1.0f;
|
mReaction.reset();
|
||||||
mIsShortcutting = false;
|
mIsShortcutting = false;
|
||||||
mShortcutProhibited = false;
|
mShortcutProhibited = false;
|
||||||
mShortcutFailPos = osg::Vec3f();
|
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)
|
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
|
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
|
||||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
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 isDestReached = (distToTarget <= destTolerance);
|
||||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||||
|
|
||||||
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
|
||||||
{
|
{
|
||||||
if (actor.getClass().isBipedal(actor))
|
if (actor.getClass().isBipedal(actor))
|
||||||
openDoors(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
|
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
|
const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
#include "aistate.hpp"
|
#include "aistate.hpp"
|
||||||
#include "aipackagetypeid.hpp"
|
#include "aipackagetypeid.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
@ -28,8 +29,6 @@ namespace ESM
|
|||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
const float AI_REACTION_TIME = 0.25f;
|
|
||||||
|
|
||||||
class CharacterController;
|
class CharacterController;
|
||||||
class PathgridGraph;
|
class PathgridGraph;
|
||||||
|
|
||||||
@ -158,7 +157,7 @@ namespace MWMechanics
|
|||||||
PathFinder mPathFinder;
|
PathFinder mPathFinder;
|
||||||
ObstacleCheck mObstacleCheck;
|
ObstacleCheck mObstacleCheck;
|
||||||
|
|
||||||
float mTimer;
|
AiReactionTimer mReaction;
|
||||||
|
|
||||||
std::string mTargetActorRefId;
|
std::string mTargetActorRefId;
|
||||||
mutable int mTargetActorId;
|
mutable int mTargetActorId;
|
||||||
|
26
apps/openmw/mwmechanics/aitimer.hpp
Normal file
26
apps/openmw/mwmechanics/aitimer.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef OPENMW_MECHANICS_AITIMER_H
|
||||||
|
#define OPENMW_MECHANICS_AITIMER_H
|
||||||
|
|
||||||
|
#include <components/misc/rng.hpp>
|
||||||
|
#include <components/misc/timer.hpp>
|
||||||
|
|
||||||
|
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
|
@ -223,15 +223,10 @@ namespace MWMechanics
|
|||||||
|
|
||||||
doPerFrameActionsForState(actor, duration, storage);
|
doPerFrameActionsForState(actor, duration, storage);
|
||||||
|
|
||||||
float& lastReaction = storage.mReaction;
|
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
|
||||||
lastReaction += duration;
|
|
||||||
if (AI_REACTION_TIME <= lastReaction)
|
|
||||||
{
|
|
||||||
lastReaction = 0;
|
|
||||||
return reactionTimeActions(actor, storage, pos);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
return reactionTimeActions(actor, storage, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
#include "aistate.hpp"
|
#include "aistate.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
@ -25,7 +26,7 @@ namespace MWMechanics
|
|||||||
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
||||||
struct AiWanderStorage : AiTemporaryBase
|
struct AiWanderStorage : AiTemporaryBase
|
||||||
{
|
{
|
||||||
float mReaction; // update some actions infrequently
|
AiReactionTimer mReaction;
|
||||||
|
|
||||||
// AiWander states
|
// AiWander states
|
||||||
enum WanderState
|
enum WanderState
|
||||||
@ -57,7 +58,6 @@ namespace MWMechanics
|
|||||||
int mStuckCount;
|
int mStuckCount;
|
||||||
|
|
||||||
AiWanderStorage():
|
AiWanderStorage():
|
||||||
mReaction(0),
|
|
||||||
mState(Wander_ChooseAction),
|
mState(Wander_ChooseAction),
|
||||||
mIsWanderingManually(false),
|
mIsWanderingManually(false),
|
||||||
mCanWanderAlongPathGrid(true),
|
mCanWanderAlongPathGrid(true),
|
||||||
|
@ -47,4 +47,9 @@ namespace Misc
|
|||||||
{
|
{
|
||||||
return static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
|
return static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Rng::deviate(float mean, float deviation, Seed& seed)
|
||||||
|
{
|
||||||
|
return std::uniform_real_distribution<float>(mean - deviation, mean + deviation)(seed.mGenerator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,8 @@ public:
|
|||||||
|
|
||||||
/// returns default seed for RNG
|
/// returns default seed for RNG
|
||||||
static unsigned int generateDefaultSeed();
|
static unsigned int generateDefaultSeed();
|
||||||
|
|
||||||
|
static float deviate(float mean, float deviation, Seed& seed = getSeed());
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
42
components/misc/timer.hpp
Normal file
42
components/misc/timer.hpp
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user