#ifndef BLANK_AI_AICONTROLLER_HPP_
#define BLANK_AI_AICONTROLLER_HPP_
+#include "../app/IntervalTimer.hpp"
#include "../world/EntityController.hpp"
#include <glm/glm.hpp>
static glm::vec3 Heading(const EntityState &) noexcept;
+ /// schedule a decision in the next minimum ± variance seconds
+ void CueDecision(
+ float minimum,
+ float variance
+ ) noexcept;
+ /// check if the scheduled decision is due already
+ bool DecisionDue() const noexcept;
+ /// random choice of 0 to num_choices - 1
+ unsigned int Decide(unsigned int num_choices) noexcept;
+
+ void EnterHalt(float speed) noexcept;
+ void ExitHalt() noexcept;
+ bool IsHalted() const noexcept;
+
void StartFleeing(const Entity &, float speed) noexcept;
void StopFleeing() noexcept;
bool IsFleeing() const noexcept;
float displacement = 1.0f
) noexcept;
void StopWandering() noexcept;
+ bool IsWandering() const noexcept;
private:
GaloisLFSR &random;
const AIState *state;
+ FineTimer decision_timer;
+
+ bool halted;
+ float halt_speed;
+
const Entity *flee_target;
float flee_speed;
#include "AIController.hpp"
#include "IdleState.hpp"
+#include "RoamState.hpp"
#include "../model/geometry.hpp"
#include "../rand/GaloisLFSR.hpp"
namespace {
IdleState idle;
+RoamState roam;
}
AIController::AIController(GaloisLFSR &rand)
: random(rand)
, state(&idle)
+, decision_timer(1.0f)
+, halted(false)
+, halt_speed(1.0f)
, flee_target(nullptr)
, flee_speed(5.0f)
, seek_target(nullptr)
}
void AIController::Update(Entity &e, float dt) {
- // movement: for now, wander only
+ decision_timer.Update(dt);
+ state->Update(*this, e, dt);
+
if (wandering) {
glm::vec3 displacement(
random.SNorm() * wander_disp,
}
glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
+ if (IsHalted()) {
+ return Halt(state, halt_speed);
+ }
glm::vec3 force(0.0f);
if (IsFleeing()) {
glm::vec3 diff(GetFleeTarget().GetState().Diff(state));
return force;
}
}
- if (wandering) {
+ if (IsWandering()) {
glm::vec3 wander_target(normalize(Heading(state) * wander_dist + wander_pos) * wander_speed);
if (MaxOutForce(force, TargetVelocity(wander_target, state, 2.0f), entity.MaxControlForce())) {
return force;
}
}
+void AIController::CueDecision(
+ float minimum,
+ float variance
+) noexcept {
+ decision_timer = FineTimer(minimum + variance * random.SNorm());
+ decision_timer.Start();
+}
+
+bool AIController::DecisionDue() const noexcept {
+ return decision_timer.HitOnce();
+}
+
+unsigned int AIController::Decide(unsigned int num_choices) noexcept {
+ return random.Next<unsigned int>() % num_choices;
+}
+
+void AIController::EnterHalt(float speed) noexcept {
+ halted = true;
+ halt_speed = speed;
+}
+
+void AIController::ExitHalt() noexcept {
+ halted = false;
+}
+
+bool AIController::IsHalted() const noexcept {
+ return halted;
+}
+
void AIController::StartFleeing(const Entity &e, float speed) noexcept {
flee_target = &e;
flee_speed = speed;
wander_radius = radius;
wander_disp = displacement;
}
+
void AIController::StopWandering() noexcept {
wandering = false;
}
+bool AIController::IsWandering() const noexcept {
+ return wandering;
+}
+
void IdleState::Enter(AIController &ctrl) const {
- ctrl.StartWandering(1.0f);
+ ctrl.EnterHalt(2.0f);
+ ctrl.StartWandering(0.001f, 1.1f);
+ ctrl.CueDecision(10.0f, 5.0f);
}
void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
+ // TODO: check for players
+ if (!ctrl.DecisionDue()) return;
+
+ unsigned int d = ctrl.Decide(10);
+ if (d == 0) {
+ // .1 chance to start going
+ ctrl.SetState(roam);
+ } else if (d < 3) {
+ // .2 chance of looking around
+ ctrl.ExitHalt();
+ } else {
+ ctrl.EnterHalt(2.0f);
+ }
+ ctrl.CueDecision(10.0f, 5.0f);
}
void IdleState::Exit(AIController &ctrl) const {
+ ctrl.ExitHalt();
+ ctrl.StopWandering();
+}
+
+void RoamState::Enter(AIController &ctrl) const {
+ ctrl.StartWandering(1.0f);
+ ctrl.CueDecision(10.0f, 5.0f);
+}
+
+void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
+ // TODO: check for players
+ if (!ctrl.DecisionDue()) return;
+
+ unsigned int d = ctrl.Decide(10);
+ if (d == 0) {
+ // .1 chance of idling
+ ctrl.SetState(idle);
+ }
+ ctrl.CueDecision(10.0f, 5.0f);
+}
+
+void RoamState::Exit(AIController &ctrl) const {
ctrl.StopWandering();
}