Housing: Fix DB2 field ordering, door placement, and fixture validation
Fix ExteriorComponentHookEntry struct field order to match DB2 LoadInfo (Position/Rotation before ID when IndexField=2), resolving garbage hook IDs during fixture resolution. Replace first-match door hook selection with center-front scoring heuristic (|X|*2 + Y) to ensure the main entrance spawns at the front of the house rather than on a side/back wall. Add fixture validation in SelectFixtureOption: enforce component type must match hook type, and only one door allowed per house. This prevents placing windows at door hooks and spawning multiple entrances. Only auto-resolve the single best door hook during SpawnExtCompTree — other fixture types (windows, chimneys, dormers) require explicit player selection, matching retail behavior where they unlock via progression. Additional fixes: unique fixture GUIDs via atomic counter (subType=5), size-aware default fixture lookup, range-based DB2 store iteration, and starter fixture migration that preserves existing roots.
This commit is contained in:
@@ -317,6 +317,14 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
|
||||
} while (fixtures->NextRow());
|
||||
}
|
||||
|
||||
// Migration: populate starter fixtures for houses created before persistence was added
|
||||
if (_fixtures.empty() && _houseType != 0)
|
||||
{
|
||||
TC_LOG_INFO("housing", "Housing::LoadFromDB: No fixtures found for house {} ? populating starter fixtures (migration)",
|
||||
_houseGuid.ToString());
|
||||
PopulateStarterFixtures();
|
||||
}
|
||||
|
||||
// Load catalog
|
||||
// 0 1 2 3
|
||||
// SELECT houseDecorId, quantity, sourceType, sourceValue
|
||||
|
||||
@@ -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);
|
||||
@@ -385,11 +385,14 @@ void HousingMap::SpawnPlotGameObjects()
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnPlotGameObjects: Plot {} using ExteriorComponentID={}, WmoDataID={}",
|
||||
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
|
||||
|
||||
// Build fixture + root override maps from player's saved fixture selections
|
||||
// Build fixture + root override maps from player's saved fixture selections.
|
||||
// Always pass rootOverrides when housing exists ? the map controls which root types spawn.
|
||||
// nullptr = no housing data (unowned plot), spawn all roots.
|
||||
// Non-null = only spawn types present in the map.
|
||||
FixtureOverrideMap fixtureOverrides = housing->GetFixtureOverrideMap();
|
||||
FixtureOverrideMap const* overridesPtr = fixtureOverrides.empty() ? nullptr : &fixtureOverrides;
|
||||
RootOverrideMap rootOverrides = housing->GetRootComponentOverrides();
|
||||
RootOverrideMap const* rootOvrPtr = rootOverrides.empty() ? nullptr : &rootOverrides;
|
||||
RootOverrideMap const* rootOvrPtr = &rootOverrides;
|
||||
|
||||
GameObject* houseGo = nullptr;
|
||||
if (housing->HasCustomPosition())
|
||||
@@ -677,7 +680,7 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
|
||||
auto fixtureOverrides = housing->GetFixtureOverrideMap();
|
||||
FixtureOverrideMap const* overridesPtr = fixtureOverrides.empty() ? nullptr : &fixtureOverrides;
|
||||
auto rootOverrides = housing->GetRootComponentOverrides();
|
||||
RootOverrideMap const* rootOvrPtr = rootOverrides.empty() ? nullptr : &rootOverrides;
|
||||
RootOverrideMap const* rootOvrPtr = &rootOverrides;
|
||||
|
||||
GameObject* go = nullptr;
|
||||
if (housing->HasCustomPosition())
|
||||
@@ -718,7 +721,7 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
|
||||
auto lateFixtureOvr = housing->GetFixtureOverrideMap();
|
||||
FixtureOverrideMap const* lateFixturePtr = lateFixtureOvr.empty() ? nullptr : &lateFixtureOvr;
|
||||
auto lateRootOvr = housing->GetRootComponentOverrides();
|
||||
RootOverrideMap const* lateRootPtr = lateRootOvr.empty() ? nullptr : &lateRootOvr;
|
||||
RootOverrideMap const* lateRootPtr = &lateRootOvr;
|
||||
|
||||
SpawnFullHouseMeshObjects(plotIdx, pos, rot,
|
||||
housing->GetHouseGuid(), lateExtCompID, lateWmoDataID, faction,
|
||||
@@ -2097,6 +2100,13 @@ void HousingMap::SpawnFullHouseMeshObjects(uint8 plotIndex, Position const& hous
|
||||
auto ovrItr = rootOverrides->find(type);
|
||||
if (ovrItr != rootOverrides->end())
|
||||
selectedCompID = ovrItr->second;
|
||||
else
|
||||
{
|
||||
// Type not in fixtures DB ? not unlocked yet, skip it
|
||||
TC_LOG_DEBUG("housing", "SpawnFullHouseMeshObjects: Skipping root type={} ? not in fixtures",
|
||||
type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. For the core type, use the player's selected coreExtCompID
|
||||
|
||||
Reference in New Issue
Block a user