]> git.localhorst.tv Git - blank.git/blob - src/ai/ai.cpp
cache some basic entity axes
[blank.git] / src / ai / ai.cpp
1 #include "AIController.hpp"
2 #include "ChaseState.hpp"
3 #include "FleeState.hpp"
4 #include "IdleState.hpp"
5 #include "RoamState.hpp"
6
7 #include "../model/geometry.hpp"
8 #include "../rand/GaloisLFSR.hpp"
9 #include "../world/Entity.hpp"
10 #include "../world/World.hpp"
11 #include "../world/WorldCollision.hpp"
12
13 #include <cmath>
14 #include <limits>
15 #include <glm/glm.hpp>
16
17
18 namespace blank {
19
20 namespace {
21
22 ChaseState chase;
23 FleeState flee;
24 IdleState idle;
25 RoamState roam;
26
27 }
28
29 AIController::AIController(World &world, GaloisLFSR &rand)
30 : world(world)
31 , random(rand)
32 , state(&idle)
33 , sight_dist(64.0f)
34 , sight_angle(0.707f)
35 , think_timer(0.5f)
36 , decision_timer(1.0f)
37 , halted(false)
38 , halt_speed(1.0f)
39 , fleeing(false)
40 , flee_target(nullptr)
41 , flee_speed(5.0f)
42 , seeking(false)
43 , seek_target(nullptr)
44 , seek_speed(5.0f)
45 , evading(false)
46 , evade_target(nullptr)
47 , evade_speed(5.0f)
48 , pursuing(false)
49 , pursuit_target(nullptr)
50 , pursuit_speed(5.0f)
51 , wandering(false)
52 , wander_pos(1.0f, 0.0f, 0.0f)
53 , wander_speed(1.0f)
54 , wander_dist(2.0f)
55 , wander_radius(1.5f)
56 , wander_disp(1.0f) {
57         think_timer.Start();
58         state->Enter(*this);
59 }
60
61 AIController::~AIController() {
62         state->Exit(*this);
63 }
64
65 void AIController::SetState(const AIState &s) {
66         state->Exit(*this);
67         state = &s;
68         state->Enter(*this);
69 }
70
71 void AIController::Update(Entity &e, float dt) {
72         think_timer.Update(dt);
73         decision_timer.Update(dt);
74         state->Update(*this, e, dt);
75
76         if (wandering) {
77                 glm::vec3 displacement(
78                         random.SNorm() * wander_disp,
79                         random.SNorm() * wander_disp,
80                         random.SNorm() * wander_disp
81                 );
82                 if (!iszero(displacement)) {
83                         wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
84                 }
85         }
86
87         if (e.Moving()) {
88                 // orient head towards heading
89                 glm::vec3 heading(e.Heading());
90                 float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z)));
91                 float tgt_yaw = std::atan2(-heading.x, -heading.z);
92                 e.SetHead(tgt_pitch, tgt_yaw);
93         }
94 }
95
96 glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
97         if (IsHalted()) {
98                 return GetHaltForce(entity, state);
99         }
100         glm::vec3 force(0.0f);
101         if (IsFleeing()) {
102                 if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
103                         return force;
104                 }
105         }
106         if (IsSeeking()) {
107                 if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) {
108                         return force;
109                 }
110         }
111         if (IsEvading()) {
112                 if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) {
113                         return force;
114                 }
115         }
116         if (IsPursuing()) {
117                 if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) {
118                         return force;
119                 }
120         }
121         if (IsWandering()) {
122                 if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) {
123                         return force;
124                 }
125         }
126         return force;
127 }
128
129 Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
130         Player *target = nullptr;
131         float distance = sight_dist;
132         const glm::ivec3 &reference(e.ChunkCoords());
133         Ray aim(e.Aim(reference));
134         for (Player &p : world.Players()) {
135                 const Entity &pe = p.GetEntity();
136
137                 // distance test
138                 const glm::vec3 diff(pe.AbsoluteDifference(e));
139                 float dist = length(diff);
140                 if (dist > distance) continue;
141
142                 // FOV test, 45° in each direction
143                 if (dot(diff / dist, aim.dir) < sight_angle) {
144                         continue;
145                 }
146
147                 // LOS test, assumes all entities are see-through
148                 WorldCollision col;
149                 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
150                         continue;
151                 }
152
153                 // we got a match
154                 target = &p;
155                 distance = dist;
156         }
157         return target;
158 }
159
160 bool AIController::LineOfSight(const Entity &from, const Entity &to) const noexcept {
161         const glm::ivec3 &reference(from.ChunkCoords());
162         Ray aim(from.Aim(reference));
163         const glm::vec3 diff(to.AbsoluteDifference(from));
164         float dist = length(diff);
165         if (dist > sight_dist || dot(diff / dist, aim.dir) < sight_angle) {
166                 return false;
167         }
168         WorldCollision col;
169         if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
170                 return false;
171         }
172         return true;
173 }
174
175 // think
176
177 bool AIController::MayThink() const noexcept {
178         return think_timer.Hit();
179 }
180
181 void AIController::SetThinkInterval(float i) noexcept {
182         think_timer = FineTimer(i);
183         think_timer.Start();
184 }
185
186 // decide
187
188 void AIController::CueDecision(
189         float minimum,
190         float variance
191 ) noexcept {
192         decision_timer = FineTimer(minimum + variance * random.SNorm());
193         decision_timer.Start();
194 }
195
196 bool AIController::DecisionDue() const noexcept {
197         return decision_timer.HitOnce();
198 }
199
200 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
201         return random.Next<unsigned int>() % num_choices;
202 }
203
204 // halt
205
206 void AIController::EnterHalt() noexcept {
207         halted = true;
208 }
209
210 void AIController::ExitHalt() noexcept {
211         halted = false;
212 }
213
214 bool AIController::IsHalted() const noexcept {
215         return halted;
216 }
217
218 void AIController::SetHaltSpeed(float speed) noexcept {
219         halt_speed = speed;
220 }
221
222 glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept {
223         return Halt(state, halt_speed);
224 }
225
226 // flee
227
228 void AIController::StartFleeing() noexcept {
229         fleeing = true;
230 }
231
232 void AIController::StopFleeing() noexcept {
233         fleeing = false;
234         if (flee_target) {
235                 flee_target->UnRef();
236                 flee_target = nullptr;
237         }
238 }
239
240 bool AIController::IsFleeing() const noexcept {
241         return fleeing && flee_target;
242 }
243
244 void AIController::SetFleeTarget(Entity &e) noexcept {
245         if (flee_target) {
246                 flee_target->UnRef();
247         }
248         flee_target = &e;
249         flee_target->Ref();
250 }
251
252 void AIController::SetFleeSpeed(float speed) noexcept {
253         flee_speed = speed;
254 }
255
256 Entity &AIController::GetFleeTarget() noexcept {
257         return *flee_target;
258 }
259
260 const Entity &AIController::GetFleeTarget() const noexcept {
261         return *flee_target;
262 }
263
264 glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept {
265         return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f);
266 }
267
268 // seek
269
270 void AIController::StartSeeking() noexcept {
271         seeking = true;
272 }
273
274 void AIController::StopSeeking() noexcept {
275         seeking = false;
276         if (seek_target) {
277                 seek_target->UnRef();
278                 seek_target = nullptr;
279         }
280 }
281
282 bool AIController::IsSeeking() const noexcept {
283         return seeking && seek_target;
284 }
285
286 void AIController::SetSeekTarget(Entity &e) noexcept {
287         if (seek_target) {
288                 seek_target->UnRef();
289         }
290         seek_target = &e;
291         seek_target->Ref();
292 }
293
294 void AIController::SetSeekSpeed(float speed) noexcept {
295         seek_speed = speed;
296 }
297
298 Entity &AIController::GetSeekTarget() noexcept {
299         return *seek_target;
300 }
301
302 const Entity &AIController::GetSeekTarget() const noexcept {
303         return *seek_target;
304 }
305
306 glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept {
307         return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f);
308 }
309
310 // evade
311
312 void AIController::StartEvading() noexcept {
313         evading = true;
314 }
315
316 void AIController::StopEvading() noexcept {
317         evading = false;
318         if (evade_target) {
319                 evade_target->UnRef();
320                 evade_target = nullptr;
321         }
322 }
323
324 bool AIController::IsEvading() const noexcept {
325         return evading && evade_target;
326 }
327
328 void AIController::SetEvadeTarget(Entity &e) noexcept {
329         if (evade_target) {
330                 evade_target->UnRef();
331         }
332         evade_target = &e;
333         evade_target->Ref();
334 }
335
336 void AIController::SetEvadeSpeed(float speed) noexcept {
337         evade_speed = speed;
338 }
339
340 Entity &AIController::GetEvadeTarget() noexcept {
341         return *evade_target;
342 }
343
344 const Entity &AIController::GetEvadeTarget() const noexcept {
345         return *evade_target;
346 }
347
348 glm::vec3 AIController::GetEvadeForce(const Entity &, const EntityState &state) const noexcept{
349         glm::vec3 cur_diff(state.Diff(GetEvadeTarget().GetState()));
350         float time_estimate = length(cur_diff) / evade_speed;
351         EntityState pred_state(GetEvadeTarget().GetState());
352         pred_state.block_pos += pred_state.velocity * time_estimate;
353         return Flee(state, pred_state, evade_speed, 2.0f);
354 }
355
356 // pursuit
357
358 void AIController::StartPursuing() noexcept {
359         pursuing = true;
360 }
361
362 void AIController::StopPursuing() noexcept {
363         pursuing = false;
364         if (pursuit_target) {
365                 pursuit_target->UnRef();
366                 pursuit_target = nullptr;
367         }
368 }
369
370 bool AIController::IsPursuing() const noexcept {
371         return pursuing && pursuit_target;
372 }
373
374 void AIController::SetPursuitTarget(Entity &e) noexcept {
375         if (pursuit_target) {
376                 pursuit_target->UnRef();
377         }
378         pursuit_target = &e;
379         pursuit_target->Ref();
380 }
381
382 void AIController::SetPursuitSpeed(float speed) noexcept {
383         pursuit_speed = speed;
384 }
385
386 Entity &AIController::GetPursuitTarget() noexcept {
387         return *pursuit_target;
388 }
389
390 const Entity &AIController::GetPursuitTarget() const noexcept {
391         return *pursuit_target;
392 }
393
394 glm::vec3 AIController::GetPursuitForce(const Entity &, const EntityState &state) const noexcept {
395         glm::vec3 cur_diff(state.Diff(GetPursuitTarget().GetState()));
396         float time_estimate = length(cur_diff) / pursuit_speed;
397         EntityState pred_state(GetPursuitTarget().GetState());
398         pred_state.block_pos += pred_state.velocity * time_estimate;
399         return Seek(state, pred_state, pursuit_speed, 2.0f);
400 }
401
402 // wander
403
404 void AIController::StartWandering() noexcept {
405         wandering = true;
406 }
407
408 void AIController::StopWandering() noexcept {
409         wandering = false;
410 }
411
412 bool AIController::IsWandering() const noexcept {
413         return wandering;
414 }
415
416 void AIController::SetWanderParams(
417         float speed,
418         float distance,
419         float radius,
420         float displacement
421 ) noexcept {
422         wander_speed = speed;
423         wander_dist = distance;
424         wander_radius = radius;
425         wander_disp = displacement;
426 }
427
428 glm::vec3 AIController::GetWanderForce(const Entity &e, const EntityState &state) const noexcept {
429         glm::vec3 wander_target(normalize(e.Heading() * wander_dist + wander_pos) * wander_speed);
430         return TargetVelocity(wander_target, state, 0.5f);
431 }
432
433
434 // chase
435
436 void ChaseState::Enter(AIController &ctrl) const {
437         ctrl.SetHaltSpeed(2.0f);
438         ctrl.SetPursuitSpeed(4.0f);
439         ctrl.StartPursuing();
440 }
441
442 void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
443         // check if target still alive and in sight
444         if (ctrl.GetPursuitTarget().Dead()) {
445                 ctrl.SetState(idle);
446                 return;
447         }
448         if (!ctrl.LineOfSight(e, ctrl.GetPursuitTarget())) {
449                 ctrl.SetState(idle);
450                 return;
451         }
452         // halt if we're close enough, flee if we're too close
453         float dist_sq = length_squared(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
454         if (dist_sq < 8.0f) {
455                 ctrl.SetFleeTarget(ctrl.GetPursuitTarget());
456                 ctrl.SetState(flee);
457         } else if (dist_sq < 25.0f) {
458                 ctrl.EnterHalt();
459         } else {
460                 ctrl.ExitHalt();
461         }
462 }
463
464 void ChaseState::Exit(AIController &ctrl) const {
465         ctrl.StopPursuing();
466         ctrl.ExitHalt();
467 }
468
469 // flee
470
471 void FleeState::Enter(AIController &ctrl) const {
472         ctrl.CueDecision(6.0f, 3.0f);
473         ctrl.SetFleeSpeed(4.0f);
474         ctrl.StartFleeing();
475 }
476
477 void FleeState::Update(AIController &ctrl, Entity &e, float dt) const {
478         if (!ctrl.DecisionDue()) return;
479         ctrl.SetState(idle);
480 }
481
482 void FleeState::Exit(AIController &ctrl) const {
483         ctrl.StopFleeing();
484 }
485
486 // idle
487
488 void IdleState::Enter(AIController &ctrl) const {
489         ctrl.SetHaltSpeed(0.5f);
490         ctrl.EnterHalt();
491         ctrl.SetWanderParams(0.001f, 1.1f);
492         ctrl.CueDecision(10.0f, 5.0f);
493 }
494
495 void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
496         if (ctrl.MayThink()) {
497                 const Player *player = ctrl.ClosestVisiblePlayer(e);
498                 if (player) {
499                         ctrl.SetPursuitTarget(player->GetEntity());
500                         ctrl.SetState(chase);
501                         return;
502                 }
503         }
504
505         if (!ctrl.DecisionDue()) return;
506
507         unsigned int d = ctrl.Decide(10);
508         if (d < 2) {
509                 // .2 chance to start going
510                 ctrl.SetState(roam);
511         } else if (d < 5) {
512                 // .3 chance of looking around
513                 ctrl.ExitHalt();
514                 ctrl.StartWandering();
515         } else {
516                 // .5 chance of doing nothing
517                 ctrl.StopWandering();
518                 ctrl.EnterHalt();
519         }
520         ctrl.CueDecision(10.0f, 5.0f);
521 }
522
523 void IdleState::Exit(AIController &ctrl) const {
524         ctrl.ExitHalt();
525         ctrl.StopWandering();
526 }
527
528 // roam
529
530 void RoamState::Enter(AIController &ctrl) const {
531         ctrl.SetWanderParams(1.0f);
532         ctrl.StartWandering();
533         ctrl.CueDecision(10.0f, 5.0f);
534 }
535
536 void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
537         if (ctrl.MayThink()) {
538                 const Player *player = ctrl.ClosestVisiblePlayer(e);
539                 if (player) {
540                         ctrl.SetPursuitTarget(player->GetEntity());
541                         ctrl.SetState(chase);
542                         return;
543                 }
544         }
545
546         if (!ctrl.DecisionDue()) return;
547
548         unsigned int d = ctrl.Decide(10);
549         if (d == 0) {
550                 // .1 chance of idling
551                 ctrl.SetState(idle);
552         }
553         ctrl.CueDecision(10.0f, 5.0f);
554 }
555
556 void RoamState::Exit(AIController &ctrl) const {
557         ctrl.StopWandering();
558 }
559
560 }