Housing: Fix exterior component tree spawning with data-driven multi-root architecture
- Fix DB2 store iteration bug: ExteriorComponentHook entries with ParentIndexField were only partially accessible via range-based iteration (1808 of 23881 entries). Use LookupEntry() over GetNumRows() to reach all entries, fixing hook resolution (compByHook was 0, now resolves all 1461 GroupXHook mappings). - Spawn all root components per HouseExteriorWmoDataID: houses consist of multiple independent roots (Base type=9, Roof type=10) sharing the same WMO data ID. Each root spawns independently at house position with its own hook children (doors on base, chimney/windows on roof). Filter by Size to avoid spawning small/medium/large variants simultaneously. - Build parent-child index from ExteriorComponent.ParentComponentID for component variants that reference a parent component. - Add _rootCompsByWmoDataId index for fast lookup of all root components belonging to a house exterior. - Fix MeshObject fixture data to pass fixtureGuid and parentFixtureGuid for proper client-side attachment hierarchy. - Fix BNetAccount dirty state in fixture edit mode by clearing update mask after PopulateCatalogStorageEntries.
This commit is contained in:
@@ -196,15 +196,26 @@ void MeshObject::InitHousingDecorData(ObjectGuid decorGuid, ObjectGuid houseGuid
|
||||
GetGUID().ToString(), decorGuid.ToString(), houseGuid.ToString(), flags, roomEntityGuid.ToString());
|
||||
}
|
||||
|
||||
void MeshObject::InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComponentID,
|
||||
void MeshObject::InitHousingFixtureData(ObjectGuid houseGuid, ObjectGuid fixtureGuid,
|
||||
ObjectGuid parentFixtureGuid, int32 exteriorComponentID,
|
||||
int32 houseExteriorWmoDataID, uint8 exteriorComponentType /*= 9*/,
|
||||
uint8 houseSize /*= 2*/, int32 exteriorComponentHookID /*= -1*/, bool isRoot /*= false*/)
|
||||
{
|
||||
if (m_housingFixtureData.has_value())
|
||||
return;
|
||||
|
||||
// Uses the same m_housingFixtureData declared on Object (Object.h:167)
|
||||
// Sniff-verified field values (11.2 retail MeshObject with FHousingFixture_C)
|
||||
// FHousingFixture_C fragment (ID 34, 96 bytes, 11 fields with HasChangesMask<11>).
|
||||
// Field order must match the client's CREATE deserializer:
|
||||
// [0] ExteriorComponentID (CompressedUInt32)
|
||||
// [1] HouseExteriorWmoDataID (CompressedUInt32)
|
||||
// [2] ExteriorComponentHookID (CompressedUInt32, defaults -1)
|
||||
// [3] HouseGUID (PackedGUID128)
|
||||
// [4] AttachParentGUID (PackedGUID128) ? parent fixture in hierarchy
|
||||
// [5] Guid (PackedGUID128) ? unique per fixture, for client identification
|
||||
// [6] GameObjectGUID (PackedGUID128) ? always empty
|
||||
// [7] ExteriorComponentType (uint8)
|
||||
// [8] Field_59 (uint8)
|
||||
// [9] Size (uint8)
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::ExteriorComponentID), exteriorComponentID);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
@@ -213,9 +224,15 @@ void MeshObject::InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComp
|
||||
.ModifyValue(&UF::HousingFixtureData::ExteriorComponentHookID), exteriorComponentHookID);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::HouseGUID), houseGuid);
|
||||
// Guid must be a Housing-type GUID (client crashes with non-Housing GUIDs here)
|
||||
// AttachParentGUID: the parent fixture's unique GUID in the hierarchy.
|
||||
// Root pieces have empty parent. Child pieces point to their parent root's fixture GUID.
|
||||
// The client uses this to build the fixture tree and resolve hook point ownership.
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::Guid), houseGuid);
|
||||
.ModifyValue(&UF::HousingFixtureData::AttachParentGUID), parentFixtureGuid);
|
||||
// Guid: unique per fixture ? the client uses this to identify individual fixtures.
|
||||
// Must be a Housing-type GUID (client crashes with non-Housing GUIDs here).
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::Guid), fixtureGuid);
|
||||
// GameObjectGUID: sniff confirms empty for all fixture pieces
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::ExteriorComponentType), exteriorComponentType);
|
||||
@@ -227,9 +244,10 @@ void MeshObject::InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComp
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingFixture_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingFixtureData));
|
||||
|
||||
// Cache for targeted fixture lookup
|
||||
// Cache for targeted fixture lookup and hierarchy traversal
|
||||
_exteriorComponentHookID = exteriorComponentHookID;
|
||||
_exteriorComponentID = exteriorComponentID;
|
||||
_fixtureGuid = fixtureGuid;
|
||||
|
||||
// Root pieces get Tag_HouseExteriorRoot (225), child pieces get Tag_HouseExteriorPiece (224).
|
||||
// The client uses Tag_HouseExteriorRoot to identify the fixture GUID for edit mode enter/exit.
|
||||
@@ -239,11 +257,11 @@ void MeshObject::InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComp
|
||||
else
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HouseExteriorPiece, IsInWorld());
|
||||
|
||||
TC_LOG_ERROR("housing", "MeshObject::InitHousingFixtureData: guid={} houseGuid={} "
|
||||
"exteriorComponentID={} wmoDataID={} hookID={} componentType={} size={} isRoot={}",
|
||||
GetGUID().ToString(), houseGuid.ToString(),
|
||||
exteriorComponentID, houseExteriorWmoDataID, exteriorComponentHookID,
|
||||
exteriorComponentType, houseSize, isRoot);
|
||||
TC_LOG_DEBUG("housing", "MeshObject::InitHousingFixtureData: meshGuid={} fixtureGuid={} "
|
||||
"parentFixtureGuid={} houseGuid={} extCompID={} wmoDataID={} hookID={} type={} size={} isRoot={}",
|
||||
GetGUID().ToString(), fixtureGuid.ToString(), parentFixtureGuid.ToString(),
|
||||
houseGuid.ToString(), exteriorComponentID, houseExteriorWmoDataID,
|
||||
exteriorComponentHookID, exteriorComponentType, houseSize, isRoot);
|
||||
}
|
||||
|
||||
void MeshObject::UpdateExteriorComponentID(int32 id)
|
||||
|
||||
@@ -66,9 +66,11 @@ public:
|
||||
// Housing fixture
|
||||
// isRoot: true for root pieces (no parent attachment), false for child pieces.
|
||||
// Root pieces get Tag_HouseExteriorRoot (225), children get Tag_HouseExteriorPiece (224).
|
||||
void InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComponentID,
|
||||
void InitHousingFixtureData(ObjectGuid houseGuid, ObjectGuid fixtureGuid,
|
||||
ObjectGuid parentFixtureGuid, int32 exteriorComponentID,
|
||||
int32 houseExteriorWmoDataID, uint8 exteriorComponentType = 9,
|
||||
uint8 houseSize = 2, int32 exteriorComponentHookID = -1, bool isRoot = false);
|
||||
ObjectGuid const& GetFixtureGuid() const { return _fixtureGuid; }
|
||||
|
||||
// Housing decor (adds FHousingDecor_C entity fragment for placed decor items)
|
||||
// Sniff-verified: retail decor is ALWAYS MeshObject (never GO). TargetGameObjectGUID=empty.
|
||||
@@ -123,6 +125,7 @@ private:
|
||||
bool _isExteriorRoot = false;
|
||||
int32 _exteriorComponentHookID = -1;
|
||||
int32 _exteriorComponentID = 0;
|
||||
ObjectGuid _fixtureGuid;
|
||||
};
|
||||
|
||||
#endif // TRINITYCORE_MESHOBJECT_H
|
||||
|
||||
@@ -753,6 +753,13 @@ void WorldSession::HandleHousingDecorSetEditMode(WorldPackets::Housing::HousingD
|
||||
player->ClearUpdateMask(false);
|
||||
}
|
||||
|
||||
// Clear Account entity dirty state on EXIT. During edit mode, decor operations
|
||||
// (place/move/remove) modify FHousingStorage_C which marks the Account dirty.
|
||||
// Without this, Map::SendObjectUpdates() sends a stale VALUES_UPDATE on the
|
||||
// next tick, which the client rejects ("Object update failed for BNetAccount").
|
||||
GetBattlenetAccount().ClearUpdateMask(true);
|
||||
GetHousingPlayerHouseEntity().ClearUpdateMask(true);
|
||||
|
||||
TC_LOG_DEBUG("housing", " EditMode EXIT: BNetAccountGuid={}",
|
||||
response.BNetAccountGuid.ToString());
|
||||
}
|
||||
@@ -1466,6 +1473,15 @@ void WorldSession::HandleHousingFixtureSetEditMode(WorldPackets::Housing::Housin
|
||||
housing->SyncUpdateFields();
|
||||
}
|
||||
|
||||
// CRITICAL: Clear Account entity dirty state BEFORE sending any packets.
|
||||
// PopulateCatalogStorageEntries() modifies FHousingStorage_C which marks the
|
||||
// Account entity dirty. Unlike decor edit mode, fixture edit mode doesn't send
|
||||
// the Account entity as CREATE here. If we leave it dirty, Map::SendObjectUpdates()
|
||||
// will send a VALUES_UPDATE on the next tick, which the client rejects because
|
||||
// MapUpdateField entries can't be added via VALUES_UPDATE when initially empty.
|
||||
// Also clear on EXIT to prevent any lingering dirty state from decor operations.
|
||||
GetBattlenetAccount().ClearUpdateMask(true);
|
||||
|
||||
// ======================================================================
|
||||
// Sniff-verified retail packet sequence (build 66337):
|
||||
// #10161 S->C SMSG_UPDATE_OBJECT (56B) ? editor mode field change
|
||||
@@ -1581,18 +1597,22 @@ void WorldSession::HandleHousingFixtureSetCoreFixture(WorldPackets::Housing::Hou
|
||||
|
||||
// Validate ExteriorComponentID against DB2 store
|
||||
uint32 componentID = housingFixtureSetCoreFixture.ExteriorComponentID;
|
||||
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE: FixtureGuid={} ExteriorComponentID={} (store has {} entries)",
|
||||
housingFixtureSetCoreFixture.FixtureGuid.ToString(), componentID, sExteriorComponentStore.GetNumRows());
|
||||
|
||||
ExteriorComponentEntry const* componentEntry = sExteriorComponentStore.LookupEntry(componentID);
|
||||
if (!componentEntry)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE ExteriorComponentID {} not found in DB2",
|
||||
componentID);
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE ExteriorComponentID {} not found in DB2 (store has {} entries)",
|
||||
componentID, sExteriorComponentStore.GetNumRows());
|
||||
WorldPackets::Housing::HousingFixtureSetCoreFixtureResponse response;
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_FIXTURE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE DB2 lookup: ExteriorComponentID={}, Name='{}', Type={}, Size={}, Flags={}, ParentCompID={}, WmoDataID={}",
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE DB2 lookup OK: ExteriorComponentID={}, Name='{}', Type={}, Size={}, Flags={}, ParentCompID={}, WmoDataID={}",
|
||||
componentID, componentEntry->Name[DEFAULT_LOCALE] ? componentEntry->Name[DEFAULT_LOCALE] : "",
|
||||
componentEntry->Type, componentEntry->Size, componentEntry->Flags,
|
||||
componentEntry->ParentComponentID, componentEntry->HouseExteriorWmoDataID);
|
||||
@@ -1773,7 +1793,7 @@ void WorldSession::HandleHousingFixtureDeleteFixture(WorldPackets::Housing::Hous
|
||||
}
|
||||
|
||||
// Spawn default component back at this hook (DB2 default)
|
||||
ExteriorComponentEntry const* defaultComp = sHousingMgr.GetComponentAtHook(static_cast<int32>(removedHookID));
|
||||
ExteriorComponentEntry const* defaultComp = sHousingMgr.GetComponentAtHook(static_cast<int32>(removedHookID), housing->GetCoreExteriorComponentID());
|
||||
if (defaultComp)
|
||||
{
|
||||
housingMap->SpawnFixtureAtHook(plotIndex, removedHookID, defaultComp->ID,
|
||||
|
||||
@@ -299,7 +299,7 @@ void HousingMap::SpawnPlotGameObjects()
|
||||
// fragment, SpellForVisuals, SpellXSpellVisualID) BEFORE the CREATE_OBJECT
|
||||
// packet is sent. The client needs FHousingPlotAreaTrigger_C and
|
||||
// DecalPropertiesId=621 in the initial create to render the plot border decal.
|
||||
AreaTrigger* plotAt = AreaTrigger::CreateStaticAreaTrigger({ .Id = 37358, .IsCustom = false }, this, atPos, -1);
|
||||
AreaTrigger* plotAt = AreaTrigger::CreateStaticAreaTrigger({ .Id = 37358, .IsCustom = false }, this, atPos, -1, false);
|
||||
if (plotAt)
|
||||
{
|
||||
PhasingHandler::InitDbPhaseShift(plotAt->GetPhaseShift(), PHASE_USE_FLAGS_ALWAYS_VISIBLE, 0, 0);
|
||||
@@ -1938,7 +1938,30 @@ MeshObject* HousingMap::SpawnHouseMeshObject(uint8 plotIndex, int32 fileDataID,
|
||||
// All other pieces (roof, door, chimney, windows) get Tag_HouseExteriorPiece (224).
|
||||
// The client uses Tag_HouseExteriorRoot to identify the fixture GUID for edit mode.
|
||||
bool isRoot = (exteriorComponentType == 9) && attachParent.IsEmpty();
|
||||
mesh->InitHousingFixtureData(houseGuid, exteriorComponentID, houseExteriorWmoDataID,
|
||||
|
||||
// Generate a unique fixture GUID per fixture. The client uses FHousingFixture_C::Guid
|
||||
// to identify individual fixtures ? if all fixtures share the same GUID (houseGuid),
|
||||
// the client can't distinguish them and reports "Fixture not found".
|
||||
// Use subType=5 (fixture), realm, hookID-or-componentID, houseGuid counter.
|
||||
uint32 fixtureArg2 = (exteriorComponentHookID > 0)
|
||||
? static_cast<uint32>(exteriorComponentHookID)
|
||||
: static_cast<uint32>(exteriorComponentID);
|
||||
ObjectGuid fixtureGuid = ObjectGuid::Create<HighGuid::Housing>(
|
||||
/*subType*/ 5, /*arg1*/ sRealmList->GetCurrentRealmId().Realm,
|
||||
/*arg2*/ fixtureArg2, houseGuid.GetCounter());
|
||||
|
||||
// Look up the parent fixture's unique GUID for AttachParentGUID field.
|
||||
// The client uses this to build the fixture hierarchy tree ? without it,
|
||||
// the client can't determine which hook points have fixtures installed.
|
||||
ObjectGuid parentFixtureGuid;
|
||||
if (!attachParent.IsEmpty())
|
||||
{
|
||||
if (MeshObject* parentMesh = GetMeshObject(attachParent))
|
||||
parentFixtureGuid = parentMesh->GetFixtureGuid();
|
||||
}
|
||||
|
||||
mesh->InitHousingFixtureData(houseGuid, fixtureGuid, parentFixtureGuid,
|
||||
exteriorComponentID, houseExteriorWmoDataID,
|
||||
exteriorComponentType, houseSize, exteriorComponentHookID, isRoot);
|
||||
|
||||
// Now add to map ? this triggers the create packet with all fragments included
|
||||
@@ -1975,139 +1998,94 @@ void HousingMap::SpawnFullHouseMeshObjects(uint8 plotIndex, Position const& hous
|
||||
uint32 coreExtCompID = static_cast<uint32>(exteriorComponentID);
|
||||
int32 groupID = sHousingMgr.GetGroupForComponent(coreExtCompID);
|
||||
|
||||
if (groupID != 0)
|
||||
ExteriorComponentEntry const* coreComp = sExteriorComponentStore.LookupEntry(coreExtCompID);
|
||||
if (coreComp && coreComp->ModelFileDataID > 0 && coreComp->HouseExteriorWmoDataID > 0)
|
||||
{
|
||||
// Find all root components in this group (those with HookID <= 0 are roots)
|
||||
std::vector<uint32> const* groupComps = sHousingMgr.GetComponentsInGroup(groupID);
|
||||
if (groupComps && !groupComps->empty())
|
||||
// A house consists of multiple independent root components (Base type=9, Roof type=10, etc.)
|
||||
// all sharing the same HouseExteriorWmoDataID. Each root is spawned independently at the
|
||||
// house position, and each has its own hook children (doors on base, chimney/windows on roof).
|
||||
//
|
||||
// Among roots of the same type, there may be variants (e.g. multiple roof styles). The
|
||||
// coreExtCompID tells us which base variant is selected; for other types we pick the
|
||||
// default (Flags & 0x1 IsDefault) or first available.
|
||||
|
||||
uint32 wmoDataID = coreComp->HouseExteriorWmoDataID;
|
||||
auto const* rootComps = sHousingMgr.GetRootComponentsForWmoData(wmoDataID);
|
||||
uint32 totalSpawned = 0;
|
||||
|
||||
if (rootComps)
|
||||
{
|
||||
uint32 totalSpawned = 0;
|
||||
for (uint32 compID : *groupComps)
|
||||
// Group roots by type, filtering to match the house's Size
|
||||
uint8 houseSize = coreComp->Size;
|
||||
std::unordered_map<uint8 /*type*/, std::vector<uint32>> rootsByType;
|
||||
for (uint32 rootID : *rootComps)
|
||||
{
|
||||
ExteriorComponentEntry const* comp = sExteriorComponentStore.LookupEntry(compID);
|
||||
if (!comp || comp->ParentComponentID > 0) // skip children ? they'll be spawned recursively
|
||||
continue;
|
||||
|
||||
// Check if this root component has been overridden (e.g., roof 1503 ? 1497)
|
||||
uint32 spawnCompID = compID;
|
||||
if (fixtureOverrides)
|
||||
{
|
||||
auto overrideItr = fixtureOverrides->find(compID);
|
||||
if (overrideItr != fixtureOverrides->end())
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnFullHouseMeshObjects: Root override for plot {} ? "
|
||||
"default {} ? override {}", plotIndex, compID, overrideItr->second);
|
||||
spawnCompID = overrideItr->second;
|
||||
}
|
||||
}
|
||||
|
||||
totalSpawned += SpawnExtCompTree(plotIndex, spawnCompID,
|
||||
housePos, houseRot,
|
||||
houseGuid, houseExteriorWmoDataID,
|
||||
ObjectGuid::Empty, nullptr, 0, fixtureOverrides);
|
||||
ExteriorComponentEntry const* rc = sExteriorComponentStore.LookupEntry(rootID);
|
||||
if (rc && rc->ModelFileDataID > 0 && rc->Size == houseSize)
|
||||
rootsByType[rc->Type].push_back(rootID);
|
||||
}
|
||||
|
||||
if (totalSpawned > 0)
|
||||
for (auto const& [type, compIDs] : rootsByType)
|
||||
{
|
||||
// Check which components were included in the data-driven group.
|
||||
// If missing, spawn them from hardcoded data.
|
||||
bool roofFound = false;
|
||||
bool doorFound = false;
|
||||
bool chimneyFound = false;
|
||||
MeshObject* basePieceDd = nullptr;
|
||||
MeshObject* roofPieceDd = nullptr;
|
||||
auto meshItr = _meshObjects.find(plotIndex);
|
||||
if (meshItr != _meshObjects.end())
|
||||
uint32 selectedCompID = 0;
|
||||
|
||||
if (type == coreComp->Type)
|
||||
{
|
||||
for (ObjectGuid const& guid : meshItr->second)
|
||||
// For the base type, use the player's selected coreExtCompID
|
||||
selectedCompID = coreExtCompID;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For other types (roof, etc.), pick IsDefault or first available
|
||||
// TODO: check fixture overrides for player-selected roof variant
|
||||
for (uint32 id : compIDs)
|
||||
{
|
||||
if (MeshObject* mesh = GetMeshObject(guid))
|
||||
ExteriorComponentEntry const* rc = sExteriorComponentStore.LookupEntry(id);
|
||||
if (!rc) continue;
|
||||
if (!selectedCompID)
|
||||
selectedCompID = id; // fallback: first available
|
||||
if (rc->Flags & 0x1)
|
||||
{
|
||||
if (mesh->GetFileDataID() == 7420602)
|
||||
{
|
||||
roofFound = true; roofPieceDd = mesh;
|
||||
}
|
||||
else if (mesh->GetFileDataID() == 7450804)
|
||||
doorFound = true;
|
||||
else if (mesh->GetFileDataID() == 6648736)
|
||||
basePieceDd = mesh;
|
||||
else if (mesh->GetFileDataID() == 7118952)
|
||||
chimneyFound = true;
|
||||
selectedCompID = id; // prefer IsDefault
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (factionRestriction == NEIGHBORHOOD_FACTION_ALLIANCE)
|
||||
if (selectedCompID)
|
||||
{
|
||||
if (!roofFound)
|
||||
{
|
||||
roofPieceDd = SpawnHouseMeshObject(plotIndex, 7420602, /*isWMO*/ true,
|
||||
housePos, houseRot, 1.0f,
|
||||
houseGuid, 1503, houseExteriorWmoDataID,
|
||||
/*exteriorComponentType*/ 10, /*houseSize*/ 2, /*hookID*/ -1,
|
||||
ObjectGuid::Empty, /*attachFlags*/ 0);
|
||||
totalSpawned++;
|
||||
}
|
||||
ExteriorComponentEntry const* selComp = sExteriorComponentStore.LookupEntry(selectedCompID);
|
||||
TC_LOG_INFO("housing", "SpawnFullHouseMeshObjects: Spawning root type={} comp={} '{}' "
|
||||
"(wmoDataID={}, size={}, ModelFDID={})",
|
||||
type, selectedCompID,
|
||||
selComp && selComp->Name[DEFAULT_LOCALE] ? selComp->Name[DEFAULT_LOCALE] : "",
|
||||
wmoDataID, houseSize, selComp ? selComp->ModelFileDataID : 0);
|
||||
|
||||
if (!doorFound && basePieceDd)
|
||||
{
|
||||
SpawnHouseMeshObject(plotIndex, 7450804, /*isWMO*/ true,
|
||||
Position(9.2805f, -3.4555f, -0.5611f, 0.0f),
|
||||
QuaternionData(0.0f, 0.0f, 0.0f, 1.0f), 1.0f,
|
||||
houseGuid, 1380, houseExteriorWmoDataID,
|
||||
/*exteriorComponentType*/ 11, /*houseSize*/ 1, /*hookID*/ 2505,
|
||||
basePieceDd->GetGUID(), /*attachFlags*/ 3, &housePos);
|
||||
totalSpawned++;
|
||||
}
|
||||
|
||||
// Chimney + windows as children of roof
|
||||
if (!chimneyFound && roofPieceDd)
|
||||
{
|
||||
ObjectGuid roofGuid = roofPieceDd->GetGUID();
|
||||
|
||||
SpawnHouseMeshObject(plotIndex, 7118952, /*isWMO*/ true,
|
||||
Position(-3.6472f, -5.6444f, 12.3556f, 0.0f),
|
||||
QuaternionData(0.0f, 0.0f, -0.7071066f, 0.70710695f), 1.0f,
|
||||
houseGuid, 1452, houseExteriorWmoDataID,
|
||||
/*exteriorComponentType*/ 16, /*houseSize*/ 2, /*hookID*/ 14931,
|
||||
roofGuid, /*attachFlags*/ 3, &housePos);
|
||||
|
||||
SpawnHouseMeshObject(plotIndex, 7450830, /*isWMO*/ true,
|
||||
Position(-3.025f, -0.0222f, 11.35f, 0.0f),
|
||||
QuaternionData(0.0f, 0.0f, -1.0f, 0.0f), 1.0f,
|
||||
houseGuid, 1448, houseExteriorWmoDataID,
|
||||
/*exteriorComponentType*/ 14, /*houseSize*/ 2, /*hookID*/ 17202,
|
||||
roofGuid, /*attachFlags*/ 3, &housePos);
|
||||
|
||||
SpawnHouseMeshObject(plotIndex, 7450830, /*isWMO*/ true,
|
||||
Position(3.0305f, -0.0222f, 11.35f, 0.0f),
|
||||
QuaternionData(0.0f, 0.0f, 0.0f, 1.0f), 1.0f,
|
||||
houseGuid, 1448, houseExteriorWmoDataID,
|
||||
/*exteriorComponentType*/ 14, /*houseSize*/ 2, /*hookID*/ 14929,
|
||||
roofGuid, /*attachFlags*/ 3, &housePos);
|
||||
|
||||
totalSpawned += 3;
|
||||
}
|
||||
totalSpawned += SpawnExtCompTree(plotIndex, selectedCompID,
|
||||
housePos, houseRot,
|
||||
houseGuid, houseExteriorWmoDataID,
|
||||
ObjectGuid::Empty, nullptr, 0, fixtureOverrides);
|
||||
}
|
||||
|
||||
TC_LOG_INFO("housing", "HousingMap::SpawnFullHouseMeshObjects: Data-driven spawn "
|
||||
"for plot {} group {} ? {} total MeshObjects (faction={}, door={})",
|
||||
plotIndex, groupID, totalSpawned,
|
||||
factionRestriction == NEIGHBORHOOD_FACTION_ALLIANCE ? "Alliance" : "Horde",
|
||||
doorFound ? "data-driven" : (factionRestriction == NEIGHBORHOOD_FACTION_ALLIANCE ? "hardcoded" : "N/A"));
|
||||
return;
|
||||
}
|
||||
|
||||
TC_LOG_WARN("housing", "HousingMap::SpawnFullHouseMeshObjects: Data-driven spawn "
|
||||
"yielded 0 meshes for plot {} group {} ? falling back to hardcoded",
|
||||
plotIndex, groupID);
|
||||
}
|
||||
|
||||
if (totalSpawned > 0)
|
||||
{
|
||||
TC_LOG_INFO("housing", "HousingMap::SpawnFullHouseMeshObjects: Data-driven spawn "
|
||||
"for plot {} wmoDataID {} coreComp {} ? {} total MeshObjects (faction={})",
|
||||
plotIndex, wmoDataID, coreExtCompID, totalSpawned,
|
||||
factionRestriction == NEIGHBORHOOD_FACTION_ALLIANCE ? "Alliance" : "Horde");
|
||||
return;
|
||||
}
|
||||
|
||||
TC_LOG_WARN("housing", "HousingMap::SpawnFullHouseMeshObjects: Data-driven spawn "
|
||||
"yielded 0 meshes for plot {} wmoDataID {} ? falling back to hardcoded",
|
||||
plotIndex, wmoDataID);
|
||||
}
|
||||
else
|
||||
else if (!coreComp)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnFullHouseMeshObjects: No group found for "
|
||||
"exteriorComponentID {} ? using hardcoded spawn for plot {}",
|
||||
exteriorComponentID, plotIndex);
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnFullHouseMeshObjects: ExteriorComponent {} not found "
|
||||
"? using hardcoded spawn for plot {}", exteriorComponentID, plotIndex);
|
||||
}
|
||||
|
||||
// === HARDCODED FALLBACK ===
|
||||
@@ -2321,10 +2299,10 @@ uint32 HousingMap::SpawnExtCompTree(uint8 plotIndex, uint32 extCompID,
|
||||
// Determine attach flags: root pieces (no parent) use 0, children use 3
|
||||
uint8 attachFlags = parentGuid.IsEmpty() ? 0 : 3;
|
||||
|
||||
// For CreateFixture: at depth=0, the hookIDOverride tells the client which hook point
|
||||
// this component occupies. The ExteriorComponent has no hook ID field ? hook relationships
|
||||
// come from ExteriorComponentHook and ExteriorComponentGroupXHook tables.
|
||||
int32 effectiveHookID = (depth == 0 && hookIDOverride > 0) ? hookIDOverride : -1;
|
||||
// The hookIDOverride tells the client which hook point this component occupies.
|
||||
// This must propagate at ALL depths (not just depth=0) because during initial house spawn
|
||||
// via SpawnFullHouseMeshObjects, hooks are iterated at depth >= 1 with valid hookIDOverride.
|
||||
int32 effectiveHookID = (hookIDOverride > 0) ? hookIDOverride : -1;
|
||||
|
||||
MeshObject* mesh = SpawnHouseMeshObject(plotIndex, comp->ModelFileDataID, /*isWMO*/ true,
|
||||
pos, rot, 1.0f,
|
||||
@@ -2346,6 +2324,8 @@ uint32 HousingMap::SpawnExtCompTree(uint8 plotIndex, uint32 extCompID,
|
||||
// Use worldPos for children: root's worldPos is itself, child's is the root's position
|
||||
Position const* childWorldPos = worldPos ? worldPos : &pos;
|
||||
|
||||
std::set<uint32> spawnedChildComps; // track to avoid double-spawning
|
||||
|
||||
// Recurse into hooks on this component
|
||||
auto const* hooks = sHousingMgr.GetHooksOnComponent(extCompID);
|
||||
if (hooks)
|
||||
@@ -2364,10 +2344,17 @@ uint32 HousingMap::SpawnExtCompTree(uint8 plotIndex, uint32 extCompID,
|
||||
childComp = sExteriorComponentStore.LookupEntry(overrideItr->second);
|
||||
}
|
||||
if (!childComp)
|
||||
childComp = sHousingMgr.GetComponentAtHook(static_cast<int32>(hook->ID));
|
||||
childComp = sHousingMgr.GetComponentAtHook(static_cast<int32>(hook->ID), extCompID);
|
||||
if (!childComp)
|
||||
continue;
|
||||
|
||||
spawnedChildComps.insert(childComp->ID);
|
||||
|
||||
TC_LOG_DEBUG("housing", "SpawnExtCompTree: parent={} hook={} (type={}) ? child comp {} '{}' (ParentComp={}, ModelFDID={})",
|
||||
extCompID, hook->ID, hook->ExteriorComponentTypeID, childComp->ID,
|
||||
childComp->Name[DEFAULT_LOCALE] ? childComp->Name[DEFAULT_LOCALE] : "",
|
||||
childComp->ParentComponentID, childComp->ModelFileDataID);
|
||||
|
||||
// Hook position/rotation are local-space offsets relative to the parent
|
||||
Position hookPos(hook->Position[0], hook->Position[1], hook->Position[2], 0.0f);
|
||||
QuaternionData hookRot;
|
||||
@@ -2392,6 +2379,48 @@ uint32 HousingMap::SpawnExtCompTree(uint8 plotIndex, uint32 extCompID,
|
||||
}
|
||||
}
|
||||
|
||||
// Also walk children linked via ExteriorComponent.ParentComponentID.
|
||||
// These are components (e.g. roofs) where ParentComponentID references this component.
|
||||
// They may not have hooks defining their position ? use the child's Position field as offset.
|
||||
auto const* children = sHousingMgr.GetChildComponents(extCompID);
|
||||
if (children)
|
||||
{
|
||||
for (uint32 childCompID : *children)
|
||||
{
|
||||
if (spawnedChildComps.count(childCompID))
|
||||
continue; // already spawned via hook
|
||||
|
||||
ExteriorComponentEntry const* childComp = sExteriorComponentStore.LookupEntry(childCompID);
|
||||
if (!childComp || childComp->ModelFileDataID <= 0)
|
||||
continue;
|
||||
|
||||
// Only spawn if this is a default component (or if there's no default, take first)
|
||||
// Skip non-default variants ? fixture overrides handle those
|
||||
if (!(childComp->Flags & 0x1))
|
||||
continue;
|
||||
|
||||
TC_LOG_INFO("housing", "SpawnExtCompTree: parent={} ? ParentComponentID child comp {} '{}' "
|
||||
"(type={}, ModelFDID={}, Pos=({:.1f},{:.1f},{:.1f}))",
|
||||
extCompID, childComp->ID,
|
||||
childComp->Name[DEFAULT_LOCALE] ? childComp->Name[DEFAULT_LOCALE] : "",
|
||||
childComp->Type, childComp->ModelFileDataID,
|
||||
childComp->Position[0], childComp->Position[1], childComp->Position[2]);
|
||||
|
||||
// Child's Position field is local-space offset from parent
|
||||
Position childPos(childComp->Position[0], childComp->Position[1], childComp->Position[2], 0.0f);
|
||||
QuaternionData childRot; // identity rotation
|
||||
childRot.x = 0.0f;
|
||||
childRot.y = 0.0f;
|
||||
childRot.z = 0.0f;
|
||||
childRot.w = 1.0f;
|
||||
|
||||
count += SpawnExtCompTree(plotIndex, childCompID,
|
||||
childPos, childRot,
|
||||
houseGuid, houseExteriorWmoDataID,
|
||||
meshGuid, childWorldPos, depth + 1, fixtureOverrides);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
@@ -1265,79 +1265,40 @@ void HousingMgr::BuildExteriorComponentIndexes()
|
||||
_exitPointByExtComp.clear();
|
||||
_groupByExtComp.clear();
|
||||
_extCompsByGroup.clear();
|
||||
_childrenByExtComp.clear();
|
||||
_rootCompsByWmoDataId.clear();
|
||||
|
||||
// 1. Build hook index: which hooks are parented to each component
|
||||
for (ExteriorComponentHookEntry const* hook : sExteriorComponentHookStore)
|
||||
// Note: store iteration only yields a subset of entries due to DB2 ParentIndexField
|
||||
// sparse indexing. Use LookupEntry(i) over GetNumRows() to reach all entries.
|
||||
for (uint32 i = 0; i < sExteriorComponentHookStore.GetNumRows(); ++i)
|
||||
{
|
||||
ExteriorComponentHookEntry const* hook = sExteriorComponentHookStore.LookupEntry(i);
|
||||
if (!hook)
|
||||
continue;
|
||||
_hooksByExtComp[hook->ExteriorComponentID].push_back(hook);
|
||||
}
|
||||
|
||||
// 2. Build reverse lookup: hookID ? default component that should be placed there.
|
||||
// Chain: ExteriorComponentGroupXHook maps groups to hooks.
|
||||
// For each hook, find components in groups linked to that hook with matching Type
|
||||
// and IsDefaultFixture flag (Flags & 0x1).
|
||||
// First, build group?hooks mapping from GroupXHook
|
||||
std::unordered_map<int32, std::vector<int32>> hooksByGroup; // groupID ? hookIDs
|
||||
for (ExteriorComponentGroupXHookEntry const* gxh : sExteriorComponentGroupXHookStore)
|
||||
{
|
||||
if (!gxh)
|
||||
continue;
|
||||
hooksByGroup[gxh->ExteriorComponentGroupID].push_back(gxh->ExteriorComponentHookID);
|
||||
}
|
||||
|
||||
// For each hook, find the default component: same Type as hook's ComponentType, IsDefaultFixture
|
||||
// We do this after step 4 (group indexes) so we can look up components in groups.
|
||||
// For now, build a type?default-components index to use below.
|
||||
std::unordered_map<int32, std::vector<ExteriorComponentEntry const*>> compsByType;
|
||||
for (ExteriorComponentEntry const* comp : sExteriorComponentStore)
|
||||
// 1a. Build child index and root-by-WMO index from ExteriorComponent.
|
||||
// ParentComponentID > 0 ? child of that component.
|
||||
// ParentComponentID == 0 ? root component, indexed by HouseExteriorWmoDataID.
|
||||
// ExteriorComponent uses ParentIndexField (HouseExteriorWmoDataID), so use
|
||||
// LookupEntry over GetNumRows() to reach all entries.
|
||||
for (uint32 i = 0; i < sExteriorComponentStore.GetNumRows(); ++i)
|
||||
{
|
||||
ExteriorComponentEntry const* comp = sExteriorComponentStore.LookupEntry(i);
|
||||
if (!comp)
|
||||
continue;
|
||||
compsByType[comp->Type].push_back(comp);
|
||||
|
||||
if (comp->ParentComponentID > 0)
|
||||
_childrenByExtComp[static_cast<uint32>(comp->ParentComponentID)].push_back(comp->ID);
|
||||
|
||||
// Root components (ParentComponentID=0) with a model, indexed by WMO data ID
|
||||
if (comp->ParentComponentID == 0 && comp->ModelFileDataID > 0 && comp->HouseExteriorWmoDataID > 0)
|
||||
_rootCompsByWmoDataId[comp->HouseExteriorWmoDataID].push_back(comp->ID);
|
||||
}
|
||||
|
||||
// Now for each hook, find the default component
|
||||
for (ExteriorComponentHookEntry const* hook : sExteriorComponentHookStore)
|
||||
{
|
||||
if (!hook)
|
||||
continue;
|
||||
|
||||
int32 hookType = hook->ExteriorComponentTypeID;
|
||||
auto typeItr = compsByType.find(hookType);
|
||||
if (typeItr == compsByType.end())
|
||||
continue;
|
||||
|
||||
// Find default component (Flags & 0x1 = IsDefaultFixture) that belongs to the
|
||||
// same parent component's group
|
||||
ExteriorComponentEntry const* bestComp = nullptr;
|
||||
for (ExteriorComponentEntry const* comp : typeItr->second)
|
||||
{
|
||||
if (comp->Flags & 0x1) // IsDefaultFixture
|
||||
{
|
||||
// Check if this component shares a group with the hook's parent component
|
||||
// via ExteriorComponentGroupXHook
|
||||
bestComp = comp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bestComp && !typeItr->second.empty())
|
||||
bestComp = typeItr->second.front(); // fallback: first component of this type
|
||||
|
||||
if (bestComp)
|
||||
_extCompByHookId[static_cast<int32>(hook->ID)] = bestComp;
|
||||
}
|
||||
|
||||
// 3. Build exit point index
|
||||
for (ExteriorComponentExitPointEntry const* exitPt : sExteriorComponentExitPointStore)
|
||||
{
|
||||
if (!exitPt)
|
||||
continue;
|
||||
_exitPointByExtComp[exitPt->ExteriorComponentID] = exitPt;
|
||||
}
|
||||
|
||||
// 4. Build group indexes from ExteriorComponentXGroup
|
||||
// 1b. Build group indexes from ExteriorComponentXGroup (needed by step 2)
|
||||
for (ExteriorComponentXGroupEntry const* xg : sExteriorComponentXGroupStore)
|
||||
{
|
||||
if (!xg)
|
||||
@@ -1348,11 +1309,93 @@ void HousingMgr::BuildExteriorComponentIndexes()
|
||||
_extCompsByGroup[groupID].push_back(compID);
|
||||
}
|
||||
|
||||
// 2. Build reverse lookup: hookID ? default component that should be placed there.
|
||||
// Chain: ExteriorComponentGroupXHook maps (GroupID ? HookID), meaning
|
||||
// "this group of components can be installed at this hook."
|
||||
// For each hook, we find which groups link to it, then pick the IsDefault
|
||||
// component from those groups with matching ExteriorComponentTypeID.
|
||||
//
|
||||
// NOTE: Hook IDs are NOT globally unique ? the ExteriorComponentHook DB2
|
||||
// uses ExteriorComponentID as a ParentIndexField, so multiple parent
|
||||
// components can share the same hook ID. The _extCompByHookId map uses
|
||||
// a composite key (hookID, parentCompID) to disambiguate.
|
||||
|
||||
// Step 2a: Build reverse index from GroupXHook: hookID ? list of groupIDs
|
||||
std::unordered_map<int32, std::vector<int32>> groupsByHookId; // hookID ? [groupID, ...]
|
||||
for (ExteriorComponentGroupXHookEntry const* gxh : sExteriorComponentGroupXHookStore)
|
||||
{
|
||||
if (!gxh)
|
||||
continue;
|
||||
groupsByHookId[gxh->ExteriorComponentHookID].push_back(gxh->ExteriorComponentGroupID);
|
||||
}
|
||||
|
||||
// Step 2b: For each hookID in GroupXHook, look up the hook entry via LookupEntry
|
||||
// (store iteration only yields a subset; LookupEntry reaches all 23k+ entries)
|
||||
for (auto const& [hookId, groupList] : groupsByHookId)
|
||||
{
|
||||
ExteriorComponentHookEntry const* hook = sExteriorComponentHookStore.LookupEntry(hookId);
|
||||
if (!hook)
|
||||
continue;
|
||||
|
||||
int32 hookType = hook->ExteriorComponentTypeID;
|
||||
int32 parentCompID = hook->ExteriorComponentID;
|
||||
int64 compositeKey = (int64(hookId) << 32) | uint32(parentCompID);
|
||||
ExteriorComponentEntry const* bestComp = nullptr;
|
||||
ExteriorComponentEntry const* fallbackComp = nullptr;
|
||||
|
||||
// Find default component from linked groups
|
||||
for (int32 groupID : groupList)
|
||||
{
|
||||
auto compItr = _extCompsByGroup.find(groupID);
|
||||
if (compItr == _extCompsByGroup.end())
|
||||
continue;
|
||||
|
||||
for (uint32 compID : compItr->second)
|
||||
{
|
||||
ExteriorComponentEntry const* comp = sExteriorComponentStore.LookupEntry(compID);
|
||||
if (!comp || comp->Type != hookType)
|
||||
continue;
|
||||
|
||||
if (!fallbackComp)
|
||||
fallbackComp = comp;
|
||||
|
||||
if (comp->Flags & 0x1) // IsDefaultFixture
|
||||
{
|
||||
bestComp = comp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bestComp)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bestComp)
|
||||
bestComp = fallbackComp;
|
||||
|
||||
if (bestComp)
|
||||
{
|
||||
_extCompByHookId[compositeKey] = bestComp;
|
||||
TC_LOG_DEBUG("housing", "HookID {} (parent={}, type={}) ? comp {} '{}' (Flags=0x{:X}, group-resolved)",
|
||||
hook->ID, parentCompID, hookType, bestComp->ID,
|
||||
bestComp->Name[DEFAULT_LOCALE] ? bestComp->Name[DEFAULT_LOCALE] : "",
|
||||
bestComp->ParentComponentID, bestComp->Flags);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Build exit point index
|
||||
for (ExteriorComponentExitPointEntry const* exitPt : sExteriorComponentExitPointStore)
|
||||
{
|
||||
if (!exitPt)
|
||||
continue;
|
||||
_exitPointByExtComp[exitPt->ExteriorComponentID] = exitPt;
|
||||
}
|
||||
|
||||
TC_LOG_INFO("housing", "HousingMgr::BuildExteriorComponentIndexes: "
|
||||
"hooks={} compByHook={} exitPoints={} groups={} compsInGroups={}",
|
||||
"hooks={} compByHook={} exitPoints={} groups={} compsInGroups={} parentChildren={} wmoRoots={}",
|
||||
uint32(_hooksByExtComp.size()), uint32(_extCompByHookId.size()),
|
||||
uint32(_exitPointByExtComp.size()), uint32(_groupByExtComp.size()),
|
||||
uint32(_extCompsByGroup.size()));
|
||||
uint32(_extCompsByGroup.size()), uint32(_childrenByExtComp.size()),
|
||||
uint32(_rootCompsByWmoDataId.size()));
|
||||
|
||||
}
|
||||
|
||||
@@ -1362,9 +1405,10 @@ std::vector<ExteriorComponentHookEntry const*> const* HousingMgr::GetHooksOnComp
|
||||
return itr != _hooksByExtComp.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
ExteriorComponentEntry const* HousingMgr::GetComponentAtHook(int32 hookID) const
|
||||
ExteriorComponentEntry const* HousingMgr::GetComponentAtHook(int32 hookID, uint32 parentCompID) const
|
||||
{
|
||||
auto itr = _extCompByHookId.find(hookID);
|
||||
int64 compositeKey = (int64(hookID) << 32) | uint32(parentCompID);
|
||||
auto itr = _extCompByHookId.find(compositeKey);
|
||||
return itr != _extCompByHookId.end() ? itr->second : nullptr;
|
||||
}
|
||||
|
||||
@@ -1380,6 +1424,18 @@ int32 HousingMgr::GetGroupForComponent(uint32 extCompID) const
|
||||
return itr != _groupByExtComp.end() ? itr->second : 0;
|
||||
}
|
||||
|
||||
std::vector<uint32> const* HousingMgr::GetChildComponents(uint32 parentCompID) const
|
||||
{
|
||||
auto itr = _childrenByExtComp.find(parentCompID);
|
||||
return itr != _childrenByExtComp.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint32> const* HousingMgr::GetRootComponentsForWmoData(uint32 wmoDataID) const
|
||||
{
|
||||
auto itr = _rootCompsByWmoDataId.find(wmoDataID);
|
||||
return itr != _rootCompsByWmoDataId.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint32> const* HousingMgr::GetComponentsInGroup(int32 groupID) const
|
||||
{
|
||||
auto itr = _extCompsByGroup.find(groupID);
|
||||
|
||||
@@ -330,9 +330,11 @@ public:
|
||||
|
||||
// ExteriorComponent indexed lookups
|
||||
std::vector<ExteriorComponentHookEntry const*> const* GetHooksOnComponent(uint32 extCompID) const;
|
||||
ExteriorComponentEntry const* GetComponentAtHook(int32 hookID) const;
|
||||
ExteriorComponentEntry const* GetComponentAtHook(int32 hookID, uint32 parentCompID) const;
|
||||
ExteriorComponentExitPointEntry const* GetExitPoint(uint32 extCompID) const;
|
||||
int32 GetGroupForComponent(uint32 extCompID) const;
|
||||
std::vector<uint32> const* GetChildComponents(uint32 parentCompID) const;
|
||||
std::vector<uint32> const* GetRootComponentsForWmoData(uint32 wmoDataID) const;
|
||||
std::vector<uint32> const* GetComponentsInGroup(int32 groupID) const;
|
||||
|
||||
// Find the first HouseRoom entry with visual components (not the base room 18)
|
||||
@@ -432,10 +434,12 @@ private:
|
||||
|
||||
// ExteriorComponent indexes
|
||||
std::unordered_map<uint32 /*extCompID*/, std::vector<ExteriorComponentHookEntry const*>> _hooksByExtComp;
|
||||
std::unordered_map<int32 /*hookID*/, ExteriorComponentEntry const*> _extCompByHookId;
|
||||
std::unordered_map<int64 /*(hookID<<32)|parentCompID*/, ExteriorComponentEntry const*> _extCompByHookId;
|
||||
std::unordered_map<uint32 /*extCompID*/, ExteriorComponentExitPointEntry const*> _exitPointByExtComp;
|
||||
std::unordered_map<uint32 /*extCompID*/, int32 /*groupID*/> _groupByExtComp;
|
||||
std::unordered_map<int32 /*groupID*/, std::vector<uint32 /*extCompID*/>> _extCompsByGroup;
|
||||
std::unordered_map<uint32 /*parentCompID*/, std::vector<uint32 /*childCompID*/>> _childrenByExtComp;
|
||||
std::unordered_map<uint32 /*wmoDataID*/, std::vector<uint32 /*compID*/>> _rootCompsByWmoDataId;
|
||||
};
|
||||
|
||||
#define sHousingMgr HousingMgr::Instance()
|
||||
|
||||
@@ -229,10 +229,20 @@ namespace WorldPackets::Housing
|
||||
|
||||
void HousingFixtureSetCoreFixture::Read()
|
||||
{
|
||||
// Diagnostic: log raw packet bytes before parsing
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE raw packet: size={} rpos={}", _worldPacket.size(), _worldPacket.rpos());
|
||||
{
|
||||
std::string hexDump;
|
||||
for (std::size_t i = _worldPacket.rpos(); i < _worldPacket.size() && i < _worldPacket.rpos() + 40; ++i)
|
||||
hexDump += fmt::format("{:02X} ", _worldPacket[i]);
|
||||
TC_LOG_INFO("housing", " raw bytes: {}", hexDump);
|
||||
}
|
||||
|
||||
_worldPacket >> FixtureGuid;
|
||||
_worldPacket >> ExteriorComponentID;
|
||||
|
||||
TC_LOG_DEBUG("network.opcode", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE FixtureGuid: {} ExteriorComponentID: {}", FixtureGuid.ToString(), ExteriorComponentID);
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_FIXTURE_SET_CORE_FIXTURE parsed: FixtureGuid={} ExteriorComponentID={} (rpos after={})",
|
||||
FixtureGuid.ToString(), ExteriorComponentID, _worldPacket.rpos());
|
||||
}
|
||||
|
||||
void HousingFixtureCreateFixture::Read()
|
||||
|
||||
Reference in New Issue
Block a user