diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 9eb43b9e3..297a3aac0 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -8,6 +8,7 @@ #include "DestroyableComponent.h" #include "EntityManager.h" #include "GameMessages.h" +#include "ActivityMessages.h" #include "InventoryComponent.h" #include "Item.h" #include "MissionComponent.h" @@ -700,8 +701,9 @@ void RacingControlComponent::Update(float deltaTime) { } // Start the race - GameMessages::SendActivityStart(m_Parent->GetObjectID(), - UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::ActivityStart activityStart; + activityStart.target = m_Parent->GetObjectID(); + activityStart.Send(UNASSIGNED_SYSTEM_ADDRESS); m_Started = true; diff --git a/dGame/dGameMessages/ActivityMessages.cpp b/dGame/dGameMessages/ActivityMessages.cpp new file mode 100644 index 000000000..4f5709e41 --- /dev/null +++ b/dGame/dGameMessages/ActivityMessages.cpp @@ -0,0 +1,82 @@ +#include "ActivityMessages.h" + +#include "BitStreamUtils.h" +#include "Entity.h" +#include "EntityManager.h" +#include "Game.h" + +namespace GameMessages { + void ActivityStop::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bExit); + bitStream.Write(bUserCancel); + } + + bool ActivityStop::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bExit)); + VALIDATE_READ(bitStream.Read(bUserCancel)); + return true; + } + + void ActivityPause::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bPause); + } + + bool ActivityPause::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bPause)); + return true; + } + + void StartActivityTime::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(startTime); + } + + bool StartActivityTime::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(startTime)); + return true; + } + + void RequestActivityEnter::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bStart); + bitStream.Write(userID); + } + + bool RequestActivityEnter::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bStart)); + VALIDATE_READ(bitStream.Read(userID)); + return true; + } + + void RequestActivityExit::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bUserCancel); + bitStream.Write(userID); + } + + bool RequestActivityExit::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bUserCancel)); + VALIDATE_READ(bitStream.Read(userID)); + return true; + } + + void RequestActivityExit::Handle(Entity& entity, const SystemAddress& sysAddr) { + if (!bUserCancel) return; + + auto* player = Game::entityManager->GetEntity(userID); + if (!player) return; + entity.RequestActivityExit(&entity, userID, bUserCancel); + } + + void ShowActivityCountdown::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bPlayAdditionalSound); + bitStream.Write(bPlayCountdownSound); + BitStreamUtils::WriteLengthPrefixed(bitStream, sndName); + bitStream.Write(stateToPlaySoundOn); + } + + bool ShowActivityCountdown::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bPlayAdditionalSound)); + VALIDATE_READ(bitStream.Read(bPlayCountdownSound)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, sndName)); + VALIDATE_READ(bitStream.Read(stateToPlaySoundOn)); + return true; + } +} diff --git a/dGame/dGameMessages/ActivityMessages.h b/dGame/dGameMessages/ActivityMessages.h new file mode 100644 index 000000000..fff7f8023 --- /dev/null +++ b/dGame/dGameMessages/ActivityMessages.h @@ -0,0 +1,88 @@ +#ifndef ACTIVITYMESSAGES_H +#define ACTIVITYMESSAGES_H + +#include "GameMessages.h" + +#include + +// Game messages for the activity lifecycle (minigames, races, shooting galleries, survival...). +// Field names and wire order match the client (legouniverse.exe 1.10.64). Fields are listed in wire order. +namespace GameMessages { + // Server -> client. No payload. + struct ActivityEnter : public NetGameMsg { + ActivityEnter() : NetGameMsg(MessageType::Game::ACTIVITY_ENTER) {} + }; + + // Server -> client. No payload. + struct ActivityExit : public NetGameMsg { + ActivityExit() : NetGameMsg(MessageType::Game::ACTIVITY_EXIT) {} + }; + + // Server -> client. No payload. + struct ActivityStart : public NetGameMsg { + ActivityStart() : NetGameMsg(MessageType::Game::ACTIVITY_START) {} + }; + + // Server -> client. + struct ActivityStop : public NetGameMsg { + ActivityStop() : NetGameMsg(MessageType::Game::ACTIVITY_STOP) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bExit{}; + bool bUserCancel{}; + }; + + // Server -> client. + struct ActivityPause : public NetGameMsg { + ActivityPause() : NetGameMsg(MessageType::Game::ACTIVITY_PAUSE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bPause{}; + }; + + // Server -> client. + struct StartActivityTime : public NetGameMsg { + StartActivityTime() : NetGameMsg(MessageType::Game::START_ACTIVITY_TIME) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + float startTime{}; + }; + + // Client -> server in the client, but historically also sent by DLU. Kept for completeness. + struct RequestActivityEnter : public NetGameMsg { + RequestActivityEnter() : NetGameMsg(MessageType::Game::REQUEST_ACTIVITY_ENTER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bStart{}; + LWOOBJID userID{}; + }; + + // Client -> server. Sent when the player leaves an activity (e.g. closes the minigame UI). + struct RequestActivityExit : public NetGameMsg { + RequestActivityExit() : NetGameMsg(MessageType::Game::REQUEST_ACTIVITY_EXIT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bUserCancel{}; + LWOOBJID userID{}; + }; + + // Server -> client. + struct ShowActivityCountdown : public NetGameMsg { + ShowActivityCountdown() : NetGameMsg(MessageType::Game::SHOW_ACTIVITY_COUNTDOWN) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bPlayAdditionalSound{}; + bool bPlayCountdownSound{}; + std::u16string sndName{}; + int32_t stateToPlaySoundOn{}; + }; +}; + +#endif // ACTIVITYMESSAGES_H diff --git a/dGame/dGameMessages/CMakeLists.txt b/dGame/dGameMessages/CMakeLists.txt index 0f28dea4f..946b84af3 100644 --- a/dGame/dGameMessages/CMakeLists.txt +++ b/dGame/dGameMessages/CMakeLists.txt @@ -1,4 +1,5 @@ set(DGAME_DGAMEMESSAGES_SOURCES + "ActivityMessages.cpp" "GameMessageHandler.cpp" "GameMessages.cpp" "PropertyDataMessage.cpp" diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 02b81f8f6..760eb9124 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -30,6 +30,7 @@ #include "StartSkill.h" #include "EchoStartSkill.h" #include "EchoSyncSkill.h" +#include "ActivityMessages.h" #include "eMissionTaskType.h" #include "eReplicaComponentType.h" #include "ServiceType.h" @@ -49,6 +50,7 @@ namespace { { REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique(); } }, { SHOOTING_GALLERY_FIRE, []() { return std::make_unique(); } }, { PICKUP_ITEM, []() { return std::make_unique(); } }, + { REQUEST_ACTIVITY_EXIT, []() { return std::make_unique(); } }, }; }; @@ -718,9 +720,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System case MessageType::Game::ZONE_SUMMARY_DISMISSED: GameMessages::HandleZoneSummaryDismissed(inStream, entity); break; - case MessageType::Game::REQUEST_ACTIVITY_EXIT: - GameMessages::HandleRequestActivityExit(inStream, entity); - break; case MessageType::Game::ADD_DONATION_ITEM: GameMessages::HandleAddDonationItem(inStream, entity, sysAddr); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c1eb1cd7b..d76edc510 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1460,43 +1460,6 @@ void GameMessages::SendRequestActivitySummaryLeaderboardData(const LWOOBJID& obj SEND_PACKET; } -void GameMessages::SendActivityPause(LWOOBJID objectId, bool pause, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ACTIVITY_PAUSE); - bitStream.Write(pause); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendStartActivityTime(LWOOBJID objectId, float_t startTime, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::START_ACTIVITY_TIME); - bitStream.Write(startTime); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::REQUEST_ACTIVITY_ENTER); - bitStream.Write(bStart); - bitStream.Write(userID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - void GameMessages::NotifyLevelRewards(LWOOBJID objectID, const SystemAddress& sysAddr, int level, bool sending_rewards) { CBITSTREAM; CMSGHEADER; @@ -4394,55 +4357,8 @@ void GameMessages::SendNotifyRacingClient(LWOOBJID objectId, int32_t eventType, } -void GameMessages::SendActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ACTIVITY_ENTER); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} -void GameMessages::SendActivityStart(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ACTIVITY_START); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendActivityExit(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ACTIVITY_EXIT); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendActivityStop(LWOOBJID objectId, bool bExit, bool bUserCancel, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ACTIVITY_STOP); - - bitStream.Write(bExit); - bitStream.Write(bUserCancel); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} void GameMessages::SendVehicleAddPassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr) { @@ -4603,28 +4519,6 @@ void GameMessages::SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, } -void GameMessages::SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::SHOW_ACTIVITY_COUNTDOWN); - - bitStream.Write(bPlayAdditionalSound); - - bitStream.Write(bPlayCountdownSound); - - bitStream.Write(sndName.size()); - for (auto character : sndName) { - bitStream.Write(character); - } - - bitStream.Write(stateToPlaySoundOn); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - //----------------------------------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------- Handlers ------------------------------------------------------------------ @@ -6061,18 +5955,6 @@ void GameMessages::SendShowBillboardInteractIcon(const SystemAddress& sysAddr, L else SEND_PACKET } -void GameMessages::HandleRequestActivityExit(RakNet::BitStream& inStream, Entity* entity) { - bool canceled = false; - inStream.Read(canceled); - if (!canceled) return; - - LWOOBJID player_id = LWOOBJID_EMPTY; - inStream.Read(player_id); - auto player = Game::entityManager->GetEntity(player_id); - if (!entity || !player) return; - entity->RequestActivityExit(entity, player_id, canceled); -} - void GameMessages::HandleAddDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { uint32_t count = 1; bool hasCount = false; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 54ad3dbc8..40c1a9699 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -618,13 +618,6 @@ namespace GameMessages { void SendNotifyRacingClient(LWOOBJID objectId, int32_t eventType, int32_t param1, LWOOBJID paramObj, std::u16string paramStr, LWOOBJID singleClient, const SystemAddress& sysAddr); - void SendActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr); - void SendActivityStart(LWOOBJID objectId, const SystemAddress& sysAddr); - void SendActivityExit(LWOOBJID objectId, const SystemAddress& sysAddr); - void SendActivityStop(LWOOBJID objectId, bool bExit, bool bUserCancel, const SystemAddress& sysAddr); - void SendActivityPause(LWOOBJID objectId, bool pause = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); - void SendStartActivityTime(LWOOBJID objectId, float_t startTime, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); - void SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID); void SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, eUseItemResponse itemResponse); // SG: @@ -675,8 +668,6 @@ namespace GameMessages { void HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response); - void SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr); - //Handlers: void HandleToggleGhostReferenceOverride(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); @@ -741,7 +732,6 @@ namespace GameMessages { void SendDeactivateBubbleBuffFromServer(LWOOBJID objectId, const SystemAddress& sysAddr); void HandleZoneSummaryDismissed(RakNet::BitStream& inStream, Entity* entity); - void HandleRequestActivityExit(RakNet::BitStream& inStream, Entity* entity); // Donation vendor void HandleAddDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); diff --git a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp index 7e8ca7e80..cd30b58fb 100644 --- a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp +++ b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp @@ -2,6 +2,7 @@ #include "EntityManager.h" #include "ScriptedActivityComponent.h" #include "GameMessages.h" +#include "ActivityMessages.h" #include "LeaderboardManager.h" #include "dServer.h" #include "eMissionTaskType.h" @@ -43,7 +44,9 @@ void NpcAgCourseStarter::OnMessageBoxResponse(Entity* self, Entity* sender, int3 Game::entityManager->SerializeEntity(self); } else if (identifier == u"player_dialog_start_course" && button == 1) { GameMessages::SendNotifyClientObject(selfId, u"start_timer", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr); - GameMessages::SendActivityStart(selfId, senderSysAddr); + GameMessages::ActivityStart activityStart; + activityStart.target = selfId; + activityStart.Send(senderSysAddr); const auto score = scriptedActivityComponent->GetActivityValue(senderId, 1); if (score != 0 && score != -1.0f) return; diff --git a/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp b/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp index c036b0236..6e05112c5 100644 --- a/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp +++ b/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp @@ -1,5 +1,6 @@ #include "NtCombatChallengeServer.h" #include "GameMessages.h" +#include "ActivityMessages.h" #include "EntityManager.h" #include "EntityInfo.h" #include "InventoryComponent.h" @@ -58,7 +59,9 @@ void NtCombatChallengeServer::OnMessageBoxResponse(Entity* self, Entity* sender, self->AddTimer("start_delay", 2.0f); - GameMessages::SendShowActivityCountdown(self->GetObjectID(), false, false, u"", 0, sender->GetSystemAddress()); + GameMessages::ShowActivityCountdown countdown; + countdown.target = self->GetObjectID(); + countdown.Send(sender->GetSystemAddress()); self->SetNetworkVar(u"toggle", true); } else if (identifier == u"CloseButton") { diff --git a/dScripts/BaseSurvivalServer.cpp b/dScripts/BaseSurvivalServer.cpp index f71991ba7..d82c1499d 100644 --- a/dScripts/BaseSurvivalServer.cpp +++ b/dScripts/BaseSurvivalServer.cpp @@ -1,5 +1,6 @@ #include "BaseSurvivalServer.h" #include "GameMessages.h" +#include "ActivityMessages.h" #include "DestroyableComponent.h" #include "EntityManager.h" #include "dZoneManager.h" @@ -278,7 +279,9 @@ void BaseSurvivalServer::PlayerAccepted(Entity* self, LWOOBJID playerID) { } void BaseSurvivalServer::StartWaves(Entity* self) { - GameMessages::SendActivityStart(self->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::ActivityStart activityStart; + activityStart.target = self->GetObjectID(); + activityStart.Send(UNASSIGNED_SYSTEM_ADDRESS); self->SetVar(PlayersReadyVariable, true); self->SetVar(BaseMobSetIndexVariable, 0); diff --git a/dScripts/BaseWavesServer.cpp b/dScripts/BaseWavesServer.cpp index e2f646f97..b92987e86 100644 --- a/dScripts/BaseWavesServer.cpp +++ b/dScripts/BaseWavesServer.cpp @@ -1,5 +1,6 @@ #include "BaseWavesServer.h" #include "GameMessages.h" +#include "ActivityMessages.h" #include "DestroyableComponent.h" #include "EntityManager.h" #include "dZoneManager.h" @@ -273,7 +274,9 @@ void BaseWavesServer::PlayerAccepted(Entity* self, LWOOBJID playerID) { // Done void BaseWavesServer::StartWaves(Entity* self) { - GameMessages::SendActivityStart(self->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::ActivityStart activityStart; + activityStart.target = self->GetObjectID(); + activityStart.Send(UNASSIGNED_SYSTEM_ADDRESS); self->SetNetworkVar(WatchingIntroVariable, ""); self->SetVar(PlayersReadyVariable, true); diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index 473cd7c52..e33b857a9 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -1,6 +1,7 @@ #include "SGCannon.h" #include "EntityManager.h" #include "GameMessages.h" +#include "ActivityMessages.h" #include "dZoneManager.h" #include "Character.h" #include "ShootingGalleryComponent.h" @@ -83,7 +84,9 @@ void SGCannon::OnActivityStateChangeRequest(Entity* self, LWOOBJID senderID, int if (player != nullptr) { LOG("Player is ready"); - GameMessages::SendActivityEnter(self->GetObjectID(), player->GetSystemAddress()); + GameMessages::ActivityEnter activityEnter; + activityEnter.target = self->GetObjectID(); + activityEnter.Send(player->GetSystemAddress()); auto* shootingGalleryComponent = self->GetComponent(); @@ -186,10 +189,16 @@ void SGCannon::SpawnWaveTimerFunc(Entity* self) { if (player != nullptr) { GameMessages::SendPlayFXEffect(player->GetObjectID(), -1, u"SG-start", ""); - GameMessages::SendStartActivityTime(self->GetObjectID(), timeLimit, player->GetSystemAddress()); + GameMessages::StartActivityTime startActivityTime; + startActivityTime.target = self->GetObjectID(); + startActivityTime.startTime = timeLimit; + startActivityTime.Send(player->GetSystemAddress()); LOG("Sending ActivityPause false"); - GameMessages::SendActivityPause(self->GetObjectID(), false, player->GetSystemAddress()); + GameMessages::ActivityPause activityPause; + activityPause.target = self->GetObjectID(); + activityPause.bPause = false; + activityPause.Send(player->GetSystemAddress()); } } } @@ -199,8 +208,12 @@ void SGCannon::EndWaveTimerFunc(Entity* self) { TimerToggle(self); RecordPlayerScore(self); + GameMessages::ActivityPause activityPause; + activityPause.target = self->GetObjectID(); + activityPause.bPause = true; + if (self->GetVar(ThisWaveVariable) >= 2) { - GameMessages::SendActivityPause(self->GetObjectID(), true); + activityPause.Send(UNASSIGNED_SYSTEM_ADDRESS); ActivityTimerStart(self, GameOverTimer, 0.1, 0.1); return; } @@ -220,7 +233,7 @@ void SGCannon::EndWaveTimerFunc(Entity* self) { LOG("Sending ActivityPause true"); - GameMessages::SendActivityPause(self->GetObjectID(), true); + activityPause.Send(UNASSIGNED_SYSTEM_ADDRESS); if (self->GetVar(SuperChargeActiveVariable) && !self->GetVar(SuperChargePausedVariable)) { PauseChargeCannon(self); } @@ -231,7 +244,10 @@ void SGCannon::GameOverTimerFunc(Entity* self) { if (player != nullptr) { LOG_DEBUG("Sending ActivityPause true"); - GameMessages::SendActivityPause(self->GetObjectID(), true, player->GetSystemAddress()); + GameMessages::ActivityPause activityPause; + activityPause.target = self->GetObjectID(); + activityPause.bPause = true; + activityPause.Send(player->GetSystemAddress()); ActivityTimerStart(self, EndGameBufferTimer, 1, 1); @@ -342,7 +358,9 @@ void SGCannon::StartGame(Entity* self) { // The client cant accept more than 10 results. GetLeaderboardData(self, player->GetObjectID(), GetConstants().activityID, 10); LOG("Sending ActivityStart"); - GameMessages::SendActivityStart(self->GetObjectID(), player->GetSystemAddress()); + GameMessages::ActivityStart activityStart; + activityStart.target = self->GetObjectID(); + activityStart.Send(player->GetSystemAddress()); GameMessages::SendPlayFXEffect(self->GetObjectID(), -1, u"start", ""); @@ -602,7 +620,11 @@ void SGCannon::StopGame(Entity* self, bool cancel) { self->SetNetworkVar(u"UI_Rewards", GeneralUtils::ASCIIToUTF16(stream.str())); } - GameMessages::SendActivityStop(self->GetObjectID(), false, cancel, player->GetSystemAddress()); + GameMessages::ActivityStop activityStop; + activityStop.target = self->GetObjectID(); + activityStop.bExit = false; + activityStop.bUserCancel = cancel; + activityStop.Send(player->GetSystemAddress()); self->SetVar(GameStartedVariable, false); ActivityTimerStopAllTimers(self); diff --git a/tests/dGameTests/dGameMessagesTests/ActivityMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/ActivityMessagesTests.cpp new file mode 100644 index 000000000..bdcf41034 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/ActivityMessagesTests.cpp @@ -0,0 +1,222 @@ +#include "ActivityMessages.h" +#include "GameDependencies.h" +#include "PacketTestUtils.h" +#include "Legacy/ActivityMessagesLegacy.h" + +#include +#include +#include +#include + +#include + +using namespace PacketTestUtils; + +namespace { + SystemAddress ClientAddress() { + SystemAddress address; + address.binaryAddress = 0x0100007f; + address.port = 2003; + return address; + } + + const std::array g_Targets = { LWOOBJID_EMPTY, 0x1000000000000001LL, 0x0102030405060708LL }; + const std::array g_Addresses = { ClientAddress(), UNASSIGNED_SYSTEM_ADDRESS }; + + PacketBytes StructPacket(const GameMessages::NetGameMsg& msg) { + RakNet::BitStream bitStream; + msg.WritePacket(bitStream); + return FromBitStream(bitStream); + } + + // Sends the same message through the frozen legacy function and through the struct, to one client and as a + // broadcast, and requires identical bytes and the same effective destination. + void ExpectSameAsLegacy(const std::function& legacySend, const GameMessages::NetGameMsg& msg) { + for (const auto& address : g_Addresses) { + SCOPED_TRACE(address == UNASSIGNED_SYSTEM_ADDRESS ? "broadcast" : "single client"); + const auto legacyPackets = Capture([&] { legacySend(address); }); + const auto newPackets = Capture([&] { msg.Send(address); }); + + ASSERT_FALSE(legacyPackets.empty()); + ASSERT_EQ(newPackets.size(), 1); + // The legacy broadcast path also did a second Send(UNASSIGNED_SYSTEM_ADDRESS, broadcast = false), + // which RakPeer::Send rejects without sending anything. Every copy must still match byte for byte. + for (const auto& legacyPacket : legacyPackets) { + EXPECT_PACKET_EQ(FromCapture(legacyPacket), FromCapture(newPackets[0])); + } + EXPECT_EQ(legacyPackets[0].broadcast, newPackets[0].broadcast); + EXPECT_EQ(legacyPackets[0].sysAddr, newPackets[0].sysAddr); + EXPECT_PACKET_EQ(FromCapture(newPackets[0]), StructPacket(msg)); + } + } + + // Serializes msg, reads it back into a fresh T and checks the fresh copy serializes to the same bytes. + template + T RoundTrip(const T& msg) { + RakNet::BitStream bitStream; + msg.Serialize(bitStream); + T copy; + EXPECT_TRUE(copy.Deserialize(bitStream)); + EXPECT_EQ(bitStream.GetNumberOfUnreadBits(), 0); + RakNet::BitStream again; + copy.Serialize(again); + EXPECT_PACKET_EQ(FromBitStream(bitStream), FromBitStream(again)); + return copy; + } +} + +class ActivityMessagesTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } +}; + +TEST_F(ActivityMessagesTests, NoPayloadMessagesMatchLegacy) { + for (const auto target : g_Targets) { + GameMessages::ActivityEnter enter; + enter.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendActivityEnter(target, a); }, enter); + + GameMessages::ActivityStart start; + start.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendActivityStart(target, a); }, start); + + GameMessages::ActivityExit exit; + exit.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendActivityExit(target, a); }, exit); + } +} + +TEST_F(ActivityMessagesTests, ActivityStopMatchesLegacy) { + for (const auto target : g_Targets) { + for (const bool bExit : { false, true }) { + for (const bool bUserCancel : { false, true }) { + GameMessages::ActivityStop msg; + msg.target = target; + msg.bExit = bExit; + msg.bUserCancel = bUserCancel; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendActivityStop(target, bExit, bUserCancel, a); }, msg); + + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.bExit, bExit); + EXPECT_EQ(copy.bUserCancel, bUserCancel); + } + } + } +} + +TEST_F(ActivityMessagesTests, ActivityPauseMatchesLegacy) { + for (const auto target : g_Targets) { + for (const bool bPause : { false, true }) { + GameMessages::ActivityPause msg; + msg.target = target; + msg.bPause = bPause; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendActivityPause(target, bPause, a); }, msg); + EXPECT_EQ(RoundTrip(msg).bPause, bPause); + } + } +} + +TEST_F(ActivityMessagesTests, StartActivityTimeMatchesLegacy) { + for (const auto target : g_Targets) { + for (const float startTime : { 0.0f, 30.0f, -1.5f, 1e-30f, std::numeric_limits::max() }) { + GameMessages::StartActivityTime msg; + msg.target = target; + msg.startTime = startTime; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendStartActivityTime(target, startTime, a); }, msg); + EXPECT_EQ(RoundTrip(msg).startTime, startTime); + } + } +} + +TEST_F(ActivityMessagesTests, RequestActivityEnterMatchesLegacy) { + for (const auto target : g_Targets) { + for (const bool bStart : { false, true }) { + for (const LWOOBJID userID : g_Targets) { + GameMessages::RequestActivityEnter msg; + msg.target = target; + msg.bStart = bStart; + msg.userID = userID; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendRequestActivityEnter(target, a, bStart, userID); }, msg); + + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.bStart, bStart); + EXPECT_EQ(copy.userID, userID); + } + } + } +} + +TEST_F(ActivityMessagesTests, ShowActivityCountdownMatchesLegacy) { + const std::u16string longName(300, u'é'); + for (const auto target : g_Targets) { + for (const bool bPlayAdditionalSound : { false, true }) { + for (const bool bPlayCountdownSound : { false, true }) { + for (const std::u16string& sndName : { std::u16string(), std::u16string(u"sfx/minigame/countdown"), longName }) { + for (const int32_t state : { 0, 3, -1 }) { + GameMessages::ShowActivityCountdown msg; + msg.target = target; + msg.bPlayAdditionalSound = bPlayAdditionalSound; + msg.bPlayCountdownSound = bPlayCountdownSound; + msg.sndName = sndName; + msg.stateToPlaySoundOn = state; + ExpectSameAsLegacy([&](const SystemAddress& a) { + LegacyGameMessages::SendShowActivityCountdown(target, bPlayAdditionalSound, bPlayCountdownSound, sndName, state, a); + }, msg); + + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.bPlayAdditionalSound, bPlayAdditionalSound); + EXPECT_EQ(copy.bPlayCountdownSound, bPlayCountdownSound); + EXPECT_EQ(copy.sndName, sndName); + EXPECT_EQ(copy.stateToPlaySoundOn, state); + } + } + } + } + } +} + +// Independent of the legacy code: pins the layout against hand computed bytes. +TEST_F(ActivityMessagesTests, GoldenBytes) { + // 53 | 05 00 (CLIENT) | 0c 00 00 00 (GAME_MSG) | 00 | target (LE i64) | msgId (LE u16) | payload + GameMessages::ActivityStop stop; + stop.target = 0x0102030405060708LL; + stop.bExit = true; + stop.bUserCancel = false; + EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 08 07 06 05 04 03 02 01 98 01 80", 146), StructPacket(stop)); + + GameMessages::StartActivityTime time; + time.target = 0x0102030405060708LL; + time.startTime = 1.5f; + EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 08 07 06 05 04 03 02 01 40 02 00 00 c0 3f"), StructPacket(time)); +} + +TEST_F(ActivityMessagesTests, RequestActivityExitReadsLikeLegacy) { + for (const bool bUserCancel : { false, true }) { + for (const LWOOBJID userID : g_Targets) { + RakNet::BitStream wire; + wire.Write(bUserCancel); + wire.Write(userID); + + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + const auto legacy = LegacyGameMessages::ReadRequestActivityExit(legacyStream); + + RakNet::BitStream newStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + GameMessages::RequestActivityExit msg; + ASSERT_TRUE(msg.Deserialize(newStream)); + + EXPECT_EQ(msg.bUserCancel, legacy.canceled); + // The legacy handler only read userID (and only acted) when the exit was a user cancel. + if (legacy.canceled) EXPECT_EQ(msg.userID, legacy.player_id); + + RakNet::BitStream reserialized; + msg.Serialize(reserialized); + EXPECT_PACKET_EQ(FromBitStream(wire), FromBitStream(reserialized)); + } + } + + RakNet::BitStream truncated; + truncated.Write(true); + GameMessages::RequestActivityExit msg; + EXPECT_FALSE(msg.Deserialize(truncated)); +} diff --git a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt index 113f190b2..b8c45be4e 100644 --- a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt +++ b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt @@ -1,4 +1,5 @@ SET(DGAMEMESSAGES_TESTS + "ActivityMessagesTests.cpp" "GameMessageTests.cpp" "GameMsgSplitTests.cpp" "UnSmashTests.cpp" diff --git a/tests/dGameTests/dGameMessagesTests/Legacy/ActivityMessagesLegacy.h b/tests/dGameTests/dGameMessagesTests/Legacy/ActivityMessagesLegacy.h new file mode 100644 index 000000000..1b35ffdd6 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/Legacy/ActivityMessagesLegacy.h @@ -0,0 +1,143 @@ +#ifndef ACTIVITYMESSAGESLEGACY_H +#define ACTIVITYMESSAGESLEGACY_H + +// FROZEN ORACLE - DO NOT EDIT. +// Verbatim copies of the hand written GameMessages::Send* functions that ActivityMessages.h replaced +// (dGame/dGameMessages/GameMessages.cpp at origin/main 129199e4). Only the namespace changed. +// The byte-equality tests send the same inputs through these and through the new structs and require +// identical bytes, so the wire format is pinned even after the production code is deleted. + +#include "BitStreamUtils.h" +#include "dCommonVars.h" +#include "dServer.h" +#include "Game.h" +#include "MessageType/Client.h" +#include "MessageType/Game.h" +#include "ServiceType.h" + +#include +#include + +namespace LegacyGameMessages { + inline void SendActivityPause(LWOOBJID objectId, bool pause = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::ACTIVITY_PAUSE); + bitStream.Write(pause); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendStartActivityTime(LWOOBJID objectId, float_t startTime, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::START_ACTIVITY_TIME); + bitStream.Write(startTime); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::REQUEST_ACTIVITY_ENTER); + bitStream.Write(bStart); + bitStream.Write(userID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::ACTIVITY_ENTER); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendActivityStart(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::ACTIVITY_START); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendActivityExit(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::ACTIVITY_EXIT); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendActivityStop(LWOOBJID objectId, bool bExit, bool bUserCancel, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::ACTIVITY_STOP); + + bitStream.Write(bExit); + bitStream.Write(bUserCancel); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::SHOW_ACTIVITY_COUNTDOWN); + + bitStream.Write(bPlayAdditionalSound); + + bitStream.Write(bPlayCountdownSound); + + bitStream.Write(sndName.size()); + for (auto character : sndName) { + bitStream.Write(character); + } + + bitStream.Write(stateToPlaySoundOn); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + // Inbound: the read sequence of the old GameMessages::HandleRequestActivityExit, returning what it read. + struct LegacyRequestActivityExitRead { + bool canceled = false; + LWOOBJID player_id = LWOOBJID_EMPTY; + }; + inline LegacyRequestActivityExitRead ReadRequestActivityExit(RakNet::BitStream& inStream) { + LegacyRequestActivityExitRead result; + inStream.Read(result.canceled); + if (!result.canceled) return result; + + inStream.Read(result.player_id); + return result; + } +} + +#endif // ACTIVITYMESSAGESLEGACY_H