From 8a8fc263e16e2d466d00c6d1bdc781942f0899a1 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 29 Sep 2026 03:31:28 -0500 Subject: [PATCH] feat(pets): write the active pet in the charxml as live did (pet@a) The client reads as its active pet's database ID with GetInt64Attribute (LWOPetControlComponent::LoadFromSaveData 0x00c18120); DLU never wrote it. Live wrote a="0" on every character (226 live charxmls, 45 with pets): the charxml is only read when the player loads, before any pet is out, and a pet summoned afterwards registers itself (RegisterPetDBID). So a is written as 0. The pet taming type p@t stays 0 as DLU already wrote it: it is an int (IntAttribute), the client sets it to 0 for every pet it is given (AddPetToPlayer in LWOPetControlComponent::HandleMessage 0x00d0fe00, which ignores the message's elemental type) and every live pet had t="0". Commented so it isn't "fixed" later. Check in game: with a tamed pet, summon it, change zones and log out and back in: the pet menu and pet naming work as before and no pet is shown as out until you summon one. Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/InventoryComponent.cpp | 7 +++++++ .../CharacterSaveFieldsTests.cpp | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 2df5d371f..661022417 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -1879,6 +1879,11 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { petInventoryElement->DeleteChildren(); + // The client reads a as its active pet's database ID (LWOPetControlComponent::LoadFromSaveData 0x00c18120). The + // charxml is only read when the player loads, when no pet is out yet (a pet is summoned again after the load and + // registers itself), so live wrote a="0" on every character (226 live charxmls) + petInventoryElement->SetAttribute("a", 0); + for (const auto& pet : m_Pets) { auto* petElement = document.NewElement("p"); @@ -1886,6 +1891,8 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { petElement->SetAttribute("l", pet.second.lot); petElement->SetAttribute("m", pet.second.moderationState); petElement->SetAttribute("n", pet.second.name.c_str()); + // The taming type: the client sets it to 0 for every pet added (AddPetToPlayer in + // LWOPetControlComponent::HandleMessage 0x00d0fe00) and live saved 0 for every pet petElement->SetAttribute("t", 0); petInventoryElement->LinkEndChild(petElement); diff --git a/tests/dGameTests/dComponentsTests/CharacterSaveFieldsTests.cpp b/tests/dGameTests/dComponentsTests/CharacterSaveFieldsTests.cpp index 9feb23612..6cc9c85f8 100644 --- a/tests/dGameTests/dComponentsTests/CharacterSaveFieldsTests.cpp +++ b/tests/dGameTests/dComponentsTests/CharacterSaveFieldsTests.cpp @@ -9,6 +9,8 @@ #include "GameMessages.h" #include "MissionComponent.h" #include "SkillComponent.h" +#include "InventoryComponent.h" +#include "DatabasePet.h" #include "CDSkillBehaviorTable.h" #include "CDClientManager.h" #include "eMissionLockState.h" @@ -163,3 +165,18 @@ TEST_F(CharacterSaveFieldsTests, SkillCooldownsMissingInOldSave) { skills->UpdateXml(doc); EXPECT_EQ(Print(doc), R"()"); } + +// : live wrote the active pet as 0 on every character (the charxml is read at load, before any pet is out), +// and each pet's taming type t as 0. Old saves without a load as before. +TEST_F(CharacterSaveFieldsTests, PetsAsLiveWroteThem) { + CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);"); + tinyxml2::XMLDocument doc; + Parse(doc, R"(

)"); + + Entity player(29, info); + auto* const inventory = player.AddComponent(-1); + inventory->LoadXml(doc); // the pets load first; there is no here + EXPECT_EQ(inventory->GetDatabasePet(1152921510000000001LL).lot, 3254); + inventory->UpdateXml(doc); + EXPECT_EQ(Print(doc), R"(

)"); +}