+ClientConnection::SpawnStatus::SpawnStatus(Entity &e)
+: entity(&e)
+, spawn_pack(-1)
+, despawn_pack(-1) {
+ entity->Ref();
+}
+
+ClientConnection::SpawnStatus::~SpawnStatus() {
+ entity->UnRef();
+}
+
+bool ClientConnection::CanSpawn(const Entity &e) const noexcept {
+ return
+ &e != player.entity &&
+ !e.Dead() &&
+ manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7;
+}
+
+bool ClientConnection::CanDespawn(const Entity &e) const noexcept {
+ return
+ e.Dead() ||
+ manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) > 7;
+}
+
+uint16_t ClientConnection::Send() {
+ return conn.Send(server.GetPacket(), server.GetSocket());
+}
+
+uint16_t ClientConnection::Send(size_t len) {
+ server.GetPacket().len = len;
+ return Send();
+}
+
+void ClientConnection::SendSpawn(SpawnStatus &status) {
+ // don't double spawn
+ if (status.spawn_pack != -1) return;
+
+ auto pack = Prepare<Packet::SpawnEntity>();
+ pack.WriteEntity(*status.entity);
+ status.spawn_pack = Send();
+ ++confirm_wait;
+}
+
+void ClientConnection::SendDespawn(SpawnStatus &status) {
+ // don't double despawn
+ if (status.despawn_pack != -1) return;
+
+ auto pack = Prepare<Packet::DespawnEntity>();
+ pack.WriteEntityID(status.entity->ID());
+ status.despawn_pack = Send();
+ ++confirm_wait;
+}
+
+void ClientConnection::SendUpdate(SpawnStatus &status) {
+ // don't send updates while spawn not ack'd or despawn sent
+ if (status.spawn_pack != -1 || status.despawn_pack != -1) return;
+
+ // TODO: pack entity updates
+ auto pack = Prepare<Packet::EntityUpdate>();
+ pack.WriteEntityCount(1);
+ pack.WriteEntity(*status.entity, 0);
+ Send(Packet::EntityUpdate::GetSize(1));
+}
+
+void ClientConnection::CheckPlayerFix() {
+ // player_update_state's position holds the client's most recent prediction
+ glm::vec3 diff = player_update_state.Diff(PlayerEntity().GetState());
+ float dist_squared = dot(diff, diff);
+
+ // if client's prediction is off by more than 1cm, send
+ // our (authoritative) state back so it can fix it
+ constexpr float fix_thresh = 0.0001f;
+
+ if (dist_squared > fix_thresh) {
+ auto pack = Prepare<Packet::PlayerCorrection>();
+ pack.WritePacketSeq(player_update_pack);
+ pack.WritePlayer(PlayerEntity());
+ Send();
+ }
+}
+
+void ClientConnection::CheckChunkQueue() {
+ if (PlayerChunks().Base() != old_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) {
+ if (manhattan_radius(pos - old_base) > PlayerChunks().Extent()) {
+ chunk_queue.push_back(pos);
+ }
+ }
+ }
+ }
+ old_base = PlayerChunks().Base();
+ }
+ if (transmitter.Transmitting()) {
+ transmitter.Transmit();
+ return;
+ }
+ if (transmitter.Idle()) {
+ int count = 0;
+ constexpr int max = 64;
+ while (count < max && !chunk_queue.empty()) {
+ Chunk::Pos pos = chunk_queue.front();
+ chunk_queue.pop_front();
+ if (PlayerChunks().InRange(pos)) {
+ Chunk *chunk = PlayerChunks().Get(pos);
+ if (chunk) {
+ transmitter.Send(*chunk);
+ return;
+ } else {
+ chunk_queue.push_back(pos);
+ }
+ ++count;
+ }
+ }
+ }
+}
+
+void ClientConnection::AttachPlayer(const Player &new_player) {