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