+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) {
+ DetachPlayer();
+ player = new_player;
+ player.entity->Ref();
+
+ old_base = player.chunks->Base();
+ Chunk::Pos begin = player.chunks->CoordsBegin();
+ Chunk::Pos end = player.chunks->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) {
+ chunk_queue.push_back(pos);
+ }
+ }
+ }
+
+ cout << "player \"" << player.entity->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;
+ transmitter.Abort();
+ chunk_queue.clear();
+}
+
+void ClientConnection::OnPacketReceived(uint16_t seq) {
+ if (transmitter.Waiting()) {
+ transmitter.Ack(seq);
+ }
+ if (!confirm_wait) return;
+ for (auto iter = spawns.begin(), end = spawns.end(); iter != end; ++iter) {
+ if (seq == iter->spawn_pack) {
+ iter->spawn_pack = -1;
+ --confirm_wait;
+ return;
+ }
+ if (seq == iter->despawn_pack) {
+ spawns.erase(iter);
+ --confirm_wait;
+ return;
+ }
+ }
+}
+
+void ClientConnection::OnPacketLost(uint16_t seq) {
+ if (transmitter.Waiting()) {
+ transmitter.Nack(seq);
+ }
+ if (!confirm_wait) return;
+ for (SpawnStatus &status : spawns) {
+ if (seq == status.spawn_pack) {
+ status.spawn_pack = -1;
+ --confirm_wait;
+ SendSpawn(status);
+ return;
+ }
+ if (seq == status.despawn_pack) {
+ status.despawn_pack = -1;
+ --confirm_wait;
+ SendDespawn(status);
+ return;
+ }
+ }
+}
+
+void ClientConnection::On(const Packet::Login &pack) {
+ string name;
+ pack.ReadPlayerName(name);
+
+ Player new_player = server.GetWorld().AddPlayer(name);
+
+ if (new_player.entity) {
+ // success!
+ AttachPlayer(new_player);
+ cout << "accepted login from player \"" << name << '"' << endl;
+ auto response = Prepare<Packet::Join>();
+ response.WritePlayer(*new_player.entity);
+ response.WriteWorldName(server.GetWorld().Name());
+ Send();
+ // set up update tracking
+ player_update_state = new_player.entity->GetState();
+ player_update_pack = pack.Seq();
+ player_update_timer.Reset();
+ player_update_timer.Start();
+ } else {
+ // aw no :(
+ cout << "rejected login from player \"" << name << '"' << endl;
+ Prepare<Packet::Part>();
+ Send();
+ conn.Close();
+ }
+}
+
+void ClientConnection::On(const Packet::Part &) {
+ conn.Close();
+}
+
+void ClientConnection::On(const Packet::PlayerUpdate &pack) {
+ if (!HasPlayer()) return;
+ 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);
+ }