1 #include "AIController.hpp"
2 #include "ChaseState.hpp"
3 #include "FleeState.hpp"
4 #include "IdleState.hpp"
5 #include "RoamState.hpp"
7 #include "../geometry/distance.hpp"
8 #include "../geometry/rotation.hpp"
9 #include "../rand/GaloisLFSR.hpp"
10 #include "../world/Entity.hpp"
11 #include "../world/World.hpp"
12 #include "../world/WorldCollision.hpp"
16 #include <glm/glm.hpp>
30 AIController::AIController(World &world, Entity &entity)
36 , decision_timer(1.0f) {
38 state->Enter(*this, entity);
41 AIController::~AIController() {
42 // ignore this for now
43 // state->Exit(*this, entity);
46 void AIController::SetState(const AIState &s, Entity &entity) {
47 state->Exit(*this, entity);
49 state->Enter(*this, entity);
52 void AIController::Update(Entity &e, float dt) {
53 think_timer.Update(dt);
54 decision_timer.Update(dt);
55 state->Update(*this, e, dt);
58 // orient head towards heading
59 glm::vec3 heading(e.Heading());
60 // only half pitch, so we don't crane our neck
61 float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z))) * 0.5f;
62 // always look straight ahead
63 // maybe look at the pursuit target if there is one
65 e.SetHead(tgt_pitch, tgt_yaw);
70 Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
71 Player *target = nullptr;
72 float distance = sight_dist;
73 const glm::ivec3 &reference(e.ChunkCoords());
74 Ray aim(e.Aim(reference));
75 for (Player &p : world.Players()) {
76 const Entity &pe = p.GetEntity();
79 const glm::vec3 diff(pe.AbsoluteDifference(e));
80 float dist = length(diff);
81 if (dist > distance) continue;
83 // FOV test, 45° in each direction
84 if (dot(diff / dist, aim.dir) < sight_angle) {
88 // LOS test, assumes all entities are see-through
90 if (world.Intersection(aim, reference, col) && col.depth < dist) {
101 bool AIController::LineOfSight(const Entity &from, const Entity &to) const noexcept {
102 const glm::ivec3 &reference(from.ChunkCoords());
103 Ray aim(from.Aim(reference));
104 const glm::vec3 diff(to.AbsoluteDifference(from));
105 float dist = length(diff);
106 if (dist > sight_dist || dot(diff / dist, aim.dir) < sight_angle) {
110 if (world.Intersection(aim, reference, col) && col.depth < dist) {
118 bool AIController::MayThink() const noexcept {
119 return think_timer.Hit();
122 void AIController::SetThinkInterval(float i) noexcept {
123 think_timer = FineTimer(i);
129 void AIController::CueDecision(
133 decision_timer = FineTimer(minimum + variance * world.Random().SNorm());
134 decision_timer.Start();
137 bool AIController::DecisionDue() const noexcept {
138 return decision_timer.HitOnce();
141 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
142 return world.Random().Next<unsigned int>() % num_choices;
148 void ChaseState::Enter(AIController &ctrl, Entity &e) const {
150 .SetAcceleration(1.0f)
152 .Enable(Steering::PURSUE_TARGET)
156 void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
157 Steering &steering = e.GetSteering();
158 // check if target still alive and in sight
160 !steering.HasTargetEntity() || // lost
161 steering.GetTargetEntity().Dead() || // dead
162 !ctrl.LineOfSight(e, steering.GetTargetEntity()) // escaped
164 steering.ClearTargetEntity();
165 ctrl.SetState(idle, e);
168 // halt if we're close enough, flee if we're too close
169 float dist_sq = length2(e.AbsoluteDifference(steering.GetTargetEntity()));
170 if (dist_sq < 8.0f) {
171 ctrl.SetState(flee, e);
172 } else if (dist_sq < 25.0f) {
173 steering.Enable(Steering::HALT).Disable(Steering::PURSUE_TARGET);
175 steering.Enable(Steering::PURSUE_TARGET).Disable(Steering::HALT);
179 void ChaseState::Exit(AIController &ctrl, Entity &e) const {
180 e.GetSteering().Disable(Steering::HALT | Steering::PURSUE_TARGET);
185 void FleeState::Enter(AIController &ctrl, Entity &e) const {
187 .SetAcceleration(1.0f)
189 .Enable(Steering::EVADE_TARGET)
191 ctrl.CueDecision(6.0f, 3.0f);
194 void FleeState::Update(AIController &ctrl, Entity &e, float dt) const {
195 if (!ctrl.DecisionDue()) return;
196 ctrl.SetState(idle, e);
199 void FleeState::Exit(AIController &ctrl, Entity &e) const {
200 e.GetSteering().Disable(Steering::EVADE_TARGET);
205 void IdleState::Enter(AIController &ctrl, Entity &e) const {
207 .SetAcceleration(0.5f)
209 .Enable(Steering::HALT)
210 .SetWanderParams(1.0f, 1.1f, 1.0f)
212 ctrl.CueDecision(10.0f, 5.0f);
215 void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
216 if (ctrl.MayThink()) {
217 const Player *player = ctrl.ClosestVisiblePlayer(e);
219 e.GetSteering().SetTargetEntity(player->GetEntity());
220 ctrl.SetState(chase, e);
225 if (!ctrl.DecisionDue()) return;
227 unsigned int d = ctrl.Decide(10);
229 // .2 chance to start going
230 ctrl.SetState(roam, e);
232 // .3 chance of looking around
233 e.GetSteering().Disable(Steering::HALT).Enable(Steering::WANDER);
235 // .5 chance of doing nothing
236 e.GetSteering().Disable(Steering::WANDER).Enable(Steering::HALT);
238 ctrl.CueDecision(10.0f, 5.0f);
241 void IdleState::Exit(AIController &ctrl, Entity &e) const {
242 e.GetSteering().Disable(Steering::HALT | Steering::WANDER);
247 void RoamState::Enter(AIController &ctrl, Entity &e) const {
249 .SetAcceleration(0.5f)
251 .SetWanderParams(1.0f, 2.0f, 1.0f)
252 .Enable(Steering::WANDER)
254 ctrl.CueDecision(10.0f, 5.0f);
257 void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
258 if (ctrl.MayThink()) {
259 const Player *player = ctrl.ClosestVisiblePlayer(e);
261 e.GetSteering().SetTargetEntity(player->GetEntity());
262 ctrl.SetState(chase, e);
267 if (!ctrl.DecisionDue()) return;
269 unsigned int d = ctrl.Decide(10);
271 // .1 chance of idling
272 ctrl.SetState(idle, e);
274 ctrl.CueDecision(10.0f, 5.0f);
277 void RoamState::Exit(AIController &ctrl, Entity &e) const {
278 e.GetSteering().Disable(Steering::WANDER);