Housing: Fix fixture edit mode using wrong EditorMode and missing FixtureGuid

The "Edit House Exterior" button sent EditorMode=4 (Customize/interior) instead
of EditorMode=6 (ExteriorCustomization). The client checks
C_HouseEditor.IsHouseEditorModeActive(ExteriorCustomization) which returned
false, so the exterior customization UI never activated.

Also the SMSG response had an empty FixtureGuid — the client uses this GUID
to determine enter vs exit state for fixture editing.

Fixes:
- Use HOUSING_EDITOR_MODE_EXTERIOR_CUSTOMIZATION (6) for fixture edit mode
- Populate FixtureGuid with the root fixture MeshObject GUID (componentType=9)
- Always CREATE Account entity in fixture mode (same pattern as decor edit)
- Include all fixture MeshObject CREATEs in same UPDATE_OBJECT packet
- Add GetPlotMeshObjects() accessor to HousingMap
This commit is contained in:
luis
2026-03-12 07:58:15 -03:00
parent 1a7d471b08
commit 9ff52e29fb
2 changed files with 50 additions and 15 deletions
+44 -12
View File
@@ -1248,16 +1248,31 @@ void WorldSession::HandleHousingFixtureSetEditMode(WorldPackets::Housing::Housin
return;
}
housing->SetEditorMode(housingFixtureSetEditMode.Active ? HOUSING_EDITOR_MODE_CUSTOMIZE : HOUSING_EDITOR_MODE_NONE);
// Client enum HouseEditorMode: 4=Customize (interior), 6=ExteriorCustomization (fixture).
// The "Edit House Exterior" button sends this opcode ? must use mode 6 so the client's
// C_HouseEditor.IsHouseEditorModeActive(Enum.HouseEditorMode.ExteriorCustomization) returns true.
housing->SetEditorMode(housingFixtureSetEditMode.Active ? HOUSING_EDITOR_MODE_EXTERIOR_CUSTOMIZATION : HOUSING_EDITOR_MODE_NONE);
// The response FixtureGuid must be the root fixture MeshObject GUID for the player's plot.
// The client compares this GUID against its stored "current exterior root" to determine
// enter vs exit state. An empty GUID causes the client to never enter fixture edit mode.
ObjectGuid fixtureGuid;
if (HousingMap* housingMap = dynamic_cast<HousingMap*>(player->GetMap()))
{
uint8 plotIndex = housing->GetPlotIndex();
auto const& meshMap = housingMap->GetPlotMeshObjects();
auto meshItr = meshMap.find(plotIndex);
if (meshItr != meshMap.end() && !meshItr->second.empty())
fixtureGuid = meshItr->second.front(); // First MeshObject = root fixture (componentType=9)
}
WorldPackets::Housing::HousingFixtureSetEditModeResponse response;
response.HouseGuid = housing->GetHouseGuid();
response.FixtureGuid = fixtureGuid;
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
SendPacket(response.Write());
// Sync entity data so the client receives budget values when switching to customize mode.
// Without this, the client sees 0/0 budgets because the Player entity's EditorMode changed
// but the HousingPlayerHouseEntity/Account entity data was never flushed.
// Sync entity data so the client receives budget/storage values when switching to fixture mode.
if (housingFixtureSetEditMode.Active)
{
housing->ResetStoragePopulated();
@@ -1272,13 +1287,9 @@ void WorldSession::HandleHousingFixtureSetEditMode(WorldPackets::Housing::Housin
WorldPacket updatePacket;
player->BuildValuesUpdateBlockForPlayer(&updateData, player);
if (player->HaveAtClient(&GetBattlenetAccount()))
GetBattlenetAccount().BuildValuesUpdateBlockForPlayer(&updateData, player);
else
{
GetBattlenetAccount().BuildCreateUpdateBlockForPlayer(&updateData, player);
player->m_clientGUIDs.insert(GetBattlenetAccount().GetGUID());
}
// Always CREATE Account entity (same reasoning as decor edit mode)
GetBattlenetAccount().BuildCreateUpdateBlockForPlayer(&updateData, player);
player->m_clientGUIDs.insert(GetBattlenetAccount().GetGUID());
if (player->HaveAtClient(&GetHousingPlayerHouseEntity()))
GetHousingPlayerHouseEntity().BuildValuesUpdateBlockForPlayer(&updateData, player);
@@ -1288,6 +1299,25 @@ void WorldSession::HandleHousingFixtureSetEditMode(WorldPackets::Housing::Housin
player->m_clientGUIDs.insert(GetHousingPlayerHouseEntity().GetGUID());
}
// Include CREATE for all fixture MeshObjects in same packet (same pattern as decor edit)
if (HousingMap* housingMap = dynamic_cast<HousingMap*>(player->GetMap()))
{
uint8 plotIndex = housing->GetPlotIndex();
auto const& meshMap = housingMap->GetPlotMeshObjects();
auto meshItr = meshMap.find(plotIndex);
if (meshItr != meshMap.end())
{
for (ObjectGuid const& meshGuid : meshItr->second)
{
MeshObject* meshObj = housingMap->GetMeshObject(meshGuid);
if (!meshObj || !meshObj->IsInWorld())
continue;
meshObj->BuildCreateUpdateBlockForPlayer(&updateData, player);
player->m_clientGUIDs.insert(meshGuid);
}
}
}
updateData.BuildPacket(&updatePacket);
player->SendDirectMessage(&updatePacket);
@@ -1296,7 +1326,9 @@ void WorldSession::HandleHousingFixtureSetEditMode(WorldPackets::Housing::Housin
GetHousingPlayerHouseEntity().ClearUpdateMask(true);
}
TC_LOG_INFO("housing", "CMSG_HOUSING_FIXTURE_SET_EDITOR_MODE_ACTIVE Active: {}", housingFixtureSetEditMode.Active);
TC_LOG_ERROR("housing", "CMSG_HOUSING_FIXTURE_SET_EDITOR_MODE_ACTIVE Active: {} FixtureGuid: {} EditorMode: {}",
housingFixtureSetEditMode.Active, fixtureGuid.ToString(),
housingFixtureSetEditMode.Active ? 6 : 0);
}
void WorldSession::HandleHousingFixtureSetCoreFixture(WorldPackets::Housing::HousingFixtureSetCoreFixture const& housingFixtureSetCoreFixture)
+6 -3
View File
@@ -92,7 +92,7 @@ public:
QuaternionData const& houseRot, ObjectGuid houseGuid);
void DespawnRoomForPlot(uint8 plotIndex);
// Decor management (all decor is MeshObject — sniff-verified, never GO)
// Decor management (all decor is MeshObject ? sniff-verified, never GO)
MeshObject* SpawnDecorItem(uint8 plotIndex, Housing::PlacedDecor const& decor, ObjectGuid houseGuid);
void DespawnDecorItem(uint8 plotIndex, ObjectGuid decorGuid);
void DespawnAllDecorForPlot(uint8 plotIndex);
@@ -108,10 +108,13 @@ public:
return itr != _playerCurrentPlot.end() ? static_cast<int8>(itr->second) : -1;
}
// Accessor for diagnostic logging (decor GUID → MeshObject GUID map)
// Accessor for diagnostic logging (decor GUID ? MeshObject GUID map)
std::unordered_map<ObjectGuid, ObjectGuid> const& GetDecorGuidMap() const { return _decorGuidToGoGuid; }
// Manual spell packet helpers — called from AddPlayerToMap and at_housing_plot AT script.
// Accessor for fixture MeshObjects (plotIndex ? vector of MeshObject GUIDs)
std::unordered_map<uint8, std::vector<ObjectGuid>> const& GetPlotMeshObjects() const { return _meshObjects; }
// Manual spell packet helpers ? called from AddPlayerToMap and at_housing_plot AT script.
// These spells don't exist in DB2, so CastSpell() silently fails; manual packets are required.
void SendPostTutorialAuras(Player* player);
void SendPlotEnterSpellPackets(Player* player, uint8 plotIndex);