diff --git a/core/src/NetSession.cpp b/core/src/NetSession.cpp index 5346de4..2b7a9ad 100644 --- a/core/src/NetSession.cpp +++ b/core/src/NetSession.cpp @@ -314,3 +314,171 @@ void NetSession::handleFrame(const Frame& frame, uint64_t tick) { uuidHi = login.uuidHi; uuidLo = login.uuidLo; + JoinGame join; + join.entityId = ownEntityId; + join.gameMode = config.gameMode; + join.dimension = config.dimension; + join.seedHash = config.seedHash; + join.maxPlayers = config.maxPlayers; + join.levelType = config.levelType; + join.viewDistance = config.viewRadius; + join.reducedDebugInfo = false; + PacketWriter body; + join.encode(body); + enqueue(PacketId::JoinGame, body, true); + enterPlay(tick); + return; + } + case PacketId::JoinGame: { + JoinGame join; + if (!join.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed join"); + return; + } + ownEntityId = join.entityId; + clientView.tracker.setRadius(join.viewDistance); + enterPlay(tick); + return; + } + case PacketId::ChunkData: { + ChunkData snapshot; + if (!snapshot.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed chunk data"); + return; + } + clientSync.applyFull(snapshot, *worldRef); + ++statistics.chunksApplied; + return; + } + case PacketId::BlockChange: { + BlockChange change; + if (!change.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed block change"); + return; + } + if (role == Role::Server) { + edits.push_back(change); + return; + } + clientSync.applyBlockChange(change, *worldRef); + ++statistics.blockChangesApplied; + return; + } + case PacketId::MultiBlockChange: { + MultiBlockChange delta; + if (!delta.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed multi block change"); + return; + } + clientSync.applyDelta(delta, *worldRef); + ++statistics.multiBlockChangesApplied; + return; + } + case PacketId::EntityMove: { + EntityMove move; + if (!move.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed entity move"); + return; + } + entities[move.entityId] = move; + ++statistics.entityMovesApplied; + return; + } + case PacketId::PlayerState: { + PlayerState state; + if (!state.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed player state"); + return; + } + if (role == Role::Server) { + recentre(Vec3{state.x, state.y, state.z}); + return; + } + latestPlayerState = state; + return; + } + case PacketId::KeepAlive: { + KeepAlive beat; + if (!beat.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed keep alive"); + return; + } + if (role == Role::Server) { + // Only a token this server actually issued, and only the first + // time it comes back, counts as proof the client is alive. + if (beat.token > pendingKeepAliveToken) return; + if (beat.token <= acceptedKeepAliveToken) return; + acceptedKeepAliveToken = beat.token; + lastKeepAliveHeard = tick; + return; + } + lastKeepAliveHeard = tick; + PacketWriter body; + beat.encode(body); + enqueue(PacketId::KeepAlive, body, true); + return; + } + } +} + +void NetSession::streamChunks() { + uint32_t budget = config.chunksPerTick; + Vec3i coord; + while (budget > 0 && clientView.takeNextPending(coord)) { + if (!clientView.tracker.contains(coord)) continue; + ChunkData snapshot; + if (!syncRef->buildFull(*worldRef, coord, snapshot)) continue; + PacketWriter body; + snapshot.encode(body); + enqueue(PacketId::ChunkData, body, true); + clientView.sent.insert(coord); + ++statistics.chunksSent; + --budget; + } +} + +void NetSession::flushBlockChanges() { + for (const Vec3i& coord : dirtyChunks) { + if (!clientView.hasChunk(coord)) { + // Nothing to patch yet: the pending full snapshot already carries + // the current contents, so the delta would be redundant. + if (clientView.tracker.contains(coord)) clientView.queueChunk(coord); + continue; + } + MultiBlockChange delta; + if (!syncRef->buildDelta(*worldRef, coord, delta)) continue; + + if (delta.changes.size() > config.multiBlockThreshold) { + PacketWriter body; + delta.encode(body); + enqueue(PacketId::MultiBlockChange, body, true); + ++statistics.multiBlockChangesSent; + continue; + } + for (const MultiBlockChange::Change& change : delta.changes) { + int local = change.localIndex; + int lx = local % chunkSize; + int lz = (local / chunkSize) % chunkSize; + int ly = local / (chunkSize * chunkSize); + BlockChange single; + single.x = coord.x * chunkSize + lx; + single.y = coord.y * chunkSize + ly; + single.z = coord.z * chunkSize + lz; + single.block = change.block; + PacketWriter body; + single.encode(body); + enqueue(PacketId::BlockChange, body, true); + ++statistics.blockChangesSent; + } + } + dirtyChunks.clear(); + dirtyLookup.clear(); +} +