-void Chaser::Update(int dt) {
- glm::vec3 diff(Target().AbsoluteDifference(Controlled()));
- float dist = length(diff);
- glm::vec3 norm_diff(diff / dist);
+void AIController::Update(Entity &e, float dt) {
+ think_timer.Update(dt);
+ decision_timer.Update(dt);
+ state->Update(*this, e, dt);
+
+ if (e.Moving()) {
+ // orient head towards heading
+ glm::vec3 heading(e.Heading());
+ // only half pitch, so we don't crane our neck
+ float tgt_pitch = std::atan(heading.y / glm::length(glm::vec2(heading.x, heading.z))) * 0.5f;
+ // always look straight ahead
+ // maybe look at the pursuit target if there is one
+ float tgt_yaw = 0.0f;
+ e.SetHead(tgt_pitch, tgt_yaw);
+ e.OrientBody(dt);
+ }
+}
+
+Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
+ Player *target = nullptr;
+ float distance = sight_dist;
+ const glm::ivec3 &reference(e.ChunkCoords());
+ Ray aim(e.Aim(reference));
+ for (Player &p : world.Players()) {
+ const Entity &pe = p.GetEntity();
+
+ // distance test
+ const glm::vec3 diff(pe.AbsoluteDifference(e));
+ float dist = glm::length(diff);
+ if (dist > distance) continue;
+
+ // FOV test, 45° in each direction
+ if (glm::dot(diff / dist, aim.dir) < sight_angle) {
+ continue;
+ }