]> git.localhorst.tv Git - blank.git/commitdiff
transmit player input from client to server
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 1 Oct 2015 10:30:54 +0000 (12:30 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 1 Oct 2015 10:52:50 +0000 (12:52 +0200)
16 files changed:
doc/protocol
src/client/Client.hpp
src/client/InteractiveState.hpp
src/client/NetworkedInput.hpp [new file with mode: 0644]
src/client/client.cpp
src/client/net.cpp
src/net/Packet.hpp
src/net/net.cpp
src/server/ClientConnection.hpp
src/server/Server.hpp
src/server/net.cpp
src/ui/DirectInput.hpp
src/ui/PlayerController.hpp
src/ui/ui.cpp
tst/net/PacketTest.cpp
tst/net/PacketTest.hpp

index 4dc245f1be0981a4669240dda0d98ecb390b8d0e..a707d2547538bffcf40e4edb912f404059bd7f9e 100644 (file)
@@ -90,8 +90,13 @@ Sent by clients to notify the server of their changes to the player.
 
 Code: 4
 Payload:
-        0 entity state of the player as seen by the client
-Length: 64
+        0 player's entity state as predicted by the client
+       64 movement input, 3x 16bit signed int, each component mapped from [-1,1] to [-32767,32767]
+       70 pitch input, 16bit signed int, mapped from [-PI/2,PI/2] to [-32767,32767]
+       72 yaw input, 16bit signed int, mapped from [-PI,PI] to [-32767,32767]
+       74 active actions, 8bit bit field, first three bits are primary, secondary, and tertiary
+       75 selected inventory slot, 8bit unsigned int
+Length: 76
 
 
 Spawn Entity
@@ -134,7 +139,7 @@ Payload:
         4 entity ID, 32bit unsigned int
         8 entity state
        72 next entity...
-Length: 4 + multiple of 68, max 452
+Length: 4 + multiple of 68, max 480
 
 
 Player Correction
index fb7eab1520ea769b7b6526b9659298a797976de7..a5f85e5f58c70ca4a69c02753a6c68278010abdf 100644 (file)
@@ -30,7 +30,13 @@ public:
        std::uint16_t SendPing();
        std::uint16_t SendLogin(const std::string &);
        std::uint16_t SendPart();
-       std::uint16_t SendPlayerUpdate(const Entity &);
+       std::uint16_t SendPlayerUpdate(
+               const EntityState &prediction,
+               const glm::vec3 &movement,
+               float pitch,
+               float yaw,
+               std::uint8_t actions,
+               std::uint8_t slot);
 
 private:
        void HandlePacket(const UDPpacket &);
index 1b9c9a5ba06fc4b42a6a92399daee24ab6ce144a..1129e997fdbcfd2f7d1377a73b9b7a61c9d5806a 100644 (file)
@@ -6,11 +6,11 @@
 
 #include "ChunkReceiver.hpp"
 #include "ChunkRequester.hpp"
+#include "NetworkedInput.hpp"
 #include "../app/IntervalTimer.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"
@@ -20,8 +20,6 @@
 #include "../world/Player.hpp"
 #include "../world/World.hpp"
 
-#include <list>
-
 
 namespace blank {
 
@@ -49,7 +47,6 @@ public:
        void Update(int dt) override;
        void Render(Viewport &) override;
 
-       void PushPlayerUpdate(const Entity &, int dt);
        void MergePlayerCorrection(std::uint16_t, const EntityState &);
 
        void SetAudio(bool) override;
@@ -66,7 +63,7 @@ private:
        Player &player;
        HUD hud;
        InteractiveManipulator manip;
-       DirectInput input;
+       NetworkedInput input;
        Interface interface;
        ChunkRequester chunk_requester;
        ChunkReceiver chunk_receiver;
@@ -76,15 +73,6 @@ private:
 
        SkyBox sky;
 
-       struct PlayerHistory {
-               EntityState state;
-               int delta_t;
-               std::uint16_t packet;
-               PlayerHistory(EntityState s, int dt, std::uint16_t p)
-               : state(s), delta_t(dt), packet(p) { }
-       };
-       std::list<PlayerHistory> player_hist;
-
 };
 
 }
diff --git a/src/client/NetworkedInput.hpp b/src/client/NetworkedInput.hpp
new file mode 100644 (file)
index 0000000..462b6a7
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef BLANK_CLIENT_NETWORKEDINPUT_HPP_
+#define BLANK_CLIENT_NETWORKEDINPUT_HPP_
+
+#include "../ui/PlayerController.hpp"
+
+#include "../world/EntityState.hpp"
+
+#include <cstdint>
+#include <list>
+
+
+namespace blank {
+namespace client {
+
+class Client;
+
+class NetworkedInput
+: public PlayerController {
+
+public:
+       explicit NetworkedInput(World &, Player &, Client &);
+
+       void Update(int dt);
+       void PushPlayerUpdate(int dt);
+       void MergePlayerCorrection(std::uint16_t, const EntityState &);
+
+       void StartPrimaryAction() override;
+       void StopPrimaryAction() override;
+       void StartSecondaryAction() override;
+       void StopSecondaryAction() override;
+       void StartTertiaryAction() override;
+       void StopTertiaryAction() override;
+
+private:
+       Client &client;
+
+       struct PlayerHistory {
+               EntityState state;
+               int delta_t;
+               std::uint16_t packet;
+               PlayerHistory(EntityState s, int dt, std::uint16_t p)
+               : state(s), delta_t(dt), packet(p) { }
+       };
+       std::list<PlayerHistory> player_hist;
+
+       std::uint8_t actions;
+
+};
+
+}
+}
+
+#endif
index cfc89cd2588dabca879a6dec218d40582dac5f48..524454b43e31a70310f9b0fa377c3a29ec3d8071 100644 (file)
@@ -111,7 +111,7 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
 , player(*world.AddPlayer(master.GetConfig().player.name))
 , hud(master.GetEnv(), master.GetConfig(), player)
 , manip(master.GetEnv(), player.GetEntity())
-, input(world, player, manip)
+, input(world, player, master.GetClient())
 , 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)
@@ -119,8 +119,7 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
 , chunk_renderer(player.GetChunks())
 , skeletons()
 , loop_timer(16)
-, sky(master.GetEnv().loader.LoadCubeMap("skybox"))
-, player_hist() {
+, sky(master.GetEnv().loader.LoadCubeMap("skybox")) {
        if (!save.Exists()) {
                save.Write(master.GetWorldConf());
        }
@@ -190,7 +189,7 @@ void InteractiveState::Update(int dt) {
        chunk_renderer.Update(dt);
 
        if (world_dt > 0) {
-               PushPlayerUpdate(player.GetEntity(), world_dt);
+               input.PushPlayerUpdate(world_dt);
        }
 
        glm::mat4 trans = player.GetEntity().Transform(player.GetEntity().ChunkCoords());
@@ -201,83 +200,6 @@ void InteractiveState::Update(int dt) {
        master.GetEnv().audio.Orientation(dir, up);
 }
 
-void InteractiveState::PushPlayerUpdate(const Entity &player, int dt) {
-       std::uint16_t packet = master.GetClient().SendPlayerUpdate(player);
-       if (player_hist.size() < 16) {
-               player_hist.emplace_back(player.GetState(), dt, packet);
-       } else {
-               auto entry = player_hist.begin();
-               entry->state = player.GetState();
-               entry->delta_t = dt;
-               entry->packet = packet;
-               player_hist.splice(player_hist.end(), player_hist, entry);
-       }
-}
-
-void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &corrected_state) {
-       if (player_hist.empty()) return;
-
-       auto entry = player_hist.begin();
-       auto end = player_hist.end();
-
-       // we may have received an older packet
-       int pack_diff = int16_t(seq) - int16_t(entry->packet);
-       if (pack_diff < 0) {
-               // indeed we have, just ignore it
-               return;
-       }
-
-       // drop anything older than the fix
-       while (entry != end) {
-               pack_diff = int16_t(seq) - int16_t(entry->packet);
-               if (pack_diff > 0) {
-                       entry = player_hist.erase(entry);
-               } else {
-                       break;
-               }
-       }
-
-       EntityState replay_state(corrected_state);
-       EntityState &player_state = player.GetEntity().GetState();
-
-       if (entry != end) {
-               entry->state.chunk_pos = replay_state.chunk_pos;
-               entry->state.block_pos = replay_state.block_pos;
-               ++entry;
-       }
-
-       while (entry != end) {
-               replay_state.velocity = entry->state.velocity;
-               replay_state.Update(entry->delta_t);
-               entry->state.chunk_pos = replay_state.chunk_pos;
-               entry->state.block_pos = replay_state.block_pos;
-               ++entry;
-       }
-
-       glm::vec3 displacement(replay_state.Diff(player_state));
-       const float disp_squared = dot(displacement, displacement);
-
-       if (disp_squared < 16.0f * numeric_limits<float>::epsilon()) {
-               return;
-       }
-
-       // if offset > 10cm, warp the player
-       // otherwise, move at most 1cm per frame towards
-       // the fixed position (160ms, so shouldn't be too noticeable)
-       constexpr float warp_thresh = 0.01f; // (1/10)^2
-       constexpr float max_disp = 0.0001f; // (1/100)^2
-
-       if (disp_squared > warp_thresh) {
-               player_state.chunk_pos = replay_state.chunk_pos;
-               player_state.block_pos = replay_state.block_pos;
-       } else if (disp_squared < max_disp) {
-               player_state.block_pos += displacement;
-       } else {
-               displacement *= 0.01f / sqrt(disp_squared);
-               player_state.block_pos += displacement;
-       }
-}
-
 void InteractiveState::Render(Viewport &viewport) {
        viewport.WorldPosition(player.GetEntity().Transform(player.GetEntity().ChunkCoords()));
        if (master.GetConfig().video.world) {
@@ -288,6 +210,10 @@ void InteractiveState::Render(Viewport &viewport) {
        hud.Render(viewport);
 }
 
+void InteractiveState::MergePlayerCorrection(std::uint16_t pack, const EntityState &state) {
+       input.MergePlayerCorrection(pack, state);
+}
+
 void InteractiveState::SetAudio(bool b) {
        master.GetConfig().audio.enabled = b;
        if (b) {
index a5feaf5f7cddd4ac4038d2f5886432fa9d49686c..6e8d00cc69de3922c063d912fdf34b82b46927b1 100644 (file)
@@ -1,11 +1,13 @@
 #include "ChunkReceiver.hpp"
 #include "ChunkTransmission.hpp"
 #include "Client.hpp"
+#include "NetworkedInput.hpp"
 
 #include "../app/init.hpp"
 #include "../net/Packet.hpp"
 #include "../world/Chunk.hpp"
 #include "../world/ChunkStore.hpp"
+#include "../world/Player.hpp"
 
 #include <iostream>
 #include <zlib.h>
@@ -236,9 +238,21 @@ uint16_t Client::SendLogin(const string &name) {
        return conn.Send(client_pack, client_sock);
 }
 
-uint16_t Client::SendPlayerUpdate(const Entity &player) {
+uint16_t Client::SendPlayerUpdate(
+       const EntityState &prediction,
+       const glm::vec3 &movement,
+       float pitch,
+       float yaw,
+       std::uint8_t actions,
+       std::uint8_t slot
+) {
        auto pack = Packet::Make<Packet::PlayerUpdate>(client_pack);
-       pack.WritePlayer(player);
+       pack.WritePredictedState(prediction);
+       pack.WriteMovement(movement);
+       pack.WritePitch(pitch);
+       pack.WriteYaw(yaw);
+       pack.WriteActions(actions);
+       pack.WriteSlot(slot);
        return conn.Send(client_pack, client_sock);
 }
 
@@ -247,5 +261,129 @@ uint16_t Client::SendPart() {
        return conn.Send(client_pack, client_sock);
 }
 
+
+NetworkedInput::NetworkedInput(World &world, Player &player, Client &client)
+: PlayerController(world, player)
+, client(client)
+, player_hist()
+, actions(0) {
+
+}
+
+void NetworkedInput::Update(int dt) {
+       Invalidate();
+       UpdatePlayer();
+}
+
+void NetworkedInput::PushPlayerUpdate(int dt) {
+       const EntityState &state = GetPlayer().GetEntity().GetState();
+
+       std::uint16_t packet = client.SendPlayerUpdate(
+               state,
+               GetMovement(),
+               GetPitch(),
+               GetYaw(),
+               actions,
+               InventorySlot()
+       );
+       if (player_hist.size() < 16) {
+               player_hist.emplace_back(state, dt, packet);
+       } else {
+               auto entry = player_hist.begin();
+               entry->state = state;
+               entry->delta_t = dt;
+               entry->packet = packet;
+               player_hist.splice(player_hist.end(), player_hist, entry);
+       }
+}
+
+void NetworkedInput::MergePlayerCorrection(uint16_t seq, const EntityState &corrected_state) {
+       if (player_hist.empty()) return;
+
+       auto entry = player_hist.begin();
+       auto end = player_hist.end();
+
+       // we may have received an older packet
+       int pack_diff = int16_t(seq) - int16_t(entry->packet);
+       if (pack_diff < 0) {
+               // indeed we have, just ignore it
+               return;
+       }
+
+       // drop anything older than the fix
+       while (entry != end) {
+               pack_diff = int16_t(seq) - int16_t(entry->packet);
+               if (pack_diff > 0) {
+                       entry = player_hist.erase(entry);
+               } else {
+                       break;
+               }
+       }
+
+       EntityState replay_state(corrected_state);
+       EntityState &player_state = GetPlayer().GetEntity().GetState();
+
+       if (entry != end) {
+               entry->state.chunk_pos = replay_state.chunk_pos;
+               entry->state.block_pos = replay_state.block_pos;
+               ++entry;
+       }
+
+       while (entry != end) {
+               replay_state.velocity = entry->state.velocity;
+               replay_state.Update(entry->delta_t);
+               entry->state.chunk_pos = replay_state.chunk_pos;
+               entry->state.block_pos = replay_state.block_pos;
+               ++entry;
+       }
+
+       glm::vec3 displacement(replay_state.Diff(player_state));
+       const float disp_squared = dot(displacement, displacement);
+
+       if (disp_squared < 16.0f * numeric_limits<float>::epsilon()) {
+               return;
+       }
+
+       // if offset > 10cm, warp the player
+       // otherwise, move at most 1cm per frame towards
+       // the fixed position (160ms, so shouldn't be too noticeable)
+       constexpr float warp_thresh = 0.01f; // (1/10)^2
+       constexpr float max_disp = 0.0001f; // (1/100)^2
+
+       if (disp_squared > warp_thresh) {
+               player_state.chunk_pos = replay_state.chunk_pos;
+               player_state.block_pos = replay_state.block_pos;
+       } else if (disp_squared < max_disp) {
+               player_state.block_pos += displacement;
+       } else {
+               displacement *= 0.01f / sqrt(disp_squared);
+               player_state.block_pos += displacement;
+       }
+}
+
+void NetworkedInput::StartPrimaryAction() {
+       actions |= 0x01;
+}
+
+void NetworkedInput::StopPrimaryAction() {
+       actions &= ~0x01;
+}
+
+void NetworkedInput::StartSecondaryAction() {
+       actions |= 0x02;
+}
+
+void NetworkedInput::StopSecondaryAction() {
+       actions &= ~0x02;
+}
+
+void NetworkedInput::StartTertiaryAction() {
+       actions |= 0x04;
+}
+
+void NetworkedInput::StopTertiaryAction() {
+       actions &= ~0x04;
+}
+
 }
 }
index e32621c451ae31c89655e45920ff43ea96d3b317..b32b42b5bceee10c901d8335426bb971b81c5746 100644 (file)
@@ -107,10 +107,20 @@ struct Packet {
 
        struct PlayerUpdate : public Payload {
                static constexpr std::uint8_t TYPE = 4;
-               static constexpr std::size_t MAX_LEN = 64;
-
-               void WritePlayer(const Entity &) noexcept;
-               void ReadPlayerState(EntityState &) const noexcept;
+               static constexpr std::size_t MAX_LEN = 76;
+
+               void WritePredictedState(const EntityState &) noexcept;
+               void ReadPredictedState(EntityState &) const noexcept;
+               void WriteMovement(const glm::vec3 &) noexcept;
+               void ReadMovement(glm::vec3 &) const noexcept;
+               void WritePitch(float) noexcept;
+               void ReadPitch(float &) const noexcept;
+               void WriteYaw(float) noexcept;
+               void ReadYaw(float &) const noexcept;
+               void WriteActions(std::uint8_t) noexcept;
+               void ReadActions(std::uint8_t &) const noexcept;
+               void WriteSlot(std::uint8_t) noexcept;
+               void ReadSlot(std::uint8_t &) const noexcept;
        };
 
        struct SpawnEntity : public Payload {
@@ -133,7 +143,7 @@ struct Packet {
 
        struct EntityUpdate : public Payload {
                static constexpr std::uint8_t TYPE = 7;
-               static constexpr std::size_t MAX_LEN = 452;
+               static constexpr std::size_t MAX_LEN = 480;
 
                static constexpr std::uint32_t MAX_ENTITIES = 7;
                static constexpr std::size_t GetSize(std::uint32_t num) noexcept {
index fa9c381f15d4961dbf9ae6578c101e408fe99dfd..e766c76d0b1201892bd1f0393a0d108f9d640b96 100644 (file)
@@ -284,14 +284,67 @@ void Packet::Join::ReadWorldName(string &name) const noexcept {
        ReadString(name, 68, 32);
 }
 
-void Packet::PlayerUpdate::WritePlayer(const Entity &player) noexcept {
-       Write(player.GetState(), 0);
+void Packet::PlayerUpdate::WritePredictedState(const EntityState &state) noexcept {
+       Write(state, 0);
 }
 
-void Packet::PlayerUpdate::ReadPlayerState(EntityState &state) const noexcept {
+void Packet::PlayerUpdate::ReadPredictedState(EntityState &state) const noexcept {
        Read(state, 0);
 }
 
+void Packet::PlayerUpdate::WriteMovement(const glm::vec3 &mov) noexcept {
+       glm::ivec3 conv = clamp(glm::ivec3(mov * 32767.0f), -32767, 32767);
+       Write(int16_t(conv.x), 64);
+       Write(int16_t(conv.y), 66);
+       Write(int16_t(conv.z), 68);
+}
+
+void Packet::PlayerUpdate::ReadMovement(glm::vec3 &mov) const noexcept {
+       int16_t x, y, z;
+       Read(x, 64);
+       Read(y, 66);
+       Read(z, 68);
+       mov = glm::vec3(x, y, z) * .00003051850947599719f;
+}
+
+void Packet::PlayerUpdate::WritePitch(float pitch) noexcept {
+       int16_t conv = pitch * 20860.12008116853786870640f;
+       Write(conv, 70);
+}
+
+void Packet::PlayerUpdate::ReadPitch(float &pitch) const noexcept {
+       int16_t conv = 0;
+       Read(conv, 70);
+       pitch = conv * .00004793836258415163f;
+}
+
+void Packet::PlayerUpdate::WriteYaw(float yaw) noexcept {
+       int16_t conv = yaw * 10430.06004058426893435320f;
+       Write(conv, 72);
+}
+
+void Packet::PlayerUpdate::ReadYaw(float &yaw) const noexcept {
+       int16_t conv = 0;
+       Read(conv, 72);
+       yaw = conv * .00009587672516830326f;
+}
+
+void Packet::PlayerUpdate::WriteActions(uint8_t actions) noexcept {
+       Write(actions, 74);
+}
+
+void Packet::PlayerUpdate::ReadActions(uint8_t &actions) const noexcept {
+       Read(actions, 74);
+}
+
+void Packet::PlayerUpdate::WriteSlot(uint8_t slot) noexcept {
+       Write(slot, 75);
+}
+
+void Packet::PlayerUpdate::ReadSlot(uint8_t &slot) const noexcept {
+       Read(slot, 75);
+}
+
 void Packet::SpawnEntity::WriteEntity(const Entity &e) noexcept {
        Write(e.ID(), 0);
        if (e.GetModel()) {
@@ -351,19 +404,19 @@ void Packet::EntityUpdate::ReadEntityCount(uint32_t &count) const noexcept {
 }
 
 void Packet::EntityUpdate::WriteEntity(const Entity &entity, uint32_t num) noexcept {
-       uint32_t off = GetSize(num);;
+       uint32_t off = GetSize(num);
 
        Write(entity.ID(), off);
        Write(entity.GetState(), off + 4);
 }
 
 void Packet::EntityUpdate::ReadEntityID(uint32_t &id, uint32_t num) const noexcept {
-       uint32_t off = GetSize(num);;
+       uint32_t off = GetSize(num);
        Read(id, off);
 }
 
 void Packet::EntityUpdate::ReadEntityState(EntityState &state, uint32_t num) const noexcept {
-       uint32_t off = GetSize(num);;
+       uint32_t off = GetSize(num);
        Read(state, off + 4);
 }
 
index 2089dd17c8eec85a7acf7e6e7d3d930f03946a5f..4927d1ce831fb9ab26c15cbeb68db9826b3c497c 100644 (file)
@@ -4,6 +4,7 @@
 #include "ChunkTransmitter.hpp"
 #include "Server.hpp"
 #include "../app/IntervalTimer.hpp"
+#include "../ui/DirectInput.hpp"
 #include "../net/Connection.hpp"
 #include "../net/ConnectionHandler.hpp"
 #include "../world/EntityState.hpp"
@@ -11,6 +12,7 @@
 
 #include <deque>
 #include <list>
+#include <memory>
 #include <SDL_net.h>
 #include <vector>
 
@@ -49,11 +51,11 @@ public:
 
        void AttachPlayer(Player &);
        void DetachPlayer();
-       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(); }
+       bool HasPlayer() const noexcept { return !!input; }
+       Entity &PlayerEntity() noexcept { return input->GetPlayer().GetEntity(); }
+       const Entity &PlayerEntity() const noexcept { return input->GetPlayer().GetEntity(); }
+       ChunkIndex &PlayerChunks() noexcept { return input->GetPlayer().GetChunks(); }
+       const ChunkIndex &PlayerChunks() const noexcept { return input->GetPlayer().GetChunks(); }
 
        void SetPlayerModel(const CompositeModel &) noexcept;
        bool HasPlayerModel() const noexcept;
@@ -95,7 +97,7 @@ private:
 private:
        Server &server;
        Connection conn;
-       Player *player;
+       std::unique_ptr<DirectInput> input;
        const CompositeModel *player_model;
        std::list<SpawnStatus> spawns;
        unsigned int confirm_wait;
@@ -105,6 +107,7 @@ private:
        EntityState player_update_state;
        std::uint16_t player_update_pack;
        IntervalTimer player_update_timer;
+       std::uint8_t old_actions;
 
        ChunkTransmitter transmitter;
        std::deque<glm::ivec3> chunk_queue;
index cf4fc0433e889f5966bee3806eee1427d57c521c..1a4c0b4f44b9c84de2cda7530737db6c1acdc80d 100644 (file)
@@ -2,6 +2,7 @@
 #define BLANK_SERVER_SERVER_HPP
 
 #include "../app/Config.hpp"
+#include "../world/WorldManipulator.hpp"
 
 #include <list>
 #include <SDL_net.h>
@@ -16,7 +17,8 @@ namespace server {
 
 class ClientConnection;
 
-class Server {
+class Server
+: public WorldManipulator {
 
 public:
        Server(const Config::Network &, World &);
@@ -35,6 +37,8 @@ public:
        bool HasPlayerModel() const noexcept;
        const CompositeModel &GetPlayerModel() const noexcept;
 
+       void SetBlock(Chunk &, int, const Block &) override;
+
 private:
        void HandlePacket(const UDPpacket &);
 
index 44a42682497276f0c647578bd592c1725e94adde..ea5b4d995f19d38262891b0919f1fb2360ed05b3 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <iostream>
 #include <zlib.h>
+#include <glm/gtx/io.hpp>
 
 using namespace std;
 
@@ -172,7 +173,7 @@ void ChunkTransmitter::Release() {
 ClientConnection::ClientConnection(Server &server, const IPaddress &addr)
 : server(server)
 , conn(addr)
-, player(nullptr)
+, input()
 , player_model(nullptr)
 , spawns()
 , confirm_wait(0)
@@ -180,6 +181,7 @@ ClientConnection::ClientConnection(Server &server, const IPaddress &addr)
 , player_update_state()
 , player_update_pack(0)
 , player_update_timer(1500)
+, old_actions(0)
 , transmitter(*this)
 , chunk_queue()
 , old_base() {
@@ -243,6 +245,7 @@ void ClientConnection::Update(int dt) {
                }
                SendUpdates();
 
+               input->Update(dt);
                CheckPlayerFix();
                CheckChunkQueue();
        }
@@ -387,9 +390,9 @@ void ClientConnection::CheckChunkQueue() {
        }
 }
 
-void ClientConnection::AttachPlayer(Player &new_player) {
+void ClientConnection::AttachPlayer(Player &player) {
        DetachPlayer();
-       player = &new_player;
+       input.reset(new DirectInput(server.GetWorld(), player, server));
        PlayerEntity().Ref();
 
        old_base = PlayerChunks().Base();
@@ -406,17 +409,18 @@ void ClientConnection::AttachPlayer(Player &new_player) {
                GetPlayerModel().Instantiate(PlayerEntity().GetModel());
        }
 
-       cout << "player \"" << player->Name() << "\" joined" << endl;
+       cout << "player \"" << player.Name() << "\" joined" << endl;
 }
 
 void ClientConnection::DetachPlayer() {
        if (!HasPlayer()) return;
-       cout << "player \"" << player->Name() << "\" left" << endl;
-       player->GetEntity().Kill();
-       player->GetEntity().UnRef();
-       player = nullptr;
+       cout << "player \"" << input->GetPlayer().Name() << "\" left" << endl;
+       PlayerEntity().Kill();
+       PlayerEntity().UnRef();
+       input.reset();
        transmitter.Abort();
        chunk_queue.clear();
+       old_actions = 0;
 }
 
 void ClientConnection::SetPlayerModel(const CompositeModel &m) noexcept {
@@ -511,13 +515,44 @@ void ClientConnection::On(const Packet::PlayerUpdate &pack) {
        int pack_diff = int16_t(pack.Seq()) - int16_t(player_update_pack);
        bool overdue = player_update_timer.HitOnce();
        player_update_timer.Reset();
-       if (pack_diff > 0 || overdue) {
-               player_update_pack = pack.Seq();
-               pack.ReadPlayerState(player_update_state);
-               // accept velocity and orientation as "user input"
-               PlayerEntity().Velocity(player_update_state.velocity);
-               PlayerEntity().Orientation(player_update_state.orient);
+       if (pack_diff <= 0 && !overdue) {
+               // drop old packets if we have a fairly recent state
+               return;
+       }
+       glm::vec3 movement(0.0f);
+       float pitch = 0.0f;
+       float yaw = 0.0f;
+       uint8_t new_actions;
+       uint8_t slot;
+
+       player_update_pack = pack.Seq();
+       pack.ReadPredictedState(player_update_state);
+       pack.ReadMovement(movement);
+       pack.ReadPitch(pitch);
+       pack.ReadYaw(yaw);
+       pack.ReadActions(new_actions);
+       pack.ReadSlot(slot);
+
+       input->SetMovement(movement);
+       input->TurnHead(pitch - input->GetPitch(), yaw - input->GetYaw());
+       input->SelectInventory(slot);
+
+       if ((new_actions & 0x01) && !(old_actions & 0x01)) {
+               input->StartPrimaryAction();
+       } else if (!(new_actions & 0x01) && (old_actions & 0x01)) {
+               input->StopPrimaryAction();
+       }
+       if ((new_actions & 0x02) && !(old_actions & 0x02)) {
+               input->StartSecondaryAction();
+       } else if (!(new_actions & 0x02) && (old_actions & 0x02)) {
+               input->StopSecondaryAction();
+       }
+       if ((new_actions & 0x04) && !(old_actions & 0x04)) {
+               input->StartTertiaryAction();
+       } else if (!(new_actions & 0x04) && (old_actions & 0x04)) {
+               input->StopTertiaryAction();
        }
+       old_actions = new_actions;
 }
 
 
@@ -608,5 +643,11 @@ const CompositeModel &Server::GetPlayerModel() const noexcept {
        return *player_model;
 }
 
+void Server::SetBlock(Chunk &chunk, int index, const Block &block) {
+       chunk.SetBlock(index, block);
+       // TODO: send to clients
+       // also TODO: batch chunk changes
+}
+
 }
 }
index d4a2e96d110b4b0c740a69048f648f540589a7c6..142da66495b9d3493375643ec249c5d417ab5a67 100644 (file)
@@ -4,8 +4,6 @@
 #include "PlayerController.hpp"
 
 #include "../app/IntervalTimer.hpp"
-#include "../world/EntityCollision.hpp"
-#include "../world/WorldCollision.hpp"
 
 
 namespace blank {
@@ -20,41 +18,23 @@ class DirectInput
 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;
-
        IntervalTimer place_timer;
        IntervalTimer remove_timer;
 
index 8d9e5a37247c3ca0622b70dd11cdb5cc0218a4ad..b29795b7313cf9cb5b8d2308e72cfcfa0f03a182 100644 (file)
@@ -3,16 +3,39 @@
 
 #include <glm/glm.hpp>
 
+#include "../world/EntityCollision.hpp"
+#include "../world/WorldCollision.hpp"
+
 
 namespace blank {
 
-struct PlayerController {
+class Player;
+class World;
+
+class PlayerController {
+
+public:
+       PlayerController(World &, Player &);
+
+       Player &GetPlayer() noexcept { return player; }
+       const Player &GetPlayer() const noexcept { return player; }
+
+       WorldCollision &BlockFocus() noexcept { return aim_world; }
+       const WorldCollision &BlockFocus() const noexcept { return aim_world; }
+       EntityCollision &EntityFocus() noexcept { return aim_entity; }
+       const EntityCollision &EntityFocus() const noexcept { return aim_entity; }
 
        /// 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;
+       void SetMovement(const glm::vec3 &) noexcept;
+       const glm::vec3 &GetMovement() const noexcept { return move_dir; }
        /// turn the controlled entity's head by given pitch and yaw deltas
-       virtual void TurnHead(float pitch, float yaw) = 0;
+       void TurnHead(float pitch, float yaw) noexcept;
+
+       /// get player pitch in radians, normalized to [-PI/2,PI/2]
+       float GetPitch() const noexcept { return pitch; }
+       /// get player yaw in radians, normalized to [-PI,PI]
+       float GetYaw() const noexcept { return yaw; }
 
        /// start doing primary action
        /// what exactly this means depends on the active item
@@ -26,7 +49,23 @@ struct PlayerController {
        virtual void StopTertiaryAction() = 0;
 
        /// set the item at given inventory slot as active
-       virtual void SelectInventory(int) = 0;
+       void SelectInventory(int) noexcept;
+       int InventorySlot() const noexcept;
+
+protected:
+       void Invalidate() noexcept;
+       void UpdatePlayer() noexcept;
+
+private:
+       World &world;
+       Player &player;
+       glm::vec3 move_dir;
+       float pitch;
+       float yaw;
+       bool dirty;
+
+       WorldCollision aim_world;
+       EntityCollision aim_entity;
 
 };
 
index 75129c1b3837ebd5460328507cded17ff3f73473..4dd9711edac447276bc721235749a6d573b27e67 100644 (file)
 
 namespace blank {
 
-DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
+PlayerController::PlayerController(World &world, Player &player)
 : world(world)
 , player(player)
-, manip(manip)
-, aim_world()
-, aim_entity()
 , move_dir(0.0f)
 , pitch(0.0f)
 , yaw(0.0f)
 , dirty(true)
-, place_timer(256)
-, remove_timer(256) {
-
-}
-
-void DirectInput::Update(int dt) {
-       dirty = true; // world has changed in the meantime
-       UpdatePlayer();
-
-       remove_timer.Update(dt);
-       if (remove_timer.Hit()) {
-               RemoveBlock();
-       }
+, aim_world()
+, aim_entity() {
 
-       place_timer.Update(dt);
-       if (place_timer.Hit()) {
-               PlaceBlock();
-       }
 }
 
-void DirectInput::SetMovement(const glm::vec3 &m) {
+void PlayerController::SetMovement(const glm::vec3 &m) noexcept {
        if (dot(m, m) > 1.0f) {
                move_dir = normalize(m);
        } else {
                move_dir = m;
        }
-       dirty = true;
+       Invalidate();
 }
 
-void DirectInput::TurnHead(float dp, float dy) {
+void PlayerController::TurnHead(float dp, float dy) noexcept {
        pitch += dp;
        if (pitch > PI / 2) {
                pitch = PI / 2;
@@ -84,9 +66,70 @@ void DirectInput::TurnHead(float dp, float dy) {
        } else if (yaw < -PI) {
                yaw += PI * 2;
        }
+       Invalidate();
+}
+
+void PlayerController::SelectInventory(int i) noexcept {
+       player.SetInventorySlot(i);
+}
+
+int PlayerController::InventorySlot() const noexcept {
+       return player.GetInventorySlot();
+}
+
+void PlayerController::Invalidate() noexcept {
        dirty = true;
 }
 
+void PlayerController::UpdatePlayer() noexcept {
+       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();
+                       }
+               }
+               dirty = false;
+       }
+}
+
+
+DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
+: PlayerController(world, player)
+, manip(manip)
+, place_timer(256)
+, remove_timer(256) {
+
+}
+
+void DirectInput::Update(int dt) {
+       Invalidate(); // 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::StartPrimaryAction() {
        if (!remove_timer.Running()) {
                RemoveBlock();
@@ -117,59 +160,29 @@ void DirectInput::StopTertiaryAction() {
        // nothing
 }
 
-void DirectInput::SelectInventory(int i) {
-       player.SetInventorySlot(i);
-}
-
-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);
+       if (!BlockFocus()) return;
+       SelectInventory(BlockFocus().GetBlock().type - 1);
 }
 
 void DirectInput::PlaceBlock() {
        UpdatePlayer();
-       if (!aim_world) return;
+       if (!BlockFocus()) return;
 
-       BlockLookup next_block(aim_world.chunk, aim_world.BlockPos(), Block::NormalFace(aim_world.normal));
+       BlockLookup next_block(BlockFocus().chunk, BlockFocus().BlockPos(), Block::NormalFace(BlockFocus().normal));
        if (!next_block) {
                return;
        }
-       manip.SetBlock(next_block.GetChunk(), next_block.GetBlockIndex(), Block(player.GetInventorySlot() + 1));
-       dirty = true;
+       manip.SetBlock(next_block.GetChunk(), next_block.GetBlockIndex(), Block(InventorySlot() + 1));
+       Invalidate();
 }
 
 void DirectInput::RemoveBlock() {
        UpdatePlayer();
-       if (!aim_world) return;
-       manip.SetBlock(aim_world.GetChunk(), aim_world.block, Block(0));
-       dirty = true;
+       if (!BlockFocus()) return;
+       manip.SetBlock(BlockFocus().GetChunk(), BlockFocus().block, Block(0));
+       Invalidate();
 }
 
 
index 3f47d0f9c4132085b420072a119397a0c1fc3207..42d83480610f14842857ec8a52f78b60b0441247 100644 (file)
@@ -3,8 +3,6 @@
 #include "model/CompositeModel.hpp"
 #include "world/Entity.hpp"
 
-#include <limits>
-
 CPPUNIT_TEST_SUITE_REGISTRATION(blank::test::PacketTest);
 
 using namespace std;
@@ -149,22 +147,61 @@ void PacketTest::testPart() {
 
 void PacketTest::testPlayerUpdate() {
        auto pack = Packet::Make<Packet::PlayerUpdate>(udp_pack);
-       AssertPacket("PlayerUpdate", 4, 64, pack);
+       AssertPacket("PlayerUpdate", 4, 76, pack);
+
+       EntityState write_state;
+       write_state.chunk_pos = { 7, 2, -3 };
+       write_state.block_pos = { 1.5f, 0.9f, 12.0f };
+       write_state.velocity = { 0.025f, 0.001f, 0.0f };
+       write_state.orient = { 1.0f, 0.0f, 0.0f, 0.0f };
+       write_state.ang_vel = { 0.01f, 0.00302f, 0.0985f };
+       glm::vec3 write_movement(0.5f, -1.0f, 1.0f);
+       float write_pitch = 1.25f;
+       float write_yaw = -2.5f;
+       uint8_t write_actions = 0x05;
+       uint8_t write_slot = 3;
+       pack.WritePredictedState(write_state);
+       pack.WriteMovement(write_movement);
+       pack.WritePitch(write_pitch);
+       pack.WriteYaw(write_yaw);
+       pack.WriteActions(write_actions);
+       pack.WriteSlot(write_slot);
 
-       Entity write_entity;
-       write_entity.ID(534574);
-       write_entity.GetState().chunk_pos = { 7, 2, -3 };
-       write_entity.GetState().block_pos = { 1.5f, 0.9f, 12.0f };
-       write_entity.GetState().velocity = { 0.025f, 0.001f, 0.0f };
-       write_entity.GetState().orient = { 1.0f, 0.0f, 0.0f, 0.0f };
-       write_entity.GetState().ang_vel = { 0.01f, 0.00302f, 0.0985f };
        EntityState read_state;
-       pack.WritePlayer(write_entity);
-
-       pack.ReadPlayerState(read_state);
+       glm::vec3 read_movement;
+       float read_pitch;
+       float read_yaw;
+       uint8_t read_actions;
+       uint8_t read_slot;
+       pack.ReadPredictedState(read_state);
+       pack.ReadMovement(read_movement);
+       pack.ReadPitch(read_pitch);
+       pack.ReadYaw(read_yaw);
+       pack.ReadActions(read_actions);
+       pack.ReadSlot(read_slot);
        AssertEqual(
-               "player entity state not correctly transported in PlayerUpdate packet",
-               write_entity.GetState(), read_state
+               "player predicted entity state not correctly transported in PlayerUpdate packet",
+               write_state, read_state
+       );
+       AssertEqual(
+               "player movement input not correctly transported in PlayerUpdate packet",
+               write_movement, read_movement, 0.0001f
+       );
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               "player pitch input not correctly transported in PlayerUpdate packet",
+               write_pitch, read_pitch, 0.0001f
+       );
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               "player yaw input not correctly transported in PlayerUpdate packet",
+               write_yaw, read_yaw, 0.0001f
+       );
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "player actions not correctly transported in PlayerUpdate packet",
+               int(write_actions), int(read_actions)
+       );
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "player inventory slot not correctly transported in PlayerUpdate packet",
+               int(write_slot), int(read_slot)
        );
 }
 
@@ -237,7 +274,7 @@ void PacketTest::testDespawnEntity() {
 
 void PacketTest::testEntityUpdate() {
        auto pack = Packet::Make<Packet::EntityUpdate>(udp_pack);
-       AssertPacket("EntityUpdate", 7, 4, 452, pack);
+       AssertPacket("EntityUpdate", 7, 4, 480, pack);
 
        pack.length = Packet::EntityUpdate::GetSize(3);
        CPPUNIT_ASSERT_EQUAL_MESSAGE(
@@ -501,19 +538,20 @@ void PacketTest::AssertEqual(
 void PacketTest::AssertEqual(
        const string &message,
        const glm::vec3 &expected,
-       const glm::vec3 &actual
+       const glm::vec3 &actual,
+       float epsilon
 ) {
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
                message + " (X component)",
-               expected.x, actual.x, numeric_limits<float>::epsilon()
+               expected.x, actual.x, epsilon
        );
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
                message + " (Y component)",
-               expected.y, actual.y, numeric_limits<float>::epsilon()
+               expected.y, actual.y, epsilon
        );
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
                message + " (Z component)",
-               expected.z, actual.z, numeric_limits<float>::epsilon()
+               expected.z, actual.z, epsilon
        );
 }
 
index 119c696de39b79159b525bb82ef5000336c7d70b..89e15dd526e35982ec69d6630c924cdc3a603e5c 100644 (file)
@@ -6,6 +6,7 @@
 #include "world/EntityState.hpp"
 
 #include <cstdint>
+#include <limits>
 #include <string>
 #include <SDL_net.h>
 #include <glm/glm.hpp>
@@ -80,7 +81,8 @@ private:
        static void AssertEqual(
                const std::string &message,
                const glm::vec3 &expected,
-               const glm::vec3 &actual);
+               const glm::vec3 &actual,
+               float epsilon = std::numeric_limits<float>::epsilon());
        static void AssertEqual(
                const std::string &message,
                const glm::quat &expected,