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)); +}