diff --git a/extensions/include/extensions/multiplayer/networkmanager.h b/extensions/include/extensions/multiplayer/networkmanager.h index ee08a90f..4596efa4 100644 --- a/extensions/include/extensions/multiplayer/networkmanager.h +++ b/extensions/include/extensions/multiplayer/networkmanager.h @@ -90,7 +90,6 @@ class NetworkManager : public MxCore { RemotePlayer* CreateAndSpawnPlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex); - void HandleJoin(const PlayerJoinMsg& p_msg); void HandleLeave(const PlayerLeaveMsg& p_msg); void HandleState(const PlayerStateMsg& p_msg); void HandleHostAssign(const HostAssignMsg& p_msg); diff --git a/extensions/include/extensions/multiplayer/protocol.h b/extensions/include/extensions/multiplayer/protocol.h index e2736a29..8c086dd8 100644 --- a/extensions/include/extensions/multiplayer/protocol.h +++ b/extensions/include/extensions/multiplayer/protocol.h @@ -10,8 +10,12 @@ class LegoPathActor; namespace Multiplayer { +// Routing target constants for MessageHeader.target +const uint32_t TARGET_BROADCAST = 0; // Broadcast to all except sender +const uint32_t TARGET_HOST = 0xFFFFFFFF; // Send to host only +const uint32_t TARGET_BROADCAST_ALL = 0xFFFFFFFE; // Broadcast to all including sender + enum MessageType : uint8_t { - MSG_JOIN = 1, MSG_LEAVE = 2, MSG_STATE = 3, MSG_HOST_ASSIGN = 4, @@ -74,12 +78,7 @@ struct MessageHeader { uint8_t type; uint32_t peerId; uint32_t sequence; -}; - -struct PlayerJoinMsg { - MessageHeader header; - uint8_t actorId; - char name[20]; + uint32_t target; }; struct PlayerLeaveMsg { @@ -115,10 +114,9 @@ struct RequestSnapshotMsg { }; // Host -> specific client: full world state blob (variable length) -// Relay reads targetPeerId at offset 9 and routes to that peer only. +// Relay reads header.target and routes to that peer only. struct WorldSnapshotMsg { MessageHeader header; - uint32_t targetPeerId; uint16_t dataLength; // Followed by dataLength bytes of serialized plant + building state }; diff --git a/extensions/src/multiplayer/networkmanager.cpp b/extensions/src/multiplayer/networkmanager.cpp index b8ad1729..677b10fc 100644 --- a/extensions/src/multiplayer/networkmanager.cpp +++ b/extensions/src/multiplayer/networkmanager.cpp @@ -312,7 +312,7 @@ void NetworkManager::BroadcastLocalState() } PlayerStateMsg msg{}; - msg.header = {MSG_STATE, m_localPeerId, m_sequence++}; + msg.header = {MSG_STATE, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.actorId = actorId; msg.worldId = (int8_t) currentWorld->GetWorldId(); msg.vehicleType = DetectVehicleType(userActor); @@ -397,16 +397,6 @@ void NetworkManager::ProcessIncomingPackets() } break; } - case MSG_JOIN: { - PlayerJoinMsg msg; - if (DeserializeMsg(data, length, msg) && msg.header.type == MSG_JOIN) { - msg.name[sizeof(msg.name) - 1] = '\0'; - if (IsValidActorId(msg.actorId)) { - HandleJoin(msg); - } - } - break; - } case MSG_LEAVE: { PlayerLeaveMsg msg; if (DeserializeMsg(data, length, msg) && msg.header.type == MSG_LEAVE) { @@ -494,18 +484,6 @@ RemotePlayer* NetworkManager::CreateAndSpawnPlayer(uint32_t p_peerId, uint8_t p_ return ptr; } -void NetworkManager::HandleJoin(const PlayerJoinMsg& p_msg) -{ - uint32_t peerId = p_msg.header.peerId; - - if (m_remotePlayers.count(peerId)) { - return; - } - - CreateAndSpawnPlayer(peerId, p_msg.actorId, p_msg.actorId - 1); - NotifyPlayerCountChanged(); -} - void NetworkManager::HandleLeave(const PlayerLeaveMsg& p_msg) { RemoveRemotePlayer(p_msg.header.peerId); @@ -594,7 +572,7 @@ void NetworkManager::SendEmote(uint8_t p_emoteId) m_thirdPersonCamera.TriggerEmote(p_emoteId); EmoteMsg msg{}; - msg.header = {MSG_EMOTE, m_localPeerId, m_sequence++}; + msg.header = {MSG_EMOTE, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.emoteId = p_emoteId; SendMessage(msg); } @@ -713,7 +691,7 @@ bool NetworkManager::IsClonedCharacter(const char* p_name) const void NetworkManager::SendCustomize(uint32_t p_targetPeerId, uint8_t p_changeType, uint8_t p_partIndex) { CustomizeMsg msg{}; - msg.header = {MSG_CUSTOMIZE, m_localPeerId, m_sequence++}; + msg.header = {MSG_CUSTOMIZE, m_localPeerId, m_sequence++, TARGET_BROADCAST_ALL}; msg.targetPeerId = p_targetPeerId; msg.changeType = p_changeType; msg.partIndex = p_partIndex; diff --git a/extensions/src/multiplayer/server/gameroom.ts b/extensions/src/multiplayer/server/gameroom.ts index e036b20c..a78b5701 100644 --- a/extensions/src/multiplayer/server/gameroom.ts +++ b/extensions/src/multiplayer/server/gameroom.ts @@ -1,14 +1,12 @@ import { HEADER_SIZE, - MSG_CUSTOMIZE, - MSG_REQUEST_SNAPSHOT, - MSG_WORLD_EVENT_REQUEST, - MSG_WORLD_SNAPSHOT, - SNAPSHOT_MIN_SIZE, + TARGET_BROADCAST, + TARGET_HOST, + TARGET_BROADCAST_ALL, createAssignIdMsg, createHostAssignMsg, createLeaveMsg, - readTargetPeerId, + readTarget, stampSender, } from "./protocol"; import type { Env } from "./relay"; @@ -166,30 +164,17 @@ export class GameRoom implements DurableObject { return; } - const msgType = data[0]; const stamped = stampSender(data, peerId); + const target = readTarget(stamped); - if ( - msgType === MSG_REQUEST_SNAPSHOT || - msgType === MSG_WORLD_EVENT_REQUEST - ) { + if (target === TARGET_BROADCAST) { + this.broadcastExcept(stamped.buffer, peerId); + } else if (target === TARGET_HOST) { this.sendToHost(stamped); - } else if ( - msgType === MSG_WORLD_SNAPSHOT && - data.length >= SNAPSHOT_MIN_SIZE - ) { - const targetId = readTargetPeerId(stamped); - if (targetId === 0) { - this.broadcastExcept(stamped.buffer, peerId); - } else { - this.sendToTarget(stamped); - } - } else if (msgType === MSG_CUSTOMIZE) { - // Broadcast to all including sender so the clicker sees effects - // on the target's clone on their own screen. + } else if (target === TARGET_BROADCAST_ALL) { this.broadcast(stamped.buffer); } else { - this.broadcastExcept(stamped.buffer, peerId); + this.sendToTarget(stamped, target); } } @@ -200,8 +185,7 @@ export class GameRoom implements DurableObject { } } - private sendToTarget(data: Uint8Array): void { - const targetId = readTargetPeerId(data); + private sendToTarget(data: Uint8Array, targetId: number): void { const targetWs = this.connections.get(targetId); if (targetWs) { if (!this.trySend(targetWs, data.buffer)) { diff --git a/extensions/src/multiplayer/server/protocol.ts b/extensions/src/multiplayer/server/protocol.ts index 0141dd8f..ba87dcac 100644 --- a/extensions/src/multiplayer/server/protocol.ts +++ b/extensions/src/multiplayer/server/protocol.ts @@ -1,30 +1,27 @@ // Protocol constants — must stay in sync with protocol.h -// MessageHeader binary layout: type(1) + peerId(4) + sequence(4) = 9 bytes -export const HEADER_SIZE = 9; +// MessageHeader binary layout: type(1) + peerId(4) + sequence(4) + target(4) = 13 bytes +export const HEADER_SIZE = 13; export const PEER_ID_OFFSET = 1; export const SEQUENCE_OFFSET = 5; +export const TARGET_OFFSET = 9; -// Message types the relay inspects for routing decisions. -// All other types are broadcast to every peer in the room. +// Routing target constants +export const TARGET_BROADCAST = 0; +export const TARGET_HOST = 0xffffffff; +export const TARGET_BROADCAST_ALL = 0xfffffffe; + +// Message types used by server message constructors only. export const MSG_LEAVE = 2; export const MSG_HOST_ASSIGN = 4; -export const MSG_REQUEST_SNAPSHOT = 5; -export const MSG_WORLD_SNAPSHOT = 6; -export const MSG_WORLD_EVENT_REQUEST = 8; -export const MSG_CUSTOMIZE = 10; export const MSG_ASSIGN_ID = 0xff; // AssignIdMsg: compact server-only message — type(1) + peerId(4) + maxActors(1) const ASSIGN_ID_SIZE = 1 + 4 + 1; -// HostAssignMsg: header(9) + hostPeerId(4) +// HostAssignMsg: header(13) + hostPeerId(4) const HOST_ASSIGN_SIZE = HEADER_SIZE + 4; -// WorldSnapshotMsg: header(9) + targetPeerId(4) + dataLength(2) + data... -export const SNAPSHOT_TARGET_OFFSET = HEADER_SIZE; -export const SNAPSHOT_MIN_SIZE = HEADER_SIZE + 4 + 2; - export function createAssignIdMsg(peerId: number, maxActors: number): ArrayBuffer { const buf = new ArrayBuffer(ASSIGN_ID_SIZE); const view = new DataView(buf); @@ -40,6 +37,7 @@ export function createHostAssignMsg(hostPeerId: number): ArrayBuffer { view.setUint8(0, MSG_HOST_ASSIGN); view.setUint32(PEER_ID_OFFSET, 0, true); view.setUint32(SEQUENCE_OFFSET, 0, true); + view.setUint32(TARGET_OFFSET, 0, true); view.setUint32(HEADER_SIZE, hostPeerId, true); return buf; } @@ -50,6 +48,7 @@ export function createLeaveMsg(peerId: number): ArrayBuffer { view.setUint8(0, MSG_LEAVE); view.setUint32(PEER_ID_OFFSET, peerId, true); view.setUint32(SEQUENCE_OFFSET, 0, true); + view.setUint32(TARGET_OFFSET, 0, true); return buf; } @@ -61,6 +60,6 @@ export function stampSender(data: Uint8Array, peerId: number): Uint8Array { return stamped; } -export function readTargetPeerId(data: Uint8Array): number { - return new DataView(data.buffer).getUint32(SNAPSHOT_TARGET_OFFSET, true); +export function readTarget(data: Uint8Array): number { + return new DataView(data.buffer).getUint32(TARGET_OFFSET, true); } diff --git a/extensions/src/multiplayer/worldstatesync.cpp b/extensions/src/multiplayer/worldstatesync.cpp index c1c0df0a..e4655640 100644 --- a/extensions/src/multiplayer/worldstatesync.cpp +++ b/extensions/src/multiplayer/worldstatesync.cpp @@ -232,7 +232,7 @@ MxBool WorldStateSync::HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_ch void WorldStateSync::SendSnapshotRequest() { RequestSnapshotMsg msg{}; - msg.header = {MSG_REQUEST_SNAPSHOT, m_localPeerId, m_sequence++}; + msg.header = {MSG_REQUEST_SNAPSHOT, m_localPeerId, m_sequence++, TARGET_HOST}; SendMessage(msg); m_snapshotRequested = true; @@ -270,8 +270,7 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId) stateBuffer[dataLength++] = (uint8_t) lightPos; WorldSnapshotMsg msg{}; - msg.header = {MSG_WORLD_SNAPSHOT, m_localPeerId, m_sequence++}; - msg.targetPeerId = p_targetPeerId; + msg.header = {MSG_WORLD_SNAPSHOT, m_localPeerId, m_sequence++, p_targetPeerId}; msg.dataLength = (uint16_t) dataLength; std::vector msgBuf(sizeof(WorldSnapshotMsg) + dataLength); @@ -284,7 +283,7 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId) void WorldStateSync::BroadcastWorldEvent(uint8_t p_entityType, uint8_t p_changeType, uint8_t p_entityIndex) { WorldEventMsg msg{}; - msg.header = {MSG_WORLD_EVENT, m_localPeerId, m_sequence++}; + msg.header = {MSG_WORLD_EVENT, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.entityType = p_entityType; msg.changeType = p_changeType; msg.entityIndex = p_entityIndex; @@ -294,7 +293,7 @@ void WorldStateSync::BroadcastWorldEvent(uint8_t p_entityType, uint8_t p_changeT void WorldStateSync::SendWorldEventRequest(uint8_t p_entityType, uint8_t p_changeType, uint8_t p_entityIndex) { WorldEventRequestMsg msg{}; - msg.header = {MSG_WORLD_EVENT_REQUEST, m_localPeerId, m_sequence++}; + msg.header = {MSG_WORLD_EVENT_REQUEST, m_localPeerId, m_sequence++, TARGET_HOST}; msg.entityType = p_entityType; msg.changeType = p_changeType; msg.entityIndex = p_entityIndex;