From: Daniel Karbach Date: Tue, 29 Sep 2015 15:10:37 +0000 (+0200) Subject: split input handling X-Git-Url: http://git.localhorst.tv/?a=commitdiff_plain;h=b066e776622f96e906600a0c4a08de392bd03676;p=blank.git split input handling this should make way for networking input also, a lot lot lot of mess has piled up that needs cleaning --- diff --git a/doc/running b/doc/running index 846f27d..6c3a2f4 100644 --- a/doc/running +++ b/doc/running @@ -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 diff --git a/src/ai/Spawner.cpp b/src/ai/Spawner.cpp index 2ea8a69..946e351 100644 --- a/src/ai/Spawner.cpp +++ b/src/ai/Spawner.cpp @@ -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() % players.size()]; + size_t player_num = random.Next() % 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() % player.chunks->TotalChunks(); + int index = random.Next() % player.GetChunks().TotalChunks(); - glm::ivec3 chunk(player.chunks->PositionOf(index)); + glm::ivec3 chunk(player.GetChunks().PositionOf(index)); glm::ivec3 pos( random.Next() % 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 index 0000000..4beb062 --- /dev/null +++ b/src/app/Config.hpp @@ -0,0 +1,57 @@ +#ifndef BLANK_APP_CONFIG_HPP_ +#define BLANK_APP_CONFIG_HPP_ + +#include +#include + + +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 diff --git a/src/app/Runtime.hpp b/src/app/Runtime.hpp index 36808a0..2d169d0 100644 --- a/src/app/Runtime.hpp +++ b/src/app/Runtime.hpp @@ -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(); }; diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index 486259a..a7beeb0 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -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); } diff --git a/src/client/Client.hpp b/src/client/Client.hpp index 68b2f7f..fb7eab1 100644 --- a/src/client/Client.hpp +++ b/src/client/Client.hpp @@ -1,6 +1,7 @@ #ifndef BLANK_CLIENT_CLIENT_HPP_ #define BLANK_CLIENT_CLIENT_HPP_ +#include "../app/Config.hpp" #include "../net/Connection.hpp" #include @@ -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(); diff --git a/src/client/InteractiveState.hpp b/src/client/InteractiveState.hpp index 5a43cd8..1b9c9a5 100644 --- a/src/client/InteractiveState.hpp +++ b/src/client/InteractiveState.hpp @@ -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 @@ -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; diff --git a/src/client/MasterState.hpp b/src/client/MasterState.hpp index 50773a6..3c9b23c 100644 --- a/src/client/MasterState.hpp +++ b/src/client/MasterState.hpp @@ -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 #include @@ -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 state; Client client; diff --git a/src/client/client.cpp b/src/client/client.cpp index 521b149..fbf6bf2 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -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()); diff --git a/src/client/net.cpp b/src/client/net.cpp index 31f13d9..a5feaf5 100644 --- a/src/client/net.cpp +++ b/src/client/net.cpp @@ -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 } { diff --git a/src/server/ClientConnection.hpp b/src/server/ClientConnection.hpp index 99657de..2089dd1 100644 --- a/src/server/ClientConnection.hpp +++ b/src/server/ClientConnection.hpp @@ -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 spawns; unsigned int confirm_wait; diff --git a/src/server/Server.hpp b/src/server/Server.hpp index bf23991..cf4fc04 100644 --- a/src/server/Server.hpp +++ b/src/server/Server.hpp @@ -1,6 +1,8 @@ #ifndef BLANK_SERVER_SERVER_HPP #define BLANK_SERVER_SERVER_HPP +#include "../app/Config.hpp" + #include #include @@ -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(); diff --git a/src/server/ServerState.cpp b/src/server/ServerState.cpp index ebe675f..8657df0 100644 --- a/src/server/ServerState.cpp +++ b/src/server/ServerState.cpp @@ -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; } diff --git a/src/server/ServerState.hpp b/src/server/ServerState.hpp index 8f564ab..31ad521 100644 --- a/src/server/ServerState.hpp +++ b/src/server/ServerState.hpp @@ -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; diff --git a/src/server/net.cpp b/src/server/net.cpp index 7359534..44a4268 100644 --- a/src/server/net.cpp +++ b/src/server/net.cpp @@ -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(); - 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() diff --git a/src/standalone/MasterState.cpp b/src/standalone/MasterState.cpp index 30925a9..272e5a3 100644 --- a/src/standalone/MasterState.cpp +++ b/src/standalone/MasterState.cpp @@ -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(); } } diff --git a/src/standalone/MasterState.hpp b/src/standalone/MasterState.hpp index ca5cab2..f2c702f 100644 --- a/src/standalone/MasterState.hpp +++ b/src/standalone/MasterState.hpp @@ -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 index 0000000..56bb78b --- /dev/null +++ b/src/ui/ClientController.hpp @@ -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 index 0000000..525c856 --- /dev/null +++ b/src/ui/DirectInput.hpp @@ -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 diff --git a/src/ui/HUD.hpp b/src/ui/HUD.hpp index 1917b13..d15643f 100644 --- a/src/ui/HUD.hpp +++ b/src/ui/HUD.hpp @@ -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 index 0000000..6975d9e --- /dev/null +++ b/src/ui/InteractiveManipulator.hpp @@ -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 diff --git a/src/ui/Interface.hpp b/src/ui/Interface.hpp index 72db5d1..aa040a1 100644 --- a/src/ui/Interface.hpp +++ b/src/ui/Interface.hpp @@ -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 -#include #include +#include 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; }; diff --git a/src/ui/Keymap.hpp b/src/ui/Keymap.hpp index 0e34ba9..a3e1d43 100644 --- a/src/ui/Keymap.hpp +++ b/src/ui/Keymap.hpp @@ -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 index 0000000..8d9e5a3 --- /dev/null +++ b/src/ui/PlayerController.hpp @@ -0,0 +1,35 @@ +#ifndef BLANK_UI_PLAYERCONTROLLER_HPP_ +#define BLANK_UI_PLAYERCONTROLLER_HPP_ + +#include + + +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 diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 02f372e..1e8a980 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -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" @@ -13,125 +18,201 @@ #include "../model/shapes.hpp" #include "../world/BlockLookup.hpp" #include "../world/World.hpp" +#include "../world/WorldManipulator.hpp" #include #include #include +#include #include #include +#include #include 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({ - { -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({ - 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({ + { -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({ + 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 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; } } diff --git a/src/world/BlockLookup.hpp b/src/world/BlockLookup.hpp index e861b24..605b896 100644 --- a/src/world/BlockLookup.hpp +++ b/src/world/BlockLookup.hpp @@ -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()); } diff --git a/src/world/Player.hpp b/src/world/Player.hpp index 68aa352..1f9af84 100644 --- a/src/world/Player.hpp +++ b/src/world/Player.hpp @@ -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; }; diff --git a/src/world/World.hpp b/src/world/World.hpp index cd63161..d58ad6f 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -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 &Players() const noexcept { return players; } + const std::list &Players() const noexcept { return players; } std::list &Entities() noexcept { return entities; } const std::list &Entities() const noexcept { return entities; } @@ -102,7 +102,7 @@ private: ChunkStore chunks; ChunkIndex &spawn_index; - std::vector players; + std::list players; std::list entities; glm::vec3 light_direction; diff --git a/src/world/WorldManipulator.hpp b/src/world/WorldManipulator.hpp new file mode 100644 index 0000000..2da0241 --- /dev/null +++ b/src/world/WorldManipulator.hpp @@ -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 diff --git a/src/world/world.cpp b/src/world/world.cpp index a35f76a..242da27 100644 --- a/src/world/world.cpp +++ b/src/world/world.cpp @@ -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 &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); } }