]> git.localhorst.tv Git - blank.git/commitdiff
split input handling
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Tue, 29 Sep 2015 15:10:37 +0000 (17:10 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Tue, 29 Sep 2015 15:10:37 +0000 (17:10 +0200)
this should make way for networking input

also, a lot lot lot of mess has piled up that needs cleaning

30 files changed:
doc/running
src/ai/Spawner.cpp
src/app/Config.hpp [new file with mode: 0644]
src/app/Runtime.hpp
src/app/runtime.cpp
src/client/Client.hpp
src/client/InteractiveState.hpp
src/client/MasterState.hpp
src/client/client.cpp
src/client/net.cpp
src/server/ClientConnection.hpp
src/server/Server.hpp
src/server/ServerState.cpp
src/server/ServerState.hpp
src/server/net.cpp
src/standalone/MasterState.cpp
src/standalone/MasterState.hpp
src/ui/ClientController.hpp [new file with mode: 0644]
src/ui/DirectInput.hpp [new file with mode: 0644]
src/ui/HUD.hpp
src/ui/InteractiveManipulator.hpp [new file with mode: 0644]
src/ui/Interface.hpp
src/ui/Keymap.hpp
src/ui/PlayerController.hpp [new file with mode: 0644]
src/ui/ui.cpp
src/world/BlockLookup.hpp
src/world/Player.hpp
src/world/World.hpp
src/world/WorldManipulator.hpp [new file with mode: 0644]
src/world/world.cpp

index 846f27d8ba389f8f98ca7062d03c9ea4245c355b..6c3a2f4e884cadc9fe9e0321f2bfdc318b2536b9 100644 (file)
@@ -97,15 +97,11 @@ as the active block, and button 3 places the active block next to the one
 you're pointing at.
 
 As an alternative to picking, you can also use the mousewheel up/down to
-flip through available blocks.
-
-Q changes the face of the active block (loops over up, down, right, left,
-front, and back) and E changes the turn (none, left, around, and right).
-
-Press N to toggle player/world collision.
+flip through available blocks or access the first 10 via the number keys.
 
 F1 toggles UI rendering.
-F3 toggles a display telling how long on average it takes to compute a frame.
+F2 toggles world rendering.
+F3 toggles the debug overlay.
 F4 toggles audio.
 
 Controls are interpreted by scancode, meaning you don't have to break your
index 2ea8a6904e2f80042b9e76656f2e66246348d385..946e3519c3fb359d8d6bba766c36d42adb4440df 100644 (file)
@@ -72,7 +72,7 @@ void Spawner::CheckDespawn() noexcept {
                }
                bool safe = false;
                for (const Player &ref : refs) {
-                       glm::vec3 diff(ref.entity->AbsoluteDifference(e));
+                       glm::vec3 diff(ref.GetEntity().AbsoluteDifference(e));
                        if (dot(diff, diff) < despawn_range) {
                                safe = true;
                                break;
@@ -95,11 +95,15 @@ void Spawner::TrySpawn() {
        // select random player to punish
        auto &players = world.Players();
        if (players.size() == 0) return;
-       const Player &player = players[random.Next<unsigned short>() % players.size()];
+       size_t player_num = 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;
 
-       int index = random.Next<unsigned int>() % player.chunks->TotalChunks();
+       int index = random.Next<unsigned int>() % player.GetChunks().TotalChunks();
 
-       glm::ivec3 chunk(player.chunks->PositionOf(index));
+       glm::ivec3 chunk(player.GetChunks().PositionOf(index));
 
        glm::ivec3 pos(
                random.Next<unsigned char>() % Chunk::width,
@@ -115,7 +119,7 @@ void Spawner::TrySpawn() {
        //}
 
        // check if the spawn block and the one above it are loaded and inhabitable
-       BlockLookup spawn_block((*player.chunks)[index], pos);
+       BlockLookup spawn_block(player.GetChunks()[index], pos);
        if (!spawn_block || spawn_block.GetType().collide_block) {
                return;
        }
@@ -125,7 +129,7 @@ void Spawner::TrySpawn() {
                return;
        }
 
-       Spawn(*player.entity, chunk, glm::vec3(pos) + glm::vec3(0.5f));
+       Spawn(player.GetEntity(), chunk, glm::vec3(pos) + glm::vec3(0.5f));
 }
 
 void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3 &pos) {
diff --git a/src/app/Config.hpp b/src/app/Config.hpp
new file mode 100644 (file)
index 0000000..4beb062
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef BLANK_APP_CONFIG_HPP_
+#define BLANK_APP_CONFIG_HPP_
+
+#include <cstdint>
+#include <string>
+
+
+namespace blank {
+
+struct Config {
+
+       struct Audio {
+
+               bool enabled = true;
+
+       } audio;
+
+       struct Input {
+
+               bool keyboard = true;
+               bool mouse = true;
+
+               float pitch_sensitivity = -0.0025f;
+               float yaw_sensitivity = -0.001f;
+
+       } input;
+
+       struct Network {
+
+               std::string host = "localhost";
+               std::uint16_t port = 12354;
+
+       } net;
+
+       struct Player {
+
+               std::string name = "default";
+
+       } player;
+
+       struct Video {
+
+               bool dblbuf = true;
+               bool vsync = true;
+               int msaa = 1;
+
+               bool hud = true;
+               bool world = true;
+               bool debug = false;
+
+       } video;
+
+};
+
+}
+
+#endif
index 36808a0e2a0e385cbcc9b2bd013e4310abdba1ef..2d169d0149abbcddf69ee8321f121484f783b7f5 100644 (file)
@@ -40,15 +40,9 @@ public:
        };
 
        struct Config {
-               bool vsync = true;
-               bool doublebuf = true;
-               int multisampling = 1;
-
-               client::Client::Config client = client::Client::Config();
-               Generator::Config gen = Generator::Config();
+               blank::Config game = blank::Config();
                HeadlessEnvironment::Config env = HeadlessEnvironment::Config();
-               Interface::Config interface = Interface::Config();
-               server::Server::Config server = server::Server::Config();
+               Generator::Config gen = Generator::Config();
                World::Config world = World::Config();
        };
 
index 486259a45cb0d89eb25d265ed8a9becf0d7b0c2b..a7beeb0f64c0e7e732809e69ccafaa2e56fc5868 100644 (file)
@@ -120,15 +120,15 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                        const char *param = arg + 2;
                                        // long option
                                        if (strcmp(param, "no-vsync") == 0) {
-                                               config.vsync = false;
+                                               config.game.video.vsync = false;
                                        } else if (strcmp(param, "no-keyboard") == 0) {
-                                               config.interface.keyboard_disabled = true;
+                                               config.game.input.keyboard = false;
                                        } else if (strcmp(param, "no-mouse") == 0) {
-                                               config.interface.mouse_disabled = true;
+                                               config.game.input.mouse = false;
                                        } else if (strcmp(param, "no-hud") == 0) {
-                                               config.interface.visual_disabled = true;
+                                               config.game.video.hud = false;
                                        } else if (strcmp(param, "no-audio") == 0) {
-                                               config.interface.audio_disabled = true;
+                                               config.game.audio.enabled = false;
                                        } else if (strcmp(param, "standalone") == 0) {
                                                target = STANDALONE;
                                        } else if (strcmp(param, "server") == 0) {
@@ -149,7 +149,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                                        cerr << "missing argument to --host" << endl;
                                                        error = true;
                                                } else {
-                                                       config.client.host = argv[i];
+                                                       config.game.net.host = argv[i];
                                                }
                                        } else if (strcmp(param, "port") == 0) {
                                                ++i;
@@ -157,8 +157,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                                        cerr << "missing argument to --port" << endl;
                                                        error = true;
                                                } else {
-                                                       config.server.port = strtoul(argv[i], nullptr, 10);
-                                                       config.client.port = config.server.port;
+                                                       config.game.net.port = strtoul(argv[i], nullptr, 10);
                                                }
                                        } else if (strcmp(param, "player-name") == 0) {
                                                ++i;
@@ -166,7 +165,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                                        cerr << "missing argument to --player-name" << endl;
                                                        error = true;
                                                } else {
-                                                       config.interface.player_name = argv[i];
+                                                       config.game.player.name = argv[i];
                                                }
                                        } else if (strcmp(param, "save-path") == 0) {
                                                ++i;
@@ -194,7 +193,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                for (int j = 1; arg[j] != '\0'; ++j) {
                                        switch (arg[j]) {
                                                case 'd':
-                                                       config.doublebuf = false;
+                                                       config.game.video.dblbuf = false;
                                                        break;
                                                case 'm':
                                                        ++i;
@@ -202,7 +201,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                                                cerr << "missing argument to -m" << endl;
                                                                error = true;
                                                        } else {
-                                                               config.multisampling = strtoul(argv[i], nullptr, 10);
+                                                               config.game.video.msaa = strtoul(argv[i], nullptr, 10);
                                                        }
                                                        break;
                                                case 'n':
@@ -309,10 +308,10 @@ int Runtime::Execute() {
 }
 
 void Runtime::RunStandalone() {
-       Init init(config.doublebuf, config.multisampling);
+       Init init(config.game.video.dblbuf, config.game.video.msaa);
 
        Environment env(init.window, config.env);
-       env.viewport.VSync(config.vsync);
+       env.viewport.VSync(config.game.video.vsync);
 
        WorldSave save(config.env.GetWorldPath(config.world.name));
        if (save.Exists()) {
@@ -324,7 +323,7 @@ void Runtime::RunStandalone() {
        }
 
        Application app(env);
-       standalone::MasterState world_state(env, config.gen, config.interface, config.world, save);
+       standalone::MasterState world_state(env, config.game, config.gen, config.world, save);
        app.PushState(&world_state);
        Run(app);
 }
@@ -342,19 +341,19 @@ void Runtime::RunServer() {
        }
 
        HeadlessApplication app(env);
-       server::ServerState server_state(env, config.gen, config.world, save, config.server);
+       server::ServerState server_state(env, config.gen, config.world, save, config.game);
        app.PushState(&server_state);
        Run(app);
 }
 
 void Runtime::RunClient() {
-       Init init(config.doublebuf, config.multisampling);
+       Init init(config.game.video.dblbuf, config.game.video.msaa);
 
        Environment env(init.window, config.env);
-       env.viewport.VSync(config.vsync);
+       env.viewport.VSync(config.game.video.vsync);
 
        Application app(env);
-       client::MasterState client_state(env, config.world, config.interface, config.client);
+       client::MasterState client_state(env, config.game, config.world);
        app.PushState(&client_state);
        Run(app);
 }
index 68b2f7fc65e5de8434484178d4d537a98a998937..fb7eab1520ea769b7b6526b9659298a797976de7 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef BLANK_CLIENT_CLIENT_HPP_
 #define BLANK_CLIENT_CLIENT_HPP_
 
+#include "../app/Config.hpp"
 #include "../net/Connection.hpp"
 
 #include <string>
@@ -16,13 +17,7 @@ namespace client {
 class Client {
 
 public:
-       struct Config {
-               std::string host = "localhost";
-               Uint16 port = 12354;
-       };
-
-public:
-       explicit Client(const Config &);
+       explicit Client(const Config::Network &);
        ~Client();
 
        void Handle();
index 5a43cd89e63b18757d4675bb7a001e0775db7884..1b9c9a5ba06fc4b42a6a92399daee24ab6ce144a 100644 (file)
@@ -1,17 +1,23 @@
 #ifndef BLANK_CLIENT_INTERACTIVESTATE_HPP_
 #define BLANK_CLIENT_INTERACTIVESTATE_HPP_
 
+#include "../app/State.hpp"
+#include "../ui/ClientController.hpp"
+
 #include "ChunkReceiver.hpp"
 #include "ChunkRequester.hpp"
 #include "../app/IntervalTimer.hpp"
-#include "../app/State.hpp"
 #include "../graphics/SkyBox.hpp"
 #include "../io/WorldSave.hpp"
 #include "../model/Skeletons.hpp"
+#include "../ui/DirectInput.hpp"
+#include "../ui/HUD.hpp"
+#include "../ui/InteractiveManipulator.hpp"
 #include "../ui/Interface.hpp"
 #include "../world/BlockTypeRegistry.hpp"
 #include "../world/ChunkRenderer.hpp"
 #include "../world/EntityState.hpp"
+#include "../world/Player.hpp"
 #include "../world/World.hpp"
 
 #include <list>
@@ -26,13 +32,14 @@ namespace client {
 class MasterState;
 
 class InteractiveState
-: public State {
+: public State
+, public ClientController {
 
 public:
        explicit InteractiveState(MasterState &, std::uint32_t player_id);
 
        World &GetWorld() noexcept { return world; }
-       Interface &GetInterface() noexcept { return interface; }
+       Player &GetPlayer() noexcept { return player; }
        ChunkReceiver &GetChunkReceiver() noexcept { return chunk_receiver; }
        Skeletons &GetSkeletons() noexcept { return skeletons; }
 
@@ -45,11 +52,21 @@ public:
        void PushPlayerUpdate(const Entity &, int dt);
        void MergePlayerCorrection(std::uint16_t, const EntityState &);
 
+       void SetAudio(bool) override;
+       void SetVideo(bool) override;
+       void SetHUD(bool) override;
+       void SetDebug(bool) override;
+       void Exit() override;
+
 private:
        MasterState &master;
        BlockTypeRegistry block_types;
        WorldSave save;
        World world;
+       Player &player;
+       HUD hud;
+       InteractiveManipulator manip;
+       DirectInput input;
        Interface interface;
        ChunkRequester chunk_requester;
        ChunkReceiver chunk_receiver;
index 50773a63d03e7d700b7e7e5b7c63d6da8f8b33c3..3c9b23c38fc571ca63351aabcb205b0862d733eb 100644 (file)
@@ -1,11 +1,13 @@
 #ifndef BLANK_CLIENT_CLIENTSTATE_HPP_
 #define BLANK_CLIENT_CLIENTSTATE_HPP_
 
+#include "../app/State.hpp"
+#include "../net/ConnectionHandler.hpp"
+
 #include "Client.hpp"
 #include "InitialState.hpp"
 #include "InteractiveState.hpp"
-#include "../app/State.hpp"
-#include "../net/ConnectionHandler.hpp"
+#include "../app/Config.hpp"
 
 #include <map>
 #include <memory>
@@ -26,18 +28,18 @@ class MasterState
 public:
        MasterState(
                Environment &,
-               const World::Config &,
-               const Interface::Config &,
-               const Client::Config &
+               Config &,
+               const World::Config &
        );
 
        Client &GetClient() noexcept { return client; }
        Environment &GetEnv() noexcept { return env; }
 
+       Config &GetConfig() noexcept { return config; }
+       const Config &GetConfig() const noexcept { return config; }
+
        World::Config &GetWorldConf() noexcept { return world_conf; }
        const World::Config &GetWorldConf() const noexcept { return world_conf; }
-       const Interface::Config &GetInterfaceConf() const noexcept { return intf_conf; }
-       const Client::Config &GetClientConf() const noexcept { return client_conf; }
 
        void Quit();
 
@@ -68,9 +70,8 @@ private:
 
 private:
        Environment &env;
+       Config &config;
        World::Config world_conf;
-       const Interface::Config &intf_conf;
-       const Client::Config &client_conf;
        std::unique_ptr<InteractiveState> state;
        Client client;
 
index 521b149ae824185cd44fda750f9ec38c48917ba7..fbf6bf28f9c2c1bb781b9d4c42edab76d45184da 100644 (file)
@@ -106,18 +106,17 @@ void InitialState::Render(Viewport &viewport) {
 InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
 : master(master)
 , block_types()
-, save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetClientConf().host))
+, save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetConfig().net.host))
 , world(block_types, master.GetWorldConf())
-, interface(
-       master.GetInterfaceConf(),
-       master.GetEnv(),
-       world,
-       world.AddPlayer(master.GetInterfaceConf().player_name, player_id)
-)
+, player(*world.AddPlayer(master.GetConfig().player.name))
+, hud(master.GetEnv(), master.GetConfig(), player)
+, manip(master.GetEnv(), player.GetEntity())
+, input(world, player, manip)
+, interface(master.GetConfig(), master.GetEnv().keymap, input, *this)
 // TODO: looks like chunk requester and receiver can and should be merged
 , chunk_requester(world.Chunks(), save)
 , chunk_receiver(world.Chunks())
-, chunk_renderer(*interface.GetPlayer().chunks)
+, chunk_renderer(player.GetChunks())
 , skeletons()
 , loop_timer(16)
 , sky(master.GetEnv().loader.LoadCubeMap("skybox"))
@@ -130,8 +129,6 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
        chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index);
        chunk_renderer.FogDensity(master.GetWorldConf().fog_density);
        skeletons.Load();
-       // TODO: better solution for initializing HUD
-       interface.SelectNext();
        loop_timer.Start();
 }
 
@@ -168,12 +165,19 @@ void InteractiveState::Handle(const SDL_Event &event) {
 }
 
 void InteractiveState::Update(int dt) {
+       input.Update(dt);
+       if (input.BlockFocus()) {
+               hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block);
+       } else if (input.EntityFocus()) {
+               hud.FocusEntity(*input.EntityFocus().entity);
+       }
+       hud.Display(block_types[player.GetInventorySlot() + 1]);
        loop_timer.Update(dt);
        master.Update(dt);
        chunk_receiver.Update(dt);
        chunk_requester.Update(dt);
 
-       interface.Update(dt);
+       hud.Update(dt);
        int world_dt = 0;
        while (loop_timer.HitOnce()) {
                world.Update(loop_timer.Interval());
@@ -182,17 +186,15 @@ void InteractiveState::Update(int dt) {
        }
        chunk_renderer.Update(dt);
 
-       Entity &player = *interface.GetPlayer().entity;
-
        if (world_dt > 0) {
-               PushPlayerUpdate(player, world_dt);
+               PushPlayerUpdate(player.GetEntity(), world_dt);
        }
 
-       glm::mat4 trans = player.Transform(player.ChunkCoords());
+       glm::mat4 trans = player.GetEntity().Transform(player.GetEntity().ChunkCoords());
        glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
        glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
-       master.GetEnv().audio.Position(player.Position());
-       master.GetEnv().audio.Velocity(player.Velocity());
+       master.GetEnv().audio.Position(player.GetEntity().Position());
+       master.GetEnv().audio.Velocity(player.GetEntity().Velocity());
        master.GetEnv().audio.Orientation(dir, up);
 }
 
@@ -233,7 +235,7 @@ void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &co
        }
 
        EntityState replay_state(corrected_state);
-       EntityState &player_state = interface.GetPlayer().entity->GetState();
+       EntityState &player_state = player.GetEntity().GetState();
 
        if (entry != end) {
                entry->state.chunk_pos = replay_state.chunk_pos;
@@ -274,26 +276,65 @@ void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &co
 }
 
 void InteractiveState::Render(Viewport &viewport) {
-       Entity &player = *interface.GetPlayer().entity;
-       viewport.WorldPosition(player.Transform(player.ChunkCoords()));
-       chunk_renderer.Render(viewport);
-       world.Render(viewport);
-       sky.Render(viewport);
-       interface.Render(viewport);
+       viewport.WorldPosition(player.GetEntity().Transform(player.GetEntity().ChunkCoords()));
+       if (master.GetConfig().video.world) {
+               chunk_renderer.Render(viewport);
+               world.Render(viewport);
+               sky.Render(viewport);
+       }
+       hud.Render(viewport);
+}
+
+void InteractiveState::SetAudio(bool b) {
+       master.GetConfig().audio.enabled = b;
+       if (b) {
+               hud.PostMessage("Audio enabled");
+       } else {
+               hud.PostMessage("Audio disabled");
+       }
+}
+
+void InteractiveState::SetVideo(bool b) {
+       master.GetConfig().video.world = b;
+       if (b) {
+               hud.PostMessage("World rendering enabled");
+       } else {
+               hud.PostMessage("World rendering disabled");
+       }
+}
+
+void InteractiveState::SetHUD(bool b) {
+       master.GetConfig().video.hud = b;
+       if (b) {
+               hud.PostMessage("HUD rendering enabled");
+       } else {
+               hud.PostMessage("HUD rendering disabled");
+       }
+}
+
+void InteractiveState::SetDebug(bool b) {
+       master.GetConfig().video.debug = b;
+       if (b) {
+               hud.PostMessage("Debug rendering enabled");
+       } else {
+               hud.PostMessage("Debug rendering disabled");
+       }
+}
+
+void InteractiveState::Exit() {
+       master.Quit();
 }
 
 
 MasterState::MasterState(
        Environment &env,
-       const World::Config &wc,
-       const Interface::Config &ic,
-       const Client::Config &cc)
+       Config &config,
+       const World::Config &wc)
 : env(env)
+, config(config)
 , world_conf(wc)
-, intf_conf(ic)
-, client_conf(cc)
 , state()
-, client(cc)
+, client(config.net)
 , init_state(*this)
 , login_packet(-1)
 , update_status()
@@ -311,7 +352,7 @@ void MasterState::Quit() {
 
 
 void MasterState::OnEnter() {
-       login_packet = client.SendLogin(intf_conf.player_name);
+       login_packet = client.SendLogin(config.player.name);
        env.state.Push(&init_state);
 }
 
@@ -335,7 +376,7 @@ void MasterState::Render(Viewport &) {
 
 void MasterState::OnPacketLost(uint16_t id) {
        if (id == login_packet) {
-               login_packet = client.SendLogin(intf_conf.player_name);
+               login_packet = client.SendLogin(config.player.name);
        }
 }
 
@@ -364,7 +405,7 @@ void MasterState::On(const Packet::Join &pack) {
        pack.ReadPlayerID(player_id);
        state.reset(new InteractiveState(*this, player_id));
 
-       pack.ReadPlayerState(state->GetInterface().GetPlayer().entity->GetState());
+       pack.ReadPlayerState(state->GetPlayer().GetEntity().GetState());
 
        env.state.PopAfter(this);
        env.state.Push(state.get());
index 31f13d947631a2e2c615bc3a5a891f96bc145238..a5feaf5f7cddd4ac4038d2f5886432fa9d49686c 100644 (file)
@@ -177,7 +177,7 @@ IPaddress client_resolve(const char *host, Uint16 port) {
 
 }
 
-Client::Client(const Config &conf)
+Client::Client(const Config::Network &conf)
 : conn(client_resolve(conf.host.c_str(), conf.port))
 , client_sock(client_bind(0))
 , client_pack{ -1, nullptr, 0 } {
index 99657de3642d262b70dbdcee5504e32efb66680b..2089dd17c8eec85a7acf7e6e7d3d930f03946a5f 100644 (file)
@@ -47,13 +47,13 @@ public:
        /// send the previously prepared packet of non-default length
        std::uint16_t Send(std::size_t len);
 
-       void AttachPlayer(const Player &);
+       void AttachPlayer(Player &);
        void DetachPlayer();
-       bool HasPlayer() const noexcept { return player.entity; }
-       Entity &PlayerEntity() noexcept { return *player.entity; }
-       const Entity &PlayerEntity() const noexcept { return *player.entity; }
-       ChunkIndex &PlayerChunks() noexcept { return *player.chunks; }
-       const ChunkIndex &PlayerChunks() const noexcept { return *player.chunks; }
+       bool HasPlayer() const noexcept { return player; }
+       Entity &PlayerEntity() noexcept { return player->GetEntity(); }
+       const Entity &PlayerEntity() const noexcept { return player->GetEntity(); }
+       ChunkIndex &PlayerChunks() noexcept { return player->GetChunks(); }
+       const ChunkIndex &PlayerChunks() const noexcept { return player->GetChunks(); }
 
        void SetPlayerModel(const CompositeModel &) noexcept;
        bool HasPlayerModel() const noexcept;
@@ -95,7 +95,7 @@ private:
 private:
        Server &server;
        Connection conn;
-       Player player;
+       Player *player;
        const CompositeModel *player_model;
        std::list<SpawnStatus> spawns;
        unsigned int confirm_wait;
index bf2399180111354191c2a922bfe6754c84464ac7..cf4fc0433e889f5966bee3806eee1427d57c521c 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef BLANK_SERVER_SERVER_HPP
 #define BLANK_SERVER_SERVER_HPP
 
+#include "../app/Config.hpp"
+
 #include <list>
 #include <SDL_net.h>
 
@@ -17,12 +19,7 @@ class ClientConnection;
 class Server {
 
 public:
-       struct Config {
-               Uint16 port = 12354;
-       };
-
-public:
-       Server(const Config &, World &);
+       Server(const Config::Network &, World &);
        ~Server();
 
        void Handle();
index ebe675f05b2908007b57626106b9a7e033ecf3fd..8657df08983e4efff57a086cfb0ba964b68853c4 100644 (file)
@@ -15,7 +15,7 @@ ServerState::ServerState(
        const Generator::Config &gc,
        const World::Config &wc,
        const WorldSave &ws,
-       const Server::Config &sc
+       const Config &config
 )
 : env(env)
 , block_types()
@@ -24,7 +24,7 @@ ServerState::ServerState(
 , chunk_loader(world.Chunks(), generator, ws)
 , skeletons()
 , spawner(world, skeletons, gc.seed)
-, server(sc, world)
+, server(config.net, world)
 , loop_timer(16) {
        TextureIndex tex_index;
        env.loader.LoadBlockTypes("default", block_types, tex_index);
@@ -34,7 +34,7 @@ ServerState::ServerState(
 
        loop_timer.Start();
 
-       std::cout << "listening on UDP port " << sc.port << std::endl;
+       std::cout << "listening on UDP port " << config.net.port << std::endl;
 }
 
 
index 8f564ab5105c3f0d7f2220fa2f5e17b669c0be4c..31ad5217535567d4bf1fd63490ae72f06f6c9b08 100644 (file)
@@ -14,6 +14,7 @@
 
 namespace blank {
 
+class Config;
 class HeadlessEnvironment;
 class WorldSave;
 
@@ -28,7 +29,7 @@ public:
                const Generator::Config &,
                const World::Config &,
                const WorldSave &,
-               const Server::Config &
+               const Config &
        );
 
        void Handle(const SDL_Event &) override;
index 73595344a30271f08b713ebbfeebc0ac860c5589..44a42682497276f0c647578bd592c1725e94adde 100644 (file)
@@ -172,7 +172,7 @@ void ChunkTransmitter::Release() {
 ClientConnection::ClientConnection(Server &server, const IPaddress &addr)
 : server(server)
 , conn(addr)
-, player(nullptr, nullptr)
+, player(nullptr)
 , player_model(nullptr)
 , spawns()
 , confirm_wait(0)
@@ -264,7 +264,7 @@ ClientConnection::SpawnStatus::~SpawnStatus() {
 
 bool ClientConnection::CanSpawn(const Entity &e) const noexcept {
        return
-               &e != player.entity &&
+               &e != &PlayerEntity() &&
                !e.Dead() &&
                manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7;
 }
@@ -387,14 +387,14 @@ void ClientConnection::CheckChunkQueue() {
        }
 }
 
-void ClientConnection::AttachPlayer(const Player &new_player) {
+void ClientConnection::AttachPlayer(Player &new_player) {
        DetachPlayer();
-       player = new_player;
-       player.entity->Ref();
+       player = &new_player;
+       PlayerEntity().Ref();
 
-       old_base = player.chunks->Base();
-       Chunk::Pos begin = player.chunks->CoordsBegin();
-       Chunk::Pos end = player.chunks->CoordsEnd();
+       old_base = PlayerChunks().Base();
+       Chunk::Pos begin = PlayerChunks().CoordsBegin();
+       Chunk::Pos end = PlayerChunks().CoordsEnd();
        for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) {
                for (pos.y = begin.y; pos.y < end.y; ++pos.y) {
                        for (pos.x = begin.x; pos.x < end.x; ++pos.x) {
@@ -403,19 +403,18 @@ void ClientConnection::AttachPlayer(const Player &new_player) {
                }
        }
        if (HasPlayerModel()) {
-               GetPlayerModel().Instantiate(player.entity->GetModel());
+               GetPlayerModel().Instantiate(PlayerEntity().GetModel());
        }
 
-       cout << "player \"" << player.entity->Name() << "\" joined" << endl;
+       cout << "player \"" << player->Name() << "\" joined" << endl;
 }
 
 void ClientConnection::DetachPlayer() {
        if (!HasPlayer()) return;
-       cout << "player \"" << player.entity->Name() << "\" left" << endl;
-       player.entity->Kill();
-       player.entity->UnRef();
-       player.entity = nullptr;
-       player.chunks = nullptr;
+       cout << "player \"" << player->Name() << "\" left" << endl;
+       player->GetEntity().Kill();
+       player->GetEntity().UnRef();
+       player = nullptr;
        transmitter.Abort();
        chunk_queue.clear();
 }
@@ -479,18 +478,18 @@ void ClientConnection::On(const Packet::Login &pack) {
        string name;
        pack.ReadPlayerName(name);
 
-       Player new_player = server.GetWorld().AddPlayer(name);
+       Player *new_player = server.GetWorld().AddPlayer(name);
 
-       if (new_player.entity) {
+       if (new_player) {
                // success!
-               AttachPlayer(new_player);
+               AttachPlayer(*new_player);
                cout << "accepted login from player \"" << name << '"' << endl;
                auto response = Prepare<Packet::Join>();
-               response.WritePlayer(*new_player.entity);
+               response.WritePlayer(new_player->GetEntity());
                response.WriteWorldName(server.GetWorld().Name());
                Send();
                // set up update tracking
-               player_update_state = new_player.entity->GetState();
+               player_update_state = new_player->GetEntity().GetState();
                player_update_pack = pack.Seq();
                player_update_timer.Reset();
                player_update_timer.Start();
@@ -522,7 +521,7 @@ void ClientConnection::On(const Packet::PlayerUpdate &pack) {
 }
 
 
-Server::Server(const Config &conf, World &world)
+Server::Server(const Config::Network &conf, World &world)
 : serv_sock(nullptr)
 , serv_pack{ -1, nullptr, 0 }
 , clients()
index 30925a971db74b41ea39cc419bcfb1b5acac70f2..272e5a39ac2afbf1caff668d89e991b847cdaf24 100644 (file)
@@ -1,5 +1,6 @@
 #include "MasterState.hpp"
 
+#include "../app/Config.hpp"
 #include "../app/Environment.hpp"
 #include "../app/init.hpp"
 #include "../app/TextureIndex.hpp"
@@ -12,18 +13,23 @@ namespace standalone {
 
 MasterState::MasterState(
        Environment &env,
+       Config &config,
        const Generator::Config &gc,
-       const Interface::Config &ic,
        const World::Config &wc,
        const WorldSave &save
 )
-: env(env)
+: config(config)
+, env(env)
 , block_types()
 , world(block_types, wc)
-, interface(ic, env, world, world.AddPlayer(ic.player_name))
+, player(*world.AddPlayer(config.player.name))
+, hud(env, config, player)
+, manip(env, player.GetEntity())
+, input(world, player, manip)
+, interface(config, env.keymap, input, *this)
 , generator(gc)
 , chunk_loader(world.Chunks(), generator, save)
-, chunk_renderer(*interface.GetPlayer().chunks)
+, chunk_renderer(player.GetChunks())
 , skeletons()
 , spawner(world, skeletons, gc.seed)
 , sky(env.loader.LoadCubeMap("skybox"))
@@ -35,8 +41,6 @@ MasterState::MasterState(
        chunk_renderer.FogDensity(wc.fog_density);
        skeletons.Load();
        spawner.LimitSkeletons(0, skeletons.Size());
-       // TODO: better solution for initializing HUD
-       interface.SelectNext();
 }
 
 
@@ -75,30 +79,76 @@ void MasterState::Handle(const SDL_Event &event) {
 }
 
 void MasterState::Update(int dt) {
-       interface.Update(dt);
+       input.Update(dt);
+       if (input.BlockFocus()) {
+               hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block);
+       } else if (input.EntityFocus()) {
+               hud.FocusEntity(*input.EntityFocus().entity);
+       }
+       hud.Display(block_types[player.GetInventorySlot() + 1]);
+       hud.Update(dt);
        spawner.Update(dt);
        world.Update(dt);
        chunk_loader.Update(dt);
        chunk_renderer.Update(dt);
 
-       Entity &player = *interface.GetPlayer().entity;
-
-       glm::mat4 trans = player.Transform(player.ChunkCoords());
+       glm::mat4 trans = player.GetEntity().Transform(player.GetEntity().ChunkCoords());
        glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
        glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
-       env.audio.Position(player.Position());
-       env.audio.Velocity(player.Velocity());
+       env.audio.Position(player.GetEntity().Position());
+       env.audio.Velocity(player.GetEntity().Velocity());
        env.audio.Orientation(dir, up);
 }
 
 void MasterState::Render(Viewport &viewport) {
-       Entity &player = *interface.GetPlayer().entity;
-       viewport.WorldPosition(player.Transform(player.ChunkCoords()));
-       chunk_renderer.Render(viewport);
-       world.Render(viewport);
-       sky.Render(viewport);
+       viewport.WorldPosition(player.GetEntity().Transform(player.GetEntity().ChunkCoords()));
+       if (config.video.world) {
+               chunk_renderer.Render(viewport);
+               world.Render(viewport);
+               sky.Render(viewport);
+       }
+       hud.Render(viewport);
+}
+
+
+void MasterState::SetAudio(bool b) {
+       config.audio.enabled = b;
+       if (b) {
+               hud.PostMessage("Audio enabled");
+       } else {
+               hud.PostMessage("Audio disabled");
+       }
+}
+
+void MasterState::SetVideo(bool b) {
+       config.video.world = b;
+       if (b) {
+               hud.PostMessage("World rendering enabled");
+       } else {
+               hud.PostMessage("World rendering disabled");
+       }
+}
+
+void MasterState::SetHUD(bool b) {
+       config.video.hud = b;
+       if (b) {
+               hud.PostMessage("HUD rendering enabled");
+       } else {
+               hud.PostMessage("HUD rendering disabled");
+       }
+}
+
+void MasterState::SetDebug(bool b) {
+       config.video.debug = b;
+       if (b) {
+               hud.PostMessage("Debug rendering enabled");
+       } else {
+               hud.PostMessage("Debug rendering disabled");
+       }
+}
 
-       interface.Render(viewport);
+void MasterState::Exit() {
+       env.state.Pop();
 }
 
 }
index ca5cab29879ebdaf6c9a7cd73ec895736213666a..f2c702fb2033df38fbfe39df28e1ac930760e5f8 100644 (file)
@@ -2,34 +2,41 @@
 #define BLANK_STANDALONE_MASTERSTATE_HPP_
 
 #include "../app/State.hpp"
+#include "../ui/ClientController.hpp"
 
 #include "PreloadState.hpp"
 #include "UnloadState.hpp"
 #include "../ai/Spawner.hpp"
 #include "../graphics/SkyBox.hpp"
 #include "../model/Skeletons.hpp"
+#include "../ui/DirectInput.hpp"
+#include "../ui/HUD.hpp"
+#include "../ui/InteractiveManipulator.hpp"
 #include "../ui/Interface.hpp"
 #include "../world/BlockTypeRegistry.hpp"
 #include "../world/ChunkLoader.hpp"
 #include "../world/ChunkRenderer.hpp"
 #include "../world/Generator.hpp"
+#include "../world/Player.hpp"
 #include "../world/World.hpp"
 
 
 namespace blank {
 
+class Config;
 class Environment;
 
 namespace standalone {
 
 class MasterState
-: public State {
+: public State
+, public ClientController {
 
 public:
        MasterState(
                Environment &,
+               Config &,
                const Generator::Config &,
-               const Interface::Config &,
                const World::Config &,
                const WorldSave &
        );
@@ -43,10 +50,21 @@ public:
        World &GetWorld() noexcept { return world; }
        Interface &GetInterface() noexcept { return interface; }
 
+       void SetAudio(bool) override;
+       void SetVideo(bool) override;
+       void SetHUD(bool) override;
+       void SetDebug(bool) override;
+       void Exit() override;
+
 private:
+       Config &config;
        Environment &env;
        BlockTypeRegistry block_types;
        World world;
+       Player &player;
+       HUD hud;
+       InteractiveManipulator manip;
+       DirectInput input;
        Interface interface;
        Generator generator;
        ChunkLoader chunk_loader;
diff --git a/src/ui/ClientController.hpp b/src/ui/ClientController.hpp
new file mode 100644 (file)
index 0000000..56bb78b
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef BLANK_UI_CLIENTCONTROLLER_HPP_
+#define BLANK_UI_CLIENTCONTROLLER_HPP_
+
+
+namespace blank {
+
+struct ClientController {
+
+       virtual void SetAudio(bool) = 0;
+       virtual void SetVideo(bool) = 0;
+       virtual void SetHUD(bool) = 0;
+       virtual void SetDebug(bool) = 0;
+
+       virtual void Exit() = 0;
+
+};
+
+}
+
+#endif
diff --git a/src/ui/DirectInput.hpp b/src/ui/DirectInput.hpp
new file mode 100644 (file)
index 0000000..525c856
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef BLANK_UI_DIRECTINPUT_HPP_
+#define BLANK_UI_DIRECTINPUT_HPP_
+
+#include "PlayerController.hpp"
+
+#include "../app/IntervalTimer.hpp"
+#include "../world/EntityCollision.hpp"
+#include "../world/WorldCollision.hpp"
+
+
+namespace blank {
+
+class Player;
+class World;
+struct WorldManipulator;
+
+class DirectInput
+: public PlayerController {
+
+public:
+       DirectInput(World &, Player &, WorldManipulator &);
+
+       const WorldCollision &BlockFocus() const noexcept { return aim_world; }
+       const EntityCollision &EntityFocus() const noexcept { return aim_entity; }
+
+       void Update(int dt);
+
+       void SetMovement(const glm::vec3 &) override;
+       void TurnHead(float pitch, float yaw) override;
+       void StartPrimaryAction() override;
+       void StopPrimaryAction() override;
+       void StartSecondaryAction() override;
+       void StopSecondaryAction() override;
+       void StartTertiaryAction() override;
+       void StopTertiaryAction() override;
+       void SelectInventory(int) override;
+
+private:
+       void UpdatePlayer();
+
+       void PickBlock();
+       void PlaceBlock();
+       void RemoveBlock();
+
+private:
+       World &world;
+       Player &player;
+       WorldManipulator &manip;
+
+       WorldCollision aim_world;
+       EntityCollision aim_entity;
+
+       glm::vec3 move_dir;
+       float pitch;
+       float yaw;
+       bool dirty;
+
+       int active_slot;
+
+       IntervalTimer place_timer;
+       IntervalTimer remove_timer;
+
+};
+
+}
+
+#endif
index 1917b130c926772b5518806c1fc04af157447271..d15643f4d4476090fc98138e10bcdaeffc2cbd14 100644 (file)
@@ -2,6 +2,7 @@
 #define BLANK_UI_HUD_H_
 
 #include "FixedText.hpp"
+#include "MessageBox.hpp"
 #include "../model/EntityModel.hpp"
 #include "../model/OutlineModel.hpp"
 
@@ -12,34 +13,75 @@ namespace blank {
 
 class Block;
 class BlockTypeRegistry;
+class Config;
+class Environment;
 class Font;
+class Player;
 class Viewport;
 
 class HUD {
 
 public:
-       HUD(const BlockTypeRegistry &, const Font &);
+       explicit HUD(Environment &, Config &, const Player &);
 
        HUD(const HUD &) = delete;
        HUD &operator =(const HUD &) = delete;
 
+       // focus
+       void FocusBlock(const Chunk &, int);
+       void FocusEntity(const Entity &);
+       void FocusNone();
+
+       // "inventory"
        void DisplayNone();
-       void Display(const Block &);
+       void Display(const BlockType &);
+
+       // debug overlay
+       void UpdateDebug();
+       void UpdateCounter();
+       void UpdatePosition();
+       void UpdateOrientation();
 
+       // message box
+       void PostMessage(const char *);
+       void PostMessage(const std::string &msg) {
+               PostMessage(msg.c_str());
+       }
+
+       void Update(int dt);
        void Render(Viewport &) noexcept;
 
 private:
-       const BlockTypeRegistry &types;
-       const Font &font;
+       Environment &env;
+       Config &config;
+       const Player &player;
+
+       // block focus
+       OutlineModel outline;
+       glm::mat4 outline_transform;
+       bool outline_visible;
 
+       // "inventory"
        EntityModel block;
        EntityModel::Buffer block_buf;
        glm::mat4 block_transform;
-
        FixedText block_label;
-
        bool block_visible;
 
+       // debug overlay
+       FixedText counter_text;
+       FixedText position_text;
+       FixedText orientation_text;
+       FixedText block_text;
+       FixedText entity_text;
+       bool show_block;
+       bool show_entity;
+
+       // message box
+       MessageBox messages;
+       IntervalTimer msg_timer;
+
+       // crosshair
        OutlineModel crosshair;
 
 };
diff --git a/src/ui/InteractiveManipulator.hpp b/src/ui/InteractiveManipulator.hpp
new file mode 100644 (file)
index 0000000..6975d9e
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef BLANK_UI_INTERACTIVEMANIPULATOR_HPP_
+#define BLANK_UI_INTERACTIVEMANIPULATOR_HPP_
+
+#include "../world/WorldManipulator.hpp"
+
+#include "../audio/Sound.hpp"
+
+
+namespace blank {
+
+class Audio;
+class Entity;
+class Environment;
+
+class InteractiveManipulator
+: public WorldManipulator {
+
+public:
+       explicit InteractiveManipulator(Environment &, Entity &);
+
+       void SetBlock(Chunk &, int, const Block &) override;
+
+private:
+       Entity &player;
+       Audio &audio;
+       Sound place_sound;
+       Sound remove_sound;
+
+};
+
+}
+
+#endif
index 72db5d13eb94fbea290c434e8eae260234925cb3..aa040a1ede174e63a20708bdb1208fe0118e4896 100644 (file)
@@ -1,50 +1,24 @@
 #ifndef BLANK_UI_INTERFACE_HPP_
 #define BLANK_UI_INTERFACE_HPP_
 
-#include "FixedText.hpp"
-#include "HUD.hpp"
-#include "MessageBox.hpp"
-#include "../app/FPSController.hpp"
-#include "../app/IntervalTimer.hpp"
-#include "../audio/Sound.hpp"
-#include "../model/geometry.hpp"
-#include "../model/OutlineModel.hpp"
-#include "../world/Block.hpp"
-#include "../world/EntityCollision.hpp"
-#include "../world/Player.hpp"
-#include "../world/WorldCollision.hpp"
+#include "../app/Config.hpp"
 
-#include <string>
-#include <glm/glm.hpp>
 #include <SDL.h>
+#include <glm/glm.hpp>
 
 
 namespace blank {
 
-class Entity;
-class Environment;
-class Viewport;
-class World;
+struct ClientController;
+class Keymap;
+struct PlayerController;
 
 class Interface {
 
 public:
-       struct Config {
-               std::string player_name = "default";
-
-               float move_velocity = 0.005f;
-               float pitch_sensitivity = -0.0025f;
-               float yaw_sensitivity = -0.001f;
+       Interface(Config &, const Keymap &, PlayerController &, ClientController &);
 
-               bool keyboard_disabled = false;
-               bool mouse_disabled = false;
-               bool audio_disabled = false;
-               bool visual_disabled = false;
-       };
-
-       Interface(const Config &, Environment &, World &, const Player &);
-
-       const Player &GetPlayer() noexcept { return player; }
+       void SetInventorySlots(int num) { num_slots = num; }
 
        void HandlePress(const SDL_KeyboardEvent &);
        void HandleRelease(const SDL_KeyboardEvent &);
@@ -53,79 +27,20 @@ public:
        void HandleRelease(const SDL_MouseButtonEvent &);
        void Handle(const SDL_MouseWheelEvent &);
 
-       void FaceBlock();
-       void TurnBlock();
-
-       void ToggleCollision();
-
-       void PickBlock();
-       void PlaceBlock();
-       void RemoveBlock() noexcept;
-
-       void SelectNext();
-       void SelectPrevious();
-
-       void ToggleAudio();
-       void ToggleVisual();
-
-       void ToggleDebug();
-       void UpdateCounter();
-       void UpdatePosition();
-       void UpdateOrientation();
-       void UpdateBlockInfo();
-       void UpdateEntityInfo();
-
-       void PostMessage(const char *);
-       void PostMessage(const std::string &msg) {
-               PostMessage(msg.c_str());
-       }
-
-       void Update(int dt);
-
-       void Render(Viewport &) noexcept;
-
 private:
-       void CheckAim();
-       void UpdateOutline();
+       void UpdateMovement();
+       void InvAbs(int slot);
+       void InvRel(int delta);
 
 private:
-       Environment &env;
-       World &world;
-       Player player;
-       FPSController ctrl;
-       HUD hud;
-
-       Ray aim;
-       WorldCollision aim_world;
-       EntityCollision aim_entity;
-
-       OutlineModel outline;
-       glm::mat4 outline_transform;
-
-       FixedText counter_text;
-       FixedText position_text;
-       FixedText orientation_text;
-       FixedText block_text;
-       FixedText entity_text;
-       Block last_block;
-       Entity *last_entity;
-       MessageBox messages;
-       IntervalTimer msg_timer;
-
-       Config config;
-
-       IntervalTimer place_timer;
-       IntervalTimer remove_timer;
-
-       Block remove;
-       Block selection;
-
-       Sound place_sound;
-       Sound remove_sound;
+       Config &config;
+       const Keymap &keymap;
+       PlayerController &player_ctrl;
+       ClientController &client_ctrl;
 
        glm::ivec3 fwd, rev;
-
-       bool debug;
+       int slot;
+       int num_slots;
 
 };
 
index 0e34ba9979d2406a78b98e91171e242702c0bcda..a3e1d436766b62d828d88d341c449da6cd28ed29 100644 (file)
@@ -21,18 +21,26 @@ public:
                MOVE_UP,
                MOVE_DOWN,
 
-               BLOCK_FACE,
-               BLOCK_TURN,
-               BLOCK_NEXT,
-               BLOCK_PREV,
+               PRIMARY,
+               SECONDARY,
+               TERTIARY,
+
+               INV_NEXT,
+               INV_PREVIOUS,
+               INV_1,
+               INV_2,
+               INV_3,
+               INV_4,
+               INV_5,
+               INV_6,
+               INV_7,
+               INV_8,
+               INV_9,
+               INV_10,
 
-               BLOCK_PLACE,
-               BLOCK_PICK,
-               BLOCK_REMOVE,
-
-               TOGGLE_COLLISION,
                TOGGLE_AUDIO,
-               TOGGLE_VISUAL,
+               TOGGLE_VIDEO,
+               TOGGLE_HUD,
                TOGGLE_DEBUG,
 
                EXIT,
@@ -45,9 +53,9 @@ public:
        Keymap();
 
        void Map(SDL_Scancode scancode, Action);
-       Action Lookup(SDL_Scancode scancode);
-       Action Lookup(const SDL_Keysym &s) { return Lookup(s.scancode); }
-       Action Lookup(const SDL_KeyboardEvent &e) { return Lookup(e.keysym); }
+       Action Lookup(SDL_Scancode scancode) const;
+       Action Lookup(const SDL_Keysym &s) const { return Lookup(s.scancode); }
+       Action Lookup(const SDL_KeyboardEvent &e) const { return Lookup(e.keysym); }
 
        void LoadDefault();
 
diff --git a/src/ui/PlayerController.hpp b/src/ui/PlayerController.hpp
new file mode 100644 (file)
index 0000000..8d9e5a3
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef BLANK_UI_PLAYERCONTROLLER_HPP_
+#define BLANK_UI_PLAYERCONTROLLER_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+struct PlayerController {
+
+       /// set desired direction of movement
+       /// the magnitude (clamped to [0..1]) can be used to attenuate target velocity
+       virtual void SetMovement(const glm::vec3 &) = 0;
+       /// turn the controlled entity's head by given pitch and yaw deltas
+       virtual void TurnHead(float pitch, float yaw) = 0;
+
+       /// start doing primary action
+       /// what exactly this means depends on the active item
+       virtual void StartPrimaryAction() = 0;
+       /// stop doing primary action
+       virtual void StopPrimaryAction() = 0;
+       // etc
+       virtual void StartSecondaryAction() = 0;
+       virtual void StopSecondaryAction() = 0;
+       virtual void StartTertiaryAction() = 0;
+       virtual void StopTertiaryAction() = 0;
+
+       /// set the item at given inventory slot as active
+       virtual void SelectInventory(int) = 0;
+
+};
+
+}
+
+#endif
index 02f372e5c66c57d7a827362814dc61c97aa66bc1..1e8a980112c0982fe69a6729849d37b75e50a959 100644 (file)
@@ -1,8 +1,13 @@
+#include "ClientController.hpp"
+#include "DirectInput.hpp"
 #include "HUD.hpp"
+#include "InteractiveManipulator.hpp"
 #include "Interface.hpp"
 #include "Keymap.hpp"
+#include "PlayerController.hpp"
 
 #include "../app/Assets.hpp"
+#include "../app/Config.hpp"
 #include "../app/Environment.hpp"
 #include "../app/FrameCounter.hpp"
 #include "../app/init.hpp"
 #include "../model/shapes.hpp"
 #include "../world/BlockLookup.hpp"
 #include "../world/World.hpp"
+#include "../world/WorldManipulator.hpp"
 
 #include <algorithm>
 #include <cmath>
 #include <iostream>
+#include <map>
 #include <sstream>
 #include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtx/rotate_vector.hpp>
 #include <glm/gtx/io.hpp>
 
 
 namespace blank {
 
-HUD::HUD(const BlockTypeRegistry &types, const Font &font)
-: types(types)
-, font(font)
-, block()
-, block_buf()
-, block_transform(1.0f)
-, block_label()
-, block_visible(false)
-, crosshair() {
-       block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
-       block_transform = glm::scale(block_transform, glm::vec3(50.0f));
-       block_transform = glm::rotate(block_transform, 3.5f, glm::vec3(1.0f, 0.0f, 0.0f));
-       block_transform = glm::rotate(block_transform, 0.35f, glm::vec3(0.0f, 1.0f, 0.0f));
+DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
+: world(world)
+, player(player)
+, manip(manip)
+, aim_world()
+, aim_entity()
+, move_dir(0.0f)
+, pitch(0.0f)
+, yaw(0.0f)
+, dirty(true)
+, active_slot(0)
+, place_timer(256)
+, remove_timer(256) {
 
-       OutlineModel::Buffer buf;
-       buf.vertices = std::vector<glm::vec3>({
-               { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
-               {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
-       });
-       buf.indices = std::vector<OutlineModel::Index>({
-               0, 1, 2, 3
-       });
-       buf.colors.resize(4, { 10.0f, 10.0f, 10.0f });
-       crosshair.Update(buf);
+}
 
-       block_label.Position(
-               glm::vec3(50.0f, 85.0f, 0.0f),
-               Gravity::NORTH_WEST,
-               Gravity::NORTH
-       );
-       block_label.Foreground(glm::vec4(1.0f));
-       block_label.Background(glm::vec4(0.5f));
+void DirectInput::Update(int dt) {
+       dirty = true; // world has changed in the meantime
+       UpdatePlayer();
+
+       remove_timer.Update(dt);
+       if (remove_timer.Hit()) {
+               RemoveBlock();
+       }
+
+       place_timer.Update(dt);
+       if (place_timer.Hit()) {
+               PlaceBlock();
+       }
 }
 
+void DirectInput::SetMovement(const glm::vec3 &m) {
+       if (dot(m, m) > 1.0f) {
+               move_dir = normalize(m);
+       } else {
+               move_dir = m;
+       }
+       dirty = true;
+}
 
-void HUD::DisplayNone() {
-       block_visible = false;
+void DirectInput::TurnHead(float dp, float dy) {
+       pitch += dp;
+       if (pitch > PI / 2) {
+               pitch = PI / 2;
+       } else if (pitch < -PI / 2) {
+               pitch = -PI / 2;
+       }
+       yaw += dy;
+       if (yaw > PI) {
+               yaw -= PI * 2;
+       } else if (yaw < -PI) {
+               yaw += PI * 2;
+       }
+       dirty = true;
+}
+
+void DirectInput::StartPrimaryAction() {
+       if (!remove_timer.Running()) {
+               RemoveBlock();
+               remove_timer.Start();
+       }
 }
 
-void HUD::Display(const Block &b) {
-       const BlockType &type = types.Get(b.type);
+void DirectInput::StopPrimaryAction() {
+       remove_timer.Stop();
+}
 
-       block_buf.Clear();
-       type.FillEntityModel(block_buf, b.Transform());
-       block.Update(block_buf);
+void DirectInput::StartSecondaryAction() {
+       if (!place_timer.Running()) {
+               PlaceBlock();
+               place_timer.Start();
+       }
+}
 
-       block_label.Set(font, type.label);
+void DirectInput::StopSecondaryAction() {
+       place_timer.Stop();
+}
 
-       block_visible = type.visible;
+void DirectInput::StartTertiaryAction() {
+       PickBlock();
 }
 
+void DirectInput::StopTertiaryAction() {
+       // nothing
+}
 
-void HUD::Render(Viewport &viewport) noexcept {
-       viewport.ClearDepth();
+void DirectInput::SelectInventory(int) {
+}
+
+void DirectInput::UpdatePlayer() {
+       constexpr float max_vel = 0.005f;
+       if (dirty) {
+               player.GetEntity().Orientation(glm::quat(glm::vec3(pitch, yaw, 0.0f)));
+               player.GetEntity().Velocity(glm::rotateY(move_dir * max_vel, yaw));
+
+               Ray aim = player.Aim();
+               if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity().ChunkCoords(), aim_world)) {
+                       aim_world = WorldCollision();
+               }
+               if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity(), aim_entity)) {
+                       aim_entity = EntityCollision();
+               }
+               if (aim_world && aim_entity) {
+                       // got both, pick the closest one
+                       if (aim_world.depth < aim_entity.depth) {
+                               aim_entity = EntityCollision();
+                       } else {
+                               aim_world = WorldCollision();
+                       }
+               }
+               // TODO: update outline if applicable
+               dirty = false;
+       }
+}
+
+void DirectInput::PickBlock() {
+       UpdatePlayer();
+       if (!aim_world) return;
+       player.SetInventorySlot(aim_world.GetBlock().type - 1);
+}
+
+void DirectInput::PlaceBlock() {
+       UpdatePlayer();
+       if (!aim_world) return;
 
-       PlainColor &outline_prog = viewport.HUDOutlineProgram();
-       viewport.EnableInvertBlending();
-       viewport.SetCursor(glm::vec3(0.0f), Gravity::CENTER);
-       outline_prog.SetM(viewport.Cursor());
-       crosshair.Draw();
-
-       if (block_visible) {
-               DirectionalLighting &world_prog = viewport.HUDProgram();
-               world_prog.SetLightDirection({ 1.0f, 3.0f, 5.0f });
-               // disable distance fog
-               world_prog.SetFogDensity(0.0f);
-
-               viewport.DisableBlending();
-               world_prog.SetM(block_transform);
-               block.Draw();
-               block_label.Render(viewport);
+       BlockLookup next_block(aim_world.chunk, aim_world.BlockPos(), Block::NormalFace(aim_world.normal));
+       if (!next_block) {
+               return;
        }
+       manip.SetBlock(next_block.GetChunk(), next_block.GetBlockIndex(), Block(player.GetInventorySlot() + 1));
+       dirty = true;
 }
 
+void DirectInput::RemoveBlock() {
+       UpdatePlayer();
+       if (!aim_world) return;
+       manip.SetBlock(aim_world.GetChunk(), aim_world.block, Block(0));
+       dirty = true;
+}
 
-Interface::Interface(
-       const Config &config,
-       Environment &env,
-       World &world,
-       const Player &player)
+
+HUD::HUD(Environment &env, Config &config, const Player &player)
 : env(env)
-, world(world)
+, config(config)
 , player(player)
-, ctrl(*player.entity)
-, hud(world.BlockTypes(), env.assets.small_ui_font)
-, aim{{ 0, 0, 0 }, { 0, 0, -1 }}
-, aim_world()
-, aim_entity()
+// block focus
 , outline()
 , outline_transform(1.0f)
+, outline_visible(false)
+// "inventory"
+, block()
+, block_buf()
+, block_transform(1.0f)
+, block_label()
+, block_visible(false)
+// debug overlay
 , counter_text()
 , position_text()
 , orientation_text()
 , block_text()
-, last_block()
-, last_entity(nullptr)
+, show_block(false)
+, show_entity(false)
+// message box
 , messages(env.assets.small_ui_font)
 , msg_timer(5000)
-, config(config)
-, place_timer(256)
-, remove_timer(256)
-, remove(0)
-, selection(0)
-, place_sound(env.loader.LoadSound("thump"))
-, remove_sound(env.loader.LoadSound("plop"))
-, fwd(0)
-, rev(0)
-, debug(false) {
+// crosshair
+, crosshair() {
+       // "inventory"
+       block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
+       block_transform = glm::scale(block_transform, glm::vec3(50.0f));
+       block_transform = glm::rotate(block_transform, 3.5f, glm::vec3(1.0f, 0.0f, 0.0f));
+       block_transform = glm::rotate(block_transform, 0.35f, glm::vec3(0.0f, 1.0f, 0.0f));
+       block_label.Position(
+               glm::vec3(50.0f, 85.0f, 0.0f),
+               Gravity::NORTH_WEST,
+               Gravity::NORTH
+       );
+       block_label.Foreground(glm::vec4(1.0f));
+       block_label.Background(glm::vec4(0.5f));
+
+       // debug overlay
        counter_text.Position(glm::vec3(-25.0f, 25.0f, 0.0f), Gravity::NORTH_EAST);
        counter_text.Foreground(glm::vec4(1.0f));
        counter_text.Background(glm::vec4(0.5f));
@@ -149,75 +230,302 @@ Interface::Interface(
        entity_text.Foreground(glm::vec4(1.0f));
        entity_text.Background(glm::vec4(0.5f));
        entity_text.Set(env.assets.small_ui_font, "Entity: none");
+
+       // message box
        messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST);
        messages.Foreground(glm::vec4(1.0f));
        messages.Background(glm::vec4(0.5f));
-       hud.DisplayNone();
+
+       // crosshair
+       OutlineModel::Buffer buf;
+       buf.vertices = std::vector<glm::vec3>({
+               { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
+               {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
+       });
+       buf.indices = std::vector<OutlineModel::Index>({
+               0, 1, 2, 3
+       });
+       buf.colors.resize(4, { 10.0f, 10.0f, 10.0f });
+       crosshair.Update(buf);
+}
+
+namespace {
+
+OutlineModel::Buffer outl_buf;
+
+}
+
+void HUD::FocusBlock(const Chunk &chunk, int index) {
+       const Block &block = chunk.BlockAt(index);
+       const BlockType &type = chunk.Type(index);
+       outl_buf.Clear();
+       type.FillOutlineModel(outl_buf);
+       outline.Update(outl_buf);
+       outline_transform = chunk.Transform(player.GetEntity().ChunkCoords());
+       outline_transform *= chunk.ToTransform(Chunk::ToPos(index), index);
+       outline_transform *= glm::scale(glm::vec3(1.005f));
+       outline_visible = true;
+       {
+               std::stringstream s;
+               s << "Block: "
+                       << type.label
+                       << ", face: " << block.GetFace()
+                       << ", turn: " << block.GetTurn();
+               block_text.Set(env.assets.small_ui_font, s.str());
+       }
+       show_block = true;
+       show_entity = false;
+}
+
+void HUD::FocusEntity(const Entity &entity) {
+       {
+               std::stringstream s;
+               s << "Entity: " << entity.Name();
+               entity_text.Set(env.assets.small_ui_font, s.str());
+       }
+       show_block = false;
+       show_entity = true;
+}
+
+void HUD::FocusNone() {
+       outline_visible = false;
+       show_block = false;
+       show_entity = false;
+}
+
+void HUD::DisplayNone() {
+       block_visible = false;
+}
+
+void HUD::Display(const BlockType &type) {
+       block_buf.Clear();
+       type.FillEntityModel(block_buf);
+       block.Update(block_buf);
+
+       block_label.Set(env.assets.small_ui_font, type.label);
+
+       block_visible = type.visible;
+}
+
+
+void HUD::UpdateDebug() {
+       UpdateCounter();
+       UpdatePosition();
+       UpdateOrientation();
+}
+
+void HUD::UpdateCounter() {
+       std::stringstream s;
+       s << std::setprecision(3) <<
+               "avg: " << env.counter.Average().running << "ms, "
+               "peak: " << env.counter.Peak().running << "ms";
+       std::string text = s.str();
+       counter_text.Set(env.assets.small_ui_font, text);
+}
+
+void HUD::UpdatePosition() {
+       std::stringstream s;
+       s << std::setprecision(3) << "pos: " << player.GetEntity().AbsolutePosition();
+       position_text.Set(env.assets.small_ui_font, s.str());
+}
+
+void HUD::UpdateOrientation() {
+       //std::stringstream s;
+       //s << std::setprecision(3) << "pitch: " << rad2deg(ctrl.Pitch())
+       //      << ", yaw: " << rad2deg(ctrl.Yaw());
+       //orientation_text.Set(env.assets.small_ui_font, s.str());
+}
+
+void HUD::PostMessage(const char *msg) {
+       messages.PushLine(msg);
+       msg_timer.Reset();
+       msg_timer.Start();
+       std::cout << msg << std::endl;
+}
+
+
+void HUD::Update(int dt) {
+       msg_timer.Update(dt);
+       if (msg_timer.HitOnce()) {
+               msg_timer.Stop();
+       }
+
+       if (config.video.debug) {
+               if (env.counter.Changed()) {
+                       UpdateCounter();
+               }
+               UpdatePosition();
+               UpdateOrientation();
+       }
+}
+
+void HUD::Render(Viewport &viewport) noexcept {
+       // block focus
+       if (outline_visible && config.video.world) {
+               PlainColor &outline_prog = viewport.WorldOutlineProgram();
+               outline_prog.SetM(outline_transform);
+               outline.Draw();
+       }
+
+       // clear depth buffer so everything renders above the world
+       viewport.ClearDepth();
+
+       if (config.video.hud) {
+               // "inventory"
+               if (block_visible) {
+                       DirectionalLighting &world_prog = viewport.HUDProgram();
+                       world_prog.SetLightDirection({ 1.0f, 3.0f, 5.0f });
+                       // disable distance fog
+                       world_prog.SetFogDensity(0.0f);
+
+                       viewport.DisableBlending();
+                       world_prog.SetM(block_transform);
+                       block.Draw();
+                       block_label.Render(viewport);
+               }
+
+               // message box
+               if (msg_timer.Running()) {
+                       messages.Render(viewport);
+               }
+
+               // crosshair
+               PlainColor &outline_prog = viewport.HUDOutlineProgram();
+               viewport.EnableInvertBlending();
+               viewport.SetCursor(glm::vec3(0.0f), Gravity::CENTER);
+               outline_prog.SetM(viewport.Cursor());
+               crosshair.Draw();
+       }
+
+       // debug overlay
+       if (config.video.debug) {
+               counter_text.Render(viewport);
+               position_text.Render(viewport);
+               orientation_text.Render(viewport);
+               if (show_block) {
+                       block_text.Render(viewport);
+               } else if (show_entity) {
+                       entity_text.Render(viewport);
+               }
+       }
+}
+
+
+InteractiveManipulator::InteractiveManipulator(Environment &env, Entity &player)
+: player(player)
+, audio(env.audio)
+, place_sound(env.loader.LoadSound("thump"))
+, remove_sound(env.loader.LoadSound("plop")) {
+
+}
+
+void InteractiveManipulator::SetBlock(Chunk &chunk, int index, const Block &block) {
+       chunk.SetBlock(index, block);
+       glm::vec3 coords = chunk.ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(index));
+       // TODO: get sound effect from block type
+       if (block.type == 0) {
+               audio.Play(remove_sound, coords);
+       } else {
+               audio.Play(place_sound, coords);
+       }
+}
+
+
+Interface::Interface(
+       Config &config,
+       const Keymap &keymap,
+       PlayerController &pc,
+       ClientController &cc)
+: config(config)
+, keymap(keymap)
+, player_ctrl(pc)
+, client_ctrl(cc)
+, fwd(0)
+, rev(0)
+, slot(0)
+, num_slots(10) {
+
 }
 
 
 void Interface::HandlePress(const SDL_KeyboardEvent &event) {
-       if (config.keyboard_disabled) return;
+       if (!config.input.keyboard) return;
 
-       switch (env.keymap.Lookup(event)) {
+       Keymap::Action action = keymap.Lookup(event);
+       switch (action) {
                case Keymap::MOVE_FORWARD:
                        rev.z = 1;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_BACKWARD:
                        fwd.z = 1;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_LEFT:
                        rev.x = 1;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_RIGHT:
                        fwd.x = 1;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_UP:
                        fwd.y = 1;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_DOWN:
                        rev.y = 1;
+                       UpdateMovement();
                        break;
 
-               case Keymap::BLOCK_FACE:
-                       FaceBlock();
+               case Keymap::PRIMARY:
+                       player_ctrl.StartPrimaryAction();
                        break;
-               case Keymap::BLOCK_TURN:
-                       TurnBlock();
+               case Keymap::SECONDARY:
+                       player_ctrl.StartSecondaryAction();
                        break;
-               case Keymap::BLOCK_NEXT:
-                       SelectNext();
-                       break;
-               case Keymap::BLOCK_PREV:
-                       SelectPrevious();
+               case Keymap::TERTIARY:
+                       player_ctrl.StartTertiaryAction();
                        break;
 
-               case Keymap::BLOCK_PLACE:
-                       PlaceBlock();
+               case Keymap::INV_NEXT:
+                       InvRel(1);
                        break;
-               case Keymap::BLOCK_PICK:
-                       PickBlock();
+               case Keymap::INV_PREVIOUS:
+                       InvRel(-1);
                        break;
-               case Keymap::BLOCK_REMOVE:
-                       RemoveBlock();
+               case Keymap::INV_1:
+               case Keymap::INV_2:
+               case Keymap::INV_3:
+               case Keymap::INV_4:
+               case Keymap::INV_5:
+               case Keymap::INV_6:
+               case Keymap::INV_7:
+               case Keymap::INV_8:
+               case Keymap::INV_9:
+               case Keymap::INV_10:
+                       InvAbs(action - Keymap::INV_1);
                        break;
 
-               case Keymap::TOGGLE_COLLISION:
-                       ToggleCollision();
+               case Keymap::EXIT:
+                       client_ctrl.Exit();
                        break;
 
-               case Keymap::TOGGLE_VISUAL:
-                       ToggleVisual();
+               case Keymap::TOGGLE_AUDIO:
+                       config.audio.enabled = !config.audio.enabled;
+                       client_ctrl.SetAudio(config.audio.enabled);
                        break;
-               case Keymap::TOGGLE_DEBUG:
-                       ToggleDebug();
+               case Keymap::TOGGLE_VIDEO:
+                       config.video.world = !config.video.world;
+                       client_ctrl.SetVideo(config.video.world);
                        break;
-               case Keymap::TOGGLE_AUDIO:
-                       ToggleAudio();
+               case Keymap::TOGGLE_HUD:
+                       config.video.hud = !config.video.hud;
+                       client_ctrl.SetHUD(config.video.hud);
                        break;
-
-               case Keymap::EXIT:
-                       env.state.Pop();
+               case Keymap::TOGGLE_DEBUG:
+                       config.video.debug = !config.video.debug;
+                       client_ctrl.SetDebug(config.video.debug);
                        break;
 
                default:
@@ -226,337 +534,112 @@ void Interface::HandlePress(const SDL_KeyboardEvent &event) {
 }
 
 void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
-       if (config.keyboard_disabled) return;
+       if (!config.input.keyboard) return;
 
-       switch (env.keymap.Lookup(event)) {
+       switch (keymap.Lookup(event)) {
                case Keymap::MOVE_FORWARD:
                        rev.z = 0;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_BACKWARD:
                        fwd.z = 0;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_LEFT:
                        rev.x = 0;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_RIGHT:
                        fwd.x = 0;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_UP:
                        fwd.y = 0;
+                       UpdateMovement();
                        break;
                case Keymap::MOVE_DOWN:
                        rev.y = 0;
+                       UpdateMovement();
                        break;
 
-               default:
+               case Keymap::PRIMARY:
+                       player_ctrl.StopPrimaryAction();
+                       break;
+               case Keymap::SECONDARY:
+                       player_ctrl.StopSecondaryAction();
+                       break;
+               case Keymap::TERTIARY:
+                       player_ctrl.StopTertiaryAction();
                        break;
-       }
-}
-
-void Interface::FaceBlock() {
-       selection.SetFace(Block::Face((selection.GetFace() + 1) % Block::FACE_COUNT));
-       hud.Display(selection);
-}
-
-void Interface::TurnBlock() {
-       selection.SetTurn(Block::Turn((selection.GetTurn() + 1) % Block::TURN_COUNT));
-       hud.Display(selection);
-}
-
-void Interface::ToggleCollision() {
-       ctrl.Controlled().WorldCollidable(!ctrl.Controlled().WorldCollidable());
-       if (ctrl.Controlled().WorldCollidable()) {
-               PostMessage("collision on");
-       } else {
-               PostMessage("collision off");
-       }
-}
-
-void Interface::ToggleAudio() {
-       config.audio_disabled = !config.audio_disabled;
-       if (config.audio_disabled) {
-               PostMessage("audio off");
-       } else {
-               PostMessage("audio on");
-       }
-}
-
-void Interface::ToggleVisual() {
-       config.visual_disabled = !config.visual_disabled;
-       if (config.visual_disabled) {
-               PostMessage("visual off");
-       } else {
-               PostMessage("visual on");
-       }
-}
-
-void Interface::ToggleDebug() {
-       debug = !debug;
-       if (debug) {
-               UpdateCounter();
-               UpdatePosition();
-               UpdateOrientation();
-               UpdateBlockInfo();
-               UpdateEntityInfo();
-       }
-}
-
-void Interface::UpdateCounter() {
-       std::stringstream s;
-       s << std::setprecision(3) <<
-               "avg: " << env.counter.Average().running << "ms, "
-               "peak: " << env.counter.Peak().running << "ms";
-       std::string text = s.str();
-       counter_text.Set(env.assets.small_ui_font, text);
-}
-
-void Interface::UpdatePosition() {
-       std::stringstream s;
-       s << std::setprecision(3) << "pos: " << ctrl.Controlled().AbsolutePosition();
-       position_text.Set(env.assets.small_ui_font, s.str());
-}
-
-void Interface::UpdateOrientation() {
-       std::stringstream s;
-       s << std::setprecision(3) << "pitch: " << rad2deg(ctrl.Pitch())
-               << ", yaw: " << rad2deg(ctrl.Yaw());
-       orientation_text.Set(env.assets.small_ui_font, s.str());
-}
-
-void Interface::UpdateBlockInfo() {
-       if (aim_world) {
-               const Block &block = aim_world.GetBlock();
-               if (last_block != block) {
-                       std::stringstream s;
-                       s << "Block: "
-                               << aim_world.GetType().label
-                               << ", face: " << block.GetFace()
-                               << ", turn: " << block.GetTurn();
-                       block_text.Set(env.assets.small_ui_font, s.str());
-                       last_block = block;
-               }
-       } else {
-               if (last_block != Block()) {
-                       std::stringstream s;
-                       s << "Block: none";
-                       block_text.Set(env.assets.small_ui_font, s.str());
-                       last_block = Block();
-               }
-       }
-}
 
-void Interface::UpdateEntityInfo() {
-       if (aim_entity) {
-               if (last_entity != aim_entity.entity) {
-                       std::stringstream s;
-                       s << "Entity: " << aim_entity.entity->Name();
-                       entity_text.Set(env.assets.small_ui_font, s.str());
-                       last_entity = aim_entity.entity;
-               }
+               default:
+                       break;
        }
 }
 
-
 void Interface::Handle(const SDL_MouseMotionEvent &event) {
-       if (config.mouse_disabled) return;
-       ctrl.RotateYaw(event.xrel * config.yaw_sensitivity);
-       ctrl.RotatePitch(event.yrel * config.pitch_sensitivity);
+       if (!config.input.mouse) return;
+       player_ctrl.TurnHead(
+               event.yrel * config.input.pitch_sensitivity,
+               event.xrel * config.input.yaw_sensitivity);
 }
 
 void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
-       if (config.mouse_disabled) return;
+       if (!config.input.mouse) return;
 
-       if (event.button == SDL_BUTTON_LEFT) {
-               RemoveBlock();
-               remove_timer.Start();
-       } else if (event.button == SDL_BUTTON_MIDDLE) {
-               PickBlock();
-       } else if (event.button == SDL_BUTTON_RIGHT) {
-               PlaceBlock();
-               place_timer.Start();
+       switch (event.button) {
+               case SDL_BUTTON_LEFT:
+                       player_ctrl.StartPrimaryAction();
+                       break;
+               case SDL_BUTTON_RIGHT:
+                       player_ctrl.StartSecondaryAction();
+                       break;
+               case SDL_BUTTON_MIDDLE:
+                       player_ctrl.StartTertiaryAction();
+                       break;
        }
 }
 
 void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
-       if (config.mouse_disabled) return;
-
-       if (event.button == SDL_BUTTON_LEFT) {
-               remove_timer.Stop();
-       } else if (event.button == SDL_BUTTON_RIGHT) {
-               place_timer.Stop();
-       }
-}
-
-void Interface::PickBlock() {
-       if (!aim_world) return;
-       selection = aim_world.GetBlock();
-       hud.Display(selection);
-}
-
-void Interface::PlaceBlock() {
-       if (!aim_world) return;
+       if (!config.input.mouse) return;
 
-       BlockLookup next_block(aim_world.chunk, aim_world.BlockPos(), Block::NormalFace(aim_world.normal));
-       if (!next_block) {
-               return;
+       switch (event.button) {
+               case SDL_BUTTON_LEFT:
+                       player_ctrl.StopPrimaryAction();
+                       break;
+               case SDL_BUTTON_RIGHT:
+                       player_ctrl.StopSecondaryAction();
+                       break;
+               case SDL_BUTTON_MIDDLE:
+                       player_ctrl.StopTertiaryAction();
+                       break;
        }
-       next_block.SetBlock(selection);
-
-       if (config.audio_disabled) return;
-       const Entity &player = ctrl.Controlled();
-       env.audio.Play(
-               place_sound,
-               next_block.GetChunk().ToSceneCoords(player.ChunkCoords(), next_block.GetBlockCoords())
-       );
-}
-
-void Interface::RemoveBlock() noexcept {
-       if (!aim_world) return;
-       aim_world.SetBlock(remove);
-
-       if (config.audio_disabled) return;
-       const Entity &player = ctrl.Controlled();
-       env.audio.Play(
-               remove_sound,
-               aim_world.GetChunk().ToSceneCoords(player.ChunkCoords(), aim_world.BlockCoords())
-       );
 }
 
 
 void Interface::Handle(const SDL_MouseWheelEvent &event) {
-       if (config.mouse_disabled) return;
+       if (!config.input.mouse) return;
 
        if (event.y < 0) {
-               SelectNext();
+               InvRel(1);
        } else if (event.y > 0) {
-               SelectPrevious();
-       }
-}
-
-void Interface::SelectNext() {
-       ++selection.type;
-       if (size_t(selection.type) >= world.BlockTypes().Size()) {
-               selection.type = 1;
-       }
-       hud.Display(selection);
-}
-
-void Interface::SelectPrevious() {
-       --selection.type;
-       if (selection.type <= 0) {
-               selection.type = world.BlockTypes().Size() - 1;
-       }
-       hud.Display(selection);
-}
-
-
-void Interface::PostMessage(const char *msg) {
-       messages.PushLine(msg);
-       msg_timer.Reset();
-       msg_timer.Start();
-       std::cout << msg << std::endl;
-}
-
-
-void Interface::Update(int dt) {
-       ctrl.Velocity(glm::vec3(fwd - rev) * config.move_velocity);
-       ctrl.Update(dt);
-
-       msg_timer.Update(dt);
-       place_timer.Update(dt);
-       remove_timer.Update(dt);
-
-       aim = ctrl.Aim();
-       CheckAim();
-
-       if (msg_timer.HitOnce()) {
-               msg_timer.Stop();
-       }
-
-       if (remove_timer.Hit()) {
-               RemoveBlock();
-               CheckAim();
-       }
-
-       if (place_timer.Hit()) {
-               PlaceBlock();
-               CheckAim();
-       }
-
-       if (debug) {
-               if (env.counter.Changed()) {
-                       UpdateCounter();
-               }
-               UpdatePosition();
-               UpdateOrientation();
+               InvRel(-1);
        }
 }
 
-namespace {
-
-OutlineModel::Buffer outl_buf;
-
+void Interface::UpdateMovement() {
+       player_ctrl.SetMovement(glm::vec3(fwd - rev));
 }
 
-void Interface::CheckAim() {
-       if (!world.Intersection(aim, glm::mat4(1.0f), ctrl.Controlled().ChunkCoords(), aim_world)) {
-               aim_world = WorldCollision();
-       }
-       if (!world.Intersection(aim, glm::mat4(1.0f), ctrl.Controlled(), aim_entity)) {
-               aim_entity = EntityCollision();
+void Interface::InvAbs(int s) {
+       slot = s % num_slots;
+       while (slot < 0) {
+               slot += num_slots;
        }
-       if (aim_world && aim_entity) {
-               // got both, pick the closest one
-               if (aim_world.depth < aim_entity.depth) {
-                       UpdateOutline();
-                       aim_entity = EntityCollision();
-               } else {
-                       aim_world = WorldCollision();
-               }
-       } else if (aim_world) {
-               UpdateOutline();
-       }
-       if (debug) {
-               UpdateBlockInfo();
-               UpdateEntityInfo();
-       }
-}
-
-void Interface::UpdateOutline() {
-       outl_buf.Clear();
-       aim_world.GetType().FillOutlineModel(outl_buf);
-       outline.Update(outl_buf);
-       outline_transform = aim_world.GetChunk().Transform(player.entity->ChunkCoords());
-       outline_transform *= aim_world.BlockTransform();
-       outline_transform *= glm::scale(glm::vec3(1.005f));
 }
 
-
-void Interface::Render(Viewport &viewport) noexcept {
-       if (config.visual_disabled) return;
-
-       if (aim_world) {
-               PlainColor &outline_prog = viewport.WorldOutlineProgram();
-               outline_prog.SetM(outline_transform);
-               outline.Draw();
-       }
-
-       if (debug) {
-               counter_text.Render(viewport);
-               position_text.Render(viewport);
-               orientation_text.Render(viewport);
-               if (aim_world) {
-                       block_text.Render(viewport);
-               } else if (aim_entity) {
-                       entity_text.Render(viewport);
-               }
-       }
-
-       if (msg_timer.Running()) {
-               messages.Render(viewport);
-       }
-
-       hud.Render(viewport);
+void Interface::InvRel(int delta) {
+       InvAbs(slot + delta);
 }
 
 
@@ -572,7 +655,7 @@ void Keymap::Map(SDL_Scancode scancode, Action action) {
        codemap[scancode] = action;
 }
 
-Keymap::Action Keymap::Lookup(SDL_Scancode scancode) {
+Keymap::Action Keymap::Lookup(SDL_Scancode scancode) const {
        if (scancode < NUM_SCANCODES) {
                return codemap[scancode];
        } else {
@@ -596,20 +679,28 @@ void Keymap::LoadDefault() {
        Map(SDL_SCANCODE_LCTRL, MOVE_DOWN);
        Map(SDL_SCANCODE_RCTRL, MOVE_DOWN);
 
-       Map(SDL_SCANCODE_Q, BLOCK_FACE);
-       Map(SDL_SCANCODE_E, BLOCK_TURN);
-       Map(SDL_SCANCODE_TAB, BLOCK_NEXT);
-       Map(SDL_SCANCODE_RIGHTBRACKET, BLOCK_NEXT);
-       Map(SDL_SCANCODE_LEFTBRACKET, BLOCK_PREV);
-
-       Map(SDL_SCANCODE_INSERT, BLOCK_PLACE);
-       Map(SDL_SCANCODE_RETURN, BLOCK_PLACE);
-       Map(SDL_SCANCODE_MENU, BLOCK_PICK);
-       Map(SDL_SCANCODE_DELETE, BLOCK_REMOVE);
-       Map(SDL_SCANCODE_BACKSPACE, BLOCK_REMOVE);
-
-       Map(SDL_SCANCODE_N, TOGGLE_COLLISION);
-       Map(SDL_SCANCODE_F1, TOGGLE_VISUAL);
+       Map(SDL_SCANCODE_TAB, INV_NEXT);
+       Map(SDL_SCANCODE_RIGHTBRACKET, INV_NEXT);
+       Map(SDL_SCANCODE_LEFTBRACKET, INV_PREVIOUS);
+       Map(SDL_SCANCODE_1, INV_1);
+       Map(SDL_SCANCODE_2, INV_2);
+       Map(SDL_SCANCODE_3, INV_3);
+       Map(SDL_SCANCODE_4, INV_4);
+       Map(SDL_SCANCODE_5, INV_5);
+       Map(SDL_SCANCODE_6, INV_6);
+       Map(SDL_SCANCODE_7, INV_7);
+       Map(SDL_SCANCODE_8, INV_8);
+       Map(SDL_SCANCODE_9, INV_9);
+       Map(SDL_SCANCODE_0, INV_10);
+
+       Map(SDL_SCANCODE_INSERT, SECONDARY);
+       Map(SDL_SCANCODE_RETURN, SECONDARY);
+       Map(SDL_SCANCODE_MENU, TERTIARY);
+       Map(SDL_SCANCODE_DELETE, PRIMARY);
+       Map(SDL_SCANCODE_BACKSPACE, PRIMARY);
+
+       Map(SDL_SCANCODE_F1, TOGGLE_HUD);
+       Map(SDL_SCANCODE_F2, TOGGLE_VIDEO);
        Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
        Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
 
@@ -665,88 +756,59 @@ void Keymap::Save(std::ostream &out) {
 }
 
 
+namespace {
+
+std::map<std::string, Keymap::Action> action_map = {
+       { "none", Keymap::NONE },
+       { "move_forward", Keymap::MOVE_FORWARD },
+       { "move_backward", Keymap::MOVE_BACKWARD },
+       { "move_left", Keymap::MOVE_LEFT },
+       { "move_right", Keymap::MOVE_RIGHT },
+       { "move_up", Keymap::MOVE_UP },
+       { "move_down", Keymap::MOVE_DOWN },
+
+       { "primary", Keymap::PRIMARY },
+       { "secondary", Keymap::SECONDARY },
+       { "tertiary", Keymap::TERTIARY },
+
+       { "inventory_next", Keymap::INV_NEXT },
+       { "inventory_prev", Keymap::INV_PREVIOUS },
+       { "inventory_1", Keymap::INV_1 },
+       { "inventory_2", Keymap::INV_2 },
+       { "inventory_3", Keymap::INV_3 },
+       { "inventory_4", Keymap::INV_4 },
+       { "inventory_5", Keymap::INV_5 },
+       { "inventory_6", Keymap::INV_6 },
+       { "inventory_7", Keymap::INV_7 },
+       { "inventory_8", Keymap::INV_8 },
+       { "inventory_9", Keymap::INV_9 },
+       { "inventory_10", Keymap::INV_10 },
+
+       { "toggle_audio", Keymap::TOGGLE_AUDIO },
+       { "toggle_video", Keymap::TOGGLE_VIDEO },
+       { "toggle_hud", Keymap::TOGGLE_HUD },
+       { "toggle_debug", Keymap::TOGGLE_DEBUG },
+
+       { "exit", Keymap::EXIT },
+};
+
+}
+
 const char *Keymap::ActionToString(Action action) {
-       switch (action) {
-               default:
-               case NONE:
-                       return "none";
-               case MOVE_FORWARD:
-                       return "move_forward";
-               case MOVE_BACKWARD:
-                       return "move_backward";
-               case MOVE_LEFT:
-                       return "move_left";
-               case MOVE_RIGHT:
-                       return "move_right";
-               case MOVE_UP:
-                       return "move_up";
-               case MOVE_DOWN:
-                       return "move_down";
-               case BLOCK_FACE:
-                       return "block_face";
-               case BLOCK_TURN:
-                       return "block_turn";
-               case BLOCK_NEXT:
-                       return "block_next";
-               case BLOCK_PREV:
-                       return "block_prev";
-               case BLOCK_PLACE:
-                       return "block_place";
-               case BLOCK_PICK:
-                       return "block_pick";
-               case BLOCK_REMOVE:
-                       return "block_remove";
-               case TOGGLE_COLLISION:
-                       return "toggle_collision";
-               case TOGGLE_AUDIO:
-                       return "toggle_audio";
-               case TOGGLE_VISUAL:
-                       return "toggle_visual";
-               case TOGGLE_DEBUG:
-                       return "toggle_debug";
-               case EXIT:
-                       return "exit";
+       for (const auto &entry : action_map) {
+               if (action == entry.second) {
+                       return entry.first.c_str();
+               }
        }
+       return "none";
 }
 
 Keymap::Action Keymap::StringToAction(const std::string &str) {
-       if (str == "move_forward") {
-               return MOVE_FORWARD;
-       } else if (str == "move_backward") {
-               return MOVE_BACKWARD;
-       } else if (str == "move_left") {
-               return MOVE_LEFT;
-       } else if (str == "move_right") {
-               return MOVE_RIGHT;
-       } else if (str == "move_up") {
-               return MOVE_UP;
-       } else if (str == "move_down") {
-               return MOVE_DOWN;
-       } else if (str == "block_face") {
-               return BLOCK_FACE;
-       } else if (str == "block_turn") {
-               return BLOCK_TURN;
-       } else if (str == "block_next") {
-               return BLOCK_NEXT;
-       } else if (str == "block_prev") {
-               return BLOCK_PREV;
-       } else if (str == "block_place") {
-               return BLOCK_PLACE;
-       } else if (str == "block_pick") {
-               return BLOCK_PICK;
-       } else if (str == "block_remove") {
-               return BLOCK_REMOVE;
-       } else if (str == "toggle_collision") {
-               return TOGGLE_COLLISION;
-       } else if (str == "toggle_audio") {
-               return TOGGLE_AUDIO;
-       } else if (str == "toggle_visual") {
-               return TOGGLE_VISUAL;
-       } else if (str == "toggle_debug") {
-               return TOGGLE_DEBUG;
-       } else if (str == "exit") {
-               return EXIT;
+       auto entry = action_map.find(str);
+       if (entry != action_map.end()) {
+               return entry->second;
        } else {
+               std::cerr << "unknown action \"" << str << '"' << std::endl;
                return NONE;
        }
 }
index e861b249efd628b8c4dc83b74ef792e2fab97cd3..605b896f2553785964c72f55e809c162f72fe8f3 100644 (file)
@@ -22,6 +22,7 @@ public:
        // only valid if lookup was successful
        Chunk &GetChunk() const noexcept { return *chunk; }
        const Chunk::Pos &GetBlockPos() const noexcept { return pos; }
+       int GetBlockIndex() const noexcept { return Chunk::ToIndex(pos); }
        Block::Pos GetBlockCoords() const noexcept { return Chunk::ToCoords(pos); }
        const Block &GetBlock() const noexcept { return GetChunk().BlockAt(GetBlockPos()); }
        const BlockType &GetType() const noexcept { return GetChunk().Type(GetBlock()); }
index 68aa352efcdcbfa202a3b30e4f1af73ebe08c165..1f9af84118af1ca88e80d415257dcef7307453ca 100644 (file)
@@ -1,18 +1,34 @@
 #ifndef BLANK_WORLD_PLAYER_HPP_
 #define BLANK_WORLD_PLAYER_HPP_
 
+#include "Entity.hpp"
+
+
 namespace blank {
 
 class ChunkIndex;
-class Entity;
 
-struct Player {
+class Player {
+
+public:
+       Player(Entity &e, ChunkIndex &i);
+       ~Player();
+
+       Entity &GetEntity() const noexcept { return entity; }
+       const std::string &Name() const noexcept { return entity.Name(); }
+       Ray Aim() const { return entity.Aim(entity.ChunkCoords()); }
+
+       ChunkIndex &GetChunks() const noexcept { return chunks; }
+
+       void SetInventorySlot(int i) noexcept { inv_slot = i; }
+       int GetInventorySlot() const noexcept { return inv_slot; }
 
-       Entity *entity;
-       ChunkIndex *chunks;
+       void Update(int dt);
 
-       Player(Entity *e, ChunkIndex *i)
-       : entity(e), chunks(i) { }
+private:
+       Entity &entity;
+       ChunkIndex &chunks;
+       int inv_slot;
 
 };
 
index cd631610a4367d261a0744aa0e78c7c1c6c29844..d58ad6f0d644be82ba3547a95f3dddce54c28056 100644 (file)
@@ -68,11 +68,11 @@ public:
        ChunkStore &Chunks() noexcept { return chunks; }
 
        /// add player with given name
-       /// returns nullptr in entity if the name is already taken
-       Player AddPlayer(const std::string &name);
+       /// returns nullptr if the name is already taken
+       Player *AddPlayer(const std::string &name);
        /// add player with given name and ID
-       /// returns nullptr in entity if the name or ID is already taken
-       Player AddPlayer(const std::string &name, std::uint32_t id);
+       /// returns nullptr if the name or ID is already taken
+       Player *AddPlayer(const std::string &name, std::uint32_t id);
        /// add an entity with an autogenerated ID
        Entity &AddEntity();
        /// add entity with given ID
@@ -82,7 +82,7 @@ public:
        /// returs an existing entity if ID is already taken
        Entity &ForceAddEntity(std::uint32_t id);
 
-       const std::vector<Player> &Players() const noexcept { return players; }
+       const std::list<Player> &Players() const noexcept { return players; }
        std::list<Entity> &Entities() noexcept { return entities; }
        const std::list<Entity> &Entities() const noexcept { return entities; }
 
@@ -102,7 +102,7 @@ private:
        ChunkStore chunks;
        ChunkIndex &spawn_index;
 
-       std::vector<Player> players;
+       std::list<Player> players;
        std::list<Entity> entities;
 
        glm::vec3 light_direction;
diff --git a/src/world/WorldManipulator.hpp b/src/world/WorldManipulator.hpp
new file mode 100644 (file)
index 0000000..2da0241
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef BLANK_WORLD_WORLDMANIPULATOR_HPP_
+#define BLANK_WORLD_WORLDMANIPULATOR_HPP_
+
+
+namespace blank {
+
+class Block;
+class Chunk;
+
+struct WorldManipulator {
+
+       virtual void SetBlock(Chunk &, int, const Block &) = 0;
+
+};
+
+}
+
+#endif
index a35f76a5d8b80733c421741af14b299419c132cf..242da2796af047fc0e41f472fdb5b20126009b05 100644 (file)
@@ -1,5 +1,6 @@
 #include "Entity.hpp"
 #include "EntityState.hpp"
+#include "Player.hpp"
 #include "World.hpp"
 
 #include "ChunkIndex.hpp"
@@ -124,6 +125,21 @@ glm::mat4 EntityState::Transform(const glm::ivec3 &reference) const noexcept {
 }
 
 
+Player::Player(Entity &e, ChunkIndex &c)
+: entity(e)
+, chunks(c) {
+
+}
+
+Player::~Player() {
+
+}
+
+void Player::Update(int dt) {
+       chunks.Rebase(entity.ChunkCoords());
+}
+
+
 World::World(const BlockTypeRegistry &types, const Config &config)
 : config(config)
 , block_type(types)
@@ -142,10 +158,10 @@ World::~World() {
 }
 
 
-Player World::AddPlayer(const std::string &name) {
+Player *World::AddPlayer(const std::string &name) {
        for (Player &p : players) {
-               if (p.entity->Name() == name) {
-                       return { nullptr, nullptr };
+               if (p.Name() == name) {
+                       return nullptr;
                }
        }
        Entity &entity = AddEntity();
@@ -154,29 +170,29 @@ Player World::AddPlayer(const std::string &name) {
        entity.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
        entity.WorldCollidable(true);
        entity.Position(config.spawn);
-       ChunkIndex *index = &chunks.MakeIndex(entity.ChunkCoords(), 6);
-       players.emplace_back(&entity, index);
-       return players.back();
+       ChunkIndex &index = chunks.MakeIndex(entity.ChunkCoords(), 6);
+       players.emplace_back(entity, index);
+       return &players.back();
 }
 
-Player World::AddPlayer(const std::string &name, std::uint32_t id) {
+Player *World::AddPlayer(const std::string &name, std::uint32_t id) {
        for (Player &p : players) {
-               if (p.entity->Name() == name) {
-                       return { nullptr, nullptr };
+               if (p.Name() == name) {
+                       return nullptr;
                }
        }
        Entity *entity = AddEntity(id);
        if (!entity) {
-               return { nullptr, nullptr };
+               return nullptr;
        }
        entity->Name(name);
        // TODO: load from save file here
        entity->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
        entity->WorldCollidable(true);
        entity->Position(config.spawn);
-       ChunkIndex *index = &chunks.MakeIndex(entity->ChunkCoords(), 6);
-       players.emplace_back(entity, index);
-       return players.back();
+       ChunkIndex &index = chunks.MakeIndex(entity->ChunkCoords(), 6);
+       players.emplace_back(*entity, index);
+       return &players.back();
 }
 
 Entity &World::AddEntity() {
@@ -359,7 +375,7 @@ void World::Update(int dt) {
                }
        }
        for (Player &player : players) {
-               player.chunks->Rebase(player.entity->ChunkCoords());
+               player.Update(dt);
        }
        for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
                if (iter->CanRemove()) {
@@ -405,8 +421,8 @@ void World::Resolve(Entity &e, std::vector<WorldCollision> &col) {
 World::EntityHandle World::RemoveEntity(EntityHandle &eh) {
        // check for player
        for (auto player = players.begin(), end = players.end(); player != end;) {
-               if (player->entity == &*eh) {
-                       chunks.UnregisterIndex(*player->chunks);
+               if (&player->GetEntity() == &*eh) {
+                       chunks.UnregisterIndex(player->GetChunks());
                        player = players.erase(player);
                        end = players.end();
                } else {
@@ -423,7 +439,7 @@ void World::Render(Viewport &viewport) {
        entity_prog.SetFogDensity(fog_density);
 
        for (Entity &entity : entities) {
-               entity.Render(entity.Transform(players[0].entity->ChunkCoords()), entity_prog);
+               entity.Render(entity.Transform(players.front().GetEntity().ChunkCoords()), entity_prog);
        }
 }