but that's by accident and will break if conversion code is ever
added)
+Common Types
+------------
+
+Name Size Type
+vec3 12 3x 32bit float
+vec3i 12 3x 32bit signed int
+quat 16 4x 32bit float
+entity state 64 vec3i, vec3, vec3, quat, vec3
+
Packets
=======
Code: 2
Payload:
0 entity ID of the player, 32bit unsigned int
- 4 chunk coords of the player, 3x 32bit signed int
- 16 pos/vel/rot/ang of the player, 13x 32bit float
+ 4 entity state of the player
68 name of the world the server's currently running
max 32 byte UTF-8 string
Length: 68-100
Code: 4
Payload:
- 0 chunk coords of the player, 3x 32bit signed int
- 12 pos/vel/rot/ang of the player, 13x 32bit float
+ 0 entity state of the player as seen by the client
Length: 64
Payload:
0 entity ID, 32bit unsigned int
4 entity's skeleton ID, 32bit unsigned int
- 8 chunk coords of the entity, 3x 32bit signed int
- 20 pos/vel/rot/ang of the entity, 13x 32bit float
+ 8 entity state
72 bounding box of the entity, 6x 32bit float
96 flags, 32bit bitfield with boolean values
1: world collision
Code: 7
Payload:
0 number of entities, 32bit int, 1-7
- 4 chunk coords of the entity, 3x 32bit signed int
- 16 pos/vel/rot/ang of the entity, 13x 32bit float
- 68 next entity...
-Length: 4 + multiple of 64, max 452
+ 4 entity ID, 32bit unsigned int
+ 8 entity state
+ 72 next entity...
+Length: 4 + multiple of 68, max 452
+
+
+Player Correction
+-----------------
+
+Sent by the server to tell a client that its prediction is way off.
+
+Code: 8
+Payload:
+ 0 sequence number of the offending packet, 16bit unsigned int
+ 2 entity state of the player's entity on the server
+Length: 66
#include "../ui/Interface.hpp"
#include "../world/BlockTypeRegistry.hpp"
#include "../world/ChunkRenderer.hpp"
+#include "../world/EntityState.hpp"
#include "../world/World.hpp"
+#include <list>
+
namespace blank {
void Update(int dt) override;
void Render(Viewport &) override;
+ void PushPlayerUpdate(const Entity &);
+ void MergePlayerCorrection(std::uint16_t, const EntityState &);
+
private:
MasterState &master;
BlockTypeRegistry block_types;
Skeletons skeletons;
IntervalTimer update_timer;
+ struct PlayerHistory {
+ EntityState state;
+ int timestamp;
+ std::uint16_t packet;
+ PlayerHistory(EntityState s, int t, std::uint16_t p)
+ : state(s), timestamp(t), packet(p) { }
+ };
+ std::list<PlayerHistory> player_hist;
+
};
}
void On(const Packet::SpawnEntity &) override;
void On(const Packet::DespawnEntity &) override;
void On(const Packet::EntityUpdate &) override;
+ void On(const Packet::PlayerCorrection &) override;
private:
/// flag entity as updated by given packet
)
, chunk_renderer(*interface.GetPlayer().chunks)
, skeletons()
-, update_timer(16) {
+, update_timer(16)
+, player_hist() {
TextureIndex tex_index;
master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index);
chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index);
Entity &player = *interface.GetPlayer().entity;
if (update_timer.Hit()) {
- master.GetClient().SendPlayerUpdate(player);
+ PushPlayerUpdate(player);
}
glm::mat4 trans = player.Transform(player.ChunkCoords());
master.GetEnv().audio.Orientation(dir, up);
}
+void InteractiveState::PushPlayerUpdate(const Entity &player) {
+ std::uint16_t packet = master.GetClient().SendPlayerUpdate(player);
+ if (player_hist.size() < 16) {
+ player_hist.emplace_back(player.GetState(), update_timer.Elapsed(), packet);
+ } else {
+ auto entry = player_hist.begin();
+ entry->state = player.GetState();
+ entry->timestamp = update_timer.Elapsed();
+ 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();
+
+ // drop anything older than the fix
+ while (entry != end) {
+ int pack_diff = int16_t(seq) - int16_t(entry->packet);
+ if (pack_diff < 0) {
+ entry = player_hist.erase(entry);
+ } else {
+ break;
+ }
+ }
+ if (entry == end) return;
+}
+
void InteractiveState::Render(Viewport &viewport) {
Entity &player = *interface.GetPlayer().entity;
viewport.WorldPosition(player.Transform(player.ChunkCoords()));
update_status.erase(entity_id);
}
+void MasterState::On(const Packet::PlayerCorrection &pack) {
+ if (!state) {
+ cout << "got player correction without a player :S" << endl;
+ Quit();
+ return;
+ }
+ uint16_t pack_seq;
+ EntityState corrected_state;
+ pack.ReadPacketSeq(pack_seq);
+ pack.ReadPlayerState(corrected_state);
+ state->MergePlayerCorrection(pack_seq, corrected_state);
+}
+
}
}
void SendDespawn(SpawnStatus &);
void SendUpdate(SpawnStatus &);
+ void CheckPlayerFix();
+
private:
Server &server;
Connection conn;
virtual void On(const Packet::SpawnEntity &) { }
virtual void On(const Packet::DespawnEntity &) { }
virtual void On(const Packet::EntityUpdate &) { }
+ virtual void On(const Packet::PlayerCorrection &) { }
};
void ReadEntityState(EntityState &, std::uint32_t) const noexcept;
};
+ struct PlayerCorrection : public Payload {
+ static constexpr std::uint8_t TYPE = 8;
+ static constexpr std::size_t MAX_LEN = 66;
+
+ void WritePacketSeq(std::uint16_t) noexcept;
+ void ReadPacketSeq(std::uint16_t &) const noexcept;
+ void WritePlayer(const Entity &) noexcept;
+ void ReadPlayerState(EntityState &) const noexcept;
+ };
+
template<class PayloadType>
PayloadType As() {
constexpr size_t Packet::SpawnEntity::MAX_LEN;
constexpr size_t Packet::DespawnEntity::MAX_LEN;
constexpr size_t Packet::EntityUpdate::MAX_LEN;
+constexpr size_t Packet::PlayerCorrection::MAX_LEN;
namespace {
SendDespawn(*local_iter);
++local_iter;
}
+
+ CheckPlayerFix();
}
if (conn.ShouldPing()) {
conn.SendPing(server.GetPacket(), server.GetSocket());
conn.Send(server.GetPacket(), server.GetSocket());
}
+void ClientConnection::CheckPlayerFix() {
+ // check always succeeds for now ;)
+ auto pack = Packet::Make<Packet::PlayerCorrection>(server.GetPacket());
+ pack.WritePacketSeq(player_update_pack);
+ pack.WritePlayer(Player());
+ conn.Send(server.GetPacket(), server.GetSocket());
+}
+
void ClientConnection::AttachPlayer(Entity &new_player) {
DetachPlayer();
player = &new_player;
return "DespawnEntity";
case EntityUpdate::TYPE:
return "EntityUpdate";
+ case PlayerCorrection::TYPE:
+ return "PlayerCorrection";
default:
return "Unknown";
}
Read(state, off + 4);
}
+void Packet::PlayerCorrection::WritePacketSeq(std::uint16_t s) noexcept {
+ Write(s, 0);
+}
+
+void Packet::PlayerCorrection::ReadPacketSeq(std::uint16_t &s) const noexcept {
+ Read(s, 0);
+}
+
+void Packet::PlayerCorrection::WritePlayer(const Entity &player) noexcept {
+ Write(player.GetState(), 2);
+}
+
+void Packet::PlayerCorrection::ReadPlayerState(EntityState &state) const noexcept {
+ Read(state, 2);
+}
+
void ConnectionHandler::Handle(const UDPpacket &udp_pack) {
const Packet &pack = *reinterpret_cast<const Packet *>(udp_pack.data);
case Packet::EntityUpdate::TYPE:
On(Packet::As<Packet::EntityUpdate>(udp_pack));
break;
+ case Packet::PlayerCorrection::TYPE:
+ On(Packet::As<Packet::PlayerCorrection>(udp_pack));
+ break;
default:
// drop unknown or unhandled packets
break;