diff --git a/extensions/include/extensions/multiplayer/protocol.h b/extensions/include/extensions/multiplayer/protocol.h index 95a0f996..fafc2212 100644 --- a/extensions/include/extensions/multiplayer/protocol.h +++ b/extensions/include/extensions/multiplayer/protocol.h @@ -78,6 +78,7 @@ enum LightChangeType : uint8_t { struct MessageHeader { uint8_t type; + uint8_t _pad; uint32_t peerId; uint32_t sequence; uint32_t target; diff --git a/extensions/src/multiplayer/networkmanager.cpp b/extensions/src/multiplayer/networkmanager.cpp index 218b05a4..79118a8e 100644 --- a/extensions/src/multiplayer/networkmanager.cpp +++ b/extensions/src/multiplayer/networkmanager.cpp @@ -175,7 +175,7 @@ MxResult NetworkManager::Tickle() } else if (IsConnected()) { AnimCancelMsg cancelMsg{}; - cancelMsg.header = {MSG_ANIM_CANCEL, m_localPeerId, m_sequence++, TARGET_HOST}; + cancelMsg.header = {MSG_ANIM_CANCEL, 0, m_localPeerId, m_sequence++, TARGET_HOST}; SendMessage(cancelMsg); } m_localPendingAnimInterest = -1; @@ -326,7 +326,7 @@ void NetworkManager::CancelLocalAnimInterest() } else if (IsConnected()) { AnimCancelMsg msg{}; - msg.header = {MSG_ANIM_CANCEL, m_localPeerId, m_sequence++, TARGET_HOST}; + msg.header = {MSG_ANIM_CANCEL, 0, m_localPeerId, m_sequence++, TARGET_HOST}; SendMessage(msg); } @@ -638,7 +638,7 @@ void NetworkManager::ProcessPendingRequests() } else if (IsConnected()) { AnimInterestMsg msg{}; - msg.header = {MSG_ANIM_INTEREST, m_localPeerId, m_sequence++, TARGET_HOST}; + msg.header = {MSG_ANIM_INTEREST, 0, m_localPeerId, m_sequence++, TARGET_HOST}; msg.animIndex = animIndex; ThirdPersonCamera::Controller* animCam = GetCamera(); msg.displayActorIndex = animCam ? animCam->GetDisplayActorIndex() : 0; @@ -715,7 +715,7 @@ void NetworkManager::BroadcastLocalState() } PlayerStateMsg msg{}; - msg.header = {MSG_STATE, m_localPeerId, m_sequence++, TARGET_BROADCAST}; + msg.header = {MSG_STATE, 0, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.actorId = actorId; msg.worldId = inRestrictedArea ? WORLD_NOT_VISIBLE : (int8_t) currentWorld->GetWorldId(); msg.vehicleType = DetectVehicleType(userActor); @@ -1066,7 +1066,7 @@ void NetworkManager::SendEmote(uint8_t p_emoteId) cam->TriggerEmote(p_emoteId); EmoteMsg msg{}; - msg.header = {MSG_EMOTE, m_localPeerId, m_sequence++, TARGET_BROADCAST}; + msg.header = {MSG_EMOTE, 0, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.emoteId = p_emoteId; SendMessage(msg); } @@ -1201,7 +1201,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++, TARGET_BROADCAST_ALL}; + msg.header = {MSG_CUSTOMIZE, 0, m_localPeerId, m_sequence++, TARGET_BROADCAST_ALL}; msg.targetPeerId = p_targetPeerId; msg.changeType = p_changeType; msg.partIndex = p_partIndex; @@ -1664,7 +1664,7 @@ void NetworkManager::HandleAnimStartLocally(uint16_t p_animIndex, bool p_localIn AnimUpdateMsg NetworkManager::BuildAnimUpdateMsg(uint16_t p_animIndex, uint32_t p_target) { AnimUpdateMsg msg{}; - msg.header = {MSG_ANIM_UPDATE, m_localPeerId, m_sequence++, p_target}; + msg.header = {MSG_ANIM_UPDATE, 0, m_localPeerId, m_sequence++, p_target}; msg.animIndex = p_animIndex; const Animation::AnimSession* session = m_animSessionHost.FindSession(p_animIndex); @@ -1699,7 +1699,7 @@ void NetworkManager::SendAnimUpdateToPlayer(uint16_t p_animIndex, uint32_t p_tar void NetworkManager::BroadcastAnimStart(uint16_t p_animIndex) { AnimStartMsg msg{}; - msg.header = {MSG_ANIM_START, m_localPeerId, m_sequence++, TARGET_BROADCAST}; + msg.header = {MSG_ANIM_START, 0, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.animIndex = p_animIndex; SendMessage(msg); @@ -1720,7 +1720,7 @@ void NetworkManager::BroadcastAnimComplete(uint16_t p_animIndex) } AnimCompleteMsg msg{}; - msg.header = {MSG_ANIM_COMPLETE, m_localPeerId, m_sequence++, TARGET_BROADCAST}; + msg.header = {MSG_ANIM_COMPLETE, 0, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.eventId = (static_cast(SDL_rand_bits()) << 32) | static_cast(SDL_rand_bits()); msg.objectId = animInfo->m_objectId; msg.participantCount = 0; diff --git a/extensions/src/multiplayer/server/protocol.ts b/extensions/src/multiplayer/server/protocol.ts index 33c89638..cbc2d976 100644 --- a/extensions/src/multiplayer/server/protocol.ts +++ b/extensions/src/multiplayer/server/protocol.ts @@ -1,10 +1,10 @@ // Protocol constants — must stay in sync with protocol.h -// 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; +// MessageHeader binary layout: type(1) + _pad(1) + peerId(4) + sequence(4) + target(4) = 14 bytes +export const HEADER_SIZE = 14; +export const PEER_ID_OFFSET = 2; +export const SEQUENCE_OFFSET = 6; +export const TARGET_OFFSET = 10; // Routing target constants export const TARGET_BROADCAST = 0; @@ -19,7 +19,7 @@ export const MSG_ASSIGN_ID = 0xff; // AssignIdMsg: compact server-only message — type(1) + peerId(4) const ASSIGN_ID_SIZE = 1 + 4; -// HostAssignMsg: header(13) + hostPeerId(4) +// HostAssignMsg: header(14) + hostPeerId(4) const HOST_ASSIGN_SIZE = HEADER_SIZE + 4; export function createAssignIdMsg(peerId: number): ArrayBuffer { diff --git a/extensions/src/multiplayer/worldstatesync.cpp b/extensions/src/multiplayer/worldstatesync.cpp index 2ac22ff2..54c73118 100644 --- a/extensions/src/multiplayer/worldstatesync.cpp +++ b/extensions/src/multiplayer/worldstatesync.cpp @@ -237,7 +237,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++, TARGET_HOST}; + msg.header = {MSG_REQUEST_SNAPSHOT, 0, m_localPeerId, m_sequence++, TARGET_HOST}; SendMessage(msg); m_snapshotRequested = true; @@ -275,7 +275,7 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId) stateBuffer[dataLength++] = (uint8_t) lightPos; WorldSnapshotMsg msg{}; - msg.header = {MSG_WORLD_SNAPSHOT, m_localPeerId, m_sequence++, p_targetPeerId}; + msg.header = {MSG_WORLD_SNAPSHOT, 0, m_localPeerId, m_sequence++, p_targetPeerId}; msg.dataLength = (uint16_t) dataLength; std::vector msgBuf(sizeof(WorldSnapshotMsg) + dataLength); @@ -288,7 +288,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++, TARGET_BROADCAST}; + msg.header = {MSG_WORLD_EVENT, 0, m_localPeerId, m_sequence++, TARGET_BROADCAST}; msg.entityType = p_entityType; msg.changeType = p_changeType; msg.entityIndex = p_entityIndex; @@ -298,7 +298,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++, TARGET_HOST}; + msg.header = {MSG_WORLD_EVENT_REQUEST, 0, m_localPeerId, m_sequence++, TARGET_HOST}; msg.entityType = p_entityType; msg.changeType = p_changeType; msg.entityIndex = p_entityIndex; diff --git a/extensions/src/thirdpersoncamera/controller.cpp b/extensions/src/thirdpersoncamera/controller.cpp index 1e715c95..369fd013 100644 --- a/extensions/src/thirdpersoncamera/controller.cpp +++ b/extensions/src/thirdpersoncamera/controller.cpp @@ -105,6 +105,16 @@ void Controller::OnActorEnter(IslePathActor* p_actor) return; } + // Stop external animation before modifying ride/display state — + // the ScenePlayer may hold a reference to the ride vehicle ROI. + if (m_animPlaying) { + if (m_animStopCallback) { + m_animStopCallback(); + } + m_animPlaying = false; + m_animStopCallback = nullptr; + } + LegoROI* newROI = userActor->GetROI(); if (!newROI) { return; @@ -157,6 +167,16 @@ void Controller::OnActorExit(IslePathActor* p_actor) return; } + // Stop external animation before clearing ride animation state — + // the ScenePlayer may hold a reference to the ride vehicle ROI. + if (m_animPlaying) { + if (m_animStopCallback) { + m_animStopCallback(); + } + m_animPlaying = false; + m_animStopCallback = nullptr; + } + if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) { m_animator.ClearRideAnimation(); m_animator.ClearAll();