diff --git a/core/src/NetSession.cpp b/core/src/NetSession.cpp index f146427..5346de4 100644 --- a/core/src/NetSession.cpp +++ b/core/src/NetSession.cpp @@ -158,3 +158,159 @@ void NetSession::beginHandshake(const std::string& playerName, uint64_t high, ui login.encode(loginBody); enqueue(PacketId::LoginStart, loginBody, true); + handshakeStarted = true; + currentPhase = SessionPhase::Login; +} + +bool NetSession::legalHere(PacketId id) const { + if (role == Role::Server) { + switch (currentPhase) { + case SessionPhase::Handshake: + return id == PacketId::Handshake; + case SessionPhase::Login: + return id == PacketId::LoginStart; + case SessionPhase::Play: + return id == PacketId::PlayerState || id == PacketId::KeepAlive || + id == PacketId::BlockChange; + case SessionPhase::Disconnected: + return false; + } + return false; + } + + switch (currentPhase) { + case SessionPhase::Handshake: + return false; + case SessionPhase::Login: + return id == PacketId::JoinGame; + case SessionPhase::Play: + return id == PacketId::ChunkData || id == PacketId::BlockChange || + id == PacketId::MultiBlockChange || id == PacketId::EntityMove || + id == PacketId::PlayerState || id == PacketId::KeepAlive; + case SessionPhase::Disconnected: + return false; + } + return false; +} + +void NetSession::enterPlay(uint64_t tick) { + currentPhase = SessionPhase::Play; + lastKeepAliveHeard = tick; + lastKeepAliveSent = tick; + if (role == Role::Server) recentre(clientView.position); +} + +void NetSession::recentre(const Vec3& position) { + clientView.position = position; + Vec3i centre = chunkOf(position); + if (clientView.tracker.hasCentre() && clientView.tracker.centre() == centre) return; + + std::vector entered; + std::vector left; + clientView.tracker.update(centre, entered, left); + clientView.centreChunk = centre; + for (const Vec3i& coord : entered) clientView.queueChunk(coord); + for (const Vec3i& coord : left) clientView.forget(coord); +} + +void NetSession::markChunkDirty(const Vec3i& coord) { + if (!dirtyLookup.insert(coord).second) return; + dirtyChunks.push_back(coord); +} + +void NetSession::sendEntityMove(int32_t movedEntityId, const Vec3& position, float yaw, float pitch, + bool onGround) { + if (currentPhase != SessionPhase::Play) return; + EntityMove move; + move.entityId = movedEntityId; + move.x = position.x; + move.y = position.y; + move.z = position.z; + move.yaw = yaw; + move.pitch = pitch; + move.onGround = onGround; + PacketWriter body; + move.encode(body); + enqueue(PacketId::EntityMove, body, false); + ++statistics.entityMovesSent; +} + +void NetSession::sendPlayerState(const PlayerState& state) { + if (currentPhase != SessionPhase::Play) return; + PacketWriter body; + state.encode(body); + // Upstream position reports age out instantly, an authoritative correction + // from the server must not. + enqueue(PacketId::PlayerState, body, role == Role::Server); +} + +void NetSession::requestBlockEdit(int32_t x, int32_t y, int32_t z, Block block) { + if (currentPhase != SessionPhase::Play) return; + BlockChange change; + change.x = x; + change.y = y; + change.z = z; + change.block = block; + PacketWriter body; + change.encode(body); + enqueue(PacketId::BlockChange, body, true); +} + +bool NetSession::entityMove(int32_t movedEntityId, EntityMove& out) const { + auto it = entities.find(movedEntityId); + if (it == entities.end()) return false; + out = it->second; + return true; +} + +void NetSession::handleMessage(const ByteBuffer& message, uint64_t tick) { + std::vector frames; + std::size_t consumed = 0; + splitFrames(message.data(), message.size(), frames, consumed); + for (const Frame& frame : frames) { + if (currentPhase == SessionPhase::Disconnected) return; + handleFrame(frame, tick); + } +} + +void NetSession::handleFrame(const Frame& frame, uint64_t tick) { + ++statistics.packetsReceived; + if (!legalHere(frame.id)) { + ++statistics.packetsRejected; + disconnect("packet out of phase"); + return; + } + + PacketReader reader(frame.payload.data(), frame.payload.size()); + switch (frame.id) { + case PacketId::Handshake: { + Handshake hello; + if (!hello.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed handshake"); + return; + } + if (hello.protocolVersion != config.protocolVersion) { + ++statistics.packetsRejected; + disconnect("protocol version mismatch"); + return; + } + if (hello.nextState != 2) { + ++statistics.packetsRejected; + disconnect("unsupported next state"); + return; + } + currentPhase = SessionPhase::Login; + return; + } + case PacketId::LoginStart: { + LoginStart login; + if (!login.decode(reader)) { + ++statistics.packetsRejected; + disconnect("malformed login"); + return; + } + name = login.name; + uuidHi = login.uuidHi; + uuidLo = login.uuidLo; +