+ return target;
+}
+
+bool AIController::LineOfSight(const Entity &from, const Entity &to) const noexcept {
+ const glm::ivec3 &reference(from.ChunkCoords());
+ Ray aim(from.Aim(reference));
+ const glm::vec3 diff(to.AbsoluteDifference(from));
+ float dist = glm::length(diff);
+ if (dist > sight_dist || glm::dot(diff / dist, aim.dir) < sight_angle) {
+ return false;
+ }
+ WorldCollision col;
+ if (world.Intersection(aim, reference, col) && col.depth < dist) {
+ return false;
+ }
+ return true;
+}
+
+// think
+
+bool AIController::MayThink() const noexcept {
+ return think_timer.Hit();
+}
+
+void AIController::SetThinkInterval(float i) noexcept {
+ think_timer = FineTimer(i);
+ think_timer.Start();
+}
+
+// decide
+
+void AIController::CueDecision(
+ float minimum,
+ float variance
+) noexcept {
+ decision_timer = FineTimer(minimum + variance * world.Random().SNorm());
+ decision_timer.Start();
+}
+
+bool AIController::DecisionDue() const noexcept {
+ return decision_timer.HitOnce();
+}
+
+unsigned int AIController::Decide(unsigned int num_choices) noexcept {
+ return world.Random().Next<unsigned int>() % num_choices;