feat(pets): write the active pet in the charxml as live did (pet@a)

The client reads <pet a> 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 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 03:31:28 -05:00
parent fd2be18161
commit 8a8fc263e1
2 changed files with 24 additions and 0 deletions

View File

@@ -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);

View File

@@ -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"(<obj v="1"><skil/></obj>)");
}
// <pet a="0">: 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"(<obj v="1"><pet><p id="1152921510000000001" l="3254" t="0" n="Fluffy" m="2"/></pet></obj>)");
Entity player(29, info);
auto* const inventory = player.AddComponent<InventoryComponent>(-1);
inventory->LoadXml(doc); // the pets load first; there is no <inv> here
EXPECT_EQ(inventory->GetDatabasePet(1152921510000000001LL).lot, 3254);
inventory->UpdateXml(doc);
EXPECT_EQ(Print(doc), R"(<obj v="1"><pet a="0"><p id="1152921510000000001" l="3254" m="2" n="Fluffy" t="0"/></pet></obj>)");
}