From 527f54488b4422f5a058de75c29a3acff53c457d Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sat, 26 Sep 2026 18:44:25 -0500 Subject: [PATCH] refactor: pet game messages as structs Converts the pet taming minigame, naming, command and bouncer messages to NetGameMsgs in PetMessages.{h,cpp}: NotifyPetTamingMinigame, NotifyTamingModelLoadedOnServer, NotifyPetTamingPuzzleSelected, PetTamingTryBuildResult, PetResponse, AddPetToPlayer, RegisterPetID, RegisterPetDBID, ShowPetActionButton, BouncerActiveStatus, SetPetName, SetPetNameModerated and PetNameChanged (sent), and PetTamingTryBuild, NotifyTamingBuildSuccess, RequestSetPetName, StartServerPetMinigameTimer, ClientExitTamingMinigame, CommandPet and DespawnPet (received, registered in GameMessageHandler's map; each Handle hands the message to the player's taming or active PetComponent exactly as the old handler did). PetComponent, BouncerComponent and the hydrant/catapult scripts build the structs; the old Send*/Handle* functions and switch cases are gone. The never-called SendClientExitTamingMinigame is folded into the ClientExitTamingMinigame struct. MarkInventoryItemAsActive stays with the inventory messages. No wire change and no change in recipients. Verified byte for byte against a frozen verbatim copy of the old functions over an input grid (to one client and broadcast), received messages compared with the old handlers' read sequences with every truncated payload rejected, hand computed golden bytes, round trips, and a mutation check. Layouts confirmed against the 1.10.64 client's Serialize/Deserialize in Ghidra. Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/BouncerComponent.cpp | 6 +- dGame/dComponents/PetComponent.cpp | 366 +++++++++++++----- dGame/dGameMessages/CMakeLists.txt | 1 + dGame/dGameMessages/GameMessageHandler.cpp | 39 +- dGame/dGameMessages/GameMessages.cpp | 361 ----------------- dGame/dGameMessages/GameMessages.h | 42 -- dGame/dGameMessages/PetMessages.cpp | 292 ++++++++++++++ dGame/dGameMessages/PetMessages.h | 222 +++++++++++ dScripts/02_server/Map/PR/HydrantBroken.cpp | 11 +- .../Map/njhub/CatapultBaseServer.cpp | 6 +- dScripts/ai/NS/WH/RockHydrantBroken.cpp | 11 +- .../dGameMessagesTests/CMakeLists.txt | 1 + .../Legacy/PetMessagesLegacy.h | 333 ++++++++++++++++ .../dGameMessagesTests/PetMessagesTests.cpp | 351 +++++++++++++++++ 14 files changed, 1509 insertions(+), 533 deletions(-) create mode 100644 dGame/dGameMessages/PetMessages.cpp create mode 100644 dGame/dGameMessages/PetMessages.h create mode 100644 tests/dGameTests/dGameMessagesTests/Legacy/PetMessagesLegacy.h create mode 100644 tests/dGameTests/dGameMessagesTests/PetMessagesTests.cpp diff --git a/dGame/dComponents/BouncerComponent.cpp b/dGame/dComponents/BouncerComponent.cpp index 3f0ffc9a5..dc1d50f22 100644 --- a/dGame/dComponents/BouncerComponent.cpp +++ b/dGame/dComponents/BouncerComponent.cpp @@ -1,4 +1,5 @@ #include "BouncerComponent.h" +#include "PetMessages.h" #include "EntityManager.h" #include "dZoneManager.h" @@ -57,7 +58,10 @@ void BouncerComponent::SetPetEnabled(bool value) { void BouncerComponent::SetPetBouncerEnabled(bool value) { m_PetBouncerEnabled = value; - GameMessages::SendBouncerActiveStatus(m_Parent->GetObjectID(), value, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::BouncerActiveStatus msg; + msg.target = m_Parent->GetObjectID(); + msg.bActive = value; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); Game::entityManager->SerializeEntity(m_Parent); diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 9771691df..c2e36929e 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -1,6 +1,7 @@ #include "PetComponent.h" #include "DashboardNotify.h" #include "GameMessages.h" +#include "PetMessages.h" #include "BrickDatabase.h" #include "CDClientDatabase.h" #include "CDTamingBuildPuzzleTable.h" @@ -205,31 +206,38 @@ void PetComponent::OnUse(Entity* originator) { auto rotation = QuatUtils::LookAt(position, petPosition); - GameMessages::SendNotifyPetTamingMinigame( - originator->GetObjectID(), - m_Parent->GetObjectID(), - LWOOBJID_EMPTY, - true, - ePetTamingNotifyType::BEGIN, - petPosition, - position, - rotation, - UNASSIGNED_SYSTEM_ADDRESS - ); + { + GameMessages::NotifyPetTamingMinigame msg; + msg.target = originator->GetObjectID(); + msg.PetID = m_Parent->GetObjectID(); + msg.PlayerTamingID = LWOOBJID_EMPTY; + msg.bForceTeleport = true; + msg.notifyType = ePetTamingNotifyType::BEGIN; + msg.petsDestPos = petPosition; + msg.telePos = position; + msg.teleRot = rotation; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); + } - GameMessages::SendNotifyPetTamingMinigame( - m_Parent->GetObjectID(), - LWOOBJID_EMPTY, - originator->GetObjectID(), - false, - ePetTamingNotifyType::BEGIN, - NiPoint3Constant::ZERO, - NiPoint3Constant::ZERO, - NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f), - UNASSIGNED_SYSTEM_ADDRESS - ); + { + GameMessages::NotifyPetTamingMinigame msg; + msg.target = m_Parent->GetObjectID(); + msg.PetID = LWOOBJID_EMPTY; + msg.PlayerTamingID = originator->GetObjectID(); + msg.bForceTeleport = false; + msg.notifyType = ePetTamingNotifyType::BEGIN; + msg.petsDestPos = NiPoint3Constant::ZERO; + msg.telePos = NiPoint3Constant::ZERO; + msg.teleRot = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f); + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); + } - GameMessages::SendNotifyPetTamingPuzzleSelected(originator->GetObjectID(), bricks, originator->GetSystemAddress()); + { + GameMessages::NotifyPetTamingPuzzleSelected msg; + msg.target = originator->GetObjectID(); + msg.bricks = bricks; + msg.Send(originator->GetSystemAddress()); + } m_Tamer = originator->GetObjectID(); SetStatus(5); @@ -440,7 +448,11 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { if (numBricks == 0) return; - GameMessages::SendPetTamingTryBuildResult(m_Tamer, !clientFailed, numBricks, tamer->GetSystemAddress()); + GameMessages::PetTamingTryBuildResult msg; + msg.target = m_Tamer; + msg.bSuccess = !clientFailed; + msg.iNumCorrect = numBricks; + msg.Send(tamer->GetSystemAddress()); } void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { @@ -472,9 +484,21 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { Game::entityManager->ConstructEntity(modelEntity); - GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); + { + GameMessages::NotifyTamingModelLoadedOnServer msg; + msg.target = m_Tamer; + msg.Send(tamer->GetSystemAddress()); + } - GameMessages::SendPetResponse(m_Tamer, m_Parent->GetObjectID(), 0, 10, 0, tamer->GetSystemAddress()); + { + GameMessages::PetResponse msg; + msg.target = m_Tamer; + msg.ObjIDPet = m_Parent->GetObjectID(); + msg.iPetCommandType = 0; + msg.iResponse = 10; + msg.iTypeID = 0; + msg.Send(tamer->GetSystemAddress()); + } auto* inventoryComponent = tamer->GetComponent(); @@ -501,11 +525,29 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { std::string petName = tamer->GetCharacter()->GetName(); petName += "'s Pet"; - GameMessages::SendAddPetToPlayer(m_Tamer, 0, GeneralUtils::UTF8ToUTF16(petName), petSubKey, m_Parent->GetLOT(), tamer->GetSystemAddress()); + { + GameMessages::AddPetToPlayer msg; + msg.target = m_Tamer; + msg.iElementalType = 0; + msg.name = GeneralUtils::UTF8ToUTF16(petName); + msg.petDBID = petSubKey; + msg.petLOT = m_Parent->GetLOT(); + msg.Send(tamer->GetSystemAddress()); + } - GameMessages::SendRegisterPetID(m_Tamer, m_Parent->GetObjectID(), tamer->GetSystemAddress()); + { + GameMessages::RegisterPetID msg; + msg.target = m_Tamer; + msg.objID = m_Parent->GetObjectID(); + msg.Send(tamer->GetSystemAddress()); + } - GameMessages::SendRegisterPetDBID(m_Tamer, petSubKey, tamer->GetSystemAddress()); + { + GameMessages::RegisterPetDBID msg; + msg.target = m_Tamer; + msg.petDBID = petSubKey; + msg.Send(tamer->GetSystemAddress()); + } inventoryComponent->AddItem(m_Parent->GetLOT(), 1, eLootSourceType::INVENTORY, eInventoryType::MODELS, {}, LWOOBJID_EMPTY, true, false, petSubKey); auto* item = inventoryComponent->FindItemBySubKey(petSubKey, MODELS); @@ -526,17 +568,18 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { m_Timer = 0; - GameMessages::SendNotifyPetTamingMinigame( - m_Tamer, - LWOOBJID_EMPTY, - LWOOBJID_EMPTY, - false, - ePetTamingNotifyType::NAMINGPET, - NiPoint3Constant::ZERO, - NiPoint3Constant::ZERO, - QuatUtils::IDENTITY, - UNASSIGNED_SYSTEM_ADDRESS - ); + { + GameMessages::NotifyPetTamingMinigame msg; + msg.target = m_Tamer; + msg.PetID = LWOOBJID_EMPTY; + msg.PlayerTamingID = LWOOBJID_EMPTY; + msg.bForceTeleport = false; + msg.notifyType = ePetTamingNotifyType::NAMINGPET; + msg.petsDestPos = NiPoint3Constant::ZERO; + msg.telePos = NiPoint3Constant::ZERO; + msg.teleRot = QuatUtils::IDENTITY; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); + } // Triggers the catch a pet missions constexpr auto PET_FLAG_BASE = 800; @@ -569,8 +612,20 @@ void PetComponent::RequestSetPetName(std::u16string name) { std::string forcedName = "Pet"; Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast(m_ModerationStatus), m_Owner }); DashboardNotify::Changed("pet_names", m_DatabaseId); - GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); - GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + { + GameMessages::SetPetName msg; + msg.target = m_Owner; + msg.name = GeneralUtils::UTF8ToUTF16(m_Name); + msg.petDBID = m_DatabaseId; + msg.Send(owner->GetSystemAddress()); + } + { + GameMessages::SetPetNameModerated msg; + msg.target = m_Owner; + msg.PetDBID = m_DatabaseId; + msg.nModerationStatus = m_ModerationStatus; + msg.Send(owner->GetSystemAddress()); + } } else { m_ModerationStatus = 1; // Pending m_Name = ""; @@ -578,8 +633,20 @@ void PetComponent::RequestSetPetName(std::u16string name) { //Save our pet's new name to the db: SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name), m_Owner); - GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); - GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + { + GameMessages::SetPetName msg; + msg.target = m_Owner; + msg.name = GeneralUtils::UTF8ToUTF16(m_Name); + msg.petDBID = m_DatabaseId; + msg.Send(owner->GetSystemAddress()); + } + { + GameMessages::SetPetNameModerated msg; + msg.target = m_Owner; + msg.PetDBID = m_DatabaseId; + msg.nModerationStatus = m_ModerationStatus; + msg.Send(owner->GetSystemAddress()); + } } } @@ -623,22 +690,48 @@ void PetComponent::RequestSetPetName(std::u16string name) { std::u16string u16name = GeneralUtils::UTF8ToUTF16(m_Name); std::u16string u16ownerName = GeneralUtils::UTF8ToUTF16(m_OwnerName); - GameMessages::SendSetPetName(m_Tamer, u16name, m_DatabaseId, tamer->GetSystemAddress()); - GameMessages::SendSetPetName(m_Tamer, u16name, LWOOBJID_EMPTY, tamer->GetSystemAddress()); - GameMessages::SendPetNameChanged(m_Parent->GetObjectID(), m_ModerationStatus, u16name, u16ownerName, UNASSIGNED_SYSTEM_ADDRESS); - GameMessages::SendSetPetNameModerated(m_Tamer, m_DatabaseId, m_ModerationStatus, tamer->GetSystemAddress()); + { + GameMessages::SetPetName msg; + msg.target = m_Tamer; + msg.name = u16name; + msg.petDBID = m_DatabaseId; + msg.Send(tamer->GetSystemAddress()); + } + { + GameMessages::SetPetName msg; + msg.target = m_Tamer; + msg.name = u16name; + msg.petDBID = LWOOBJID_EMPTY; + msg.Send(tamer->GetSystemAddress()); + } + { + GameMessages::PetNameChanged msg; + msg.target = m_Parent->GetObjectID(); + msg.moderationStatus = m_ModerationStatus; + msg.name = u16name; + msg.ownerName = u16ownerName; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); + } + { + GameMessages::SetPetNameModerated msg; + msg.target = m_Tamer; + msg.PetDBID = m_DatabaseId; + msg.nModerationStatus = m_ModerationStatus; + msg.Send(tamer->GetSystemAddress()); + } - GameMessages::SendNotifyPetTamingMinigame( - m_Tamer, - m_Parent->GetObjectID(), - m_Tamer, - false, - ePetTamingNotifyType::SUCCESS, - NiPoint3Constant::ZERO, - NiPoint3Constant::ZERO, - QuatUtils::IDENTITY, - UNASSIGNED_SYSTEM_ADDRESS - ); + { + GameMessages::NotifyPetTamingMinigame msg; + msg.target = m_Tamer; + msg.PetID = m_Parent->GetObjectID(); + msg.PlayerTamingID = m_Tamer; + msg.bForceTeleport = false; + msg.notifyType = ePetTamingNotifyType::SUCCESS; + msg.petsDestPos = NiPoint3Constant::ZERO; + msg.telePos = NiPoint3Constant::ZERO; + msg.teleRot = QuatUtils::IDENTITY; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); + } auto* characterComponent = tamer->GetComponent(); if (characterComponent != nullptr) { @@ -672,24 +765,29 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { return; } - GameMessages::SendNotifyPetTamingMinigame( - m_Tamer, - m_Parent->GetObjectID(), - m_Tamer, - false, - ePetTamingNotifyType::QUIT, - NiPoint3Constant::ZERO, - NiPoint3Constant::ZERO, - QuatUtils::IDENTITY, - UNASSIGNED_SYSTEM_ADDRESS - ); + { + GameMessages::NotifyPetTamingMinigame msg; + msg.target = m_Tamer; + msg.PetID = m_Parent->GetObjectID(); + msg.PlayerTamingID = m_Tamer; + msg.bForceTeleport = false; + msg.notifyType = ePetTamingNotifyType::QUIT; + msg.petsDestPos = NiPoint3Constant::ZERO; + msg.telePos = NiPoint3Constant::ZERO; + msg.teleRot = QuatUtils::IDENTITY; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); + } auto* characterComponent = tamer->GetComponent(); if (characterComponent != nullptr) { characterComponent->SetCurrentActivity(eGameActivity::NONE); Game::entityManager->SerializeEntity(tamer); } - GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); + { + GameMessages::NotifyTamingModelLoadedOnServer msg; + msg.target = m_Tamer; + msg.Send(tamer->GetSystemAddress()); + } GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); @@ -723,24 +821,29 @@ void PetComponent::ClientFailTamingMinigame() { return; } - GameMessages::SendNotifyPetTamingMinigame( - m_Tamer, - m_Parent->GetObjectID(), - m_Tamer, - false, - ePetTamingNotifyType::FAILED, - NiPoint3Constant::ZERO, - NiPoint3Constant::ZERO, - QuatUtils::IDENTITY, - UNASSIGNED_SYSTEM_ADDRESS - ); + { + GameMessages::NotifyPetTamingMinigame msg; + msg.target = m_Tamer; + msg.PetID = m_Parent->GetObjectID(); + msg.PlayerTamingID = m_Tamer; + msg.bForceTeleport = false; + msg.notifyType = ePetTamingNotifyType::FAILED; + msg.petsDestPos = NiPoint3Constant::ZERO; + msg.telePos = NiPoint3Constant::ZERO; + msg.teleRot = QuatUtils::IDENTITY; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); + } auto* characterComponent = tamer->GetComponent(); if (characterComponent != nullptr) { characterComponent->SetCurrentActivity(eGameActivity::NONE); Game::entityManager->SerializeEntity(tamer); } - GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); + { + GameMessages::NotifyTamingModelLoadedOnServer msg; + msg.target = m_Tamer; + msg.Send(tamer->GetSystemAddress()); + } GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); @@ -840,8 +943,20 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { m_OwnerName = owner->GetCharacter()->GetName(); if (updatedModerationStatus) { - GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); - GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + { + GameMessages::SetPetName msg; + msg.target = m_Owner; + msg.name = GeneralUtils::UTF8ToUTF16(m_Name); + msg.petDBID = m_DatabaseId; + msg.Send(owner->GetSystemAddress()); + } + { + GameMessages::SetPetNameModerated msg; + msg.target = m_Owner; + msg.PetDBID = m_DatabaseId; + msg.nModerationStatus = m_ModerationStatus; + msg.Send(owner->GetSystemAddress()); + } } GameMessages::SendMarkInventoryItemAsActive(m_Owner, true, eUnequippableActiveType::PET, m_ItemId, GetOwner()->GetSystemAddress()); @@ -855,11 +970,29 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { owner->GetCharacter()->SetPlayerFlag(ePlayerFlag::FIRST_MANUAL_PET_HIBERNATE, true); if (registerPet) { - GameMessages::SendAddPetToPlayer(m_Owner, 0, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, m_Parent->GetLOT(), owner->GetSystemAddress()); + { + GameMessages::AddPetToPlayer msg; + msg.target = m_Owner; + msg.iElementalType = 0; + msg.name = GeneralUtils::UTF8ToUTF16(m_Name); + msg.petDBID = m_DatabaseId; + msg.petLOT = m_Parent->GetLOT(); + msg.Send(owner->GetSystemAddress()); + } - GameMessages::SendRegisterPetID(m_Owner, m_Parent->GetObjectID(), owner->GetSystemAddress()); + { + GameMessages::RegisterPetID msg; + msg.target = m_Owner; + msg.objID = m_Parent->GetObjectID(); + msg.Send(owner->GetSystemAddress()); + } - GameMessages::SendRegisterPetDBID(m_Owner, m_DatabaseId, owner->GetSystemAddress()); + { + GameMessages::RegisterPetDBID msg; + msg.target = m_Owner; + msg.petDBID = m_DatabaseId; + msg.Send(owner->GetSystemAddress()); + } } } @@ -915,13 +1048,37 @@ void PetComponent::Deactivate() { GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, eUnequippableActiveType::PET, m_ItemId, owner->GetSystemAddress()); - GameMessages::SendAddPetToPlayer(m_Owner, 0, u"", LWOOBJID_EMPTY, LOT_NULL, owner->GetSystemAddress()); + { + GameMessages::AddPetToPlayer msg; + msg.target = m_Owner; + msg.iElementalType = 0; + msg.name = u""; + msg.petDBID = LWOOBJID_EMPTY; + msg.petLOT = LOT_NULL; + msg.Send(owner->GetSystemAddress()); + } - GameMessages::SendRegisterPetID(m_Owner, LWOOBJID_EMPTY, owner->GetSystemAddress()); + { + GameMessages::RegisterPetID msg; + msg.target = m_Owner; + msg.objID = LWOOBJID_EMPTY; + msg.Send(owner->GetSystemAddress()); + } - GameMessages::SendRegisterPetDBID(m_Owner, LWOOBJID_EMPTY, owner->GetSystemAddress()); + { + GameMessages::RegisterPetDBID msg; + msg.target = m_Owner; + msg.petDBID = LWOOBJID_EMPTY; + msg.Send(owner->GetSystemAddress()); + } - GameMessages::SendShowPetActionButton(m_Owner, ePetAbilityType::Invalid, false, owner->GetSystemAddress()); + { + GameMessages::ShowPetActionButton msg; + msg.target = m_Owner; + msg.ButtonLabel = ePetAbilityType::Invalid; + msg.bShow = false; + msg.Send(owner->GetSystemAddress()); + } } void PetComponent::Release() { @@ -1092,9 +1249,28 @@ void PetComponent::ApplyNameModeration(const bool approved) { data.moderationState = m_ModerationStatus; inventoryComponent->SetDatabasePet(m_DatabaseId, data); } - GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); - GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + { + GameMessages::SetPetName msg; + msg.target = m_Owner; + msg.name = GeneralUtils::UTF8ToUTF16(m_Name); + msg.petDBID = m_DatabaseId; + msg.Send(owner->GetSystemAddress()); + } + { + GameMessages::SetPetNameModerated msg; + msg.target = m_Owner; + msg.PetDBID = m_DatabaseId; + msg.nModerationStatus = m_ModerationStatus; + msg.Send(owner->GetSystemAddress()); + } + } + { + GameMessages::PetNameChanged msg; + msg.target = m_Parent->GetObjectID(); + msg.moderationStatus = m_ModerationStatus; + msg.name = GeneralUtils::UTF8ToUTF16(m_Name); + msg.ownerName = GeneralUtils::UTF8ToUTF16(m_OwnerName); + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); } - GameMessages::SendPetNameChanged(m_Parent->GetObjectID(), m_ModerationStatus, GeneralUtils::UTF8ToUTF16(m_Name), GeneralUtils::UTF8ToUTF16(m_OwnerName), UNASSIGNED_SYSTEM_ADDRESS); Game::entityManager->SerializeEntity(m_Parent); } diff --git a/dGame/dGameMessages/CMakeLists.txt b/dGame/dGameMessages/CMakeLists.txt index e3b0236d8..eb37bd62b 100644 --- a/dGame/dGameMessages/CMakeLists.txt +++ b/dGame/dGameMessages/CMakeLists.txt @@ -4,6 +4,7 @@ set(DGAME_DGAMEMESSAGES_SOURCES "GameMessageHandler.cpp" "GameMessages.cpp" "MissionMessages.cpp" + "PetMessages.cpp" "RacingMessages.cpp" "PropertyDataMessage.cpp" "PropertySelectQueryProperty.cpp") diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index d33fcd7e1..ed7dff096 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -34,6 +34,7 @@ #include "ActivityMessages.h" #include "RacingMessages.h" #include "MissionMessages.h" +#include "PetMessages.h" #include "eMissionTaskType.h" #include "eReplicaComponentType.h" #include "ServiceType.h" @@ -73,6 +74,15 @@ namespace { { REQUEST_LINKED_MISSION, []() { return std::make_unique(); } }, { SET_FLAG, []() { return std::make_unique(); } }, { HAS_BEEN_COLLECTED, []() { return std::make_unique(); } }, + + // Pets + { PET_TAMING_TRY_BUILD, []() { return std::make_unique(); } }, + { NOTIFY_TAMING_BUILD_SUCCESS, []() { return std::make_unique(); } }, + { REQUEST_SET_PET_NAME, []() { return std::make_unique(); } }, + { START_SERVER_PET_MINIGAME_TIMER, []() { return std::make_unique(); } }, + { CLIENT_EXIT_TAMING_MINIGAME, []() { return std::make_unique(); } }, + { COMMAND_PET, []() { return std::make_unique(); } }, + { DESPAWN_PET, []() { return std::make_unique(); } }, }; }; @@ -506,35 +516,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System GameMessages::HandleClientTradeUpdate(inStream, entity, sysAddr); break; - // Pets - case MessageType::Game::PET_TAMING_TRY_BUILD: - GameMessages::HandlePetTamingTryBuild(inStream, entity, sysAddr); - break; - - case MessageType::Game::NOTIFY_TAMING_BUILD_SUCCESS: - GameMessages::HandleNotifyTamingBuildSuccess(inStream, entity, sysAddr); - break; - - case MessageType::Game::REQUEST_SET_PET_NAME: - GameMessages::HandleRequestSetPetName(inStream, entity, sysAddr); - break; - - case MessageType::Game::START_SERVER_PET_MINIGAME_TIMER: - GameMessages::HandleStartServerPetMinigameTimer(inStream, entity, sysAddr); - break; - - case MessageType::Game::CLIENT_EXIT_TAMING_MINIGAME: - GameMessages::HandleClientExitTamingMinigame(inStream, entity, sysAddr); - break; - - case MessageType::Game::COMMAND_PET: - GameMessages::HandleCommandPet(inStream, entity, sysAddr); - break; - - case MessageType::Game::DESPAWN_PET: - GameMessages::HandleDespawnPet(inStream, entity, sysAddr); - break; - case MessageType::Game::MESSAGE_BOX_RESPOND: GameMessages::HandleMessageBoxResponse(inStream, entity, sysAddr); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index aaaf77dd1..3d73ea679 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -3188,133 +3188,6 @@ void GameMessages::HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity* //Pets: -void GameMessages::SendNotifyPetTamingMinigame(LWOOBJID objectId, LWOOBJID petId, LWOOBJID playerTamingId, bool bForceTeleport, ePetTamingNotifyType notifyType, NiPoint3 petsDestPos, NiPoint3 telePos, NiQuaternion teleRot, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::NOTIFY_PET_TAMING_MINIGAME); - - bitStream.Write(petId); - bitStream.Write(playerTamingId); - bitStream.Write(bForceTeleport); - bitStream.Write(notifyType); - bitStream.Write(petsDestPos); - bitStream.Write(telePos); - - const bool hasDefault = teleRot != QuatUtils::IDENTITY; - bitStream.Write(hasDefault); - if (hasDefault) bitStream.Write(teleRot); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendNotifyTamingModelLoadedOnServer(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::NOTIFY_TAMING_MODEL_LOADED_ON_SERVER); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendNotifyPetTamingPuzzleSelected(LWOOBJID objectId, const std::vector& bricks, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::NOTIFY_TAMING_PUZZLE_SELECTED); - - bitStream.Write(bricks.size()); - for (const auto& brick : bricks) { - bitStream.Write(brick.designerID); - bitStream.Write(brick.materialID); - } - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendPetTamingTryBuildResult(LWOOBJID objectId, bool bSuccess, int32_t iNumCorrect, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::PET_TAMING_TRY_BUILD_RESULT); - - bitStream.Write(bSuccess); - bitStream.Write(iNumCorrect != 0); - if (iNumCorrect != 0) bitStream.Write(iNumCorrect); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendPetResponse(LWOOBJID objectId, LWOOBJID objIDPet, int32_t iPetCommandType, int32_t iResponse, int32_t iTypeID, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::PET_RESPONSE); - - bitStream.Write(objIDPet); - bitStream.Write(iPetCommandType); - bitStream.Write(iResponse); - bitStream.Write(iTypeID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendAddPetToPlayer(LWOOBJID objectId, int32_t iElementalType, std::u16string name, LWOOBJID petDBID, LOT petLOT, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ADD_PET_TO_PLAYER); - - bitStream.Write(iElementalType); - bitStream.Write(name.size()); - for (const auto character : name) { - bitStream.Write(character); - } - - bitStream.Write(petDBID); - bitStream.Write(petLOT); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendRegisterPetID(LWOOBJID objectId, LWOOBJID objID, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::REGISTER_PET_ID); - - bitStream.Write(objID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendRegisterPetDBID(LWOOBJID objectId, LWOOBJID petDBID, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::REGISTER_PET_DBID); - - bitStream.Write(petDBID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - void GameMessages::SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, eUnequippableActiveType iType, LWOOBJID itemID, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -3334,33 +3207,6 @@ void GameMessages::SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive SEND_PACKET; } -void GameMessages::SendClientExitTamingMinigame(LWOOBJID objectId, bool bVoluntaryExit, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::CLIENT_EXIT_TAMING_MINIGAME); - - bitStream.Write(bVoluntaryExit); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendShowPetActionButton(const LWOOBJID objectId, const ePetAbilityType petAbility, const bool bShow, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::SHOW_PET_ACTION_BUTTON); - - bitStream.Write(petAbility); - bitStream.Write(bShow); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - void GameMessages::SendPlayEmote(LWOOBJID objectId, int32_t emoteID, LWOOBJID target, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -3390,213 +3236,6 @@ void GameMessages::SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeI SEND_PACKET_BROADCAST; } -void GameMessages::SendBouncerActiveStatus(LWOOBJID objectId, bool bActive, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::BOUNCER_ACTIVE_STATUS); - - bitStream.Write(bActive); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendSetPetName(LWOOBJID objectId, std::u16string name, LWOOBJID petDBID, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::SET_PET_NAME); - - bitStream.Write(name.size()); - for (const auto character : name) { - bitStream.Write(character); - } - - bitStream.Write(petDBID != LWOOBJID_EMPTY); - if (petDBID != LWOOBJID_EMPTY) bitStream.Write(petDBID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendSetPetNameModerated(LWOOBJID objectId, LWOOBJID petDBID, int32_t nModerationStatus, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::SET_PET_NAME_MODERATED); - - bitStream.Write(petDBID != LWOOBJID_EMPTY); - if (petDBID != LWOOBJID_EMPTY) bitStream.Write(petDBID); - - bitStream.Write(nModerationStatus); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendPetNameChanged(LWOOBJID objectId, int32_t moderationStatus, std::u16string name, std::u16string ownerName, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::PET_NAME_CHANGED); - - bitStream.Write(moderationStatus); - - bitStream.Write(name.size()); - for (const auto character : name) { - bitStream.Write(character); - } - - bitStream.Write(ownerName.size()); - for (const auto character : ownerName) { - bitStream.Write(character); - } - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::HandleClientExitTamingMinigame(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - bool bVoluntaryExit = inStream.ReadBit(); - - auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID()); - - if (petComponent == nullptr) { - return; - } - - petComponent->ClientExitTamingMinigame(bVoluntaryExit); -} - -void GameMessages::HandleStartServerPetMinigameTimer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID()); - - if (petComponent == nullptr) { - return; - } - - petComponent->StartTimer(); -} - -void GameMessages::HandlePetTamingTryBuild(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - uint32_t brickCount; - std::vector bricks; - bool clientFailed; - - inStream.Read(brickCount); - - if (brickCount > MAX_MESSAGE_LENGTH) return; // Prevent DoS via unbounded brick count - - bricks.reserve(brickCount); - - for (uint32_t i = 0; i < brickCount; i++) { - Brick brick; - - inStream.Read(brick); - - bricks.push_back(brick); - } - - clientFailed = inStream.ReadBit(); - - auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID()); - - if (petComponent == nullptr) { - return; - } - - petComponent->TryBuild(bricks.size(), clientFailed); -} - -void GameMessages::HandleNotifyTamingBuildSuccess(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - NiPoint3 position; - - inStream.Read(position); - - auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID()); - - if (petComponent == nullptr) { - return; - } - - petComponent->NotifyTamingBuildSuccess(position); -} - -void GameMessages::HandleRequestSetPetName(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - uint32_t nameLength; - std::u16string name; - - inStream.Read(nameLength); - - if (nameLength > MAX_MESSAGE_LENGTH) return; - for (size_t i = 0; i < nameLength; i++) { - char16_t character; - inStream.Read(character); - name.push_back(character); - } - - auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID()); - - if (petComponent == nullptr) { - petComponent = PetComponent::GetActivePet(entity->GetObjectID()); - - if (petComponent == nullptr) { - return; - } - } - - petComponent->RequestSetPetName(name); -} - -void GameMessages::HandleCommandPet(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - NiPoint3 genericPosInfo; - LWOOBJID objIdSource; - int32_t iPetCommandType; - int32_t iTypeID; - bool overrideObey; - - inStream.Read(genericPosInfo); - inStream.Read(objIdSource); - inStream.Read(iPetCommandType); - inStream.Read(iTypeID); - overrideObey = inStream.ReadBit(); - - auto* petComponent = entity->GetComponent(); - - if (petComponent == nullptr) { - return; - } - - petComponent->Command(genericPosInfo, objIdSource, iPetCommandType, iTypeID, overrideObey); -} - -void GameMessages::HandleDespawnPet(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - bool bDeletePet; - - bDeletePet = inStream.ReadBit(); - - auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); - - if (petComponent == nullptr) { - return; - } - - if (bDeletePet) { - petComponent->Release(); - } else { - petComponent->Deactivate(); - } -} - void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { int32_t iButton; uint32_t identifierLength; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 06b88ad8d..5494df961 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -446,52 +446,10 @@ namespace GameMessages { void HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); //Pets: - void SendNotifyPetTamingMinigame(LWOOBJID objectId, LWOOBJID petId, LWOOBJID playerTamingId, bool bForceTeleport, ePetTamingNotifyType notifyType, NiPoint3 petsDestPos, NiPoint3 telePos, NiQuaternion teleRot, const SystemAddress& sysAddr); - - void SendNotifyPetTamingPuzzleSelected(LWOOBJID objectId, const std::vector& bricks, const SystemAddress& sysAddr); - - void SendNotifyTamingModelLoadedOnServer(LWOOBJID objectId, const SystemAddress& sysAddr); - - void SendPetTamingTryBuildResult(LWOOBJID objectId, bool bSuccess, int32_t iNumCorrect, const SystemAddress& sysAddr); - - void SendPetResponse(LWOOBJID objectId, LWOOBJID objIDPet, int32_t iPetCommandType, int32_t iResponse, int32_t iTypeID, const SystemAddress& sysAddr); - - void SendAddPetToPlayer(LWOOBJID objectId, int32_t iElementalType, std::u16string name, LWOOBJID petDBID, LOT petLOT, const SystemAddress& sysAddr); - - void SendRegisterPetID(LWOOBJID objectId, LWOOBJID objID, const SystemAddress& sysAddr); - - void SendRegisterPetDBID(LWOOBJID objectId, LWOOBJID petDBID, const SystemAddress& sysAddr); - void SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, eUnequippableActiveType iType, LWOOBJID itemID, const SystemAddress& sysAddr); - void SendClientExitTamingMinigame(LWOOBJID objectId, bool bVoluntaryExit, const SystemAddress& sysAddr); - - void SendShowPetActionButton(const LWOOBJID objectId, const ePetAbilityType petAbility, const bool bShow, const SystemAddress& sysAddr); - void SendPlayEmote(LWOOBJID objectId, int32_t emoteID, LWOOBJID target, const SystemAddress& sysAddr); - void SendBouncerActiveStatus(LWOOBJID objectId, bool bActive, const SystemAddress& sysAddr); - - void SendSetPetName(LWOOBJID objectId, std::u16string name, LWOOBJID petDBID, const SystemAddress& sysAddr); - - void SendSetPetNameModerated(LWOOBJID objectId, LWOOBJID petDBID, int32_t nModerationStatus, const SystemAddress& sysAddr); - - void SendPetNameChanged(LWOOBJID objectId, int32_t moderationStatus, std::u16string name, std::u16string ownerName, const SystemAddress& sysAddr); - - void HandleClientExitTamingMinigame(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleStartServerPetMinigameTimer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandlePetTamingTryBuild(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleNotifyTamingBuildSuccess(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleRequestSetPetName(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleCommandPet(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleDespawnPet(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - void HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); diff --git a/dGame/dGameMessages/PetMessages.cpp b/dGame/dGameMessages/PetMessages.cpp new file mode 100644 index 000000000..082a19677 --- /dev/null +++ b/dGame/dGameMessages/PetMessages.cpp @@ -0,0 +1,292 @@ +#include "PetMessages.h" + +#include "BitStreamUtils.h" +#include "Entity.h" +#include "PetComponent.h" + +namespace { + bool ReadBricks(RakNet::BitStream& bitStream, std::vector& bricks) { + uint32_t count{}; + VALIDATE_READ(bitStream.Read(count)); + if (count > MAX_MESSAGE_LENGTH) return false; // Prevent DoS via unbounded brick count + if (static_cast(count) * 64 > bitStream.GetNumberOfUnreadBits()) return false; + bricks.resize(count); + for (auto& brick : bricks) { + VALIDATE_READ(bitStream.Read(brick.designerID)); + VALIDATE_READ(bitStream.Read(brick.materialID)); + } + return true; + } + + void WriteBricks(RakNet::BitStream& bitStream, const std::vector& bricks) { + bitStream.Write(bricks.size()); + for (const auto& brick : bricks) { + bitStream.Write(brick.designerID); + bitStream.Write(brick.materialID); + } + } +} + +namespace GameMessages { + void NotifyPetTamingMinigame::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(PetID); + bitStream.Write(PlayerTamingID); + bitStream.Write(bForceTeleport); + bitStream.Write(notifyType); + bitStream.Write(petsDestPos); + bitStream.Write(telePos); + BitStreamUtils::WriteOptional(bitStream, teleRot, QuatUtils::IDENTITY); + } + + bool NotifyPetTamingMinigame::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(PetID)); + VALIDATE_READ(bitStream.Read(PlayerTamingID)); + VALIDATE_READ(bitStream.Read(bForceTeleport)); + VALIDATE_READ(bitStream.Read(notifyType)); + VALIDATE_READ(bitStream.Read(petsDestPos)); + VALIDATE_READ(bitStream.Read(telePos)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, teleRot, QuatUtils::IDENTITY)); + return true; + } + + void ClientExitTamingMinigame::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bVoluntaryExit); + } + + bool ClientExitTamingMinigame::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bVoluntaryExit)); + return true; + } + + void ClientExitTamingMinigame::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* petComponent = PetComponent::GetTamingPet(entity.GetObjectID()); + if (petComponent == nullptr) return; + petComponent->ClientExitTamingMinigame(bVoluntaryExit); + } + + void StartServerPetMinigameTimer::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* petComponent = PetComponent::GetTamingPet(entity.GetObjectID()); + if (petComponent == nullptr) return; + petComponent->StartTimer(); + } + + void NotifyPetTamingPuzzleSelected::Serialize(RakNet::BitStream& bitStream) const { + WriteBricks(bitStream, bricks); + } + + bool NotifyPetTamingPuzzleSelected::Deserialize(RakNet::BitStream& bitStream) { + return ReadBricks(bitStream, bricks); + } + + void PetTamingTryBuild::Serialize(RakNet::BitStream& bitStream) const { + WriteBricks(bitStream, bricks); + bitStream.Write(clientFailed); + } + + bool PetTamingTryBuild::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(ReadBricks(bitStream, bricks)); + VALIDATE_READ(bitStream.Read(clientFailed)); + return true; + } + + void PetTamingTryBuild::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* petComponent = PetComponent::GetTamingPet(entity.GetObjectID()); + if (petComponent == nullptr) return; + petComponent->TryBuild(bricks.size(), clientFailed); + } + + void PetTamingTryBuildResult::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bSuccess); + BitStreamUtils::WriteOptional(bitStream, iNumCorrect, 0); + } + + bool PetTamingTryBuildResult::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bSuccess)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iNumCorrect, 0)); + return true; + } + + void NotifyTamingBuildSuccess::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(buildPosition); + } + + bool NotifyTamingBuildSuccess::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(buildPosition)); + return true; + } + + void NotifyTamingBuildSuccess::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* petComponent = PetComponent::GetTamingPet(entity.GetObjectID()); + if (petComponent == nullptr) return; + petComponent->NotifyTamingBuildSuccess(buildPosition); + } + + void PetResponse::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(ObjIDPet); + bitStream.Write(iPetCommandType); + bitStream.Write(iResponse); + bitStream.Write(iTypeID); + } + + bool PetResponse::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(ObjIDPet)); + VALIDATE_READ(bitStream.Read(iPetCommandType)); + VALIDATE_READ(bitStream.Read(iResponse)); + VALIDATE_READ(bitStream.Read(iTypeID)); + return true; + } + + void AddPetToPlayer::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(iElementalType); + BitStreamUtils::WriteLengthPrefixed(bitStream, name); + bitStream.Write(petDBID); + bitStream.Write(petLOT); + } + + bool AddPetToPlayer::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(iElementalType)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, name)); + VALIDATE_READ(bitStream.Read(petDBID)); + VALIDATE_READ(bitStream.Read(petLOT)); + return true; + } + + void RegisterPetID::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(objID); + } + + bool RegisterPetID::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(objID)); + return true; + } + + void RegisterPetDBID::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(petDBID); + } + + bool RegisterPetDBID::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(petDBID)); + return true; + } + + void ShowPetActionButton::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(ButtonLabel); + bitStream.Write(bShow); + } + + bool ShowPetActionButton::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(ButtonLabel)); + VALIDATE_READ(bitStream.Read(bShow)); + return true; + } + + void BouncerActiveStatus::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bActive); + } + + bool BouncerActiveStatus::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bActive)); + return true; + } + + void RequestSetPetName::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteLengthPrefixed(bitStream, name); + } + + bool RequestSetPetName::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, name)); + return true; + } + + void RequestSetPetName::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* petComponent = PetComponent::GetTamingPet(entity.GetObjectID()); + + if (petComponent == nullptr) { + petComponent = PetComponent::GetActivePet(entity.GetObjectID()); + + if (petComponent == nullptr) { + return; + } + } + + petComponent->RequestSetPetName(name); + } + + void SetPetName::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteLengthPrefixed(bitStream, name); + BitStreamUtils::WriteOptional(bitStream, petDBID, LWOOBJID_EMPTY); + } + + bool SetPetName::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, name)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, petDBID, LWOOBJID_EMPTY)); + return true; + } + + void SetPetNameModerated::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, PetDBID, LWOOBJID_EMPTY); + bitStream.Write(nModerationStatus); + } + + bool SetPetNameModerated::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, PetDBID, LWOOBJID_EMPTY)); + VALIDATE_READ(bitStream.Read(nModerationStatus)); + return true; + } + + void PetNameChanged::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(moderationStatus); + BitStreamUtils::WriteLengthPrefixed(bitStream, name); + BitStreamUtils::WriteLengthPrefixed(bitStream, ownerName); + } + + bool PetNameChanged::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(moderationStatus)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, name)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, ownerName)); + return true; + } + + void CommandPet::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(GenericPosInfo); + bitStream.Write(ObjIDSource); + bitStream.Write(iPetCommandType); + bitStream.Write(iTypeID); + bitStream.Write(overrideObey); + } + + bool CommandPet::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(GenericPosInfo)); + VALIDATE_READ(bitStream.Read(ObjIDSource)); + VALIDATE_READ(bitStream.Read(iPetCommandType)); + VALIDATE_READ(bitStream.Read(iTypeID)); + VALIDATE_READ(bitStream.Read(overrideObey)); + return true; + } + + void CommandPet::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* petComponent = entity.GetComponent(); + if (petComponent == nullptr) return; + petComponent->Command(GenericPosInfo, ObjIDSource, iPetCommandType, iTypeID, overrideObey); + } + + void DespawnPet::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bDeletePet); + } + + bool DespawnPet::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bDeletePet)); + return true; + } + + void DespawnPet::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* petComponent = PetComponent::GetActivePet(entity.GetObjectID()); + if (petComponent == nullptr) return; + + if (bDeletePet) { + petComponent->Release(); + } else { + petComponent->Deactivate(); + } + } +} diff --git a/dGame/dGameMessages/PetMessages.h b/dGame/dGameMessages/PetMessages.h new file mode 100644 index 000000000..961519180 --- /dev/null +++ b/dGame/dGameMessages/PetMessages.h @@ -0,0 +1,222 @@ +#ifndef PETMESSAGES_H +#define PETMESSAGES_H + +#include "GameMessages.h" + +#include "Brick.h" +#include "ePetAbilityType.h" +#include "ePetTamingNotifyType.h" +#include "NiQuaternion.h" + +#include +#include + +// Game messages for pets: the taming minigame, naming, commands and pet bouncers. +// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order. +// Messages received from a client are handled by the player's taming or active pet (PetComponent). +namespace GameMessages { + // Server -> client. + struct NotifyPetTamingMinigame : public NetGameMsg { + NotifyPetTamingMinigame() : NetGameMsg(MessageType::Game::NOTIFY_PET_TAMING_MINIGAME) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID PetID{}; + LWOOBJID PlayerTamingID{}; + bool bForceTeleport{}; + ePetTamingNotifyType notifyType{}; + NiPoint3 petsDestPos{}; + NiPoint3 telePos{}; + NiQuaternion teleRot{ QuatUtils::IDENTITY }; // optional + }; + + // Both directions. The server never sends it; the client sends it when the player leaves the minigame. + struct ClientExitTamingMinigame : public NetGameMsg { + ClientExitTamingMinigame() : NetGameMsg(MessageType::Game::CLIENT_EXIT_TAMING_MINIGAME) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bVoluntaryExit{ true }; + }; + + // Client -> server. No payload. + struct StartServerPetMinigameTimer : public NetGameMsg { + StartServerPetMinigameTimer() : NetGameMsg(MessageType::Game::START_SERVER_PET_MINIGAME_TIMER) {} + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; + + // Server -> client. No payload. + struct NotifyTamingModelLoadedOnServer : public NetGameMsg { + NotifyTamingModelLoadedOnServer() : NetGameMsg(MessageType::Game::NOTIFY_TAMING_MODEL_LOADED_ON_SERVER) {} + }; + + // Server -> client. The client's Deserialize (0x00e3a7c0) reads one more u32 after the bricks that its own + // Serialize (0x00db6880) never writes; DLU writes what Serialize writes. + struct NotifyPetTamingPuzzleSelected : public NetGameMsg { + NotifyPetTamingPuzzleSelected() : NetGameMsg(MessageType::Game::NOTIFY_TAMING_PUZZLE_SELECTED) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + std::vector bricks{}; // u32 count, then designerID and materialID per brick + }; + + // Client -> server. + struct PetTamingTryBuild : public NetGameMsg { + PetTamingTryBuild() : NetGameMsg(MessageType::Game::PET_TAMING_TRY_BUILD) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + std::vector bricks{}; // u32 count, then designerID and materialID per brick + bool clientFailed{}; + }; + + // Server -> client. + struct PetTamingTryBuildResult : public NetGameMsg { + PetTamingTryBuildResult() : NetGameMsg(MessageType::Game::PET_TAMING_TRY_BUILD_RESULT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bSuccess{ true }; + int32_t iNumCorrect{}; // optional + }; + + // Client -> server. + struct NotifyTamingBuildSuccess : public NetGameMsg { + NotifyTamingBuildSuccess() : NetGameMsg(MessageType::Game::NOTIFY_TAMING_BUILD_SUCCESS) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + NiPoint3 buildPosition{}; + }; + + // Server -> client. + struct PetResponse : public NetGameMsg { + PetResponse() : NetGameMsg(MessageType::Game::PET_RESPONSE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID ObjIDPet{}; + int32_t iPetCommandType{}; + int32_t iResponse{}; + int32_t iTypeID{}; + }; + + // Server -> client. + struct AddPetToPlayer : public NetGameMsg { + AddPetToPlayer() : NetGameMsg(MessageType::Game::ADD_PET_TO_PLAYER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + int32_t iElementalType{}; + std::u16string name{}; + LWOOBJID petDBID{}; + LOT petLOT{}; + }; + + // Server -> client. + struct RegisterPetID : public NetGameMsg { + RegisterPetID() : NetGameMsg(MessageType::Game::REGISTER_PET_ID) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID objID{}; + }; + + // Server -> client. + struct RegisterPetDBID : public NetGameMsg { + RegisterPetDBID() : NetGameMsg(MessageType::Game::REGISTER_PET_DBID) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID petDBID{}; + }; + + // Server -> client. + struct ShowPetActionButton : public NetGameMsg { + ShowPetActionButton() : NetGameMsg(MessageType::Game::SHOW_PET_ACTION_BUTTON) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + ePetAbilityType ButtonLabel{}; + bool bShow{}; + }; + + // Server -> client. Turns a bouncer (usually one a pet uncovered) on or off. + struct BouncerActiveStatus : public NetGameMsg { + BouncerActiveStatus() : NetGameMsg(MessageType::Game::BOUNCER_ACTIVE_STATUS) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bActive{}; + }; + + // Client -> server. + struct RequestSetPetName : public NetGameMsg { + RequestSetPetName() : NetGameMsg(MessageType::Game::REQUEST_SET_PET_NAME) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + std::u16string name{}; + }; + + // Server -> client. + struct SetPetName : public NetGameMsg { + SetPetName() : NetGameMsg(MessageType::Game::SET_PET_NAME) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + std::u16string name{}; + LWOOBJID petDBID{ LWOOBJID_EMPTY }; // optional + }; + + // Server -> client. + struct SetPetNameModerated : public NetGameMsg { + SetPetNameModerated() : NetGameMsg(MessageType::Game::SET_PET_NAME_MODERATED) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID PetDBID{ LWOOBJID_EMPTY }; // optional + int32_t nModerationStatus{}; + }; + + // Server -> client. + struct PetNameChanged : public NetGameMsg { + PetNameChanged() : NetGameMsg(MessageType::Game::PET_NAME_CHANGED) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + int32_t moderationStatus{}; + std::u16string name{}; + std::u16string ownerName{}; + }; + + // Client -> server. + struct CommandPet : public NetGameMsg { + CommandPet() : NetGameMsg(MessageType::Game::COMMAND_PET) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + NiPoint3 GenericPosInfo{}; + LWOOBJID ObjIDSource{}; + int32_t iPetCommandType{}; + int32_t iTypeID{}; + bool overrideObey{}; + }; + + // Client -> server. + struct DespawnPet : public NetGameMsg { + DespawnPet() : NetGameMsg(MessageType::Game::DESPAWN_PET) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bDeletePet{}; + }; +}; + +#endif // PETMESSAGES_H diff --git a/dScripts/02_server/Map/PR/HydrantBroken.cpp b/dScripts/02_server/Map/PR/HydrantBroken.cpp index 2694b4250..c1702cc26 100644 --- a/dScripts/02_server/Map/PR/HydrantBroken.cpp +++ b/dScripts/02_server/Map/PR/HydrantBroken.cpp @@ -1,4 +1,5 @@ #include "HydrantBroken.h" +#include "PetMessages.h" #include "EntityManager.h" #include "GameMessages.h" @@ -12,7 +13,10 @@ void HydrantBroken::OnStartup(Entity* self) { for (auto* bouncer : bouncers) { self->SetVar(u"bouncer", bouncer->GetObjectID()); - GameMessages::SendBouncerActiveStatus(bouncer->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::BouncerActiveStatus msg; + msg.target = bouncer->GetObjectID(); + msg.bActive = true; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendNotifyObject(bouncer->GetObjectID(), self->GetObjectID(), u"enableCollision", UNASSIGNED_SYSTEM_ADDRESS); } @@ -25,7 +29,10 @@ void HydrantBroken::OnTimerDone(Entity* self, std::string timerName) { auto* bouncer = Game::entityManager->GetEntity(self->GetVar(u"bouncer")); if (bouncer != nullptr) { - GameMessages::SendBouncerActiveStatus(bouncer->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::BouncerActiveStatus msg; + msg.target = bouncer->GetObjectID(); + msg.bActive = false; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendNotifyObject(bouncer->GetObjectID(), self->GetObjectID(), u"disableCollision", UNASSIGNED_SYSTEM_ADDRESS); } diff --git a/dScripts/02_server/Map/njhub/CatapultBaseServer.cpp b/dScripts/02_server/Map/njhub/CatapultBaseServer.cpp index 8bed629c8..45b6f99af 100644 --- a/dScripts/02_server/Map/njhub/CatapultBaseServer.cpp +++ b/dScripts/02_server/Map/njhub/CatapultBaseServer.cpp @@ -1,4 +1,5 @@ #include "CatapultBaseServer.h" +#include "PetMessages.h" #include "GameMessages.h" #include "EntityManager.h" #include "Entity.h" @@ -12,7 +13,10 @@ void CatapultBaseServer::OnNotifyObject(Entity* self, Entity* sender, const std: // set the bouncer so we can use it later self->SetVar(u"Bouncer", sender->GetObjectID()); - GameMessages::SendBouncerActiveStatus(sender->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::BouncerActiveStatus msg; + msg.target = sender->GetObjectID(); + msg.bActive = true; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); } } diff --git a/dScripts/ai/NS/WH/RockHydrantBroken.cpp b/dScripts/ai/NS/WH/RockHydrantBroken.cpp index 243761d30..bb4484258 100644 --- a/dScripts/ai/NS/WH/RockHydrantBroken.cpp +++ b/dScripts/ai/NS/WH/RockHydrantBroken.cpp @@ -1,4 +1,5 @@ #include "RockHydrantBroken.h" +#include "PetMessages.h" #include "EntityManager.h" #include "GameMessages.h" @@ -13,7 +14,10 @@ void RockHydrantBroken::OnStartup(Entity* self) { self->SetVar(u"bouncer", bouncer->GetObjectID()); - GameMessages::SendBouncerActiveStatus(bouncer->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::BouncerActiveStatus msg; + msg.target = bouncer->GetObjectID(); + msg.bActive = true; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendNotifyObject(bouncer->GetObjectID(), self->GetObjectID(), u"enableCollision", UNASSIGNED_SYSTEM_ADDRESS); } @@ -26,7 +30,10 @@ void RockHydrantBroken::OnTimerDone(Entity* self, std::string timerName) { auto* bouncer = Game::entityManager->GetEntity(self->GetVar(u"bouncer")); if (bouncer != nullptr) { - GameMessages::SendBouncerActiveStatus(bouncer->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::BouncerActiveStatus msg; + msg.target = bouncer->GetObjectID(); + msg.bActive = false; + msg.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendNotifyObject(bouncer->GetObjectID(), self->GetObjectID(), u"disableCollision", UNASSIGNED_SYSTEM_ADDRESS); } diff --git a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt index 1390c7360..6d0ebfca9 100644 --- a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt +++ b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt @@ -3,6 +3,7 @@ SET(DGAMEMESSAGES_TESTS "GameMessageTests.cpp" "GameMsgSplitTests.cpp" "MissionMessagesTests.cpp" + "PetMessagesTests.cpp" "RacingMessagesTests.cpp" "UnSmashTests.cpp" "LegacyGameMessageTests.cpp" diff --git a/tests/dGameTests/dGameMessagesTests/Legacy/PetMessagesLegacy.h b/tests/dGameTests/dGameMessagesTests/Legacy/PetMessagesLegacy.h new file mode 100644 index 000000000..adf082d1e --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/Legacy/PetMessagesLegacy.h @@ -0,0 +1,333 @@ +#ifndef PETMESSAGESLEGACY_H +#define PETMESSAGESLEGACY_H + +// FROZEN ORACLE - DO NOT EDIT. +// Verbatim copies of the hand written GameMessages functions that PetMessages.h replaced +// (dGame/dGameMessages/GameMessages.cpp, branched from origin/main 129199e4). Only the namespace changed. +// The Read* functions are the read sequences of the replaced GameMessages::Handle* functions. + +#include "BitStreamUtils.h" +#include "Brick.h" +#include "dCommonVars.h" +#include "dServer.h" +#include "ePetAbilityType.h" +#include "ePetTamingNotifyType.h" +#include "Game.h" +#include "MessageType/Client.h" +#include "MessageType/Game.h" +#include "NiPoint3.h" +#include "NiQuaternion.h" +#include "ServiceType.h" + +#include +#include + +namespace LegacyGameMessages { + inline void SendNotifyPetTamingMinigame(LWOOBJID objectId, LWOOBJID petId, LWOOBJID playerTamingId, bool bForceTeleport, ePetTamingNotifyType notifyType, NiPoint3 petsDestPos, NiPoint3 telePos, NiQuaternion teleRot, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::NOTIFY_PET_TAMING_MINIGAME); + + bitStream.Write(petId); + bitStream.Write(playerTamingId); + bitStream.Write(bForceTeleport); + bitStream.Write(notifyType); + bitStream.Write(petsDestPos); + bitStream.Write(telePos); + + const bool hasDefault = teleRot != QuatUtils::IDENTITY; + bitStream.Write(hasDefault); + if (hasDefault) bitStream.Write(teleRot); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendNotifyTamingModelLoadedOnServer(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::NOTIFY_TAMING_MODEL_LOADED_ON_SERVER); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendNotifyPetTamingPuzzleSelected(LWOOBJID objectId, const std::vector& bricks, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::NOTIFY_TAMING_PUZZLE_SELECTED); + + bitStream.Write(bricks.size()); + for (const auto& brick : bricks) { + bitStream.Write(brick.designerID); + bitStream.Write(brick.materialID); + } + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendPetTamingTryBuildResult(LWOOBJID objectId, bool bSuccess, int32_t iNumCorrect, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::PET_TAMING_TRY_BUILD_RESULT); + + bitStream.Write(bSuccess); + bitStream.Write(iNumCorrect != 0); + if (iNumCorrect != 0) bitStream.Write(iNumCorrect); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendPetResponse(LWOOBJID objectId, LWOOBJID objIDPet, int32_t iPetCommandType, int32_t iResponse, int32_t iTypeID, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::PET_RESPONSE); + + bitStream.Write(objIDPet); + bitStream.Write(iPetCommandType); + bitStream.Write(iResponse); + bitStream.Write(iTypeID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendAddPetToPlayer(LWOOBJID objectId, int32_t iElementalType, std::u16string name, LWOOBJID petDBID, LOT petLOT, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::ADD_PET_TO_PLAYER); + + bitStream.Write(iElementalType); + bitStream.Write(name.size()); + for (const auto character : name) { + bitStream.Write(character); + } + + bitStream.Write(petDBID); + bitStream.Write(petLOT); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRegisterPetID(LWOOBJID objectId, LWOOBJID objID, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::REGISTER_PET_ID); + + bitStream.Write(objID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRegisterPetDBID(LWOOBJID objectId, LWOOBJID petDBID, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::REGISTER_PET_DBID); + + bitStream.Write(petDBID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendClientExitTamingMinigame(LWOOBJID objectId, bool bVoluntaryExit, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::CLIENT_EXIT_TAMING_MINIGAME); + + bitStream.Write(bVoluntaryExit); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendShowPetActionButton(const LWOOBJID objectId, const ePetAbilityType petAbility, const bool bShow, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::SHOW_PET_ACTION_BUTTON); + + bitStream.Write(petAbility); + bitStream.Write(bShow); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendBouncerActiveStatus(LWOOBJID objectId, bool bActive, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::BOUNCER_ACTIVE_STATUS); + + bitStream.Write(bActive); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendSetPetName(LWOOBJID objectId, std::u16string name, LWOOBJID petDBID, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::SET_PET_NAME); + + bitStream.Write(name.size()); + for (const auto character : name) { + bitStream.Write(character); + } + + bitStream.Write(petDBID != LWOOBJID_EMPTY); + if (petDBID != LWOOBJID_EMPTY) bitStream.Write(petDBID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendSetPetNameModerated(LWOOBJID objectId, LWOOBJID petDBID, int32_t nModerationStatus, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::SET_PET_NAME_MODERATED); + + bitStream.Write(petDBID != LWOOBJID_EMPTY); + if (petDBID != LWOOBJID_EMPTY) bitStream.Write(petDBID); + + bitStream.Write(nModerationStatus); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendPetNameChanged(LWOOBJID objectId, int32_t moderationStatus, std::u16string name, std::u16string ownerName, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::PET_NAME_CHANGED); + + bitStream.Write(moderationStatus); + + bitStream.Write(name.size()); + for (const auto character : name) { + bitStream.Write(character); + } + + bitStream.Write(ownerName.size()); + for (const auto character : ownerName) { + bitStream.Write(character); + } + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + // GameMessages::HandleClientExitTamingMinigame + inline bool ReadClientExitTamingMinigame(RakNet::BitStream& inStream) { + bool bVoluntaryExit = inStream.ReadBit(); + return bVoluntaryExit; + } + + // GameMessages::HandlePetTamingTryBuild + struct LegacyPetTamingTryBuild { std::vector bricks; bool clientFailed{}; bool rejected{}; }; + inline LegacyPetTamingTryBuild ReadPetTamingTryBuild(RakNet::BitStream& inStream) { + uint32_t brickCount; + std::vector bricks; + bool clientFailed; + + inStream.Read(brickCount); + + if (brickCount > MAX_MESSAGE_LENGTH) return { {}, false, true }; // Prevent DoS via unbounded brick count + + bricks.reserve(brickCount); + + for (uint32_t i = 0; i < brickCount; i++) { + Brick brick; + + inStream.Read(brick); + + bricks.push_back(brick); + } + + clientFailed = inStream.ReadBit(); + return { bricks, clientFailed, false }; + } + + // GameMessages::HandleNotifyTamingBuildSuccess + inline NiPoint3 ReadNotifyTamingBuildSuccess(RakNet::BitStream& inStream) { + NiPoint3 position; + + inStream.Read(position); + return position; + } + + // GameMessages::HandleRequestSetPetName + struct LegacyRequestSetPetName { std::u16string name; bool rejected{}; }; + inline LegacyRequestSetPetName ReadRequestSetPetName(RakNet::BitStream& inStream) { + uint32_t nameLength; + std::u16string name; + + inStream.Read(nameLength); + + if (nameLength > MAX_MESSAGE_LENGTH) return { {}, true }; + for (size_t i = 0; i < nameLength; i++) { + char16_t character; + inStream.Read(character); + name.push_back(character); + } + return { name, false }; + } + + // GameMessages::HandleCommandPet + struct LegacyCommandPet { NiPoint3 genericPosInfo; LWOOBJID objIdSource{}; int32_t iPetCommandType{}; int32_t iTypeID{}; bool overrideObey{}; }; + inline LegacyCommandPet ReadCommandPet(RakNet::BitStream& inStream) { + NiPoint3 genericPosInfo; + LWOOBJID objIdSource; + int32_t iPetCommandType; + int32_t iTypeID; + bool overrideObey; + + inStream.Read(genericPosInfo); + inStream.Read(objIdSource); + inStream.Read(iPetCommandType); + inStream.Read(iTypeID); + overrideObey = inStream.ReadBit(); + return { genericPosInfo, objIdSource, iPetCommandType, iTypeID, overrideObey }; + } + + // GameMessages::HandleDespawnPet + inline bool ReadDespawnPet(RakNet::BitStream& inStream) { + bool bDeletePet; + + bDeletePet = inStream.ReadBit(); + return bDeletePet; + } +} + +#endif // PETMESSAGESLEGACY_H diff --git a/tests/dGameTests/dGameMessagesTests/PetMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/PetMessagesTests.cpp new file mode 100644 index 000000000..57f9410fa --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/PetMessagesTests.cpp @@ -0,0 +1,351 @@ +#include "PetMessages.h" +#include "GameDependencies.h" +#include "GameMessageTestUtils.h" +#include "Legacy/PetMessagesLegacy.h" + +#include +#include +#include +#include + +#include + +using namespace GameMessageTestUtils; + +namespace { + const std::vector g_Ints = { 0, 1, -1, 1727, std::numeric_limits::max(), std::numeric_limits::min() }; + const std::vector g_Strings = { u"", u"Rex", u"Pét ☃ \U0001F436", std::u16string(300, u'x') }; + const std::vector g_Points = { NiPoint3Constant::ZERO, NiPoint3(1.5f, -2.0f, 1e6f) }; + const std::vector g_Rotations = { QuatUtils::IDENTITY, NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f), NiQuaternion(0.5f, 0.5f, -0.5f, 0.5f) }; + const std::vector> g_BrickSets = { {}, { { 3001, 21 } }, { { 1, 2 }, { 0xFFFFFFFF, 0 }, { 3040, 194 } } }; + + PacketBytes Payload(const GameMessages::NetGameMsg& msg) { + RakNet::BitStream bitStream; + msg.Serialize(bitStream); + return FromBitStream(bitStream); + } + + // Serializes msg, then reads it with the legacy read sequence; both must consume exactly the same bits. + template + Result ReadWithLegacy(const GameMessages::NetGameMsg& msg, const std::function& read) { + RakNet::BitStream wire; + msg.Serialize(wire); + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + auto result = read(legacyStream); + EXPECT_EQ(legacyStream.GetReadOffset(), wire.GetNumberOfBitsUsed()); + return result; + } + + bool SameBricks(const std::vector& a, const std::vector& b) { + if (a.size() != b.size()) return false; + for (size_t i = 0; i < a.size(); i++) { + if (a[i].designerID != b[i].designerID || a[i].materialID != b[i].materialID) return false; + } + return true; + } +} + +class PetMessagesTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } +}; + +TEST_F(PetMessagesTests, NotifyPetTamingMinigameMatchesLegacy) { + for (const auto target : g_Targets) { + for (const auto id : g_Targets) { + for (const bool force : { false, true }) { + for (const auto type : { ePetTamingNotifyType::SUCCESS, ePetTamingNotifyType::BEGIN, ePetTamingNotifyType::NAMINGPET, static_cast(0xFFFFFFFF) }) { + for (const auto& point : g_Points) { + for (const auto& rotation : g_Rotations) { + GameMessages::NotifyPetTamingMinigame msg; + msg.target = target; + msg.PetID = id; + msg.PlayerTamingID = target; + msg.bForceTeleport = force; + msg.notifyType = type; + msg.petsDestPos = point; + msg.telePos = NiPoint3(point.z, point.x, point.y); + msg.teleRot = rotation; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendNotifyPetTamingMinigame(target, id, target, force, type, point, msg.telePos, rotation, a); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.PetID, id); + EXPECT_EQ(copy.notifyType, type); + EXPECT_EQ(copy.teleRot, rotation); + ExpectTruncatedFails(msg); + } + } + } + } + } + } +} + +TEST_F(PetMessagesTests, NoPayloadMessagesMatchLegacy) { + for (const auto target : g_Targets) { + GameMessages::NotifyTamingModelLoadedOnServer msg; + msg.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendNotifyTamingModelLoadedOnServer(target, a); }, msg); + EXPECT_EQ(Payload(msg).bits, 0); + } +} + +TEST_F(PetMessagesTests, BrickMessagesMatchLegacy) { + for (const auto target : g_Targets) { + for (const auto& bricks : g_BrickSets) { + GameMessages::NotifyPetTamingPuzzleSelected msg; + msg.target = target; + msg.bricks = bricks; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendNotifyPetTamingPuzzleSelected(target, bricks, a); }, msg); + EXPECT_TRUE(SameBricks(RoundTrip(msg).bricks, bricks)); + ExpectTruncatedFails(msg); + + for (const bool clientFailed : { false, true }) { + GameMessages::PetTamingTryBuild tryBuild; + tryBuild.bricks = bricks; + tryBuild.clientFailed = clientFailed; + const auto legacy = ReadWithLegacy(tryBuild, LegacyGameMessages::ReadPetTamingTryBuild); + const auto copy = RoundTrip(tryBuild); + EXPECT_FALSE(legacy.rejected); + EXPECT_TRUE(SameBricks(copy.bricks, legacy.bricks)); + EXPECT_EQ(copy.clientFailed, legacy.clientFailed); + ExpectTruncatedFails(tryBuild); + } + } + } + + // A brick count the stream cannot hold is rejected before anything is allocated. + RakNet::BitStream huge; + huge.Write(MAX_MESSAGE_LENGTH + 1); + GameMessages::PetTamingTryBuild tryBuild; + EXPECT_FALSE(tryBuild.Deserialize(huge)); + RakNet::BitStream lying; + lying.Write(1000); + lying.Write(0); + EXPECT_FALSE(tryBuild.Deserialize(lying)); +} + +TEST_F(PetMessagesTests, PetTamingTryBuildResultMatchesLegacy) { + for (const auto target : g_Targets) { + for (const bool success : { false, true }) { + for (const auto numCorrect : g_Ints) { + GameMessages::PetTamingTryBuildResult msg; + msg.target = target; + msg.bSuccess = success; + msg.iNumCorrect = numCorrect; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendPetTamingTryBuildResult(target, success, numCorrect, a); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.bSuccess, success); + EXPECT_EQ(copy.iNumCorrect, numCorrect); + } + } + } +} + +TEST_F(PetMessagesTests, PetResponseMatchesLegacy) { + for (const auto target : g_Targets) { + for (const auto value : g_Ints) { + GameMessages::PetResponse msg; + msg.target = target; + msg.ObjIDPet = target ^ 0x55; + msg.iPetCommandType = value; + msg.iResponse = -value; + msg.iTypeID = value / 2; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendPetResponse(target, msg.ObjIDPet, msg.iPetCommandType, msg.iResponse, msg.iTypeID, a); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.ObjIDPet, msg.ObjIDPet); + EXPECT_EQ(copy.iResponse, msg.iResponse); + ExpectTruncatedFails(msg); + } + } +} + +TEST_F(PetMessagesTests, AddPetToPlayerMatchesLegacy) { + for (const auto target : g_Targets) { + for (const auto& name : g_Strings) { + for (const LOT lot : { LOT_NULL, 3050, 0 }) { + GameMessages::AddPetToPlayer msg; + msg.target = target; + msg.iElementalType = lot / 7; + msg.name = name; + msg.petDBID = target; + msg.petLOT = lot; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendAddPetToPlayer(target, msg.iElementalType, name, target, lot, a); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.name, name); + EXPECT_EQ(copy.petLOT, lot); + ExpectTruncatedFails(msg); + } + } + } +} + +TEST_F(PetMessagesTests, RegisterPetMessagesMatchLegacy) { + for (const auto target : g_Targets) { + for (const auto id : g_Targets) { + GameMessages::RegisterPetID petId; + petId.target = target; + petId.objID = id; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendRegisterPetID(target, id, a); }, petId); + EXPECT_EQ(RoundTrip(petId).objID, id); + + GameMessages::RegisterPetDBID dbId; + dbId.target = target; + dbId.petDBID = id; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendRegisterPetDBID(target, id, a); }, dbId); + EXPECT_EQ(RoundTrip(dbId).petDBID, id); + } + } +} + +TEST_F(PetMessagesTests, BoolMessagesMatchLegacy) { + for (const auto target : g_Targets) { + for (const bool value : { false, true }) { + GameMessages::ClientExitTamingMinigame exit; + exit.target = target; + exit.bVoluntaryExit = value; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendClientExitTamingMinigame(target, value, a); }, exit); + EXPECT_EQ(ReadWithLegacy(exit, LegacyGameMessages::ReadClientExitTamingMinigame), value); + EXPECT_EQ(RoundTrip(exit).bVoluntaryExit, value); + ExpectTruncatedFails(exit); + + GameMessages::BouncerActiveStatus bouncer; + bouncer.target = target; + bouncer.bActive = value; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendBouncerActiveStatus(target, value, a); }, bouncer); + EXPECT_EQ(RoundTrip(bouncer).bActive, value); + + GameMessages::DespawnPet despawn; + despawn.bDeletePet = value; + EXPECT_EQ(ReadWithLegacy(despawn, LegacyGameMessages::ReadDespawnPet), value); + EXPECT_EQ(RoundTrip(despawn).bDeletePet, value); + ExpectTruncatedFails(despawn); + + for (const auto ability : { ePetAbilityType::Invalid, ePetAbilityType::DigAtPosition, static_cast(0xFFFFFFFF) }) { + GameMessages::ShowPetActionButton button; + button.target = target; + button.ButtonLabel = ability; + button.bShow = value; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendShowPetActionButton(target, ability, value, a); }, button); + const auto copy = RoundTrip(button); + EXPECT_EQ(copy.ButtonLabel, ability); + EXPECT_EQ(copy.bShow, value); + } + } + } +} + +TEST_F(PetMessagesTests, PetNameMessagesMatchLegacy) { + for (const auto target : g_Targets) { + for (const auto& name : g_Strings) { + for (const auto dbId : g_Targets) { + GameMessages::SetPetName setName; + setName.target = target; + setName.name = name; + setName.petDBID = dbId; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendSetPetName(target, name, dbId, a); }, setName); + const auto copy = RoundTrip(setName); + EXPECT_EQ(copy.name, name); + EXPECT_EQ(copy.petDBID, dbId); + ExpectTruncatedFails(setName); + } + + for (const auto status : g_Ints) { + GameMessages::PetNameChanged changed; + changed.target = target; + changed.moderationStatus = status; + changed.name = name; + changed.ownerName = name + u"'s owner"; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendPetNameChanged(target, status, name, changed.ownerName, a); }, changed); + const auto copy = RoundTrip(changed); + EXPECT_EQ(copy.name, name); + EXPECT_EQ(copy.ownerName, changed.ownerName); + ExpectTruncatedFails(changed); + } + + GameMessages::RequestSetPetName request; + request.name = name; + const auto legacy = ReadWithLegacy(request, LegacyGameMessages::ReadRequestSetPetName); + EXPECT_FALSE(legacy.rejected); + EXPECT_EQ(RoundTrip(request).name, legacy.name); + ExpectTruncatedFails(request); + } + + for (const auto dbId : g_Targets) { + for (const auto status : g_Ints) { + GameMessages::SetPetNameModerated moderated; + moderated.target = target; + moderated.PetDBID = dbId; + moderated.nModerationStatus = status; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendSetPetNameModerated(target, dbId, status, a); }, moderated); + const auto copy = RoundTrip(moderated); + EXPECT_EQ(copy.PetDBID, dbId); + EXPECT_EQ(copy.nModerationStatus, status); + } + } + } +} + +TEST_F(PetMessagesTests, InboundReadsLikeLegacy) { + for (const auto& point : g_Points) { + GameMessages::NotifyTamingBuildSuccess success; + success.buildPosition = point; + EXPECT_EQ(ReadWithLegacy(success, LegacyGameMessages::ReadNotifyTamingBuildSuccess), point); + EXPECT_EQ(RoundTrip(success).buildPosition, point); + ExpectTruncatedFails(success); + + for (const auto id : g_Targets) { + for (const auto value : g_Ints) { + for (const bool obey : { false, true }) { + GameMessages::CommandPet command; + command.GenericPosInfo = point; + command.ObjIDSource = id; + command.iPetCommandType = value; + command.iTypeID = -value; + command.overrideObey = obey; + const auto legacy = ReadWithLegacy(command, LegacyGameMessages::ReadCommandPet); + const auto copy = RoundTrip(command); + EXPECT_EQ(copy.GenericPosInfo, legacy.genericPosInfo); + EXPECT_EQ(copy.ObjIDSource, legacy.objIdSource); + EXPECT_EQ(copy.iPetCommandType, legacy.iPetCommandType); + EXPECT_EQ(copy.iTypeID, legacy.iTypeID); + EXPECT_EQ(copy.overrideObey, legacy.overrideObey); + ExpectTruncatedFails(command); + } + } + } + } +} + +// Independent of the legacy code: hand computed payloads (RakNet writes MSB first). +TEST_F(PetMessagesTests, GoldenBytes) { + GameMessages::PetTamingTryBuildResult result; + EXPECT_PACKET_EQ(FromHex("80", 2), Payload(result)); + result.bSuccess = false; + result.iNumCorrect = 3; + // 0, 1, then 03 00 00 00 shifted right by two bits + EXPECT_PACKET_EQ(FromHex("40 c0 00 00 00", 34), Payload(result)); + + GameMessages::NotifyPetTamingPuzzleSelected puzzle; + puzzle.bricks = { { 3001, 21 } }; + EXPECT_PACKET_EQ(FromHex("01 00 00 00 b9 0b 00 00 15 00 00 00"), Payload(puzzle)); + + GameMessages::SetPetName setName; + setName.name = u"Hi"; + EXPECT_PACKET_EQ(FromHex("02 00 00 00 48 00 69 00 00", 65), Payload(setName)); + setName.petDBID = 0x11; + EXPECT_PACKET_EQ(FromHex("02 00 00 00 48 00 69 00 88 80 00 00 00 00 00 00 00", 129), Payload(setName)); + + GameMessages::SetPetNameModerated moderated; + moderated.nModerationStatus = 2; + EXPECT_PACKET_EQ(FromHex("01 00 00 00 00", 33), Payload(moderated)); + + GameMessages::ShowPetActionButton button; + button.ButtonLabel = ePetAbilityType::JumpOnObject; + button.bShow = true; + EXPECT_PACKET_EQ(FromHex("02 00 00 00 80", 33), Payload(button)); + + GameMessages::DespawnPet despawn; + despawn.bDeletePet = true; + EXPECT_PACKET_EQ(FromHex("80", 1), Payload(despawn)); +}