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