]> git.localhorst.tv Git - blank.git/commitdiff
move steering behaviours into entity
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 27 Nov 2015 15:43:02 +0000 (16:43 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 27 Nov 2015 15:43:02 +0000 (16:43 +0100)
20 files changed:
src/ai/AIController.hpp
src/ai/AIState.hpp
src/ai/ChaseState.hpp
src/ai/FleeState.hpp
src/ai/IdleState.hpp
src/ai/RoamState.hpp
src/ai/Spawner.cpp
src/ai/Spawner.hpp
src/ai/ai.cpp
src/app/Environment.hpp
src/app/runtime.cpp
src/server/ServerState.cpp
src/standalone/standalone.cpp
src/ui/PlayerController.hpp
src/ui/ui.cpp
src/world/Entity.hpp
src/world/EntityController.hpp
src/world/Steering.hpp [new file with mode: 0644]
src/world/World.hpp
src/world/world.cpp

index 15645a256dd66aa604b70b491214da8e24e1e3c3..cfd49d94a218a12539c98050f45eea0c29416ab9 100644 (file)
@@ -12,23 +12,24 @@ namespace blank {
 
 class AIState;
 class Entity;
-class GaloisLFSR;
 class Player;
 class World;
 
+// TODO: AI and entities are tightly coupled, maybe AIcontroller should
+//       be part of Entity. In that case, players could either be separated
+//       from other entities use function as a degenerate AI which blindly
+//       executes whatever its human tell it to.
 class AIController
 : public EntityController {
 
 public:
-       AIController(World &, GaloisLFSR &);
+       AIController(World &, Entity &);
        ~AIController();
 
-       void SetState(const AIState &);
+       void SetState(const AIState &, Entity &);
 
        void Update(Entity &, float dt) override;
 
-       glm::vec3 ControlForce(const Entity &, const EntityState &) const override;
-
        /// get the closest player that given entity can see
        /// returns nullptr if none are in sight
        Player *ClosestVisiblePlayer(const Entity &) noexcept;
@@ -49,72 +50,8 @@ public:
        /// random choice of 0 to num_choices - 1
        unsigned int Decide(unsigned int num_choices) noexcept;
 
-       void EnterHalt() noexcept;
-       void ExitHalt() noexcept;
-       bool IsHalted() const noexcept;
-       void SetHaltSpeed(float) noexcept;
-       glm::vec3 GetHaltForce(const Entity &, const EntityState &) const noexcept;
-
-       void StartAvoidingObstacles() noexcept;
-       void StopAvoidingObstacles() noexcept;
-       bool IsAvoidingObstacles() const noexcept;
-       glm::vec3 GetObstacleAvoidanceForce(const Entity &, const EntityState &) const noexcept;
-
-       void StartFleeing() noexcept;
-       void StopFleeing() noexcept;
-       bool IsFleeing() const noexcept;
-       void SetFleeTarget(Entity &) noexcept;
-       void SetFleeSpeed(float) noexcept;
-       Entity &GetFleeTarget() noexcept;
-       const Entity &GetFleeTarget() const noexcept;
-       glm::vec3 GetFleeForce(const Entity &, const EntityState &) const noexcept;
-
-       void StartSeeking() noexcept;
-       void StopSeeking() noexcept;
-       bool IsSeeking() const noexcept;
-       void SetSeekTarget(Entity &) noexcept;
-       void SetSeekSpeed(float) noexcept;
-       Entity &GetSeekTarget() noexcept;
-       const Entity &GetSeekTarget() const noexcept;
-       glm::vec3 GetSeekForce(const Entity &, const EntityState &) const noexcept;
-
-       void StartEvading() noexcept;
-       void StopEvading() noexcept;
-       bool IsEvading() const noexcept;
-       void SetEvadeTarget(Entity &) noexcept;
-       void SetEvadeSpeed(float) noexcept;
-       Entity &GetEvadeTarget() noexcept;
-       const Entity &GetEvadeTarget() const noexcept;
-       glm::vec3 GetEvadeForce(const Entity &, const EntityState &) const noexcept;
-
-       void StartPursuing() noexcept;
-       void StopPursuing() noexcept;
-       bool IsPursuing() const noexcept;
-       void SetPursuitTarget(Entity &) noexcept;
-       void SetPursuitSpeed(float) noexcept;
-       Entity &GetPursuitTarget() noexcept;
-       const Entity &GetPursuitTarget() const noexcept;
-       glm::vec3 GetPursuitForce(const Entity &, const EntityState &) const noexcept;
-
-       /// start wandering randomly
-       void StartWandering() noexcept;
-       void StopWandering() noexcept;
-       bool IsWandering() const noexcept;
-       /// change how wandering is performed
-       /// the trajectory is modified by targetting a blip on a sphere
-       /// in front of the entity which moves randomly
-       /// the displacement is given (roughly) in units per second
-       void SetWanderParams(
-               float speed,
-               float distance = 2.0f,
-               float radius = 1.0f,
-               float displacement = 1.0f
-       ) noexcept;
-       glm::vec3 GetWanderForce(const Entity &, const EntityState &) const noexcept;
-
 private:
        World &world;
-       GaloisLFSR &random;
        const AIState *state;
 
        /// how far controlled entities can see
@@ -125,36 +62,6 @@ private:
        FineTimer think_timer;
        FineTimer decision_timer;
 
-       bool halted;
-       float halt_speed;
-
-       bool avoid_obstacles;
-       AABB obstacle_box;
-       glm::mat4 obstacle_transform;
-
-       bool fleeing;
-       Entity *flee_target;
-       float flee_speed;
-
-       bool seeking;
-       Entity *seek_target;
-       float seek_speed;
-
-       bool evading;
-       Entity *evade_target;
-       float evade_speed;
-
-       bool pursuing;
-       Entity *pursuit_target;
-       float pursuit_speed;
-
-       bool wandering;
-       glm::vec3 wander_pos;
-       float wander_speed;
-       float wander_dist;
-       float wander_radius;
-       float wander_disp;
-
 };
 
 }
index ed82cc09d9fcabeb196ebd27650a3fb5645395db..7642ebdc3ecf630583f6e5c0e3df88a9c091a152 100644 (file)
@@ -6,9 +6,9 @@ namespace blank {
 
 struct AIState {
 
-       virtual void Enter(AIController &) const = 0;
+       virtual void Enter(AIController &, Entity &) const = 0;
        virtual void Update(AIController &, Entity &, float dt) const = 0;
-       virtual void Exit(AIController &) const = 0;
+       virtual void Exit(AIController &, Entity &) const = 0;
 
 };
 
index 97e68956d817e157159c9a89a87b986e0eaad8f0..3b707de8c128f161535426a45ec8df0dac18f661 100644 (file)
@@ -14,9 +14,9 @@ namespace blank {
 class ChaseState
 : public AIState {
 
-       void Enter(AIController &) const override;
+       void Enter(AIController &, Entity &) const override;
        void Update(AIController &, Entity &, float dt) const override;
-       void Exit(AIController &) const override;
+       void Exit(AIController &, Entity &) const override;
 
 };
 
index 8664e581bf1f59087e003c151934bc03920067e0..75a5d4bef2fb719b267a598ff66496785056c17a 100644 (file)
@@ -14,9 +14,9 @@ namespace blank {
 class FleeState
 : public AIState {
 
-       void Enter(AIController &) const override;
+       void Enter(AIController &, Entity &) const override;
        void Update(AIController &, Entity &, float dt) const override;
-       void Exit(AIController &) const override;
+       void Exit(AIController &, Entity &) const override;
 
 };
 
index b663d9164fc459ab20be2076476ebd230f912c7a..ec2592a713e28f519d0faf05576557b00e316f9a 100644 (file)
@@ -14,9 +14,9 @@ namespace blank {
 class IdleState
 : public AIState {
 
-       void Enter(AIController &) const override;
+       void Enter(AIController &, Entity &) const override;
        void Update(AIController &, Entity &, float dt) const override;
-       void Exit(AIController &) const override;
+       void Exit(AIController &, Entity &) const override;
 
 };
 
index 3a560e09af7c9c89aa12faa08ba4ff996895865d..321e1f4e4f8263e95e700796f200103a60c557af 100644 (file)
@@ -13,9 +13,9 @@ namespace blank {
 class RoamState
 : public AIState {
 
-       void Enter(AIController &) const override;
+       void Enter(AIController &, Entity &) const override;
        void Update(AIController &, Entity &, float dt) const override;
-       void Exit(AIController &) const override;
+       void Exit(AIController &, Entity &) const override;
 
 };
 
index fabc6dd6a4377999751098b4719a4b12eb2a2fe2..1f9573b862fca871d0d05e53a2687a5924d339e8 100644 (file)
@@ -18,11 +18,10 @@ using namespace std;
 
 namespace blank {
 
-Spawner::Spawner(World &world, ModelRegistry &models, GaloisLFSR &rand)
+Spawner::Spawner(World &world, ModelRegistry &models)
 : world(world)
 , models(models)
 , entities()
-, random(rand)
 , timer(64)
 , despawn_range(128 * 128)
 , spawn_distance(16 * 16)
@@ -93,13 +92,13 @@ void Spawner::TrySpawn() {
        // select random player to punish
        auto &players = world.Players();
        if (players.size() == 0) return;
-       size_t player_num = random.Next<unsigned short>() % players.size();
+       size_t player_num = world.Random().Next<unsigned short>() % players.size();
        auto i = players.begin(), end = players.end();
        for (; player_num > 0 && i != end; ++i, --player_num) {
        }
        const Player &player = *i;
 
-       BlockLookup spawn_block(player.GetChunks().RandomBlock(random));
+       BlockLookup spawn_block(player.GetChunks().RandomBlock(world.Random()));
 
        // distance check
        //glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.entity->Position());
@@ -127,14 +126,14 @@ void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3
        e.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
        e.WorldCollidable(true);
        RandomModel().Instantiate(e.GetModel());
-       e.SetController(new AIController(world, random));
+       e.SetController(new AIController(world, e));
        e.Name("spawned");
        e.Ref();
        entities.emplace_back(&e);
 }
 
 Model &Spawner::RandomModel() noexcept {
-       std::size_t offset = (random.Next<std::size_t>() % model_length) + model_offset;
+       std::size_t offset = (world.Random().Next<std::size_t>() % model_length) + model_offset;
        return models[offset];
 }
 
index f99029e28f7fdec7b1f08f34d4ef6ff9ef28d269..72d2ac136f84c6c7b3228465e3ac959106db5c2a 100644 (file)
@@ -10,7 +10,6 @@
 namespace blank {
 
 class Entity;
-class GaloisLFSR;
 class Model;
 class ModelRegistry;
 class World;
@@ -18,7 +17,7 @@ class World;
 class Spawner {
 
 public:
-       Spawner(World &, ModelRegistry &, GaloisLFSR &);
+       Spawner(World &, ModelRegistry &);
        ~Spawner();
 
        void LimitModels(std::size_t begin, std::size_t end);
@@ -37,8 +36,6 @@ private:
        ModelRegistry &models;
        std::vector<Entity *> entities;
 
-       GaloisLFSR &random;
-
        CoarseTimer timer;
        float despawn_range;
        float spawn_distance;
index 4b76856ea9664db0bb5dc17681155bfe501090be..820eb4d19a996aabdc1e89a188ec06710f11d0eb 100644 (file)
@@ -27,49 +27,26 @@ RoamState roam;
 
 }
 
-AIController::AIController(World &world, GaloisLFSR &rand)
+AIController::AIController(World &world, Entity &entity)
 : world(world)
-, random(rand)
 , state(&idle)
 , sight_dist(64.0f)
 , sight_angle(0.707f)
 , think_timer(0.5f)
-, decision_timer(1.0f)
-, halted(false)
-, halt_speed(1.0f)
-, avoid_obstacles(true)
-, obstacle_box{ glm::vec3(0.0f), glm::vec3(0.0f) }
-, obstacle_transform(1.0f)
-, fleeing(false)
-, flee_target(nullptr)
-, flee_speed(5.0f)
-, seeking(false)
-, seek_target(nullptr)
-, seek_speed(5.0f)
-, evading(false)
-, evade_target(nullptr)
-, evade_speed(5.0f)
-, pursuing(false)
-, pursuit_target(nullptr)
-, pursuit_speed(5.0f)
-, wandering(false)
-, wander_pos(1.0f, 0.0f, 0.0f)
-, wander_speed(1.0f)
-, wander_dist(2.0f)
-, wander_radius(1.5f)
-, wander_disp(1.0f) {
+, decision_timer(1.0f) {
        think_timer.Start();
-       state->Enter(*this);
+       state->Enter(*this, entity);
 }
 
 AIController::~AIController() {
-       state->Exit(*this);
+       // ignore this for now
+       // state->Exit(*this, entity);
 }
 
-void AIController::SetState(const AIState &s) {
-       state->Exit(*this);
+void AIController::SetState(const AIState &s, Entity &entity) {
+       state->Exit(*this, entity);
        state = &s;
-       state->Enter(*this);
+       state->Enter(*this, entity);
 }
 
 void AIController::Update(Entity &e, float dt) {
@@ -77,27 +54,6 @@ void AIController::Update(Entity &e, float dt) {
        decision_timer.Update(dt);
        state->Update(*this, e, dt);
 
-       if (avoid_obstacles && e.Moving()) {
-               obstacle_box = e.Bounds();
-               obstacle_box.min.z = -e.Speed();
-               obstacle_box.max.x = 0.0f;
-               // our box is oriented for -Z velocity
-               obstacle_transform = glm::mat4(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), e.Heading()));
-               // and positioned relative to the entity's chunk
-               obstacle_transform[3] = glm::vec4(e.GetState().pos.block, 1.0f);
-       }
-
-       if (wandering) {
-               glm::vec3 displacement(
-                       random.SNorm() * wander_disp,
-                       random.SNorm() * wander_disp,
-                       random.SNorm() * wander_disp
-               );
-               if (!iszero(displacement)) {
-                       wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
-               }
-       }
-
        if (e.Moving()) {
                // orient head towards heading
                glm::vec3 heading(e.Heading());
@@ -111,44 +67,6 @@ void AIController::Update(Entity &e, float dt) {
        }
 }
 
-glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
-       if (IsHalted()) {
-               return GetHaltForce(entity, state);
-       }
-       glm::vec3 force(0.0f);
-       if (IsAvoidingObstacles() && entity.Moving()) {
-               if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) {
-                       return force;
-               }
-       }
-       if (IsFleeing()) {
-               if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
-                       return force;
-               }
-       }
-       if (IsSeeking()) {
-               if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) {
-                       return force;
-               }
-       }
-       if (IsEvading()) {
-               if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) {
-                       return force;
-               }
-       }
-       if (IsPursuing()) {
-               if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) {
-                       return force;
-               }
-       }
-       if (IsWandering()) {
-               if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) {
-                       return force;
-               }
-       }
-       return force;
-}
-
 Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
        Player *target = nullptr;
        float distance = sight_dist;
@@ -212,7 +130,7 @@ void AIController::CueDecision(
        float minimum,
        float variance
 ) noexcept {
-       decision_timer = FineTimer(minimum + variance * random.SNorm());
+       decision_timer = FineTimer(minimum + variance * world.Random().SNorm());
        decision_timer.Start();
 }
 
@@ -221,354 +139,76 @@ bool AIController::DecisionDue() const noexcept {
 }
 
 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
-       return random.Next<unsigned int>() % num_choices;
-}
-
-// halt
-
-void AIController::EnterHalt() noexcept {
-       halted = true;
-}
-
-void AIController::ExitHalt() noexcept {
-       halted = false;
-}
-
-bool AIController::IsHalted() const noexcept {
-       return halted;
-}
-
-void AIController::SetHaltSpeed(float speed) noexcept {
-       halt_speed = speed;
-}
-
-glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept {
-       return Halt(state, halt_speed);
-}
-
-// obstacle avoidance
-
-void AIController::StartAvoidingObstacles() noexcept {
-       avoid_obstacles = true;
-}
-
-void AIController::StopAvoidingObstacles() noexcept {
-       avoid_obstacles = false;
-}
-
-bool AIController::IsAvoidingObstacles() const noexcept {
-       return avoid_obstacles;
-}
-
-namespace {
-
-std::vector<WorldCollision> col;
-
-}
-
-glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept {
-       if (!e.Moving()) {
-               return glm::vec3(0.0f);
-       }
-       col.clear();
-       if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) {
-               return glm::vec3(0.0f);
-       }
-       // find the nearest block
-       WorldCollision *nearest = nullptr;
-       glm::vec3 difference(0.0f);
-       float distance = std::numeric_limits<float>::infinity();
-       for (WorldCollision &c : col) {
-               // diff points from block to state
-               glm::vec3 diff = state.RelativePosition(c.ChunkPos()) - c.BlockCoords();
-               float dist = length2(diff);
-               if (dist < distance) {
-                       nearest = &c;
-                       difference = diff;
-                       distance = dist;
-               }
-       }
-       if (!nearest) {
-               // intersection test lied to us
-               return glm::vec3(0.0f);
-       }
-       // and steer away from it
-       // to_go is the distance between our position and the
-       // point on the "velocity ray" closest to obstacle
-       float to_go = dot(difference, e.Heading());
-       // point is our future position if we keep going our way
-       glm::vec3 point(e.GetState().pos.block + e.Heading() * to_go);
-       // now steer away in the direction of (point - block)
-       // with a magniture proportional to speed/distance
-       return normalize(point - nearest->BlockCoords()) * (e.Speed() / std::sqrt(distance));
-}
-
-// flee
-
-void AIController::StartFleeing() noexcept {
-       fleeing = true;
-}
-
-void AIController::StopFleeing() noexcept {
-       fleeing = false;
-       if (flee_target) {
-               flee_target->UnRef();
-               flee_target = nullptr;
-       }
-}
-
-bool AIController::IsFleeing() const noexcept {
-       return fleeing && flee_target;
-}
-
-void AIController::SetFleeTarget(Entity &e) noexcept {
-       if (flee_target) {
-               flee_target->UnRef();
-       }
-       flee_target = &e;
-       flee_target->Ref();
-}
-
-void AIController::SetFleeSpeed(float speed) noexcept {
-       flee_speed = speed;
-}
-
-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);
+       return world.Random().Next<unsigned int>() % num_choices;
 }
 
 
 // chase
 
-void ChaseState::Enter(AIController &ctrl) const {
-       ctrl.SetHaltSpeed(2.0f);
-       ctrl.SetPursuitSpeed(4.0f);
-       ctrl.StartPursuing();
+void ChaseState::Enter(AIController &ctrl, Entity &e) const {
+       e.GetSteering()
+               .SetAcceleration(1.0f)
+               .SetSpeed(4.0f)
+               .Enable(Steering::PURSUE_TARGET)
+       ;
 }
 
 void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
+       Steering &steering = e.GetSteering();
        // 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);
+       if (
+               !steering.HasTargetEntity() || // lost
+               steering.GetTargetEntity().Dead() || // dead
+               !ctrl.LineOfSight(e, steering.GetTargetEntity()) // escaped
+       ) {
+               steering.ClearTargetEntity();
+               ctrl.SetState(idle, e);
                return;
        }
        // halt if we're close enough, flee if we're too close
-       float dist_sq = length2(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
+       float dist_sq = length2(e.AbsoluteDifference(steering.GetTargetEntity()));
        if (dist_sq < 8.0f) {
-               ctrl.SetFleeTarget(ctrl.GetPursuitTarget());
-               ctrl.SetState(flee);
+               ctrl.SetState(flee, e);
        } else if (dist_sq < 25.0f) {
-               ctrl.EnterHalt();
+               steering.Enable(Steering::HALT).Disable(Steering::PURSUE_TARGET);
        } else {
-               ctrl.ExitHalt();
+               steering.Enable(Steering::PURSUE_TARGET).Disable(Steering::HALT);
        }
 }
 
-void ChaseState::Exit(AIController &ctrl) const {
-       ctrl.StopPursuing();
-       ctrl.ExitHalt();
+void ChaseState::Exit(AIController &ctrl, Entity &e) const {
+       e.GetSteering().Disable(Steering::HALT | Steering::PURSUE_TARGET);
 }
 
 // flee
 
-void FleeState::Enter(AIController &ctrl) const {
+void FleeState::Enter(AIController &ctrl, Entity &e) const {
+       e.GetSteering()
+               .SetAcceleration(1.0f)
+               .SetSpeed(4.0f)
+               .Enable(Steering::EVADE_TARGET)
+       ;
        ctrl.CueDecision(6.0f, 3.0f);
-       ctrl.SetFleeSpeed(4.0f);
-       ctrl.StartFleeing();
 }
 
 void FleeState::Update(AIController &ctrl, Entity &e, float dt) const {
        if (!ctrl.DecisionDue()) return;
-       ctrl.SetState(idle);
+       ctrl.SetState(idle, e);
 }
 
-void FleeState::Exit(AIController &ctrl) const {
-       ctrl.StopFleeing();
+void FleeState::Exit(AIController &ctrl, Entity &e) const {
+       e.GetSteering().Disable(Steering::EVADE_TARGET);
 }
 
 // idle
 
-void IdleState::Enter(AIController &ctrl) const {
-       ctrl.SetHaltSpeed(0.5f);
-       ctrl.EnterHalt();
-       ctrl.SetWanderParams(0.001f, 1.1f);
+void IdleState::Enter(AIController &ctrl, Entity &e) const {
+       e.GetSteering()
+               .SetAcceleration(0.5f)
+               .SetSpeed(0.01f)
+               .Enable(Steering::HALT)
+               .SetWanderParams(1.0f, 1.1f, 1.0f)
+       ;
        ctrl.CueDecision(10.0f, 5.0f);
 }
 
@@ -576,8 +216,8 @@ void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
        if (ctrl.MayThink()) {
                const Player *player = ctrl.ClosestVisiblePlayer(e);
                if (player) {
-                       ctrl.SetPursuitTarget(player->GetEntity());
-                       ctrl.SetState(chase);
+                       e.GetSteering().SetTargetEntity(player->GetEntity());
+                       ctrl.SetState(chase, e);
                        return;
                }
        }
@@ -587,29 +227,30 @@ void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
        unsigned int d = ctrl.Decide(10);
        if (d < 2) {
                // .2 chance to start going
-               ctrl.SetState(roam);
+               ctrl.SetState(roam, e);
        } else if (d < 5) {
                // .3 chance of looking around
-               ctrl.ExitHalt();
-               ctrl.StartWandering();
+               e.GetSteering().Disable(Steering::HALT).Enable(Steering::WANDER);
        } else {
                // .5 chance of doing nothing
-               ctrl.StopWandering();
-               ctrl.EnterHalt();
+               e.GetSteering().Disable(Steering::WANDER).Enable(Steering::HALT);
        }
        ctrl.CueDecision(10.0f, 5.0f);
 }
 
-void IdleState::Exit(AIController &ctrl) const {
-       ctrl.ExitHalt();
-       ctrl.StopWandering();
+void IdleState::Exit(AIController &ctrl, Entity &e) const {
+       e.GetSteering().Disable(Steering::HALT | Steering::WANDER);
 }
 
 // roam
 
-void RoamState::Enter(AIController &ctrl) const {
-       ctrl.SetWanderParams(1.0f);
-       ctrl.StartWandering();
+void RoamState::Enter(AIController &ctrl, Entity &e) const {
+       e.GetSteering()
+               .SetAcceleration(0.5f)
+               .SetSpeed(1.0f)
+               .SetWanderParams(1.0f, 2.0f, 1.0f)
+               .Enable(Steering::WANDER)
+       ;
        ctrl.CueDecision(10.0f, 5.0f);
 }
 
@@ -617,8 +258,8 @@ void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
        if (ctrl.MayThink()) {
                const Player *player = ctrl.ClosestVisiblePlayer(e);
                if (player) {
-                       ctrl.SetPursuitTarget(player->GetEntity());
-                       ctrl.SetState(chase);
+                       e.GetSteering().SetTargetEntity(player->GetEntity());
+                       ctrl.SetState(chase, e);
                        return;
                }
        }
@@ -628,13 +269,13 @@ void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
        unsigned int d = ctrl.Decide(10);
        if (d == 0) {
                // .1 chance of idling
-               ctrl.SetState(idle);
+               ctrl.SetState(idle, e);
        }
        ctrl.CueDecision(10.0f, 5.0f);
 }
 
-void RoamState::Exit(AIController &ctrl) const {
-       ctrl.StopWandering();
+void RoamState::Exit(AIController &ctrl, Entity &e) const {
+       e.GetSteering().Disable(Steering::WANDER);
 }
 
 }
index 7061000fa221ffe1f3da97ad7e42ff43d4a08fcf..2cab6709fd46135540c8f3582267887a9123fbe0 100644 (file)
@@ -6,7 +6,6 @@
 #include "StateControl.hpp"
 #include "../audio/Audio.hpp"
 #include "../graphics/Viewport.hpp"
-#include "../rand/GaloisLFSR.hpp"
 #include "../shared/MessageState.hpp"
 #include "../ui/Keymap.hpp"
 
@@ -38,8 +37,6 @@ struct HeadlessEnvironment {
 
        StateControl state;
 
-       GaloisLFSR rng;
-
 
        explicit HeadlessEnvironment(const Config &);
 
index c80fa765998069a185556e6ad2bdde046d73bc82..2481f93a2edcac66feaf5f09f02aff38f30aaf75 100644 (file)
@@ -118,17 +118,8 @@ HeadlessEnvironment::HeadlessEnvironment(const Config &config)
 : config(config)
 , loader(config.asset_path)
 , counter()
-, state()
-, rng(
-#ifdef BLANK_PROFILING
-0
-#else
-std::time(nullptr)
-#endif
-){
-       for (int i = 0; i < 4; ++i) {
-               rng.Next<int>();
-       }
+, state() {
+
 }
 
 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const {
index bb6a8a9d02f6644a73b24ccfc664cb967ac608c9..860b71f4716e24f6d2c6fb8deb96364764bdfc00 100644 (file)
@@ -22,7 +22,7 @@ ServerState::ServerState(
 , world(res.block_types, wc)
 , generator(gc)
 , chunk_loader(world.Chunks(), generator, ws)
-, spawner(world, res.models, env.rng)
+, spawner(world, res.models)
 , server(config.net, world, wc, ws)
 , loop_timer(16) {
        res.Load(env.loader, "default");
index 2ffe6bc862c85a72600cf5ce4512d4efbdecb323..3c3487cd13d2336bb4371bfde65889eec5649ede 100644 (file)
@@ -59,7 +59,7 @@ MasterState::MasterState(
 , generator(gc)
 , chunk_loader(world.Chunks(), generator, save)
 , chunk_renderer(player.GetChunks())
-, spawner(world, res.models, env.rng)
+, spawner(world, res.models)
 , sky(env.loader.LoadCubeMap("skybox"))
 , cli(world)
 , cli_ctx(player, hud)
index 9cf0bca465eb3550af7c009dfd4049f73b1c2679..48c3bdcfbc51ff07e65ca1779735a03becac584c 100644 (file)
@@ -35,8 +35,6 @@ public:
        void SetMovement(const glm::vec3 &) noexcept;
        const glm::vec3 &GetMovement() const noexcept { return move_dir; }
 
-       glm::vec3 ControlForce(const Entity &, const EntityState &) const override;
-
        /// turn the controlled entity's head by given pitch and yaw deltas
        void TurnHead(float pitch, float yaw) noexcept;
 
index 838b1ee8e94168fd02c163803f123383b559c701..bd529d555aab44fb972429029089815c4a480fa2 100644 (file)
@@ -43,6 +43,7 @@ PlayerController::PlayerController(World &world, Player &player)
 , aim_world()
 , aim_entity() {
        player.GetEntity().SetController(*this);
+       player.GetEntity().GetSteering().SetAcceleration(5.0f);
 }
 
 PlayerController::~PlayerController() {
@@ -60,16 +61,6 @@ void PlayerController::SetMovement(const glm::vec3 &m) noexcept {
        Invalidate();
 }
 
-glm::vec3 PlayerController::ControlForce(const Entity &e, const EntityState &s) const {
-       if (!iszero(move_dir)) {
-               // scale input by max velocity, apply yaw, and transform to world space
-               return TargetVelocity(glm::vec3(glm::vec4(rotateY(move_dir * e.MaxVelocity(), s.yaw), 0.0f) * transpose(e.Transform())), s, 5.0f);
-       } else {
-               // target velocity of 0 is the same as halt
-               return Halt(s, 5.0f);
-       }
-}
-
 void PlayerController::TurnHead(float dp, float dy) noexcept {
        player.GetEntity().TurnHead(dp, dy);
 }
@@ -97,20 +88,36 @@ void PlayerController::Invalidate() noexcept {
 void PlayerController::UpdatePlayer() noexcept {
        if (dirty) {
                Ray aim = player.Aim();
-               if (!world.Intersection(aim, player.GetEntity().ChunkCoords(), aim_world)) {
+               Entity &entity = player.GetEntity();
+               if (!world.Intersection(aim, entity.ChunkCoords(), aim_world)) {
                        aim_world = WorldCollision();
                }
-               if (!world.Intersection(aim, player.GetEntity(), aim_entity)) {
+               if (!world.Intersection(aim, entity, aim_entity)) {
                        aim_entity = EntityCollision();
                }
                if (aim_world && aim_entity) {
                        // got both, pick the closest one
                        if (aim_world.depth < aim_entity.depth) {
+                               // FIXME: somehow this can get stuck on an entity?
                                aim_entity = EntityCollision();
                        } else {
                                aim_world = WorldCollision();
                        }
                }
+               Steering &steering = entity.GetSteering();
+               if (!iszero(move_dir)) {
+                       // scale input by max velocity, apply yaw, and transform to world space
+                       steering.SetTargetVelocity(glm::vec3(
+                               glm::vec4(rotateY(move_dir * entity.MaxVelocity(), entity.Yaw()), 0.0f)
+                               * transpose(entity.Transform())
+                       ));
+                       steering.Enable(Steering::TARGET_VELOCITY);
+                       steering.Disable(Steering::HALT);
+               } else {
+                       // target velocity of 0 is the same as halt
+                       steering.Enable(Steering::HALT);
+                       steering.Disable(Steering::TARGET_VELOCITY);
+               }
                dirty = false;
        }
 }
index 0117fa9cb0d0801deb53750c54b0b93c2d236cc1..36f686848e6542353e3c47d20e94fdb9af58902e 100644 (file)
@@ -4,6 +4,7 @@
 #include "Chunk.hpp"
 #include "EntityDerivative.hpp"
 #include "EntityState.hpp"
+#include "Steering.hpp"
 #include "../geometry/primitive.hpp"
 #include "../model/Instance.hpp"
 
@@ -32,6 +33,9 @@ public:
        Entity(const Entity &) noexcept;
        Entity &operator =(const Entity &) = delete;
 
+       Steering &GetSteering() noexcept { return steering; }
+       const Steering &GetSteering() const noexcept { return steering; }
+
        bool HasController() const noexcept { return ctrl; }
        // entity takes over ownership of controller
        void SetController(EntityController *c) noexcept;
@@ -142,6 +146,7 @@ private:
 
 
 private:
+       Steering steering;
        EntityController *ctrl;
        Instance model;
 
index ce3d0cf00998fbccf7826aa052766aca7a66cbe1..2724c5e9c8278e67858d3da5d384a24fa26c3796 100644 (file)
@@ -1,11 +1,6 @@
 #ifndef BLANK_WORLD_ENTITYCONTROLLER_HPP_
 #define BLANK_WORLD_ENTITYCONTROLLER_HPP_
 
-#include "EntityState.hpp"
-
-#include <glm/glm.hpp>
-
-
 namespace blank {
 
 class Entity;
@@ -16,52 +11,6 @@ struct EntityController {
 
        virtual void Update(Entity &, float dt) = 0;
 
-       virtual glm::vec3 ControlForce(const Entity &, const EntityState &) const = 0;
-
-
-       /// try to add as much of add to out so it doesn't exceed max
-       /// returns true if it's maxed out
-       static bool MaxOutForce(
-               glm::vec3 &out,
-               const glm::vec3 &add,
-               float max
-       ) noexcept;
-       /// give a force that makes state come to a halt over 1/n seconds
-       static inline glm::vec3 Halt(
-               const EntityState &state,
-               float n
-       ) noexcept {
-               return state.velocity * -n;
-       }
-       /// give a force that makes state's velocity converge with given
-       /// target velocity over 1/n seconds
-       static inline glm::vec3 TargetVelocity(
-               const glm::vec3 &target,
-               const EntityState &state,
-               float n
-       ) noexcept {
-               return (target - state.velocity) * n;
-       }
-       /// give a force that makes state go after target with an attempted
-       /// acceleration over 1/n seconds and a speed of s
-       static inline glm::vec3 Seek(
-               const EntityState &state,
-               const EntityState &target,
-               float s,
-               float n
-       ) noexcept {
-               return TargetVelocity(normalize(target.Diff(state)) * s, state, n);
-       }
-       /// opposite of seek
-       static inline glm::vec3 Flee(
-               const EntityState &state,
-               const EntityState &target,
-               float s,
-               float n
-       ) noexcept {
-               return TargetVelocity(normalize(state.Diff(target)) * s, state, n);
-       }
-
 };
 
 }
diff --git a/src/world/Steering.hpp b/src/world/Steering.hpp
new file mode 100644 (file)
index 0000000..d6ec57f
--- /dev/null
@@ -0,0 +1,120 @@
+#ifndef BLANK_WORLD_STEERING_HPP
+#define BLANK_WORLD_STEERING_HPP
+
+#include "../geometry/Location.hpp"
+#include "../geometry/primitive.hpp"
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Entity;
+class EntityState;
+class World;
+
+class Steering {
+
+public:
+       enum Behaviour {
+               // standalone behaviours
+               HALT = 0x0001,
+               WANDER = 0x0002,
+               OBSTACLE_AVOIDANCE = 0x0004,
+
+               // requires target velocity
+               TARGET_VELOCITY = 0x0008,
+
+               // behaviours requiring a target entity
+               EVADE_TARGET = 0x0010,
+               PURSUE_TARGET = 0x0020,
+       };
+
+       explicit Steering(const Entity &);
+       ~Steering();
+
+       Steering &Enable(unsigned int b) noexcept { enabled |= b; return *this; }
+       Steering &Disable(unsigned int b) noexcept { enabled &= ~b; return *this; }
+       bool AnyEnabled(unsigned int b) const noexcept { return enabled & b; }
+       bool AllEnabled(unsigned int b) const noexcept { return (enabled & b) == b; }
+
+       Steering &SetTargetEntity(Entity &) noexcept;
+       Steering &ClearTargetEntity() noexcept;
+       bool HasTargetEntity() const noexcept { return target_entity; }
+       const Entity &GetTargetEntity() const noexcept { return *target_entity; }
+
+       Steering &SetTargetVelocity(const glm::vec3 &v) noexcept { target_velocity = v; return *this; }
+       const glm::vec3 &GetTargetVelocity() const noexcept { return target_velocity; }
+
+       /// time in seconds in which steering tried to arrive at target velocity
+       Steering &SetAcceleration(float a) noexcept { accel = a; return *this; }
+       /// maximum magnitude of velocity that behaviours generate
+       Steering &SetSpeed(float s) noexcept { speed = s; return *this; }
+
+       /// configure wandering
+       /// r is the radius of the sphere
+       /// dist is the distance between the entity and the sphere's center
+       /// disp is the maximum variance of the point on the sphere in units per second
+       Steering &SetWanderParams(float r, float dist, float disp) noexcept {
+               wander_radius = r;
+               wander_dist = dist;
+               wander_disp = disp;
+               return *this;
+       }
+
+       void Update(World &, float dt);
+
+       glm::vec3 Force(const EntityState &) const noexcept;
+
+private:
+       void UpdateWander(World &, float dt);
+       void UpdateObstacle(World &);
+
+       /// try to add as much of in to out so it doesn't exceed max
+       /// returns true if it's maxed out
+       static bool SumForce(glm::vec3 &out, const glm::vec3 &in, float max) noexcept;
+
+       /// slow down to a halt
+       glm::vec3 Halt(const EntityState &) const noexcept;
+       /// accelerate to match given velocity
+       glm::vec3 TargetVelocity(const EntityState &, const glm::vec3 &) const noexcept;
+       /// move towards given location
+       glm::vec3 Seek(const EntityState &, const ExactLocation &) const noexcept;
+       /// move away from given location
+       glm::vec3 Flee(const EntityState &, const ExactLocation &) const noexcept;
+       /// try to halt at given location
+       glm::vec3 Arrive(const EntityState &, const ExactLocation &) const noexcept;
+       /// seek given entity's predicted position
+       glm::vec3 Pursuit(const EntityState &, const Entity &) const noexcept;
+       /// flee given entity's predicted position
+       glm::vec3 Evade(const EntityState &, const Entity &) const noexcept;
+       /// move around for no reason
+       glm::vec3 Wander(const EntityState &) const noexcept;
+       /// try not to crash into blocks
+       glm::vec3 ObstacleAvoidance(const EntityState &) const noexcept;
+
+private:
+       const Entity &entity;
+
+       Entity *target_entity;
+       glm::vec3 target_velocity;
+
+       float accel;
+       float speed;
+
+       float wander_radius;
+       float wander_dist;
+       float wander_disp;
+       glm::vec3 wander_pos;
+
+       glm::vec3 obstacle_dir;
+
+       unsigned int enabled;
+
+
+
+};
+
+}
+
+#endif
index 7936f98cfd6dcec82a639454d4f8cf9f02c33f84..40136c8dff4a99315003582bd3b58a37d7cc5374 100644 (file)
@@ -5,6 +5,7 @@
 #include "Entity.hpp"
 #include "Generator.hpp"
 #include "Player.hpp"
+#include "../rand/GaloisLFSR.hpp"
 
 #include <cstdint>
 #include <list>
@@ -41,6 +42,9 @@ public:
 
        const std::string &Name() const noexcept { return config.name; }
 
+       /// get the shared random source for this world
+       GaloisLFSR &Random() noexcept { return rng; }
+
        /// check if this ray hits a block
        /// depth in the collision is the distance between the ray's
        /// origin and the intersection point
@@ -130,6 +134,8 @@ private:
        std::list<Player> players;
        std::list<Entity> entities;
 
+       GaloisLFSR rng;
+
        glm::vec3 light_direction;
        float fog_density;
 
index 4f6d908720764b4de8fe3b4835150f5ac2889a6c..d933dc4ec77f86d913d2cd66de5ced6e4a19d65f 100644 (file)
@@ -12,6 +12,7 @@
 #include "../app/Assets.hpp"
 #include "../geometry/const.hpp"
 #include "../geometry/distance.hpp"
+#include "../geometry/rotation.hpp"
 #include "../graphics/Format.hpp"
 #include "../graphics/Viewport.hpp"
 
 
 namespace blank {
 
+namespace {
+
+/// used as a buffer for merging collisions
+std::vector<WorldCollision> col;
+
+}
+
 Entity::Entity() noexcept
-: ctrl(nullptr)
+: steering(*this)
+, ctrl(nullptr)
 , model()
 , id(-1)
 , name("anonymous")
@@ -52,7 +61,8 @@ Entity::~Entity() noexcept {
 }
 
 Entity::Entity(const Entity &other) noexcept
-: ctrl(other.ctrl)
+: steering(*this)
+, ctrl(other.ctrl)
 , model(other.model)
 , id(-1)
 , name(other.name)
@@ -91,14 +101,7 @@ void Entity::UnsetController() noexcept {
 }
 
 glm::vec3 Entity::ControlForce(const EntityState &s) const noexcept {
-       glm::vec3 force;
-       if (HasController()) {
-               force = GetController().ControlForce(*this, s);
-       } else {
-               force = -s.velocity;
-       }
-       limit(force, max_force);
-       return force;
+       return steering.Force(s);
 }
 
 void Entity::Position(const glm::ivec3 &c, const glm::vec3 &b) noexcept {
@@ -137,6 +140,7 @@ void Entity::Update(World &world, float dt) {
        if (HasController()) {
                GetController().Update(*this, dt);
        }
+       steering.Update(world, dt);
        UpdatePhysics(world, dt);
        UpdateTransforms();
        UpdateHeading();
@@ -330,29 +334,6 @@ EntityController::~EntityController() {
 
 }
 
-bool EntityController::MaxOutForce(
-       glm::vec3 &out,
-       const glm::vec3 &add,
-       float max
-) noexcept {
-       if (iszero(add) || any(isnan(add))) {
-               return false;
-       }
-       float current = iszero(out) ? 0.0f : length(out);
-       float remain = max - current;
-       if (remain <= 0.0f) {
-               return true;
-       }
-       float additional = length(add);
-       if (additional > remain) {
-               out += normalize(add) * remain;
-               return true;
-       } else {
-               out += add;
-               return false;
-       }
-}
-
 
 EntityState::EntityState()
 : pos()
@@ -414,15 +395,253 @@ void Player::Update(int dt) {
 }
 
 
+Steering::Steering(const Entity &e)
+: entity(e)
+, target_entity(nullptr)
+, target_velocity(0.0f)
+, accel(1.0f)
+, speed(entity.MaxVelocity())
+, wander_radius(1.0f)
+, wander_dist(2.0f)
+, wander_disp(1.0f)
+, wander_pos(1.0f, 0.0f, 0.0f)
+, obstacle_dir(0.0f)
+, enabled(0) {
+
+}
+
+Steering::~Steering() {
+       ClearTargetEntity();
+}
+
+Steering &Steering::SetTargetEntity(Entity &e) noexcept {
+       ClearTargetEntity();
+       target_entity = &e;
+       e.Ref();
+       return *this;
+}
+
+Steering &Steering::ClearTargetEntity() noexcept {
+       if (target_entity) {
+               target_entity->UnRef();
+               target_entity = nullptr;
+       }
+       return *this;
+}
+
+void Steering::Update(World &world, float dt) {
+       if (AnyEnabled(WANDER)) {
+               UpdateWander(world, dt);
+       }
+       if (AnyEnabled(OBSTACLE_AVOIDANCE)) {
+               UpdateObstacle(world);
+       }
+}
+
+void Steering::UpdateWander(World &world, float dt) {
+       glm::vec3 displacement(
+               world.Random().SNorm() * wander_disp,
+               world.Random().SNorm() * wander_disp,
+               world.Random().SNorm() * wander_disp
+       );
+       if (!iszero(displacement)) {
+               wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
+       }
+}
+
+void Steering::UpdateObstacle(World &world) {
+       if (!entity.Moving()) {
+               obstacle_dir = glm::vec3(0.0f);
+               return;
+       }
+       AABB box(entity.Bounds());
+       box.min.z = -entity.Speed();
+       box.max.z = 0.0f;
+       glm::mat4 transform(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), entity.Heading()));
+       transform[3] = glm::vec4(entity.Position(), 1.0f);
+       // check if that box intersects with any blocks
+       col.clear();
+       if (!world.Intersection(box, transform, entity.ChunkCoords(), col)) {
+               obstacle_dir = glm::vec3(0.0f);
+               return;
+       }
+       // if so, pick the nearest collision
+       const WorldCollision *nearest = nullptr;
+       glm::vec3 difference(0.0f);
+       float distance = std::numeric_limits<float>::infinity();
+       for (const WorldCollision &c : col) {
+               // diff points from block to state
+               glm::vec3 diff = entity.GetState().RelativePosition(c.ChunkPos()) - c.BlockCoords();
+               float dist = length2(diff);
+               if (dist < distance) {
+                       nearest = &c;
+                       difference = diff;
+                       distance = dist;
+               }
+       }
+       if (!nearest) {
+               // intersection test lied to us
+               obstacle_dir = glm::vec3(0.0f);
+               return;
+       }
+       // and try to avoid it
+       float to_go = dot(difference, entity.Heading());
+       glm::vec3 point(entity.Position() + entity.Heading() * to_go);
+       obstacle_dir = normalize(point - nearest->BlockCoords()) * (entity.Speed() / std::sqrt(distance));
+}
+
+glm::vec3 Steering::Force(const EntityState &state) const noexcept {
+       glm::vec3 force(0.0f);
+       if (!enabled) {
+               return force;
+       }
+       const float max = entity.MaxControlForce();
+       if (AnyEnabled(HALT)) {
+               if (SumForce(force, Halt(state), max)) {
+                       return force;
+               }
+       }
+       if (AnyEnabled(TARGET_VELOCITY)) {
+               if (SumForce(force, TargetVelocity(state, target_velocity), max)) {
+                       return force;
+               }
+       }
+       if (AnyEnabled(OBSTACLE_AVOIDANCE)) {
+               if (SumForce(force, ObstacleAvoidance(state), max)) {
+                       return force;
+               }
+       }
+       if (AnyEnabled(EVADE_TARGET)) {
+               if (HasTargetEntity()) {
+                       if (SumForce(force, Evade(state, GetTargetEntity()), max)) {
+                               return force;
+                       }
+               } else {
+                       std::cout << "Steering: evade enabled, but target entity not set" << std::endl;
+               }
+       }
+       if (AnyEnabled(PURSUE_TARGET)) {
+               if (HasTargetEntity()) {
+                       if (SumForce(force, Pursuit(state, GetTargetEntity()), max)) {
+                               return force;
+                       }
+               } else {
+                       std::cout << "Steering: pursuit enabled, but target entity not set" << std::endl;
+               }
+       }
+       if (AnyEnabled(WANDER)) {
+               if (SumForce(force, Wander(state), max)) {
+                       return force;
+               }
+       }
+       return force;
+}
+
+bool Steering::SumForce(glm::vec3 &out, const glm::vec3 &in, float max) noexcept {
+       if (iszero(in) || any(isnan(in))) {
+               return false;
+       }
+       float current = iszero(out) ? 0.0f : length(out);
+       float remain = max - current;
+       if (remain <= 0.0f) {
+               return true;
+       }
+       float additional = length(in);
+       if (additional > remain) {
+               out += normalize(in) * remain;
+               return true;
+       } else {
+               out += in;
+               return false;
+       }
+}
+
+glm::vec3 Steering::Halt(const EntityState &state) const noexcept {
+       return state.velocity * -accel;
+}
+
+glm::vec3 Steering::TargetVelocity(const EntityState &state, const glm::vec3 &vel) const noexcept {
+       return (vel - state.velocity) * accel;
+}
+
+glm::vec3 Steering::Seek(const EntityState &state, const ExactLocation &loc) const noexcept {
+       const glm::vec3 diff(loc.Difference(state.pos).Absolute());
+       if (iszero(diff)) {
+               return glm::vec3(0.0f);
+       } else {
+               return TargetVelocity(state, normalize(diff) * speed);
+       }
+}
+
+glm::vec3 Steering::Flee(const EntityState &state, const ExactLocation &loc) const noexcept {
+       const glm::vec3 diff(state.pos.Difference(loc).Absolute());
+       if (iszero(diff)) {
+               return glm::vec3(0.0f);
+       } else {
+               return TargetVelocity(state, normalize(diff) * speed);
+       }
+}
+
+glm::vec3 Steering::Arrive(const EntityState &state, const ExactLocation &loc) const noexcept {
+       const glm::vec3 diff(loc.Difference(state.pos).Absolute());
+       const float dist = length(diff);
+       if (dist < std::numeric_limits<float>::epsilon()) {
+               return glm::vec3(0.0f);
+       } else {
+               const float att_speed = std::min(dist * accel, speed);
+               return TargetVelocity(state, diff * att_speed / dist);
+       }
+}
+
+glm::vec3 Steering::Pursuit(const EntityState &state, const Entity &other) const noexcept {
+       const glm::vec3 diff(state.Diff(other.GetState()));
+       if (iszero(diff)) {
+               return TargetVelocity(state, other.Velocity());
+       } else {
+               const float time_estimate = length(diff) / speed;
+               ExactLocation prediction(other.ChunkCoords(), other.Position() + (other.Velocity() * time_estimate));
+               return Seek(state, prediction);
+       }
+}
+
+glm::vec3 Steering::Evade(const EntityState &state, const Entity &other) const noexcept {
+       const glm::vec3 diff(state.Diff(other.GetState()));
+       if (iszero(diff)) {
+               return TargetVelocity(state, -other.Velocity());
+       } else {
+               const float time_estimate = length(diff) / speed;
+               ExactLocation prediction(other.ChunkCoords(), other.Position() + (other.Velocity() * time_estimate));
+               return Flee(state, prediction);
+       }
+}
+
+glm::vec3 Steering::Wander(const EntityState &state) const noexcept {
+       return TargetVelocity(state, normalize(entity.Heading() * wander_dist + wander_pos) * speed);
+}
+
+glm::vec3 Steering::ObstacleAvoidance(const EntityState &state) const noexcept {
+       return obstacle_dir;
+}
+
+
 World::World(const BlockTypeRegistry &types, const Config &config)
 : config(config)
 , block_type(types)
 , chunks(types)
 , players()
 , entities()
+, rng(
+#ifdef BLANK_PROFILING
+0
+#else
+std::time(nullptr)
+#endif
+)
 , light_direction(config.light_direction)
 , fog_density(config.fog_density) {
-
+       for (int i = 0; i < 4; ++i) {
+               rng.Next<int>();
+       }
 }
 
 World::~World() {
@@ -673,12 +892,6 @@ void World::Update(int dt) {
        }
 }
 
-namespace {
-
-std::vector<WorldCollision> col;
-
-}
-
 void World::ResolveWorldCollision(
        const Entity &entity,
        EntityState &state