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
+vec3b 3 3x 8bit signed int
+packn 2 16bit signed int representing a float value normalized to [-1,1]
+ it can be unpacked by dividing by 32767
+packu 2 16bit unsigned int representing a float value normalized to [0,1]
+ it can be unpacked by dividing by 65535
+vec3n 6 3x packn
+vec3u 6 3x packu
+quat 8 4x packn float
+entity state 50 [ 0] vec3i chunk pos (there's a variation where this is a vec3b)
+ [12] vec3u block pos by 16,
+ [18] vec3 velocity,
+ [30] quat orientation,
+ [38] vec3 angular velocity
Packets
Login
-----
-Sent from client to serveri as a request to join. The server may
+Sent from client to server as a request to join. The server may
respond negatively if the player name is already taken or some cap has
been reached.
Payload:
0 entity ID of the player, 32bit unsigned int
4 entity state of the player
- 68 name of the world the server's currently running
+ 54 name of the world the server's currently running
max 32 byte UTF-8 string
-Length: 68-100
+Length: 54-86
Part
Code: 4
Payload:
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
+ 50 movement input, vec3n
+ 56 pitch input by PI/2, packn
+ 58 yaw input by PI, packn
+ 60 active actions, 8bit bit field, first three bits are primary, secondary, and tertiary
+ 61 selected inventory slot, 8bit unsigned int
+Length: 62
Spawn Entity
Code: 5
Payload:
- 0 entity ID, 32bit unsigned int
- 4 entity's skeleton ID, 32bit unsigned int
- 8 entity state
- 72 bounding box of the entity, 6x 32bit float
- 96 flags, 32bit bitfield with boolean values
- 1: world collision
- 100 entity name, max 32 byte UTF-8 string
-Length: 100 - 132
+ 0 entity ID, 32bit unsigned int
+ 4 entity's model ID, 32bit unsigned int
+ 8 entity state
+ 58 bounding box of the entity, 6x 32bit float
+ 82 flags, 32bit bitfield with boolean values
+ 1: world collision
+ 86 entity name, max 32 byte UTF-8 string
+Length: 87 - 118
Despawn Entity
Code: 7
Payload:
- 0 number of entities, 32bit int, 1-7
- 4 entity ID, 32bit unsigned int
- 8 entity state
- 72 next entity...
-Length: 4 + multiple of 68, max 480
+ 0 number of entities, 32bit int, 1-10
+ 4 base for chunk coordinates, vec3i
+ 16 entity ID, 32bit unsigned int
+ 20 entity state with vec3b as chunk position (rather than vec3i)
+ 62 next entity...
+Length: 16 + multiple of 45, max 466
Player Correction
Payload:
0 sequence number of the offending packet, 16bit unsigned int
2 entity state of the player's entity on the server
-Length: 66
+Length: 52
Chunk Begin
Entity &entity = world.ForceAddEntity(entity_id);
UpdateEntity(entity_id, pack.Seq());
pack.ReadEntity(entity);
- uint32_t skel_id;
- pack.ReadSkeletonID(skel_id);
- if (skel_id > 0 && skel_id <= res.models.size()) {
- Model &skel = res.models.Get(skel_id);
- skel.Instantiate(entity.GetModel());
+ uint32_t model_id;
+ pack.ReadModelID(model_id);
+ if (model_id > 0 && model_id <= res.models.size()) {
+ res.models.Get(model_id).Instantiate(entity.GetModel());
}
cout << "spawned entity #" << entity_id << " (" << entity.Name()
<< ") at " << entity.AbsolutePosition() << endl;
auto world_end = world.Entities().end();
uint32_t count = 0;
+ glm::ivec3 base;
pack.ReadEntityCount(count);
+ pack.ReadChunkBase(base);
for (uint32_t i = 0; i < count; ++i) {
uint32_t entity_id = 0;
}
if (world_iter->ID() == entity_id) {
if (UpdateEntity(entity_id, pack.Seq())) {
- pack.ReadEntityState(world_iter->GetState(), i);
+ pack.ReadEntityState(world_iter->GetState(), base, i);
}
}
}
constexpr float PI_1p5 = PI * 1.5f;
constexpr float PI_2p0 = PI * 2.0f;
+constexpr float PI_inv = 1.0f / PI;
+constexpr float PI_0p5_inv = 1.0f / PI_0p5;
+
constexpr float DEG_RAD_FACTOR = PI / 180.0f;
constexpr float RAD_DEG_FACTOR = 180.0f / PI;
template<class T>
void Read(T &, size_t off) const noexcept;
+ void Write(const glm::quat &, size_t off) noexcept;
+ void Read(glm::quat &, size_t off) const noexcept;
+ void Write(const EntityState &, size_t off) noexcept;
+ void Read(EntityState &, size_t off) const noexcept;
+ void Write(const EntityState &, const glm::ivec3 &, size_t off) noexcept;
+ void Read(EntityState &, const glm::ivec3 &, size_t off) const noexcept;
+
void WriteString(const std::string &src, std::size_t off, std::size_t maxlen) noexcept;
void ReadString(std::string &dst, std::size_t off, std::size_t maxlen) const noexcept;
+
+ void WritePackB(const glm::ivec3 &, size_t off) noexcept;
+ void ReadPackB(glm::ivec3 &, size_t off) const noexcept;
+
+ void WritePackN(float, size_t off) noexcept;
+ void ReadPackN(float &, size_t off) const noexcept;
+ void WritePackN(const glm::vec3 &, size_t off) noexcept;
+ void ReadPackN(glm::vec3 &, size_t off) const noexcept;
+
+ void WritePackU(float, size_t off) noexcept;
+ void ReadPackU(float &, size_t off) const noexcept;
+ void WritePackU(const glm::vec3 &, size_t off) noexcept;
+ void ReadPackU(glm::vec3 &, size_t off) const noexcept;
};
struct Ping : public Payload {
struct Join : public Payload {
static constexpr std::uint8_t TYPE = 2;
- static constexpr std::size_t MAX_LEN = 100;
+ static constexpr std::size_t MAX_LEN = 86;
void WritePlayer(const Entity &) noexcept;
void ReadPlayerID(std::uint32_t &) const noexcept;
struct PlayerUpdate : public Payload {
static constexpr std::uint8_t TYPE = 4;
- static constexpr std::size_t MAX_LEN = 76;
+ static constexpr std::size_t MAX_LEN = 62;
void WritePredictedState(const EntityState &) noexcept;
void ReadPredictedState(EntityState &) const noexcept;
struct SpawnEntity : public Payload {
static constexpr std::uint8_t TYPE = 5;
- static constexpr std::size_t MAX_LEN = 132;
+ static constexpr std::size_t MAX_LEN = 118;
void WriteEntity(const Entity &) noexcept;
void ReadEntityID(std::uint32_t &) const noexcept;
- void ReadSkeletonID(std::uint32_t &) const noexcept;
+ void ReadModelID(std::uint32_t &) const noexcept;
void ReadEntity(Entity &) const noexcept;
};
struct EntityUpdate : public Payload {
static constexpr std::uint8_t TYPE = 7;
- static constexpr std::size_t MAX_LEN = 480;
+ static constexpr std::size_t MAX_LEN = 466;
- static constexpr std::uint32_t MAX_ENTITIES = 7;
+ static constexpr std::uint32_t MAX_ENTITIES = 10;
static constexpr std::size_t GetSize(std::uint32_t num) noexcept {
- return 4 + (num * 68);
+ return 16 + (num * 45);
}
void WriteEntityCount(std::uint32_t) noexcept;
void ReadEntityCount(std::uint32_t &) const noexcept;
+ void WriteChunkBase(const glm::ivec3 &) noexcept;
+ void ReadChunkBase(glm::ivec3 &) const noexcept;
- void WriteEntity(const Entity &, std::uint32_t) noexcept;
+ void WriteEntity(const Entity &, const glm::ivec3 &, std::uint32_t) noexcept;
void ReadEntityID(std::uint32_t &, std::uint32_t) const noexcept;
- void ReadEntityState(EntityState &, std::uint32_t) const noexcept;
+ void ReadEntityState(EntityState &, const glm::ivec3 &, std::uint32_t) const noexcept;
};
struct PlayerCorrection : public Payload {
static constexpr std::uint8_t TYPE = 8;
- static constexpr std::size_t MAX_LEN = 66;
+ static constexpr std::size_t MAX_LEN = 52;
void WritePacketSeq(std::uint16_t) noexcept;
void ReadPacketSeq(std::uint16_t &) const noexcept;
}
}
+void Packet::Payload::Write(const glm::quat &val, size_t off) noexcept {
+ WritePackN(val.w, off);
+ WritePackN(val.x, off + 2);
+ WritePackN(val.y, off + 4);
+ WritePackN(val.z, off + 6);
+}
+
+void Packet::Payload::Read(glm::quat &val, size_t off) const noexcept {
+ ReadPackN(val.w, off);
+ ReadPackN(val.x, off + 2);
+ ReadPackN(val.y, off + 4);
+ ReadPackN(val.z, off + 6);
+ val = normalize(val);
+}
+
+void Packet::Payload::Write(const EntityState &state, size_t off) noexcept {
+ Write(state.chunk_pos, off);
+ WritePackU(state.block_pos * (1.0f / 16.0f), off + 12);
+ Write(state.velocity, off + 18);
+ Write(state.orient, off + 30);
+ Write(state.ang_vel, off + 38);
+}
+
+void Packet::Payload::Read(EntityState &state, size_t off) const noexcept {
+ Read(state.chunk_pos, off);
+ ReadPackU(state.block_pos, off + 12);
+ Read(state.velocity, off + 18);
+ Read(state.orient, off + 30);
+ Read(state.ang_vel, off + 38);
+ state.block_pos *= 16.0f;
+}
+
+void Packet::Payload::Write(const EntityState &state, const glm::ivec3 &base, size_t off) noexcept {
+ WritePackB(state.chunk_pos - base, off);
+ WritePackU(state.block_pos * (1.0f / 16.0f), off + 3);
+ Write(state.velocity, off + 9);
+ Write(state.orient, off + 21);
+ Write(state.ang_vel, off + 29);
+}
+
+void Packet::Payload::Read(EntityState &state, const glm::ivec3 &base, size_t off) const noexcept {
+ ReadPackB(state.chunk_pos, off);
+ ReadPackU(state.block_pos, off + 3);
+ Read(state.velocity, off + 9);
+ Read(state.orient, off + 21);
+ Read(state.ang_vel, off + 29);
+ state.chunk_pos += base;
+ state.block_pos *= 16.0f;
+}
+
+void Packet::Payload::WritePackN(float val, size_t off) noexcept {
+ int16_t raw = glm::clamp(glm::round(val * 32767.0f), -32767.0f, 32767.0f);
+ Write(raw, off);
+}
+
+void Packet::Payload::WritePackB(const glm::ivec3 &val, size_t off) noexcept {
+ Write(int8_t(val.x), off);
+ Write(int8_t(val.y), off + 1);
+ Write(int8_t(val.z), off + 2);
+}
+
+void Packet::Payload::ReadPackB(glm::ivec3 &val, size_t off) const noexcept {
+ int8_t conv = 0;
+ Read(conv, off);
+ val.x = conv;
+ Read(conv, off + 1);
+ val.y = conv;
+ Read(conv, off + 2);
+ val.z = conv;
+}
+
+void Packet::Payload::ReadPackN(float &val, size_t off) const noexcept {
+ int16_t raw = 0;
+ Read(raw, off);
+ val = raw * (1.0f/32767.0f);
+}
+
+void Packet::Payload::WritePackN(const glm::vec3 &val, size_t off) noexcept {
+ WritePackN(val.x, off);
+ WritePackN(val.y, off + 2);
+ WritePackN(val.z, off + 4);
+}
+
+void Packet::Payload::ReadPackN(glm::vec3 &val, size_t off) const noexcept {
+ ReadPackN(val.x, off);
+ ReadPackN(val.y, off + 2);
+ ReadPackN(val.z, off + 4);
+}
+
+void Packet::Payload::WritePackU(float val, size_t off) noexcept {
+ uint16_t raw = glm::clamp(glm::round(val * 65535.0f), 0.0f, 65535.0f);
+ Write(raw, off);
+}
+
+void Packet::Payload::ReadPackU(float &val, size_t off) const noexcept {
+ uint16_t raw = 0;
+ Read(raw, off);
+ val = raw * (1.0f/65535.0f);
+}
+
+void Packet::Payload::WritePackU(const glm::vec3 &val, size_t off) noexcept {
+ WritePackU(val.x, off);
+ WritePackU(val.y, off + 2);
+ WritePackU(val.z, off + 4);
+}
+
+void Packet::Payload::ReadPackU(glm::vec3 &val, size_t off) const noexcept {
+ ReadPackU(val.x, off);
+ ReadPackU(val.y, off + 2);
+ ReadPackU(val.z, off + 4);
+}
+
void Packet::Login::WritePlayerName(const string &name) noexcept {
WriteString(name, 0, 32);
}
void Packet::Join::WriteWorldName(const string &name) noexcept {
- WriteString(name, 68, 32);
+ WriteString(name, 54, 32);
}
void Packet::Join::ReadWorldName(string &name) const noexcept {
- ReadString(name, 68, 32);
+ ReadString(name, 54, 32);
}
void Packet::PlayerUpdate::WritePredictedState(const EntityState &state) noexcept {
}
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);
+ WritePackN(mov, 50);
}
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;
+ ReadPackN(mov, 50);
}
void Packet::PlayerUpdate::WritePitch(float pitch) noexcept {
- int16_t conv = pitch * 20860.12008116853786870640f;
- Write(conv, 70);
+ float conv = pitch * PI_0p5_inv;
+ WritePackN(conv, 56);
}
void Packet::PlayerUpdate::ReadPitch(float &pitch) const noexcept {
- int16_t conv = 0;
- Read(conv, 70);
- pitch = conv * .00004793836258415163f;
+ ReadPackN(pitch, 56);
+ pitch *= PI_0p5;
}
void Packet::PlayerUpdate::WriteYaw(float yaw) noexcept {
- int16_t conv = yaw * 10430.06004058426893435320f;
- Write(conv, 72);
+ float conv = yaw * PI_inv;
+ WritePackN(conv, 58);
}
void Packet::PlayerUpdate::ReadYaw(float &yaw) const noexcept {
- int16_t conv = 0;
- Read(conv, 72);
- yaw = conv * .00009587672516830326f;
+ ReadPackN(yaw, 58);
+ yaw *= PI;
}
void Packet::PlayerUpdate::WriteActions(uint8_t actions) noexcept {
- Write(actions, 74);
+ Write(actions, 60);
}
void Packet::PlayerUpdate::ReadActions(uint8_t &actions) const noexcept {
- Read(actions, 74);
+ Read(actions, 60);
}
void Packet::PlayerUpdate::WriteSlot(uint8_t slot) noexcept {
- Write(slot, 75);
+ Write(slot, 61);
}
void Packet::PlayerUpdate::ReadSlot(uint8_t &slot) const noexcept {
- Read(slot, 75);
+ Read(slot, 61);
}
void Packet::SpawnEntity::WriteEntity(const Entity &e) noexcept {
Write(uint32_t(0), 4);
}
Write(e.GetState(), 8);
- Write(e.Bounds(), 72);
+ Write(e.Bounds(), 58);
uint32_t flags = 0;
if (e.WorldCollidable()) {
flags |= 1;
}
- Write(flags, 96);
- WriteString(e.Name(), 100, 32);
+ Write(flags, 82);
+ WriteString(e.Name(), 86, 32);
}
void Packet::SpawnEntity::ReadEntityID(uint32_t &id) const noexcept {
Read(id, 0);
}
-void Packet::SpawnEntity::ReadSkeletonID(uint32_t &id) const noexcept {
+void Packet::SpawnEntity::ReadModelID(uint32_t &id) const noexcept {
Read(id, 4);
}
string name;
Read(state, 8);
- Read(bounds, 72);
- Read(flags, 96);
- ReadString(name, 100, 32);
+ Read(bounds, 58);
+ Read(flags, 82);
+ ReadString(name, 86, 32);
e.SetState(state);
e.Bounds(bounds);
Read(count, 0);
}
-void Packet::EntityUpdate::WriteEntity(const Entity &entity, uint32_t num) noexcept {
+void Packet::EntityUpdate::WriteChunkBase(const glm::ivec3 &base) noexcept {
+ Write(base, 4);
+}
+
+void Packet::EntityUpdate::ReadChunkBase(glm::ivec3 &base) const noexcept {
+ Read(base, 4);
+}
+
+void Packet::EntityUpdate::WriteEntity(const Entity &entity, const glm::ivec3 &base, uint32_t num) noexcept {
uint32_t off = GetSize(num);
Write(entity.ID(), off);
- Write(entity.GetState(), off + 4);
+ Write(entity.GetState(), base, off + 4);
}
void Packet::EntityUpdate::ReadEntityID(uint32_t &id, uint32_t num) const noexcept {
Read(id, off);
}
-void Packet::EntityUpdate::ReadEntityState(EntityState &state, uint32_t num) const noexcept {
+void Packet::EntityUpdate::ReadEntityState(EntityState &state, const glm::ivec3 &base, uint32_t num) const noexcept {
uint32_t off = GetSize(num);
- Read(state, off + 4);
+ Read(state, base, off + 4);
}
void Packet::PlayerCorrection::WritePacketSeq(std::uint16_t s) noexcept {
}
void ClientConnection::SendUpdates() {
+ auto base = PlayerChunks().Base();
auto pack = Prepare<Packet::EntityUpdate>();
+ pack.WriteChunkBase(base);
int entity_pos = 0;
for (SpawnStatus *status : entity_updates) {
- pack.WriteEntity(*status->entity, entity_pos);
+ pack.WriteEntity(*status->entity, base, entity_pos);
++entity_pos;
if (entity_pos == Packet::EntityUpdate::MAX_ENTITIES) {
pack.WriteEntityCount(entity_pos);
"unexpected size of vec3i",
size_t(12), sizeof(glm::ivec3)
);
- CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "unexpected size of quat",
- size_t(16), sizeof(glm::quat)
- );
- CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "unexpected size of entity state",
- size_t(64), sizeof(EntityState)
- );
}
void PacketTest::testControl() {
void PacketTest::testJoin() {
auto pack = Packet::Make<Packet::Join>(udp_pack);
- AssertPacket("Join", 2, 68, 100, pack);
+ AssertPacket("Join", 2, 54, 86, pack);
Entity write_entity;
write_entity.ID(534574);
void PacketTest::testPlayerUpdate() {
auto pack = Packet::Make<Packet::PlayerUpdate>(udp_pack);
- AssertPacket("PlayerUpdate", 4, 76, pack);
+ AssertPacket("PlayerUpdate", 4, 62, pack);
EntityState write_state;
write_state.chunk_pos = { 7, 2, -3 };
void PacketTest::testSpawnEntity() {
auto pack = Packet::Make<Packet::SpawnEntity>(udp_pack);
- AssertPacket("SpawnEntity", 5, 100, 132, pack);
+ AssertPacket("SpawnEntity", 5, 87, 118, pack);
Entity write_entity;
write_entity.ID(534574);
pack.WriteEntity(write_entity);
uint32_t entity_id;
- uint32_t skeleton_id;
+ uint32_t model_id;
Entity read_entity;
pack.ReadEntityID(entity_id);
- pack.ReadSkeletonID(skeleton_id);
+ pack.ReadModelID(model_id);
pack.ReadEntity(read_entity);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
write_entity.ID(), entity_id
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "skeleton ID not correctly transported in SpawnEntity packet",
- write_entity.GetModel().GetModel().ID(), skeleton_id
+ "model ID not correctly transported in SpawnEntity packet",
+ write_entity.GetModel().GetModel().ID(), model_id
);
AssertEqual(
"entity state not correctly transported in PlayerUpdate packet",
void PacketTest::testEntityUpdate() {
auto pack = Packet::Make<Packet::EntityUpdate>(udp_pack);
- AssertPacket("EntityUpdate", 7, 4, 480, pack);
+ AssertPacket("EntityUpdate", 7, 16, 466, pack);
pack.length = Packet::EntityUpdate::GetSize(3);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
"length not correctly set in EntityUpdate packet",
- size_t(4 + 3 * 68), pack.length
+ size_t(16 + 3 * 45), pack.length
);
uint32_t write_count = 3;
- uint32_t read_count;
+ glm::ivec3 write_base(8, -15, 1);
pack.WriteEntityCount(write_count);
+ pack.WriteChunkBase(write_base);
+
+ uint32_t read_count;
+ glm::ivec3 read_base;
pack.ReadEntityCount(read_count);
+ pack.ReadChunkBase(read_base);
+
CPPUNIT_ASSERT_EQUAL_MESSAGE(
"entity count not correctly transported in EntityUpdate packet",
write_count, read_count
);
+ AssertEqual(
+ "chunk base not correctly transported in EntityUpdate packet",
+ write_base, read_base
+ );
Entity write_entity;
write_entity.ID(8567234);
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 };
- pack.WriteEntity(write_entity, 1);
- pack.WriteEntity(write_entity, 0);
- pack.WriteEntity(write_entity, 2);
+ pack.WriteEntity(write_entity, write_base, 1);
+ pack.WriteEntity(write_entity, write_base, 0);
+ pack.WriteEntity(write_entity, write_base, 2);
uint32_t read_id;
EntityState read_state;
pack.ReadEntityID(read_id, 1);
- pack.ReadEntityState(read_state, 1);
+ pack.ReadEntityState(read_state, write_base, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
"entity ID not correctly transported in EntityUpdate packet",
write_entity.ID(), read_id
void PacketTest::testPlayerCorrection() {
auto pack = Packet::Make<Packet::PlayerCorrection>(udp_pack);
- AssertPacket("PlayerCorrection", 8, 66, pack);
+ AssertPacket("PlayerCorrection", 8, 52, pack);
uint16_t write_seq = 50050;
uint16_t read_seq;
);
AssertEqual(
message + ": bad block position",
- expected.block_pos, actual.block_pos
+ expected.block_pos, actual.block_pos, 16.0f/65535.0f // that's about the max accuracy that packing's going to give us
);
AssertEqual(
message + ": bad velocity",