+Entity &AIController::GetFleeTarget() noexcept {
+ return *flee_target;
+}
+
+const Entity &AIController::GetFleeTarget() const noexcept {
+ return *flee_target;
+}
+
+glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept {
+ return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f);
+}
+
+// seek
+
+void AIController::StartSeeking() noexcept {
+ seeking = true;
+}
+
+void AIController::StopSeeking() noexcept {
+ seeking = false;
+ if (seek_target) {
+ seek_target->UnRef();
+ seek_target = nullptr;
+ }
+}
+
+bool AIController::IsSeeking() const noexcept {
+ return seeking && seek_target;
+}
+
+void AIController::SetSeekTarget(Entity &e) noexcept {
+ if (seek_target) {
+ seek_target->UnRef();
+ }
+ seek_target = &e;
+ seek_target->Ref();
+}
+
+void AIController::SetSeekSpeed(float speed) noexcept {
+ seek_speed = speed;
+}
+
+Entity &AIController::GetSeekTarget() noexcept {
+ return *seek_target;
+}
+
+const Entity &AIController::GetSeekTarget() const noexcept {
+ return *seek_target;
+}
+
+glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept {
+ return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f);
+}
+
+// evade
+
+void AIController::StartEvading() noexcept {
+ evading = true;
+}
+
+void AIController::StopEvading() noexcept {
+ evading = false;
+ if (evade_target) {
+ evade_target->UnRef();
+ evade_target = nullptr;
+ }
+}
+
+bool AIController::IsEvading() const noexcept {
+ return evading && evade_target;
+}
+
+void AIController::SetEvadeTarget(Entity &e) noexcept {
+ if (evade_target) {
+ evade_target->UnRef();
+ }
+ evade_target = &e;
+ evade_target->Ref();
+}
+
+void AIController::SetEvadeSpeed(float speed) noexcept {
+ evade_speed = speed;
+}
+
+Entity &AIController::GetEvadeTarget() noexcept {
+ return *evade_target;
+}
+
+const Entity &AIController::GetEvadeTarget() const noexcept {
+ return *evade_target;
+}
+
+glm::vec3 AIController::GetEvadeForce(const Entity &, const EntityState &state) const noexcept{
+ glm::vec3 cur_diff(state.Diff(GetEvadeTarget().GetState()));
+ float time_estimate = length(cur_diff) / evade_speed;
+ EntityState pred_state(GetEvadeTarget().GetState());
+ pred_state.pos.block += pred_state.velocity * time_estimate;
+ return Flee(state, pred_state, evade_speed, 2.0f);
+}
+
+// pursuit
+
+void AIController::StartPursuing() noexcept {
+ pursuing = true;
+}
+
+void AIController::StopPursuing() noexcept {
+ pursuing = false;
+ if (pursuit_target) {
+ pursuit_target->UnRef();
+ pursuit_target = nullptr;
+ }
+}
+
+bool AIController::IsPursuing() const noexcept {
+ return pursuing && pursuit_target;
+}
+
+void AIController::SetPursuitTarget(Entity &e) noexcept {
+ if (pursuit_target) {
+ pursuit_target->UnRef();
+ }
+ pursuit_target = &e;
+ pursuit_target->Ref();
+}
+
+void AIController::SetPursuitSpeed(float speed) noexcept {
+ pursuit_speed = speed;
+}
+
+Entity &AIController::GetPursuitTarget() noexcept {
+ return *pursuit_target;
+}
+
+const Entity &AIController::GetPursuitTarget() const noexcept {
+ return *pursuit_target;
+}
+
+glm::vec3 AIController::GetPursuitForce(const Entity &, const EntityState &state) const noexcept {
+ glm::vec3 cur_diff(state.Diff(GetPursuitTarget().GetState()));
+ float time_estimate = length(cur_diff) / pursuit_speed;
+ EntityState pred_state(GetPursuitTarget().GetState());
+ pred_state.pos.block += pred_state.velocity * time_estimate;
+ return Seek(state, pred_state, pursuit_speed, 2.0f);
+}
+
+// wander
+
+void AIController::StartWandering() noexcept {
+ wandering = true;
+}
+
+void AIController::StopWandering() noexcept {
+ wandering = false;
+}
+
+bool AIController::IsWandering() const noexcept {
+ return wandering;
+}
+
+void AIController::SetWanderParams(
+ float speed,
+ float distance,
+ float radius,
+ float displacement
+) noexcept {
+ wander_speed = speed;
+ wander_dist = distance;
+ wander_radius = radius;
+ wander_disp = displacement;
+}
+
+glm::vec3 AIController::GetWanderForce(const Entity &e, const EntityState &state) const noexcept {
+ glm::vec3 wander_target(normalize(e.Heading() * wander_dist + wander_pos) * wander_speed);
+ return TargetVelocity(wander_target, state, 0.5f);
+}
+
+
+// chase
+
+void ChaseState::Enter(AIController &ctrl) const {
+ ctrl.SetHaltSpeed(2.0f);
+ ctrl.SetPursuitSpeed(4.0f);
+ ctrl.StartPursuing();
+}
+
+void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
+ // check if target still alive and in sight
+ if (ctrl.GetPursuitTarget().Dead()) {
+ ctrl.SetState(idle);
+ return;
+ }
+ if (!ctrl.LineOfSight(e, ctrl.GetPursuitTarget())) {
+ ctrl.SetState(idle);
+ return;
+ }
+ // halt if we're close enough, flee if we're too close
+ float dist_sq = length2(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
+ if (dist_sq < 8.0f) {
+ ctrl.SetFleeTarget(ctrl.GetPursuitTarget());
+ ctrl.SetState(flee);
+ } else if (dist_sq < 25.0f) {
+ ctrl.EnterHalt();