trading post

This commit is contained in:
luis
2026-03-17 01:14:23 -03:00
parent 494d808780
commit 5a56fa0773
20 changed files with 845 additions and 804 deletions
@@ -43,6 +43,18 @@ void WorldSession::HandlePerkProgramRequestPurchase(WorldPackets::PerksProgram::
GetPerkProgramMgr()->BuyItem(packet.VendorItemID, ObjectGuid::Empty);
}
void WorldSession::HandlePerkProgramRequestCartCheckout(WorldPackets::PerksProgram::PerksProgramRequestCartCheckout& packet)
{
if (!_player)
return;
// Process each item in the cart
for (int32 vendorItemID : packet.VendorItemIDs)
{
GetPerkProgramMgr()->BuyItem(vendorItemID, ObjectGuid::Empty);
}
}
void WorldSession::HandlePerkProgramSetFrozenVendorItem(WorldPackets::PerksProgram::PerksProgramSetFrozenVendorItem& packet)
{
if (!_player)
+30 -30
View File
@@ -40,11 +40,11 @@
HouseInteriorMap::HouseInteriorMap(uint32 id, time_t expiry, uint32 instanceId, ObjectGuid const& owner)
: Map(id, expiry, instanceId, DIFFICULTY_NORMAL),
_owner(owner),
_loadingPlayer(nullptr),
_sourceNeighborhoodMapId(0),
_sourcePlotIndex(0),
_roomsSpawned(false)
_owner(owner),
_loadingPlayer(nullptr),
_sourceNeighborhoodMapId(0),
_sourcePlotIndex(0),
_roomsSpawned(false)
{
HouseInteriorMap::InitVisibilityDistance();
@@ -60,7 +60,7 @@ HouseInteriorMap::HouseInteriorMap(uint32 id, time_t expiry, uint32 instanceId,
}
else
{
TC_LOG_INFO("housing", "HouseInteriorMap::CTOR: No NeighborhoodMap DB2 entry for map {} ? "
TC_LOG_INFO("housing", "HouseInteriorMap::CTOR: No NeighborhoodMap DB2 entry for map {} — "
"using default origin ({:.1f}, {:.1f}, {:.1f})",
id, _originX, _originY, _originZ);
}
@@ -104,7 +104,7 @@ void HouseInteriorMap::SpawnRoomMeshObjects(Housing* housing, int32 factionRestr
if (rooms.empty())
{
TC_LOG_ERROR("housing", "HouseInteriorMap::SpawnRoomMeshObjects: No rooms to spawn for owner {} "
"(new house ? rooms will appear when placed via editor)",
"(new house — rooms will appear when placed via editor)",
_owner.ToString());
return;
}
@@ -129,12 +129,12 @@ void HouseInteriorMap::SpawnRoomMeshObjects(Housing* housing, int32 factionRestr
// --- DB2 lookups ---
// 1. HouseRoom ? RoomWmoDataID
// 1. HouseRoom → RoomWmoDataID
int32 roomWmoDataID = roomData->RoomWmoDataID;
// 2. RoomWmoData ? Geobox bounds (bounding box for OutsidePlotBounds check)
// 2. RoomWmoData → Geobox bounds (bounding box for OutsidePlotBounds check)
float geoMinX = -35.0f, geoMinY = -30.0f, geoMinZ = -1.01f;
float geoMaxX = 35.0f, geoMaxY = 30.0f, geoMaxZ = 125.01f;
float geoMaxX = 35.0f, geoMaxY = 30.0f, geoMaxZ = 125.01f;
RoomWmoDataEntry const* wmoData = roomWmoDataID ? sRoomWmoDataStore.LookupEntry(roomWmoDataID) : nullptr;
if (wmoData)
{
@@ -265,10 +265,10 @@ void HouseInteriorMap::SpawnRoomMeshObjects(Housing* housing, int32 factionRestr
// Hardcoded fallback (sniff-verified: walls=24, floors=40, ceilings=54)
switch (comp.Type)
{
case 1: roomComponentTextureID = 24; break;
case 2: roomComponentTextureID = 40; break;
case 3: roomComponentTextureID = 54; break;
default: break;
case 1: roomComponentTextureID = 24; break;
case 2: roomComponentTextureID = 40; break;
case 3: roomComponentTextureID = 54; break;
default: break;
}
}
}
@@ -276,7 +276,7 @@ void HouseInteriorMap::SpawnRoomMeshObjects(Housing* housing, int32 factionRestr
// Component position/rotation: local to room entity
Position compPos(comp.OffsetPos[0], comp.OffsetPos[1], comp.OffsetPos[2], 0.0f);
QuaternionData compRot;
// DB2 OffsetRot is in DEGREES ? convert to radians for quaternion math
// DB2 OffsetRot is in DEGREES — convert to radians for quaternion math
static constexpr float DEG_TO_RAD = static_cast<float>(M_PI / 180.0);
float rx = comp.OffsetRot[0] * DEG_TO_RAD;
float ry = comp.OffsetRot[1] * DEG_TO_RAD;
@@ -317,14 +317,14 @@ void HouseInteriorMap::SpawnRoomMeshObjects(Housing* housing, int32 factionRestr
// Count by component type for diagnostic summary
switch (comp.Type)
{
case 1: ++wallCount; break;
case 2: ++floorCount; break;
case 3: ++ceilingCount; break;
case 4: ++doorwayCount; break;
case 5: ++stairsCount; break;
case 6: ++pillarCount; break;
case 7: ++doorwayWallCount; break;
default: ++otherCount; break;
case 1: ++wallCount; break;
case 2: ++floorCount; break;
case 3: ++ceilingCount; break;
case 4: ++doorwayCount; break;
case 5: ++stairsCount; break;
case 6: ++pillarCount; break;
case 7: ++doorwayWallCount; break;
default: ++otherCount; break;
}
TC_LOG_ERROR("housing", " Component: compID={} type={} fileDataID={} optionID={} themeID={} "
@@ -423,7 +423,7 @@ void HouseInteriorMap::SpawnInteriorDecor(Housing* housing)
uint32 exteriorSkipped = 0;
uint32 totalDecor = uint32(housing->GetPlacedDecorMap().size());
TC_LOG_ERROR("housing", "HouseInteriorMap::SpawnInteriorDecor: Starting ? totalDecor={} "
TC_LOG_ERROR("housing", "HouseInteriorMap::SpawnInteriorDecor: Starting — totalDecor={} "
"_roomMeshObjects entries={} owner={}",
totalDecor, uint32(_roomMeshObjects.size()), _owner.ToString());
@@ -598,7 +598,7 @@ void HouseInteriorMap::SpawnSingleInteriorDecor(Housing::PlacedDecor const& deco
QuaternionData rot(decor.RotationX, decor.RotationY, decor.RotationZ, decor.RotationW);
// Convert world ? local with inverse rotation of the room entity's facing.
// Convert world → local with inverse rotation of the room entity's facing.
float localX = worldX, localY = worldY, localZ = worldZ;
if (!roomEntityGuid.IsEmpty())
{
@@ -607,7 +607,7 @@ void HouseInteriorMap::SpawnSingleInteriorDecor(Housing::PlacedDecor const& deco
float roomFacing = roomWorldPos.GetOrientation();
float cosF = std::cos(roomFacing);
float sinF = std::sin(roomFacing);
localX = cosF * dx + sinF * dy;
localX = cosF * dx + sinF * dy;
localY = -sinF * dx + cosF * dy;
localZ = worldZ - roomWorldPos.GetPositionZ();
}
@@ -761,7 +761,7 @@ bool HouseInteriorMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/
// Send SMSG_HOUSING_GET_CURRENT_HOUSE_INFO_RESPONSE so the client knows the
// active house context. Without this, the client has no house context and the
// editor UI (C_HouseEditor.GetHouseEditorModeAvailability) won't show.
// The exterior map (HousingMap::AddPlayerToMap) sends this ? the interior must too.
// The exterior map (HousingMap::AddPlayerToMap) sends this — the interior must too.
{
WorldPackets::Housing::HousingGetCurrentHouseInfoResponse houseInfo;
houseInfo.House.HouseGuid = housing->GetHouseGuid();
@@ -791,12 +791,12 @@ bool HouseInteriorMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/
statusResponse.OwnerPlayerGuid = player->GetGUID();
statusResponse.NeighborhoodGuid = housing->GetNeighborhoodGuid();
statusResponse.Status = 1; // Interior
statusResponse.FlagByte = 0xC0; // bit7=Decor, bit6=Room only ? Fixture context managed by dedicated ENTER/EXIT response
statusResponse.FlagByte = 0xC0; // bit7=Decor, bit6=Room only — Fixture context managed by dedicated ENTER/EXIT response
player->SendDirectMessage(statusResponse.Write());
// Send post-tutorial auras so the client unlocks all editor modes.
// These auras signal "tutorial complete" and are lost on map transfer.
// The exterior (HousingMap) sends them via SendPostTutorialAuras ?
// The exterior (HousingMap) sends them via SendPostTutorialAuras —
// the interior must do the same or the editor remains locked.
SendPostTutorialAuras(player);
@@ -864,7 +864,7 @@ bool HouseInteriorMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/
}
else
{
TC_LOG_ERROR("housing", "HouseInteriorMap::AddPlayerToMap: NO HOUSING for player {} ? "
TC_LOG_ERROR("housing", "HouseInteriorMap::AddPlayerToMap: NO HOUSING for player {} — "
"cannot spawn rooms/decor", player->GetGUID().ToString());
}
+2 -2
View File
@@ -72,7 +72,7 @@ public:
/// Despawn a single decor item by its Housing decor GUID.
void DespawnDecorItem(ObjectGuid decorGuid);
/// Get the interior decor GUID ? MeshObject GUID map (for edit mode CREATEs).
/// Get the interior decor GUID → MeshObject GUID map (for edit mode CREATEs).
std::unordered_map<ObjectGuid, ObjectGuid> const& GetDecorGuidMap() const { return _decorGuidToObjGuid; }
/// Send post-tutorial aura packets so the client knows the tutorial is complete
@@ -94,7 +94,7 @@ private:
/// GUIDs of all spawned room MeshObjects, indexed by room GUID
std::unordered_map<ObjectGuid /*roomGuid*/, std::vector<ObjectGuid>> _roomMeshObjects;
/// Decor GUID ? visual object GUID (for despawning individual decor items)
/// Decor GUID → visual object GUID (for despawning individual decor items)
std::unordered_map<ObjectGuid, ObjectGuid> _decorGuidToObjGuid;
};
+49 -48
View File
@@ -34,9 +34,9 @@
#include <queue>
#include <unordered_set>
// Global DB ID generators ? initialized from MAX(id) at server startup
std::atomic<uint64> Housing::s_nextDecorDbId{ 1 };
std::atomic<uint64> Housing::s_nextRoomDbId{ 1 };
// Global DB ID generators — initialized from MAX(id) at server startup
std::atomic<uint64> Housing::s_nextDecorDbId{1};
std::atomic<uint64> Housing::s_nextRoomDbId{1};
Housing::Housing(Player* owner)
: _owner(owner)
@@ -76,7 +76,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
Field* fields = housing->Fetch();
// Expected columns: houseId, neighborhoodGuid, plotIndex, level, favor, settingsFlags, exteriorLocked, houseSize, houseType, ...
// fields[0] = houseId (DB2 entry ID) ? NOT used as GUID counter.
// fields[0] = houseId (DB2 entry ID) — NOT used as GUID counter.
// Housing GUID counter must match HousingPlayerHouseEntity GUID (WorldSession.cpp), which uses battlenetAccountId.
uint32 bnetAccountId = _owner->GetSession()->GetBattlenetAccountId();
_houseGuid = ObjectGuid::Create<HighGuid::Housing>(/*subType*/ 3, /*arg1*/ sRealmList->GetCurrentRealmId().Realm, /*arg2*/ 7, uint64(bnetAccountId));
@@ -258,7 +258,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
if (wrongRoomGuid.IsEmpty())
wrongRoomGuid = guid;
else
hasVisualRoom = true; // Multiple visual rooms ? don't mess with them
hasVisualRoom = true; // Multiple visual rooms — don't mess with them
}
// Replace wrong room with correct one
@@ -270,7 +270,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
uint32 oldEntry = wrongItr->second.RoomEntryId;
uint32 oldSlot = wrongItr->second.SlotIndex;
// Erase from map ? SaveToDB will persist the change on next save
// Erase from map — SaveToDB will persist the change on next save
_rooms.erase(wrongItr);
// Place new room in same slot
@@ -281,7 +281,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
}
}
// No visual room at all ? add one
// No visual room at all — add one
if (!hasVisualRoom && wrongRoomGuid.IsEmpty() && correctVisualRoom)
{
// Find the next free slot (slot 0 is base room)
@@ -348,7 +348,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
if ((!hasBaseRoot || !hasRoofRoot || !hasDoor) && _houseType != 0)
{
TC_LOG_INFO("housing", "Housing::LoadFromDB: Missing starter fixtures (base={}, roof={}, door={}) for house {} ? populating (migration)",
TC_LOG_INFO("housing", "Housing::LoadFromDB: Missing starter fixtures (base={}, roof={}, door={}) for house {} — populating (migration)",
hasBaseRoot, hasRoofRoot, hasDoor, _houseGuid.ToString());
PopulateStarterFixtures();
}
@@ -386,7 +386,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
entry.DecorEntryId = decorId;
entry.Count = qty;
}
TC_LOG_ERROR("housing", "Housing::LoadFromDB: Catalog was empty for house {} ? auto-populated {} starter decor types for player {}",
TC_LOG_ERROR("housing", "Housing::LoadFromDB: Catalog was empty for house {} — auto-populated {} starter decor types for player {}",
_houseGuid.ToString(), uint32(starterDecorWithQty.size()), _owner->GetGUID().ToString());
// Persist the fixup to DB so it only happens once
@@ -422,7 +422,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
// Recalculate budget weights from loaded data
RecalculateBudgets();
// NOTE: FHousingStorage_C is NOT populated at login ? retail flow confirms it is only sent
// NOTE: FHousingStorage_C is NOT populated at login — retail flow confirms it is only sent
// when the player enters edit mode or sends REQUEST_STORAGE. Populating it at login causes
// client crashes (BLZ_ALLOC for HouseDecorGUID) because the client doesn't expect storage
// data in the initial Account entity CREATE. Storage entries (both placed and catalog) are
@@ -591,7 +591,7 @@ HousingResult Housing::Create(ObjectGuid neighborhoodGuid, uint8 plotIndex)
_editorMode = HOUSING_EDITOR_MODE_NONE;
_exteriorLocked = false;
_houseSize = HOUSING_FIXTURE_SIZE_SMALL;
// Racial house style: Night Elf ? 55, Blood Elf ? 56, other Alliance ? 9, other Horde ? 87
// Racial house style: Night Elf → 55, Blood Elf → 56, other Alliance → 9, other Horde → 87
_houseType = HousingMgr::GetRacialWmoDataID(_owner->GetRace(), _owner->GetTeam());
_createTime = static_cast<uint32>(GameTime::GetGameTime());
_hasCustomPosition = false;
@@ -604,13 +604,13 @@ HousingResult Housing::Create(ObjectGuid neighborhoodGuid, uint8 plotIndex)
uint32 bnetAccountId = _owner->GetSession() ? _owner->GetSession()->GetBattlenetAccountId() : 0;
if (bnetAccountId == 0)
{
TC_LOG_ERROR("housing", "Housing::Create: BNetAccountId is 0 for player {} ? falling back to player GUID counter",
TC_LOG_ERROR("housing", "Housing::Create: BNetAccountId is 0 for player {} — falling back to player GUID counter",
_owner->GetGUID().ToString());
bnetAccountId = static_cast<uint32>(_owner->GetGUID().GetCounter());
}
_houseGuid = ObjectGuid::Create<HighGuid::Housing>(/*subType*/ 3, /*arg1*/ sRealmList->GetCurrentRealmId().Realm, /*arg2*/ 7, uint64(bnetAccountId));
TC_LOG_ERROR("housing", "Housing::Create: Player {} (BNetAcct {}) created house on plot {} in neighborhood {} ? HouseGuid={}",
TC_LOG_ERROR("housing", "Housing::Create: Player {} (BNetAcct {}) created house on plot {} in neighborhood {} — HouseGuid={}",
_owner->GetName(), bnetAccountId, plotIndex, _neighborhoodGuid.ToString(), _houseGuid.ToString());
SyncUpdateFields();
@@ -621,7 +621,7 @@ HousingResult Housing::Create(ObjectGuid neighborhoodGuid, uint8 plotIndex)
PlaceRoom(sHousingMgr.GetEntryHallRoomEntryId(), /*slotIndex*/ 0, /*orientation*/ 0, /*mirrored*/ false);
// Also place a default visual room so the interior renders walls/floor/ceiling.
// Base room (18) only provides the geobox boundary ? visual geometry needs a separate room.
// Base room (18) only provides the geobox boundary — visual geometry needs a separate room.
uint32 visualRoom = sHousingMgr.GetDefaultVisualRoomEntry();
if (visualRoom)
{
@@ -633,14 +633,14 @@ HousingResult Housing::Create(ObjectGuid neighborhoodGuid, uint8 plotIndex)
}
else
{
TC_LOG_ERROR("housing", "Housing::Create: PlaceRoom FAILED for visual room entry {} ? result={} ? "
TC_LOG_ERROR("housing", "Housing::Create: PlaceRoom FAILED for visual room entry {} — result={} — "
"interior will be empty for player {}",
visualRoom, visualResult, _owner->GetName());
}
}
else
{
TC_LOG_ERROR("housing", "Housing::Create: No visual room entry found ? interior will be empty for player {}",
TC_LOG_ERROR("housing", "Housing::Create: No visual room entry found — interior will be empty for player {}",
_owner->GetName());
}
@@ -721,7 +721,7 @@ ObjectGuid Housing::StartPlacingNewDecor(uint32 catalogEntryId, HousingResult& r
}
// Generate a GUID for this pending placement.
// Must use subType=1 (decor GUID format) ? subType=0 returns ObjectGuid::Empty!
// Must use subType=1 (decor GUID format) — subType=0 returns ObjectGuid::Empty!
uint64 newDbId = GenerateDecorDbId();
ObjectGuid decorGuid = ObjectGuid::Create<HighGuid::Housing>(
/*subType*/ 1, /*arg1*/ sRealmList->GetCurrentRealmId().Realm,
@@ -928,7 +928,7 @@ HousingResult Housing::PlaceDecor(uint32 decorEntryId, float x, float y, float z
return HOUSING_RESULT_DECOR_NOT_FOUND_IN_STORAGE;
// Generate a new decor guid.
// Must use subType=1 (decor GUID format) ? subType=0 returns ObjectGuid::Empty!
// Must use subType=1 (decor GUID format) — subType=0 returns ObjectGuid::Empty!
uint64 newDbId = GenerateDecorDbId();
ObjectGuid decorGuid = ObjectGuid::Create<HighGuid::Housing>(
/*subType*/ 1, /*arg1*/ sRealmList->GetCurrentRealmId().Realm,
@@ -997,7 +997,7 @@ HousingResult Housing::PlaceDecor(uint32 decorEntryId, float x, float y, float z
CharacterDatabase.Execute(stmt);
}
// Update account decor storage UpdateField (only if storage is populated ? not during LoadFromDB)
// Update account decor storage UpdateField (only if storage is populated — not during LoadFromDB)
if (_storagePopulated && _owner->GetSession())
_owner->GetSession()->GetBattlenetAccount().SetHousingDecorStorageEntry(decorGuid, _houseGuid, decor.SourceType, decor.SourceValue);
@@ -1032,13 +1032,13 @@ uint32 Housing::PlaceStarterDecor()
if (visualRoomGuid.IsEmpty())
{
TC_LOG_ERROR("housing", "Housing::PlaceStarterDecor: No visual room found for house {} ? cannot place starter decor",
TC_LOG_ERROR("housing", "Housing::PlaceStarterDecor: No visual room found for house {} — cannot place starter decor",
_houseGuid.ToString());
return 0;
}
// Sniff-verified starter decor positions (room-local coordinates in the visual room).
// Both factions use the same Room 1 geometry ? only the DecorEntryIDs differ.
// Both factions use the same Room 1 geometry — only the DecorEntryIDs differ.
// Positions from horde_housing sniff: painting on wall, table on floor, chandelier on ceiling,
// 2nd painting on opposite wall, fireplace against wall.
struct StarterDecorPlacement
@@ -1064,7 +1064,7 @@ uint32 Housing::PlaceStarterDecor()
}
else
{
// Alliance starter decor ? same room geometry, faction-specific items.
// Alliance starter decor — same room geometry, faction-specific items.
// Using equivalent positions (wall art, table, ceiling fixture, wall art, hearth).
placements = {
{ 389, 11.458f, 7.588f, 2.984f, 0.0f, 0.0f, -0.9999962f, 0.0027621f }, // wall art
@@ -1089,7 +1089,7 @@ uint32 Housing::PlaceStarterDecor()
if (result == HOUSING_RESULT_SUCCESS)
++placedCount;
else
TC_LOG_ERROR("housing", "Housing::PlaceStarterDecor: Failed to place decor entry {} ? result={}",
TC_LOG_ERROR("housing", "Housing::PlaceStarterDecor: Failed to place decor entry {} — result={}",
p.DecorEntryId, result);
}
@@ -1116,8 +1116,8 @@ HousingResult Housing::MoveDecor(ObjectGuid decorGuid, float x, float y, float z
if (itr == _placedDecor.end())
return HOUSING_RESULT_DECOR_NOT_FOUND;
// Sniff-verified: Lock?Move is valid (the locker is the one moving).
// Lock only prevents OTHER editors from modifying ? not the owner.
// Sniff-verified: Lock→Move is valid (the locker is the one moving).
// Lock only prevents OTHER editors from modifying — not the owner.
// TODO: When multi-editor support is added, track LockedByGuid and check here.
PlacedDecor& decor = itr->second;
@@ -1158,9 +1158,9 @@ HousingResult Housing::RemoveDecor(ObjectGuid decorGuid)
if (itr == _placedDecor.end())
return HOUSING_RESULT_DECOR_NOT_FOUND;
// Sniff-verified: Lock?Remove is a valid retail flow (packet #27117 LOCK then
// Sniff-verified: Lock→Remove is a valid retail flow (packet #27117 LOCK then
// #27139 REMOVE with Result=0). The house owner can always remove their own decor.
// Lock only prevents OTHER editors from modifying ? not the owner.
// Lock only prevents OTHER editors from modifying — not the owner.
// Refund WeightCost budget (route to correct budget based on room)
uint32 decorEntryId = itr->second.DecorEntryId;
@@ -1192,7 +1192,7 @@ HousingResult Housing::RemoveDecor(ObjectGuid decorGuid)
_placedDecor.erase(itr);
// Immediate persist for crash safety ? delete the placed decor row
// Immediate persist for crash safety — delete the placed decor row
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_HOUSING_DECOR_SINGLE);
stmt->setUInt64(0, _owner->GetGUID().GetCounter());
@@ -1340,7 +1340,7 @@ HousingResult Housing::PlaceRoom(uint32 roomEntryId, uint32 slotIndex, uint32 or
}
// NOTE: Doorway components (Type 7) are OPTIONAL in the DB2.
// Standard rooms (1-15) have 0 doorway components ? they use wall segments (Type 1) instead.
// Standard rooms (1-15) have 0 doorway components — they use wall segments (Type 1) instead.
// Only prefab/custom rooms (113+) have explicit doorway components.
// Retail places rooms without doorways, so we don't enforce this check.
@@ -1543,7 +1543,7 @@ bool Housing::IsRoomGraphConnectedWithout(ObjectGuid excludeRoomGuid) const
uint32 currentSlot = currentItr->second.SlotIndex;
// Check adjacent slots (slot ± 1)
// Check adjacent slots (slot ± 1)
for (int32 offset : { -1, 1 })
{
uint32 adjacentSlot = currentSlot + offset;
@@ -1699,7 +1699,7 @@ HousingResult Housing::SelectFixtureOption(uint32 fixturePointId, uint32 optionI
if (_houseGuid.IsEmpty())
return HOUSING_RESULT_HOUSE_NOT_FOUND;
// Root fixture selections (optionId == 0) use componentID as fixturePointId ? skip hook validation
// Root fixture selections (optionId == 0) use componentID as fixturePointId — skip hook validation
if (optionId != 0)
{
// Validate hook exists in DB2
@@ -1721,7 +1721,7 @@ HousingResult Housing::SelectFixtureOption(uint32 fixturePointId, uint32 optionI
// Validate component type matches hook's expected type
if (compEntry->Type != hookEntry->ExteriorComponentTypeID)
{
TC_LOG_DEBUG("housing", "SelectFixtureOption: type mismatch ? component {} type {} vs hook {} expected type {}",
TC_LOG_DEBUG("housing", "SelectFixtureOption: type mismatch — component {} type {} vs hook {} expected type {}",
optionId, compEntry->Type, fixturePointId, hookEntry->ExteriorComponentTypeID);
return HOUSING_RESULT_GENERIC_FAILURE;
}
@@ -1760,7 +1760,7 @@ HousingResult Housing::SelectFixtureOption(uint32 fixturePointId, uint32 optionI
ExteriorComponentEntry const* oldComp = sExteriorComponentStore.LookupEntry(fixture.FixturePointId);
if (oldComp && oldComp->Type == newType)
{
TC_LOG_INFO("housing", "SelectFixtureOption: replacing root type {} ? removing old comp {} in favor of new comp {}",
TC_LOG_INFO("housing", "SelectFixtureOption: replacing root type {} — removing old comp {} in favor of new comp {}",
newType, pointId, fixturePointId);
toRemove.push_back(pointId);
}
@@ -1796,7 +1796,7 @@ HousingResult Housing::SelectFixtureOption(uint32 fixturePointId, uint32 optionI
fixture.FixturePointId = fixturePointId;
fixture.OptionId = optionId;
// Immediate persist ? use REPLACE semantics (delete old + insert new)
// Immediate persist — use REPLACE semantics (delete old + insert new)
if (!isNew)
PersistFixtureToDB(fixturePointId, optionId);
else
@@ -1855,7 +1855,7 @@ HousingResult Housing::RemoveFixture(uint32 componentID, uint32* outHookID /*= n
_fixtures.erase(itr);
// Immediate persist ? delete single fixture from DB
// Immediate persist — delete single fixture from DB
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_HOUSING_FIXTURE_SINGLE);
stmt->setUInt64(0, _owner->GetGUID().GetCounter());
@@ -1905,7 +1905,7 @@ std::unordered_map<uint8, uint32> Housing::GetRootComponentOverrides() const
// Build override map for player-selected root components per type.
// Core fixtures (OptionId == 0) represent the player's choice for a structural root type.
// These include both base variants (ParentComponentID == 0) and color/style variants
// (ParentComponentID != 0) ? color variants are valid selections via SetCoreFixture.
// (ParentComponentID != 0) — color variants are valid selections via SetCoreFixture.
std::unordered_map<uint8, uint32> result;
for (auto const& [pointId, fixture] : _fixtures)
@@ -1916,26 +1916,26 @@ std::unordered_map<uint8, uint32> Housing::GetRootComponentOverrides() const
ExteriorComponentEntry const* comp = sExteriorComponentStore.LookupEntry(fixture.FixturePointId);
if (!comp)
{
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: fixturePointId={} ? DB2 lookup failed", fixture.FixturePointId);
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: fixturePointId={} — DB2 lookup failed", fixture.FixturePointId);
continue;
}
// Only structural root types (Base=9, Roof=10) are valid here.
// Fixture types (Door=11, Window=12, etc.) stored with OptionId=0 would be invalid.
if (comp->Type != HOUSING_FIXTURE_TYPE_BASE && comp->Type != HOUSING_FIXTURE_TYPE_ROOF)
{
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: comp={} type={} ? not a structural root type, skipping",
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: comp={} type={} — not a structural root type, skipping",
comp->ID, comp->Type);
continue;
}
if (_houseType != 0 && comp->HouseExteriorWmoDataID != static_cast<uint32>(_houseType))
{
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: comp={} type={} ? wmo={} != houseType={} (wrong style)",
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: comp={} type={} — wmo={} != houseType={} (wrong style)",
comp->ID, comp->Type, comp->HouseExteriorWmoDataID, _houseType);
continue;
}
result[comp->Type] = fixture.FixturePointId;
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: type={} ? comp={} (wmo={}, parentComp={})",
TC_LOG_DEBUG("housing", "GetRootComponentOverrides: type={} → comp={} (wmo={}, parentComp={})",
comp->Type, fixture.FixturePointId, comp->HouseExteriorWmoDataID, comp->ParentComponentID);
}
@@ -1947,7 +1947,7 @@ std::unordered_map<uint8, uint32> Housing::GetRootComponentOverrides() const
uint32 Housing::GetCoreExteriorComponentID() const
{
// The core fixture is the primary component set via SetCoreFixture (OptionId == 0).
// It can be any root type ? Base (9) for Alliance, or different types for Horde.
// It can be any root type — Base (9) for Alliance, or different types for Horde.
for (auto const& [pointId, fixture] : _fixtures)
{
if (fixture.OptionId == 0)
@@ -1960,7 +1960,7 @@ uint32 Housing::GetCoreExteriorComponentID() const
return fixture.FixturePointId;
}
}
// No explicit core fixture set ? find first default root component for this house's WMO data ID.
// No explicit core fixture set — find first default root component for this house's WMO data ID.
if (_houseType > 0)
{
auto const* roots = sHousingMgr.GetRootComponentsForWmoData(static_cast<uint32>(_houseType));
@@ -1984,7 +1984,7 @@ uint32 Housing::GetCoreExteriorComponentID() const
}
else
{
TC_LOG_ERROR("housing", "Housing::GetCoreExteriorComponentID: No fixtures and houseType=0 ? cannot determine base component");
TC_LOG_ERROR("housing", "Housing::GetCoreExteriorComponentID: No fixtures and houseType=0 — cannot determine base component");
}
return 0;
}
@@ -2271,7 +2271,7 @@ void Housing::SyncUpdateFields()
houseEntity.SetPlotIndex(static_cast<int32>(_plotIndex));
houseEntity.SetLevel(_level);
houseEntity.SetFavor(_favor64);
// Send MAX budgets ? the client computes remaining locally by summing placed decor weight
// Send MAX budgets — the client computes remaining locally by summing placed decor weight
// from FHousingStorage_C entries. Sending (max - used) would cause double-subtraction.
houseEntity.SetBudgets(
GetMaxInteriorDecorBudget(),
@@ -2296,11 +2296,11 @@ void Housing::PopulateCatalogStorageEntries()
Battlenet::Account& account = _owner->GetSession()->GetBattlenetAccount();
// 1. Placed decor ? HouseGUID=_houseGuid, SourceType from decor instance
// 1. Placed decor → HouseGUID=_houseGuid, SourceType from decor instance
for (auto const& [decorGuid, decor] : _placedDecor)
account.SetHousingDecorStorageEntry(decorGuid, _houseGuid, decor.SourceType, decor.SourceValue);
// 2. Catalog (unplaced/available) entries ? HouseGUID=Empty, SourceType=0
// 2. Catalog (unplaced/available) entries → HouseGUID=Empty, SourceType=0
// Sniff-verified: items in storage have HouseGUID=Empty, placed items have non-empty HouseGUID.
// Catalog Count includes placed instances, so subtract them to get the storage-only count.
std::unordered_map<uint32, uint32> placedCountByEntry;
@@ -2504,7 +2504,7 @@ void Housing::PopulateStarterFixtures()
{
if (existingRootTypes.count(fixtureType))
{
TC_LOG_INFO("housing", "Housing::PopulateStarterFixtures: type={} already has a root ? skipping",
TC_LOG_INFO("housing", "Housing::PopulateStarterFixtures: type={} already has a root — skipping",
fixtureType);
continue;
}
@@ -2512,7 +2512,7 @@ void Housing::PopulateStarterFixtures()
uint32 compID = sHousingMgr.GetDefaultFixtureForType(fixtureType, _houseType, _houseSize);
if (!compID)
{
TC_LOG_ERROR("housing", "Housing::PopulateStarterFixtures: No default component for type={} wmo={} size={} ? skipping",
TC_LOG_ERROR("housing", "Housing::PopulateStarterFixtures: No default component for type={} wmo={} size={} — skipping",
fixtureType, _houseType, _houseSize);
continue;
}
@@ -2614,3 +2614,4 @@ void Housing::PopulateStarterFixtures()
}
}
}
+4 -4
View File
@@ -87,7 +87,7 @@ public:
explicit Housing(Player* owner);
// Global DB ID generators ? must be called once during server startup
// Global DB ID generators — must be called once during server startup
// before any Housing objects are loaded, to prevent cross-player ID collisions.
static void InitializeDbIdGenerators();
@@ -121,7 +121,7 @@ public:
void SetInInterior(bool interior) { _isInInterior = interior; }
bool IsInInterior() const { return _isInInterior; }
// Decor operations ? StartPlacingNewDecor creates a pending placement, PlaceDecorWithGuid commits it
// Decor operations — StartPlacingNewDecor creates a pending placement, PlaceDecorWithGuid commits it
ObjectGuid StartPlacingNewDecor(uint32 catalogEntryId, HousingResult& result);
uint32 GetPendingPlacementEntryId(ObjectGuid decorGuid) const;
void CancelPendingPlacement(ObjectGuid decorGuid);
@@ -226,7 +226,7 @@ public:
void ResetStoragePopulated() { _storagePopulated = false; }
// Populate ALL decor entries (placed + catalog) into the Account entity's FHousingStorage_C.
// Called on-demand by REQUEST_STORAGE handler. Retail does NOT populate storage at login ?
// Called on-demand by REQUEST_STORAGE handler. Retail does NOT populate storage at login —
// FHousingStorage_C is only sent when the player enters edit mode or requests storage.
void PopulateCatalogStorageEntries();
@@ -269,7 +269,7 @@ private:
float _houseFacing = 0.0f;
ObjectGuid _cosmeticOwnerGuid; // Display owner for guild housing
bool _hasCustomPosition = false;
bool _storagePopulated = false; // True after PopulateCatalogStorageEntries() ? gates Account entity updates
bool _storagePopulated = false; // True after PopulateCatalogStorageEntries() — gates Account entity updates
bool _photoSharingAuthorized = false; // Per-session photo sharing authorization state
// WeightCost-based budget tracking
File diff suppressed because it is too large Load Diff
+187 -187
View File
@@ -88,8 +88,7 @@ namespace
uint32 counter4, uint32 counter5)
: _playerGuid(playerGuid)
, _counter1(counter1), _counter2(counter2), _counter3(counter3)
, _counter4(counter4), _counter5(counter5) {
}
, _counter4(counter4), _counter5(counter5) { }
bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) override
{
@@ -676,25 +675,25 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
}
else
{
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: Plot {} spawning house with ExteriorComponentID={}, WmoDataID={}",
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: Plot {} spawning house with ExteriorComponentID={}, WmoDataID={}",
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
auto fixtureOverrides = housing->GetFixtureOverrideMap();
FixtureOverrideMap const* overridesPtr = fixtureOverrides.empty() ? nullptr : &fixtureOverrides;
auto rootOverrides = housing->GetRootComponentOverrides();
RootOverrideMap const* rootOvrPtr = &rootOverrides;
auto fixtureOverrides = housing->GetFixtureOverrideMap();
FixtureOverrideMap const* overridesPtr = fixtureOverrides.empty() ? nullptr : &fixtureOverrides;
auto rootOverrides = housing->GetRootComponentOverrides();
RootOverrideMap const* rootOvrPtr = &rootOverrides;
GameObject* go = nullptr;
if (housing->HasCustomPosition())
{
Position customPos = housing->GetHousePosition();
go = SpawnHouseForPlot(plotIdx, &customPos, exteriorComponentID, houseExteriorWmoDataID, overridesPtr, rootOvrPtr);
}
else
go = SpawnHouseForPlot(plotIdx, nullptr, exteriorComponentID, houseExteriorWmoDataID, overridesPtr, rootOvrPtr);
GameObject* go = nullptr;
if (housing->HasCustomPosition())
{
Position customPos = housing->GetHousePosition();
go = SpawnHouseForPlot(plotIdx, &customPos, exteriorComponentID, houseExteriorWmoDataID, overridesPtr, rootOvrPtr);
}
else
go = SpawnHouseForPlot(plotIdx, nullptr, exteriorComponentID, houseExteriorWmoDataID, overridesPtr, rootOvrPtr);
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: SpawnHouseForPlot result for plot {}: {}",
plotIdx, go ? go->GetGUID().ToString() : "FAILED");
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: SpawnHouseForPlot result for plot {}: {}",
plotIdx, go ? go->GetGUID().ToString() : "FAILED");
} // else (valid exteriorComponentID && houseExteriorWmoDataID)
}
else
@@ -839,186 +838,187 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
ObjectGuid neighborhoodGuid = housing->GetNeighborhoodGuid();
uint8 deferredPlotIndex = plotIndex;
player->m_Events.AddEventAtOffset([playerGuid, deferredPlotIndex, houseGuid, neighborhoodGuid]()
{
Player* p = ObjectAccessor::FindPlayer(playerGuid);
if (!p || !p->IsInWorld())
return;
HousingMap* hMap = dynamic_cast<HousingMap*>(p->GetMap());
if (!hMap)
return;
AreaTrigger* plotAt = hMap->GetPlotAreaTrigger(deferredPlotIndex);
if (!plotAt)
{
Player* p = ObjectAccessor::FindPlayer(playerGuid);
if (!p || !p->IsInWorld())
return;
TC_LOG_ERROR("housing", "HousingMap deferred ENTER_PLOT: No AT for plot {} player {}",
deferredPlotIndex, playerGuid.ToString());
return;
}
HousingMap* hMap = dynamic_cast<HousingMap*>(p->GetMap());
if (!hMap)
return;
WorldPackets::Neighborhood::NeighborhoodPlayerEnterPlot enterPlot;
enterPlot.PlotAreaTriggerGuid = plotAt->GetGUID();
p->SendDirectMessage(enterPlot.Write());
AreaTrigger* plotAt = hMap->GetPlotAreaTrigger(deferredPlotIndex);
if (!plotAt)
// Re-send HouseStatusResponse + GetPlayerPermissionsResponse after ENTER_PLOT.
// ENTER_PLOT's client handler (vtable[22]) resets the editor state including the
// stored HouseGuid; we must re-establish it via HouseStatusResponse (vtable[25])
// then arm the editor gate check via GetPlayerPermissionsResponse (vtable[24]).
{
WorldPackets::Housing::HousingHouseStatusResponse statusResponse;
statusResponse.HouseGuid = houseGuid;
statusResponse.AccountGuid = p->GetSession()->GetBattlenetAccountGUID();
statusResponse.OwnerPlayerGuid = playerGuid;
statusResponse.NeighborhoodGuid = neighborhoodGuid;
statusResponse.Status = 0;
statusResponse.FlagByte = 0xE0; // bit7=houseEditing, bit6=plotEntry, bit5=houseEntry
p->SendDirectMessage(statusResponse.Write());
WorldPackets::Housing::HousingGetPlayerPermissionsResponse permResponse;
permResponse.HouseGuid = houseGuid;
permResponse.ResultCode = 0;
permResponse.PermissionFlags = 0xE0; // owner: all permissions
p->SendDirectMessage(permResponse.Write());
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Sent HouseStatus+Permissions for owner {}",
playerGuid.ToString());
}
// Send SMSG_HOUSING_FIXTURE_CREATE_BASIC_HOUSE_RESPONSE with Result=0.
// This triggers the client's fixture frame initialization: the handler
// calls teardown + rebuild, which sets the fixture manager's house GUID
// (state+96/+104) from the NeighborhoodSystem. This MUST come BEFORE any
// fixture entity CREATEs, because the CREATE callback compares each
// entity's FHousingFixture_C::HouseGUID against state+96/+104 ? if they
// don't match, the entity is silently skipped and the hook shows "None".
{
WorldPackets::Housing::HousingFixtureCreateBasicHouseResponse fixtureInit;
fixtureInit.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
p->SendDirectMessage(fixtureInit.Write());
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Sent CREATE_BASIC_HOUSE_RESPONSE (fixture init) for plot {}",
deferredPlotIndex);
}
// Re-CREATE ALL fixture MeshObjects for this plot AFTER the rebuild.
// The rebuild (triggered by CREATE_BASIC_HOUSE_RESPONSE above) sets the
// fixture manager's house GUID. The CREATE callback then compares each
// entity's HouseGUID against it ? if they match, it registers the entity
// at its hook point. Without this re-CREATE, entities that were already
// sent during the initial map load are never re-processed.
// (Same pattern as the decor fix: re-CREATE after the system is ready.)
{
auto const& meshMap = hMap->GetPlotMeshObjects();
auto meshItr = meshMap.find(deferredPlotIndex);
if (meshItr != meshMap.end())
{
TC_LOG_ERROR("housing", "HousingMap deferred ENTER_PLOT: No AT for plot {} player {}",
deferredPlotIndex, playerGuid.ToString());
return;
}
UpdateData fixtureUpdate(p->GetMapId());
uint32 fixtureCreateCount = 0;
WorldPackets::Neighborhood::NeighborhoodPlayerEnterPlot enterPlot;
enterPlot.PlotAreaTriggerGuid = plotAt->GetGUID();
p->SendDirectMessage(enterPlot.Write());
// Re-send HouseStatusResponse + GetPlayerPermissionsResponse after ENTER_PLOT.
// ENTER_PLOT's client handler (vtable[22]) resets the editor state including the
// stored HouseGuid; we must re-establish it via HouseStatusResponse (vtable[25])
// then arm the editor gate check via GetPlayerPermissionsResponse (vtable[24]).
{
WorldPackets::Housing::HousingHouseStatusResponse statusResponse;
statusResponse.HouseGuid = houseGuid;
statusResponse.AccountGuid = p->GetSession()->GetBattlenetAccountGUID();
statusResponse.OwnerPlayerGuid = playerGuid;
statusResponse.NeighborhoodGuid = neighborhoodGuid;
statusResponse.Status = 0;
statusResponse.FlagByte = 0xE0; // bit7=houseEditing, bit6=plotEntry, bit5=houseEntry
p->SendDirectMessage(statusResponse.Write());
WorldPackets::Housing::HousingGetPlayerPermissionsResponse permResponse;
permResponse.HouseGuid = houseGuid;
permResponse.ResultCode = 0;
permResponse.PermissionFlags = 0xE0; // owner: all permissions
p->SendDirectMessage(permResponse.Write());
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Sent HouseStatus+Permissions for owner {}",
playerGuid.ToString());
}
// Send SMSG_HOUSING_FIXTURE_CREATE_BASIC_HOUSE_RESPONSE with Result=0.
// This triggers the client's fixture frame initialization: the handler
// calls teardown + rebuild, which sets the fixture manager's house GUID
// (state+96/+104) from the NeighborhoodSystem. This MUST come BEFORE any
// fixture entity CREATEs, because the CREATE callback compares each
// entity's FHousingFixture_C::HouseGUID against state+96/+104 ? if they
// don't match, the entity is silently skipped and the hook shows "None".
{
WorldPackets::Housing::HousingFixtureCreateBasicHouseResponse fixtureInit;
fixtureInit.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
p->SendDirectMessage(fixtureInit.Write());
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Sent CREATE_BASIC_HOUSE_RESPONSE (fixture init) for plot {}",
deferredPlotIndex);
}
// Re-CREATE ALL fixture MeshObjects for this plot AFTER the rebuild.
// The rebuild (triggered by CREATE_BASIC_HOUSE_RESPONSE above) sets the
// fixture manager's house GUID. The CREATE callback then compares each
// entity's HouseGUID against it ? if they match, it registers the entity
// at its hook point. Without this re-CREATE, entities that were already
// sent during the initial map load are never re-processed.
// (Same pattern as the decor fix: re-CREATE after the system is ready.)
{
auto const& meshMap = hMap->GetPlotMeshObjects();
auto meshItr = meshMap.find(deferredPlotIndex);
if (meshItr != meshMap.end())
for (ObjectGuid const& meshGuid : meshItr->second)
{
UpdateData fixtureUpdate(p->GetMapId());
uint32 fixtureCreateCount = 0;
for (ObjectGuid const& meshGuid : meshItr->second)
MeshObject* meshObj = hMap->GetMeshObject(meshGuid);
if (meshObj && meshObj->IsInWorld() && meshObj->m_housingFixtureData.has_value())
{
MeshObject* meshObj = hMap->GetMeshObject(meshGuid);
if (meshObj && meshObj->IsInWorld() && meshObj->m_housingFixtureData.has_value())
{
meshObj->BuildCreateUpdateBlockForPlayer(&fixtureUpdate, p);
p->m_clientGUIDs.insert(meshGuid);
++fixtureCreateCount;
}
meshObj->BuildCreateUpdateBlockForPlayer(&fixtureUpdate, p);
p->m_clientGUIDs.insert(meshGuid);
++fixtureCreateCount;
}
if (fixtureCreateCount > 0)
{
WorldPacket fixturePacket;
fixtureUpdate.BuildPacket(&fixturePacket);
p->SendDirectMessage(&fixturePacket);
}
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Re-CREATE {} fixture MeshObjects for plot {} (post-rebuild)",
fixtureCreateCount, deferredPlotIndex);
}
}
// Proactively populate FHousingStorage_C (decor list) and budget fields.
// At login, PopulateCatalogStorageEntries() is NOT called to avoid crashes when
// storage data appears in the initial Account entity CREATE.
//
// CRITICAL: Must send Account as CREATE (not VALUES_UPDATE). The initial login
// Account CREATE had an empty FHousingStorage_C.Decor MapUpdateField.
// A VALUES_UPDATE for a MapUpdateField that was empty at CREATE time does NOT
// properly convey new entries to the client ? the client never processes them.
// Sending a second CREATE with all current data works because the client handles
// re-CREATE for an existing entity gracefully (replaces the old data).
//
// Also bundle ALL decor MeshObject CREATEs in the SAME UPDATE_OBJECT packet.
// The client correlates MeshObject FHousingDecor_C.DecorGUID with Account
// FHousingStorage_C entries to build the Placed Decor list. If they arrive in
// separate packets, the client may not retroactively associate them.
if (Housing* housing = p->GetHousing())
if (fixtureCreateCount > 0)
{
WorldPacket fixturePacket;
fixtureUpdate.BuildPacket(&fixturePacket);
p->SendDirectMessage(&fixturePacket);
}
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Re-CREATE {} fixture MeshObjects for plot {} (post-rebuild)",
fixtureCreateCount, deferredPlotIndex);
}
}
// Proactively populate FHousingStorage_C (decor list) and budget fields.
// At login, PopulateCatalogStorageEntries() is NOT called to avoid crashes when
// storage data appears in the initial Account entity CREATE.
//
// CRITICAL: Must send Account as CREATE (not VALUES_UPDATE). The initial login
// Account CREATE had an empty FHousingStorage_C.Decor MapUpdateField.
// A VALUES_UPDATE for a MapUpdateField that was empty at CREATE time does NOT
// properly convey new entries to the client ? the client never processes them.
// Sending a second CREATE with all current data works because the client handles
// re-CREATE for an existing entity gracefully (replaces the old data).
//
// Also bundle ALL decor MeshObject CREATEs in the SAME UPDATE_OBJECT packet.
// The client correlates MeshObject FHousingDecor_C.DecorGUID with Account
// FHousingStorage_C entries to build the Placed Decor list. If they arrive in
// separate packets, the client may not retroactively associate them.
if (Housing* housing = p->GetHousing())
{
housing->PopulateCatalogStorageEntries();
housing->SyncUpdateFields();
WorldSession* session = p->GetSession();
UpdateData storageUpdate(p->GetMapId());
WorldPacket storagePacket;
// Account entity as CREATE (includes full FHousingStorage_C with Decor map)
session->GetBattlenetAccount().BuildCreateUpdateBlockForPlayer(&storageUpdate, p);
p->m_clientGUIDs.insert(session->GetBattlenetAccount().GetGUID());
// HousingPlayerHouseEntity (budgets)
if (p->HaveAtClient(&session->GetHousingPlayerHouseEntity()))
session->GetHousingPlayerHouseEntity().BuildValuesUpdateBlockForPlayer(&storageUpdate, p);
else
{
housing->PopulateCatalogStorageEntries();
housing->SyncUpdateFields();
WorldSession* session = p->GetSession();
UpdateData storageUpdate(p->GetMapId());
WorldPacket storagePacket;
// Account entity as CREATE (includes full FHousingStorage_C with Decor map)
session->GetBattlenetAccount().BuildCreateUpdateBlockForPlayer(&storageUpdate, p);
p->m_clientGUIDs.insert(session->GetBattlenetAccount().GetGUID());
// HousingPlayerHouseEntity (budgets)
if (p->HaveAtClient(&session->GetHousingPlayerHouseEntity()))
session->GetHousingPlayerHouseEntity().BuildValuesUpdateBlockForPlayer(&storageUpdate, p);
else
{
session->GetHousingPlayerHouseEntity().BuildCreateUpdateBlockForPlayer(&storageUpdate, p);
p->m_clientGUIDs.insert(session->GetHousingPlayerHouseEntity().GetGUID());
}
// Bundle ALL decor MeshObject CREATEs so the client can correlate
// FHousingDecor_C.DecorGUID with FHousingStorage_C entries in one pass.
uint32 meshCreateCount = 0;
for (auto const& [decorGuid, meshObjGuid] : hMap->GetDecorGuidMap())
{
MeshObject* meshObj = hMap->GetMeshObject(meshObjGuid);
if (!meshObj || !meshObj->IsInWorld())
continue;
meshObj->BuildCreateUpdateBlockForPlayer(&storageUpdate, p);
p->m_clientGUIDs.insert(meshObjGuid);
++meshCreateCount;
}
storageUpdate.BuildPacket(&storagePacket);
p->SendDirectMessage(&storagePacket);
session->GetBattlenetAccount().ClearUpdateMask(true);
session->GetHousingPlayerHouseEntity().ClearUpdateMask(true);
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Sent Account CREATE + {} decor MeshObject CREATEs + budget for player {}",
meshCreateCount, playerGuid.ToString());
session->GetHousingPlayerHouseEntity().BuildCreateUpdateBlockForPlayer(&storageUpdate, p);
p->m_clientGUIDs.insert(session->GetHousingPlayerHouseEntity().GetGUID());
}
// Post-tutorial auras first (slots 8,9,50), then plot enter auras (slots 50,56,9).
// Retail applies tutorial-done auras once (at quest reward) but we re-send each
// map entry since they don't exist in DB2 and can't persist as real auras.
hMap->SendPostTutorialAuras(p);
hMap->SendPlotEnterSpellPackets(p, deferredPlotIndex);
// Bundle ALL decor MeshObject CREATEs so the client can correlate
// FHousingDecor_C.DecorGUID with FHousingStorage_C entries in one pass.
uint32 meshCreateCount = 0;
for (auto const& [decorGuid, meshObjGuid] : hMap->GetDecorGuidMap())
{
MeshObject* meshObj = hMap->GetMeshObject(meshObjGuid);
if (!meshObj || !meshObj->IsInWorld())
continue;
// Diagnostic: print AT position vs player position for OutsidePlotBounds debugging
float dist2d = p->GetExactDist2d(plotAt);
float dist3d = p->GetExactDist(plotAt);
bool inBox = p->IsWithinBox(*plotAt, 35.0f, 30.0f, 47.0f); // half-extents from SQL ShapeData
meshObj->BuildCreateUpdateBlockForPlayer(&storageUpdate, p);
p->m_clientGUIDs.insert(meshObjGuid);
++meshCreateCount;
}
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: player {} plot {} AT {}\n"
" AT pos: ({:.1f}, {:.1f}, {:.1f}, facing={:.3f})\n"
" Player pos: ({:.1f}, {:.1f}, {:.1f})\n"
" Dist2D={:.1f} Dist3D={:.1f} InBox={} HasPlayers={}",
playerGuid.ToString(), deferredPlotIndex, plotAt->GetGUID().ToString(),
plotAt->GetPositionX(), plotAt->GetPositionY(), plotAt->GetPositionZ(), plotAt->GetOrientation(),
p->GetPositionX(), p->GetPositionY(), p->GetPositionZ(),
dist2d, dist3d, inBox,
plotAt->HasAreaTriggerFlag(AreaTriggerFieldFlags::HasPlayers));
}, Milliseconds(500));
storageUpdate.BuildPacket(&storagePacket);
p->SendDirectMessage(&storagePacket);
session->GetBattlenetAccount().ClearUpdateMask(true);
session->GetHousingPlayerHouseEntity().ClearUpdateMask(true);
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: Sent Account CREATE + {} decor MeshObject CREATEs + budget for player {}",
meshCreateCount, playerGuid.ToString());
}
// Post-tutorial auras first (slots 8,9,50), then plot enter auras (slots 50,56,9).
// Retail applies tutorial-done auras once (at quest reward) but we re-send each
// map entry since they don't exist in DB2 and can't persist as real auras.
hMap->SendPostTutorialAuras(p);
hMap->SendPlotEnterSpellPackets(p, deferredPlotIndex);
// Diagnostic: print AT position vs player position for OutsidePlotBounds debugging
float dist2d = p->GetExactDist2d(plotAt);
float dist3d = p->GetExactDist(plotAt);
bool inBox = p->IsWithinBox(*plotAt, 35.0f, 30.0f, 47.0f); // half-extents from SQL ShapeData
TC_LOG_DEBUG("housing", "HousingMap deferred ENTER_PLOT: player {} plot {} AT {}\n"
" AT pos: ({:.1f}, {:.1f}, {:.1f}, facing={:.3f})\n"
" Player pos: ({:.1f}, {:.1f}, {:.1f})\n"
" Dist2D={:.1f} Dist3D={:.1f} InBox={} HasPlayers={}",
playerGuid.ToString(), deferredPlotIndex, plotAt->GetGUID().ToString(),
plotAt->GetPositionX(), plotAt->GetPositionY(), plotAt->GetPositionZ(), plotAt->GetOrientation(),
p->GetPositionX(), p->GetPositionY(), p->GetPositionZ(),
dist2d, dist3d, inBox,
plotAt->HasAreaTriggerFlag(AreaTriggerFieldFlags::HasPlayers));
}, Milliseconds(500));
}
// Start the periodic housing WorldState counter timer.
@@ -1837,7 +1837,7 @@ void HousingMap::SpawnRoomForPlot(uint8 plotIndex, Position const& housePos,
// The client processes fragments independently, so the extra fragments are harmless.
int32 HOUSE_ROOM_ID = static_cast<int32>(sHousingMgr.GetBaseRoomEntryId());
static constexpr int32 ROOM_FLAGS = 1; // BASE_ROOM
static constexpr int32 ROOM_FLAGS = 1; // BASE_ROOM
static constexpr int32 ROOM_COMPONENT_ID = 196; // Sniff-verified: same for alliance+horde
// Clean up any existing room entities for this plot
@@ -1852,7 +1852,7 @@ void HousingMap::SpawnRoomForPlot(uint8 plotIndex, Position const& housePos,
// 2. RoomWmoData ? Geobox bounds (bounding box for OutsidePlotBounds check)
// Sniff fallback: (-35,-30,-1.01)?(35,30,125.01)
float geoMinX = -35.0f, geoMinY = -30.0f, geoMinZ = -1.01f;
float geoMaxX = 35.0f, geoMaxY = 30.0f, geoMaxZ = 125.01f;
float geoMaxX = 35.0f, geoMaxY = 30.0f, geoMaxZ = 125.01f;
RoomWmoDataEntry const* wmoData = roomWmoDataID ? sRoomWmoDataStore.LookupEntry(roomWmoDataID) : nullptr;
if (wmoData)
{
@@ -2046,7 +2046,7 @@ MeshObject* HousingMap::SpawnHouseMeshObject(uint8 plotIndex, int32 fileDataID,
// the client can't distinguish them and reports "Fixture not found".
// Use subType=5 (fixture), realm, sequential counter, houseGuid counter.
// Generate a unique fixture GUID using a monotonic counter.
static std::atomic<uint32> s_fixtureCounter{ 1 };
static std::atomic<uint32> s_fixtureCounter{1};
uint32 fixtureSeq = s_fixtureCounter.fetch_add(1, std::memory_order_relaxed);
ObjectGuid fixtureGuid = ObjectGuid::Create<HighGuid::Housing>(
/*subType*/ 5, /*arg1*/ sRealmList->GetCurrentRealmId().Realm,
@@ -2775,7 +2775,7 @@ MeshObject* HousingMap::SpawnDecorItem(uint8 plotIndex, Housing::PlacedDecor con
float roomFacing = roomWorldPos.GetOrientation();
float cosF = std::cos(roomFacing);
float sinF = std::sin(roomFacing);
localX = cosF * dx + sinF * dy;
localX = cosF * dx + sinF * dy;
localY = -sinF * dx + cosF * dy;
localZ = worldZ - roomWorldPos.GetPositionZ();
}
+6 -6
View File
@@ -56,11 +56,11 @@ public:
void AddPlayerHousing(ObjectGuid playerGuid, Housing* housing);
void RemovePlayerHousing(ObjectGuid playerGuid);
// Fixture override map: hookID ? ExteriorComponentID from player's fixture selections.
// Fixture override map: hookID → ExteriorComponentID from player's fixture selections.
// When provided, SpawnExtCompTree uses these instead of the DB2 default component at each hook.
using FixtureOverrideMap = std::unordered_map<uint32 /*hookID*/, uint32 /*extCompID*/>;
// Root override map: componentType ? componentID from player's root fixture selections
// Root override map: componentType → componentID from player's root fixture selections
// (e.g., player chose a specific roof variant). When provided, SpawnFullHouseMeshObjects
// uses these instead of the DB2 default root for that type.
using RootOverrideMap = std::unordered_map<uint8 /*componentType*/, uint32 /*compID*/>;
@@ -114,7 +114,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);
@@ -130,13 +130,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; }
// Accessor for fixture MeshObjects (plotIndex ? vector of MeshObject GUIDs)
// 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.
// 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);
+39 -45
View File
@@ -572,7 +572,7 @@ int32 HousingMgr::ResolvePlotIndex(ObjectGuid cornerstoneGuid, Neighborhood cons
}
// Only GameObject GUIDs encode a GO entry that can be matched against cornerstone entries.
// Housing/Neighborhood GUIDs (HighGuid 55) don't have a GO entry ? callers sometimes
// Housing/Neighborhood GUIDs (HighGuid 55) don't have a GO entry — callers sometimes
// pass these for diagnostic purposes; silently return -1.
if (cornerstoneGuid.GetHigh() != HighGuid::GameObject)
{
@@ -712,8 +712,8 @@ std::vector<uint32> HousingMgr::GetStarterDecorIds(uint32 teamId) const
// FirstTimeDecorAcquisition sends one packet per UNIQUE decor ID.
// StartingQuantity determines catalog count, NOT notification count.
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_ALLIANCE = 0x1;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_HORDE = 0x2;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_MASK = 0x3;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_HORDE = 0x2;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_MASK = 0x3;
int32 factionBit = (teamId == ALLIANCE) ? HOUSE_DECOR_FLAG_FACTION_ALLIANCE : HOUSE_DECOR_FLAG_FACTION_HORDE;
@@ -735,8 +735,8 @@ std::vector<std::pair<uint32, int32>> HousingMgr::GetStarterDecorWithQuantities(
{
// Returns {DecorID, StartingQuantity} pairs for populating the catalog
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_ALLIANCE = 0x1;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_HORDE = 0x2;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_MASK = 0x3;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_HORDE = 0x2;
static constexpr int32 HOUSE_DECOR_FLAG_FACTION_MASK = 0x3;
int32 factionBit = (teamId == ALLIANCE) ? HOUSE_DECOR_FLAG_FACTION_ALLIANCE : HOUSE_DECOR_FLAG_FACTION_HORDE;
@@ -763,21 +763,21 @@ bool HousingMgr::CanVisitorAccess(Player const* visitor, Player const* owner, ui
return true;
// Select the correct flag group based on access type
uint32 anyoneFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_ANYONE : HOUSE_SETTING_PLOT_ACCESS_ANYONE;
uint32 anyoneFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_ANYONE : HOUSE_SETTING_PLOT_ACCESS_ANYONE;
uint32 neighborsFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_NEIGHBORS : HOUSE_SETTING_PLOT_ACCESS_NEIGHBORS;
uint32 guildFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_GUILD : HOUSE_SETTING_PLOT_ACCESS_GUILD;
uint32 friendsFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_FRIENDS : HOUSE_SETTING_PLOT_ACCESS_FRIENDS;
uint32 partyFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_PARTY : HOUSE_SETTING_PLOT_ACCESS_PARTY;
uint32 guildFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_GUILD : HOUSE_SETTING_PLOT_ACCESS_GUILD;
uint32 friendsFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_FRIENDS : HOUSE_SETTING_PLOT_ACCESS_FRIENDS;
uint32 partyFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_PARTY : HOUSE_SETTING_PLOT_ACCESS_PARTY;
// If no flags are set at all, default to open access (sniff behavior: plots are public by default)
uint32 accessMask = isInterior
? (HOUSE_SETTING_HOUSE_ACCESS_ANYONE | HOUSE_SETTING_HOUSE_ACCESS_NEIGHBORS |
HOUSE_SETTING_HOUSE_ACCESS_GUILD | HOUSE_SETTING_HOUSE_ACCESS_FRIENDS | HOUSE_SETTING_HOUSE_ACCESS_PARTY)
HOUSE_SETTING_HOUSE_ACCESS_GUILD | HOUSE_SETTING_HOUSE_ACCESS_FRIENDS | HOUSE_SETTING_HOUSE_ACCESS_PARTY)
: (HOUSE_SETTING_PLOT_ACCESS_ANYONE | HOUSE_SETTING_PLOT_ACCESS_NEIGHBORS |
HOUSE_SETTING_PLOT_ACCESS_GUILD | HOUSE_SETTING_PLOT_ACCESS_FRIENDS | HOUSE_SETTING_PLOT_ACCESS_PARTY);
HOUSE_SETTING_PLOT_ACCESS_GUILD | HOUSE_SETTING_PLOT_ACCESS_FRIENDS | HOUSE_SETTING_PLOT_ACCESS_PARTY);
if ((settingsFlags & accessMask) == 0)
return true; // No restrictions configured ? open to all
return true; // No restrictions configured — open to all
if (settingsFlags & anyoneFlag)
return true;
@@ -995,12 +995,12 @@ void HousingMgr::LoadRoomComponentData()
{
switch (c.Type)
{
case HOUSING_ROOM_COMPONENT_WALL: ++wallCount; break;
case HOUSING_ROOM_COMPONENT_FLOOR: ++floorCount; break;
case HOUSING_ROOM_COMPONENT_CEILING: ++ceilCount; break;
case HOUSING_ROOM_COMPONENT_DOORWAY:
case HOUSING_ROOM_COMPONENT_DOORWAY_WALL: ++doorwayCount2; break;
default: ++otherCount; break;
case HOUSING_ROOM_COMPONENT_WALL: ++wallCount; break;
case HOUSING_ROOM_COMPONENT_FLOOR: ++floorCount; break;
case HOUSING_ROOM_COMPONENT_CEILING: ++ceilCount; break;
case HOUSING_ROOM_COMPONENT_DOORWAY:
case HOUSING_ROOM_COMPONENT_DOORWAY_WALL: ++doorwayCount2; break;
default: ++otherCount; break;
}
}
}
@@ -1188,7 +1188,7 @@ void HousingMgr::BuildRoomComponentTextureIndex()
_textureByOptionId.clear();
_textureByComponentType.clear();
// Build option?texture link from RoomComponentOptionTexture join table
// Build option→texture link from RoomComponentOptionTexture join table
for (RoomComponentOptionTextureEntry const* link : sRoomComponentOptionTextureStore)
{
if (!link)
@@ -1196,7 +1196,7 @@ void HousingMgr::BuildRoomComponentTextureIndex()
_textureByOptionId[link->RoomComponentOptionID] = link->RoomComponentTextureID;
}
// Build type?texture fallback from RoomComponentTexture
// Build type→texture fallback from RoomComponentTexture
// "Type" in RoomComponentTexture maps to component type (1=wall, 2=floor, 3=ceiling)
for (RoomComponentTextureEntry const* tex : sRoomComponentTextureStore)
{
@@ -1208,7 +1208,7 @@ void HousingMgr::BuildRoomComponentTextureIndex()
}
TC_LOG_INFO("housing", "HousingMgr::BuildRoomComponentTextureIndex: "
"{} option?texture links, {} type?texture fallbacks "
"{} option→texture links, {} type→texture fallbacks "
"(RoomComponentTexture store: {} entries, RoomComponentOptionTexture store: {} entries)",
uint32(_textureByOptionId.size()), uint32(_textureByComponentType.size()),
sRoomComponentTextureStore.GetNumRows(), sRoomComponentOptionTextureStore.GetNumRows());
@@ -1234,7 +1234,7 @@ void HousingMgr::DumpRoomComponentTextureDiagnostics()
{
if (!link)
continue;
TC_LOG_INFO("housing", " OptionTexture [{}] OptionID={} ? TextureID={}",
TC_LOG_INFO("housing", " OptionTexture [{}] OptionID={} → TextureID={}",
link->ID, link->RoomComponentOptionID, link->RoomComponentTextureID);
}
@@ -1281,8 +1281,8 @@ void HousingMgr::BuildExteriorComponentIndexes()
_hooksByExtComp[hook->ExteriorComponentID].push_back(hook);
}
// 1a. Build child index and root-by-WMO index from ExteriorComponent.
// ParentComponentID > 0 ? color/dye variant of that component.
// ParentComponentID == 0 ? base variant, and if the Type is a structural root,
// ParentComponentID > 0 → color/dye variant of that component.
// ParentComponentID == 0 → base variant, and if the Type is a structural root,
// it's indexed by HouseExteriorWmoDataID for independent spawning.
//
// Structural root check: look up ExteriorComponentType by comp->Type.
@@ -1340,14 +1340,14 @@ void HousingMgr::BuildExteriorComponentIndexes()
_extCompsByGroup[groupID].push_back(compID);
}
// 2. Build fixture resolution index: (componentType, wmoDataID) ? default component ID.
// 2. Build fixture resolution index: (componentType, wmoDataID) → default component ID.
// For each hook on a parent component, the fixture that goes there is determined by:
// - Hook's ExteriorComponentTypeID (e.g., Door=11, Window=12, Chimney=16)
// - Parent component's HouseExteriorWmoDataID (e.g., 9=Human, 55=NightElf)
// The default fixture is the root component (ParentComponentID==0) with matching
// Type and WmoDataID that has Flags & 0x1 (IsDefault).
//
// This replaces the old GroupXHook?Group?XGroup chain which was incorrect ?
// This replaces the old GroupXHook→Group→XGroup chain which was incorrect —
// groups are for UI organization, not fixture resolution.
for (ExteriorComponentEntry const* comp : sExteriorComponentStore)
{
@@ -1361,12 +1361,12 @@ void HousingMgr::BuildExteriorComponentIndexes()
auto existing = _defaultFixtureByTypeWmo.find(key);
if (existing == _defaultFixtureByTypeWmo.end())
{
// First component for this (type, wmo, size) ? insert it
// First component for this (type, wmo, size) — insert it
_defaultFixtureByTypeWmo[key] = comp->ID;
}
else if (isDefault)
{
// This component is the default ? override any non-default already stored
// This component is the default — override any non-default already stored
_defaultFixtureByTypeWmo[key] = comp->ID;
}
}
@@ -1407,7 +1407,7 @@ uint32 HousingMgr::GetDefaultFixtureForType(uint8 componentType, uint32 wmoDataI
return itr->second;
}
// Fallback: scan all sizes for this (type, wmo) ? useful when caller doesn't know the size
// Fallback: scan all sizes for this (type, wmo) — useful when caller doesn't know the size
for (uint8 sz = 1; sz <= 4; ++sz)
{
if (sz == houseSize)
@@ -1428,10 +1428,10 @@ uint32 HousingMgr::GetRacialWmoDataID(uint8 race, uint32 teamId)
{
switch (race)
{
case RACE_NIGHTELF: return 55; // Woodland
case RACE_BLOODELF: return 56; // Engraved
default:
return (teamId == HORDE) ? 87 : 9; // Orc / Human
case RACE_NIGHTELF: return 55; // Woodland
case RACE_BLOODELF: return 56; // Engraved
default:
return (teamId == HORDE) ? 87 : 9; // Orc / Human
}
}
@@ -1506,9 +1506,7 @@ void HousingMgr::DumpExteriorComponentDiagnostics()
bool isKnown = false;
for (uint32 compID : knownCompIDs)
if (hook->ExteriorComponentID == compID)
{
isKnown = true; break;
}
{ isKnown = true; break; }
if (isKnown)
{
@@ -1530,9 +1528,7 @@ void HousingMgr::DumpExteriorComponentDiagnostics()
bool isKnown = false;
for (uint32 compID : knownCompIDs)
if (exitPt->ExteriorComponentID == compID)
{
isKnown = true; break;
}
{ isKnown = true; break; }
if (isKnown)
{
@@ -1544,14 +1540,14 @@ void HousingMgr::DumpExteriorComponentDiagnostics()
}
}
// Dump component?parent relationship (ParentComponentID)
// Dump component→parent relationship (ParentComponentID)
TC_LOG_INFO("housing", " --- Component parent relationships ---");
for (uint32 compID : knownCompIDs)
{
ExteriorComponentEntry const* comp = sExteriorComponentStore.LookupEntry(compID);
if (!comp || comp->ParentComponentID <= 0)
continue;
TC_LOG_INFO("housing", " comp {} ? parent comp {}", compID, comp->ParentComponentID);
TC_LOG_INFO("housing", " comp {} → parent comp {}", compID, comp->ParentComponentID);
}
// Dump ExteriorComponentXGroup mappings
@@ -1563,13 +1559,11 @@ void HousingMgr::DumpExteriorComponentDiagnostics()
bool isKnown = false;
for (uint32 compID : knownCompIDs)
if (static_cast<uint32>(xg->ExteriorComponentID) == compID)
{
isKnown = true; break;
}
{ isKnown = true; break; }
if (isKnown)
{
TC_LOG_INFO("housing", " XGroup [{}] comp={} ? group={}",
TC_LOG_INFO("housing", " XGroup [{}] comp={} → group={}",
xg->ID, xg->ExteriorComponentID, xg->ExteriorComponentGroupID);
}
}
+7 -7
View File
@@ -343,7 +343,7 @@ public:
uint32 GetDefaultFixtureForType(uint8 componentType, uint32 wmoDataID, uint8 houseSize = 0) const;
// Racial house style: maps player race to the appropriate HouseExteriorWmoDataID.
// Night Elf ? 55, Blood Elf ? 56, other Alliance ? 9 (Human), other Horde ? 87 (Orc).
// Night Elf → 55, Blood Elf → 56, other Alliance → 9 (Human), other Horde → 87 (Orc).
static uint32 GetRacialWmoDataID(uint8 race, uint32 teamId);
// Find the first HouseRoom entry with visual components (not the base room 18)
@@ -356,7 +356,7 @@ public:
// Returns {DecorID, StartingQuantity} pairs for populating the catalog on purchase
std::vector<std::pair<uint32, int32>> GetStarterDecorWithQuantities(uint32 teamId) const;
// Access control ? checks if visitor can access a plot/house based on owner's settings
// Access control — checks if visitor can access a plot/house based on owner's settings
// accessMask = HOUSE_SETTING_HOUSE_ACCESS_* for interior, HOUSE_SETTING_PLOT_ACCESS_* for exterior
bool CanVisitorAccess(Player const* visitor, Player const* owner, uint32 settingsFlags, bool isInterior) const;
@@ -425,10 +425,10 @@ private:
void DumpExteriorComponentDiagnostics();
void DumpRoomComponentTextureDiagnostics();
// Base room entry ID ? exterior geobox (from DB2 IsBaseRoom flag scan, fallback 18)
// Base room entry ID — exterior geobox (from DB2 IsBaseRoom flag scan, fallback 18)
uint32 _baseRoomEntryId = 0;
// Entry hall room entry ID ? interior base room (second BASE_ROOM in DB2, fallback to _baseRoomEntryId)
// Entry hall room entry ID — interior base room (second BASE_ROOM in DB2, fallback to _baseRoomEntryId)
// Sniff-verified: Room 46 is the entry corridor with door connecting to the visual room
uint32 _entryHallRoomEntryId = 0;
@@ -436,9 +436,9 @@ private:
float _roomGridSpacing = 15.0f;
// RoomComponentTexture indexes
// RoomComponentOptionID ? RoomComponentTextureID (from RoomComponentOptionTexture join)
// RoomComponentOptionID → RoomComponentTextureID (from RoomComponentOptionTexture join)
std::unordered_map<int32 /*optionID*/, int32 /*textureID*/> _textureByOptionId;
// componentType ? textureID (first matching texture per type, fallback)
// componentType → textureID (first matching texture per type, fallback)
std::unordered_map<uint8 /*type*/, int32 /*textureID*/> _textureByComponentType;
// ExteriorComponent indexes
@@ -449,7 +449,7 @@ private:
std::unordered_map<uint32 /*parentCompID*/, std::vector<uint32 /*childCompID*/>> _childrenByExtComp;
std::unordered_map<uint32 /*wmoDataID*/, std::vector<uint32 /*compID*/>> _rootCompsByWmoDataId;
// Fixture resolution: (componentType, wmoDataID, size) ? default component ID
// Fixture resolution: (componentType, wmoDataID, size) → default component ID
// Key = (uint64(componentType) << 40) | (uint64(wmoDataID) << 8) | size
std::unordered_map<uint64, uint32> _defaultFixtureByTypeWmo;
};
+28 -28
View File
@@ -93,8 +93,8 @@ void InitiativeManager::BuildDB2IndexMaps()
// Sort tasks by SortOrder within each initiative
for (auto& [initId, tasks] : _initiativeTasks)
std::sort(tasks.begin(), tasks.end(), [](InitiativeTaskData const& a, InitiativeTaskData const& b) {
return a.SortOrder < b.SortOrder;
});
return a.SortOrder < b.SortOrder;
});
// Build CycleID -> Milestones map
_cycleMilestones.clear();
@@ -115,8 +115,8 @@ void InitiativeManager::BuildDB2IndexMaps()
// Sort milestones by index within each cycle
for (auto& [cycleId, milestones] : _cycleMilestones)
std::sort(milestones.begin(), milestones.end(), [](InitiativeMilestoneData const& a, InitiativeMilestoneData const& b) {
return a.MilestoneOrderIndex < b.MilestoneOrderIndex;
});
return a.MilestoneOrderIndex < b.MilestoneOrderIndex;
});
// Build InitiativeID -> active CycleID map (pick lowest CycleIndex as the "active" cycle)
_initiativeActiveCycle.clear();
@@ -198,9 +198,9 @@ void InitiativeManager::LoadFromDB()
do
{
Field* f = taskResult->Fetch();
uint32 taskId = f[0].GetUInt32();
uint32 taskId = f[0].GetUInt32();
uint32 progress = f[1].GetUInt32();
uint8 status = f[2].GetUInt8();
uint8 status = f[2].GetUInt8();
auto tItr = initiative->TaskProgress.find(taskId);
if (tItr != initiative->TaskProgress.end())
@@ -233,7 +233,7 @@ void InitiativeManager::LoadFromDB()
{
Field* f = msResult->Fetch();
uint32 milestoneIdx = f[0].GetUInt32();
bool reached = f[1].GetUInt8() != 0;
bool reached = f[1].GetUInt8() != 0;
initiative->MilestonesReached[milestoneIdx] = reached;
} while (msResult->NextRow());
}
@@ -252,8 +252,8 @@ void InitiativeManager::LoadFromDB()
{
Field* f = contribResult->Fetch();
uint64 playerGuid = f[0].GetUInt64();
uint32 taskId = f[1].GetUInt32();
uint32 amount = f[2].GetUInt32();
uint32 taskId = f[1].GetUInt32();
uint32 amount = f[2].GetUInt32();
initiative->PlayerContributions[playerGuid][taskId] = amount;
} while (contribResult->NextRow());
}
@@ -268,7 +268,7 @@ void InitiativeManager::LoadFromDB()
{
Field* f = claimResult->Fetch();
uint32 milestoneIdx = f[0].GetUInt32();
uint64 claimPlayer = f[1].GetUInt64();
uint64 claimPlayer = f[1].GetUInt64();
initiative->RewardClaims[milestoneIdx].insert(claimPlayer);
} while (claimResult->NextRow());
}
@@ -687,17 +687,17 @@ void InitiativeManager::BuildCriteriaIndex()
}
CriteriaMgr::WalkCriteriaTree(tree, [&](CriteriaTree const* node)
{
if (node->Criteria)
{
if (node->Criteria)
{
CriteriaTaskLink link;
link.NeighborhoodGuid = nhGuid;
link.InitiativeID = initiative->InitiativeID;
link.TaskID = task.TaskID;
_criteriaToTasks[node->Criteria->ID].push_back(link);
++linkCount;
}
});
CriteriaTaskLink link;
link.NeighborhoodGuid = nhGuid;
link.InitiativeID = initiative->InitiativeID;
link.TaskID = task.TaskID;
_criteriaToTasks[node->Criteria->ID].push_back(link);
++linkCount;
}
});
}
}
}
@@ -711,7 +711,7 @@ void InitiativeManager::OnCriteriaProgress(Player* player, uint32 criteriaId)
if (!player)
return;
// Look up the reverse index ? is this criteria referenced by any initiative task?
// Look up the reverse index — is this criteria referenced by any initiative task?
auto itr = _criteriaToTasks.find(criteriaId);
if (itr == _criteriaToTasks.end())
return;
@@ -858,12 +858,12 @@ void InitiativeManager::SendPlayerInitiativeInfo(WorldSession* session, ObjectGu
uint32 cycleID = GetActiveCycleForInitiative(active->InitiativeID);
// Compute remaining duration from initiative start + cycle duration.
// Sniff-verified: RemainingDuration is in seconds (sniff value 972957 ? 11.25 days).
// Sniff-verified: RemainingDuration is in seconds (sniff value 972957 ≈ 11.25 days).
// If duration would be 0, use a 7-day fallback so the client shows the initiative
// as active (Duration=0 ? client treats as expired ? empty endeavor list).
// as active (Duration=0 → client treats as expired → empty endeavor list).
int64 remainingDuration = 0;
{
// Duration comes from NeighborhoodInitiative DB2 (not InitiativeCycle ? that has HouseXPCap)
// Duration comes from NeighborhoodInitiative DB2 (not InitiativeCycle — that has HouseXPCap)
int64 totalDurationSec = 0;
NeighborhoodInitiativeEntry const* initEntry = sNeighborhoodInitiativeStore.LookupEntry(active->InitiativeID);
if (initEntry && initEntry->Duration > 0)
@@ -1097,7 +1097,7 @@ void InitiativeManager::CheckAndStartInitiatives()
}
return false;
});
});
}
// For each neighborhood that doesn't have an active initiative, start one
@@ -1138,12 +1138,12 @@ void InitiativeManager::CheckAndStartInitiatives()
if (recentlyCompleted)
continue;
// Skip initiatives that have no tasks defined ? they produce empty
// Skip initiatives that have no tasks defined — they produce empty
// endeavor lists and waste a neighborhood's active initiative slot.
if (_initiativeTasks.find(entry->ID) == _initiativeTasks.end())
continue;
// Skip initiatives that have no cycle defined ? the client requires a
// Skip initiatives that have no cycle defined — the client requires a
// valid CycleID to display milestones, duration, and rewards in the UI.
if (GetActiveCycleForInitiative(entry->ID) == 0)
continue;
@@ -1376,7 +1376,7 @@ std::vector<std::pair<uint64, uint32>> InitiativeManager::GetTopContributors(
// Sort descending by contribution amount
std::sort(result.begin(), result.end(), [](auto const& a, auto const& b) {
return a.second > b.second;
});
});
// Trim to limit
if (limit > 0 && result.size() > limit)
+4 -4
View File
@@ -33,9 +33,9 @@ class WorldSession;
// Status of an individual task within an initiative
enum InitiativeTaskStatus : uint32
{
INITIATIVE_TASK_STATUS_NOT_STARTED = 0,
INITIATIVE_TASK_STATUS_IN_PROGRESS = 1,
INITIATIVE_TASK_STATUS_COMPLETE = 2
INITIATIVE_TASK_STATUS_NOT_STARTED = 0,
INITIATIVE_TASK_STATUS_IN_PROGRESS = 1,
INITIATIVE_TASK_STATUS_COMPLETE = 2
};
// Tracks a single task's progress for a player within a neighborhood initiative
@@ -119,7 +119,7 @@ public:
void UpdateTaskProgress(uint64 neighborhoodGuid, uint32 initiativeID, uint32 taskID, uint32 progressDelta, Player* contributor);
void ClearTaskCriteria(uint64 neighborhoodGuid, uint32 initiativeID, uint32 taskID);
// CriteriaTree-based task matching ? called from CriteriaHandler when criteria progress fires
// CriteriaTree-based task matching — called from CriteriaHandler when criteria progress fires
// Checks if the updated criteria is referenced by any active initiative task's CriteriaTree,
// and credits the community initiative accordingly.
void OnCriteriaProgress(Player* player, uint32 criteriaId);
+28 -28
View File
@@ -45,15 +45,15 @@ bool Neighborhood::LoadFromDB(PreparedQueryResult neighborhood, PreparedQueryRes
// createTime FROM neighborhoods WHERE guid = ?
// _guid is already set in constructor
_name = fields[1].GetString();
_neighborhoodMapID = fields[2].GetUInt32();
_name = fields[1].GetString();
_neighborhoodMapID = fields[2].GetUInt32();
{
uint64 ownerCounter = fields[3].GetUInt64();
_ownerGuid = ownerCounter ? ObjectGuid::Create<HighGuid::Player>(ownerCounter) : ObjectGuid::Empty;
}
_factionRestriction = fields[4].GetInt32();
_isPublic = fields[5].GetBool();
_createTime = fields[6].GetUInt32();
_isPublic = fields[5].GetBool();
_createTime = fields[6].GetUInt32();
TC_LOG_DEBUG("housing", "Neighborhood::LoadFromDB: Loaded neighborhood '{}' (guid: {}), owner: {}, mapId: {}, members: loading...",
_name, _guid.ToString(), _ownerGuid.ToString(), _neighborhoodMapID);
@@ -72,18 +72,18 @@ bool Neighborhood::LoadFromDB(PreparedQueryResult neighborhood, PreparedQueryRes
// WHERE nm.neighborhoodGuid = ?
Member member;
member.PlayerGuid = ObjectGuid::Create<HighGuid::Player>(memberFields[0].GetUInt64());
member.Role = memberFields[1].GetUInt8();
member.JoinTime = memberFields[2].GetUInt32();
member.PlotIndex = memberFields[3].GetUInt8();
member.PlayerGuid = ObjectGuid::Create<HighGuid::Player>(memberFields[0].GetUInt64());
member.Role = memberFields[1].GetUInt8();
member.JoinTime = memberFields[2].GetUInt32();
member.PlotIndex = memberFields[3].GetUInt8();
_members.push_back(member);
// Build plot info from members that have plots assigned
if (member.PlotIndex != INVALID_PLOT_INDEX && member.PlotIndex < MAX_NEIGHBORHOOD_PLOTS)
{
_plots[member.PlotIndex].PlotIndex = member.PlotIndex;
_plots[member.PlotIndex].OwnerGuid = member.PlayerGuid;
_plots[member.PlotIndex].PlotIndex = member.PlotIndex;
_plots[member.PlotIndex].OwnerGuid = member.PlayerGuid;
// Resolve BNet account GUID from characters.account JOIN (column 5).
// The client requires non-zero HouseOwnerBnetAccountGUID on the plot AreaTrigger's
@@ -120,9 +120,9 @@ bool Neighborhood::LoadFromDB(PreparedQueryResult neighborhood, PreparedQueryRes
// FROM neighborhood_invites WHERE neighborhoodGuid = ?
PendingInvite invite;
invite.InviteeGuid = ObjectGuid::Create<HighGuid::Player>(inviteFields[0].GetUInt64());
invite.InviterGuid = ObjectGuid::Create<HighGuid::Player>(inviteFields[1].GetUInt64());
invite.InviteTime = inviteFields[2].GetUInt32();
invite.InviteeGuid = ObjectGuid::Create<HighGuid::Player>(inviteFields[0].GetUInt64());
invite.InviterGuid = ObjectGuid::Create<HighGuid::Player>(inviteFields[1].GetUInt64());
invite.InviteTime = inviteFields[2].GetUInt32();
_pendingInvites.push_back(invite);
} while (invites->NextRow());
@@ -427,9 +427,9 @@ HousingResult Neighborhood::InviteResident(ObjectGuid inviterGuid, ObjectGuid in
// Create the pending invite
PendingInvite invite;
invite.InviteeGuid = inviteeGuid;
invite.InviterGuid = inviterGuid;
invite.InviteTime = static_cast<uint32>(GameTime::GetGameTime());
invite.InviteeGuid = inviteeGuid;
invite.InviterGuid = inviterGuid;
invite.InviteTime = static_cast<uint32>(GameTime::GetGameTime());
_pendingInvites.push_back(invite);
// Persist to DB immediately
@@ -499,10 +499,10 @@ HousingResult Neighborhood::AcceptInvitation(ObjectGuid playerGuid)
// Add as resident
Member newMember;
newMember.PlayerGuid = playerGuid;
newMember.Role = NEIGHBORHOOD_ROLE_RESIDENT;
newMember.JoinTime = static_cast<uint32>(GameTime::GetGameTime());
newMember.PlotIndex = INVALID_PLOT_INDEX;
newMember.PlayerGuid = playerGuid;
newMember.Role = NEIGHBORHOOD_ROLE_RESIDENT;
newMember.JoinTime = static_cast<uint32>(GameTime::GetGameTime());
newMember.PlotIndex = INVALID_PLOT_INDEX;
_members.push_back(newMember);
// Remove the invite
@@ -559,10 +559,10 @@ HousingResult Neighborhood::AddResident(ObjectGuid playerGuid)
}
Member newMember;
newMember.PlayerGuid = playerGuid;
newMember.Role = NEIGHBORHOOD_ROLE_RESIDENT;
newMember.JoinTime = static_cast<uint32>(GameTime::GetGameTime());
newMember.PlotIndex = INVALID_PLOT_INDEX;
newMember.PlayerGuid = playerGuid;
newMember.Role = NEIGHBORHOOD_ROLE_RESIDENT;
newMember.JoinTime = static_cast<uint32>(GameTime::GetGameTime());
newMember.PlotIndex = INVALID_PLOT_INDEX;
_members.push_back(newMember);
// Persist to DB
@@ -892,8 +892,8 @@ HousingResult Neighborhood::PurchasePlot(ObjectGuid playerGuid, uint8 plotIndex)
// Assign the plot
buyer->PlotIndex = plotIndex;
_plots[plotIndex].PlotIndex = plotIndex;
_plots[plotIndex].OwnerGuid = playerGuid;
_plots[plotIndex].PlotIndex = plotIndex;
_plots[plotIndex].OwnerGuid = playerGuid;
// Persist the plot assignment to DB
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NEIGHBORHOOD_MEMBER_PLOT);
@@ -902,7 +902,7 @@ HousingResult Neighborhood::PurchasePlot(ObjectGuid playerGuid, uint8 plotIndex)
stmt->setUInt64(2, playerGuid.GetCounter());
CharacterDatabase.Execute(stmt);
TC_LOG_ERROR("housing", "Neighborhood::PurchasePlot: Player {} purchased plot {} in neighborhood '{}' ? _plots[{}].PlotIndex={}, _plots[{}].OwnerGuid={}",
TC_LOG_ERROR("housing", "Neighborhood::PurchasePlot: Player {} purchased plot {} in neighborhood '{}' — _plots[{}].PlotIndex={}, _plots[{}].OwnerGuid={}",
playerGuid.ToString(), plotIndex, _name, plotIndex, _plots[plotIndex].PlotIndex, plotIndex, _plots[plotIndex].OwnerGuid.ToString());
return HOUSING_RESULT_SUCCESS;
@@ -1061,7 +1061,7 @@ void Neighborhood::RefreshMirrorDataForOnlineMembers() const
mirrorEntity.SetName(_name);
mirrorEntity.SetOwnerGUID(_ownerGuid);
// Houses ? rebuild from plots. Add ALL 55 entries so Houses[i] = PlotIndex i.
// Houses — rebuild from plots. Add ALL 55 entries so Houses[i] = PlotIndex i.
// The client uses the array index as the plot identifier; skipping empty slots
// causes the client to show the wrong plots as occupied.
mirrorEntity.ClearHouses();
+13 -17
View File
@@ -425,12 +425,12 @@ Neighborhood* NeighborhoodMgr::FindOrCreatePublicNeighborhood(uint32 teamId)
return nullptr;
}
// Look for an existing public neighborhood ? no membership changes
// Look for an existing public neighborhood — no membership changes
Neighborhood* found = FindPublicNeighborhoodForMap(targetMapId);
if (found)
return found;
// None exists yet ? EnsurePublicNeighborhoods should have created them at startup.
// None exists yet — EnsurePublicNeighborhoods should have created them at startup.
// Force-run it now as a fallback, then retry.
TC_LOG_WARN("housing", "FindOrCreatePublicNeighborhood: No public neighborhood for map {}, running EnsurePublicNeighborhoods", targetMapId);
EnsurePublicNeighborhoods();
@@ -492,7 +492,7 @@ void NeighborhoodMgr::VerifyNeighborhoodFactions()
correctFaction = NEIGHBORHOOD_FACTION_HORDE;
if (correctFaction == NEIGHBORHOOD_FACTION_NONE)
continue; // Ambiguous or no faction ? skip
continue; // Ambiguous or no faction — skip
int32 currentFaction = nb->GetFactionRestriction();
if (currentFaction == correctFaction)
@@ -576,7 +576,7 @@ void NeighborhoodMgr::EnsurePublicNeighborhoods()
if (hasAlliancePublic && hasHordePublic)
TC_LOG_INFO("server.loading", ">> Public neighborhoods verified for both factions");
else if (!hasAlliancePublic || !hasHordePublic)
TC_LOG_WARN("server.loading", ">> Missing public neighborhood for {} ? no system-generatable NeighborhoodMap found",
TC_LOG_WARN("server.loading", ">> Missing public neighborhood for {} — no system-generatable NeighborhoodMap found",
!hasAlliancePublic ? "Alliance" : "Horde");
}
@@ -636,13 +636,13 @@ void NeighborhoodMgr::MigrateWrongFactionResidents()
fields[3].GetUInt8(),
fields[4].GetUInt32(),
fields[5].GetUInt8()
});
});
} while (result->NextRow());
// Build set of existing memberships for quick lookup: (playerGuid, nbGuid)
std::set<std::pair<uint64, uint64>> membershipSet;
for (auto const& m : allMembers)
membershipSet.insert({ m.PlayerGuidLow, m.NbGuidLow });
membershipSet.insert({m.PlayerGuidLow, m.NbGuidLow});
// Pre-populate used plots in each target neighborhood
std::set<uint8> usedPlotsInAlliance;
@@ -665,7 +665,7 @@ void NeighborhoodMgr::MigrateWrongFactionResidents()
if (m.NbGuidLow == correctNbLow)
continue; // Already in correct faction's neighborhood
bool alreadyInCorrect = membershipSet.count({ m.PlayerGuidLow, correctNbLow }) > 0;
bool alreadyInCorrect = membershipSet.count({m.PlayerGuidLow, correctNbLow}) > 0;
// Delete old wrong-faction membership
CharacterDatabasePreparedStatement* delStmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NEIGHBORHOOD_MEMBER);
@@ -675,7 +675,7 @@ void NeighborhoodMgr::MigrateWrongFactionResidents()
if (!alreadyInCorrect)
{
// Player doesn't have a membership in the correct neighborhood yet ? create one
// Player doesn't have a membership in the correct neighborhood yet — create one
std::set<uint8>& usedPlots = (correctNbLow == allianceNbLow) ? usedPlotsInAlliance : usedPlotsInHorde;
uint8 newPlotIndex = m.PlotIndex;
@@ -733,7 +733,7 @@ void NeighborhoodMgr::MigrateWrongFactionResidents()
if (migratedCount > 0)
{
TC_LOG_INFO("server.loading", ">> Migrated {} resident(s) to correct faction neighborhoods ? reloading", migratedCount);
TC_LOG_INFO("server.loading", ">> Migrated {} resident(s) to correct faction neighborhoods — reloading", migratedCount);
LoadFromDB(); // Reload to pick up the changes
}
}
@@ -848,7 +848,7 @@ void NeighborhoodMgr::CheckAndExpandNeighborhoods()
uint32 occupiedPlots = neighborhood->GetOccupiedPlotCount();
uint32 memberCount = neighborhood->GetMemberCount();
// Check both occupied plots AND member count ? either can be the bottleneck.
// Check both occupied plots AND member count — either can be the bottleneck.
// A neighborhood might have many members (invited/added) but few plots occupied,
// or vice versa. Use the higher of the two for capacity assessment.
uint32 usage = std::max(occupiedPlots, memberCount);
@@ -864,7 +864,7 @@ void NeighborhoodMgr::CheckAndExpandNeighborhoods()
if (hasCapacity)
continue;
// All public neighborhoods for this faction are at or above 50% ? create a new one
// All public neighborhoods for this faction are at or above 50% — create a new one
// Find the correct NeighborhoodMapID for this faction
uint32 targetMapId = 0;
for (auto const& [id, data] : sHousingMgr.GetAllNeighborhoodMapData())
@@ -878,13 +878,9 @@ void NeighborhoodMgr::CheckAndExpandNeighborhoods()
continue;
if (faction == NEIGHBORHOOD_FACTION_ALLIANCE && isAlliance)
{
targetMapId = id; break;
}
{ targetMapId = id; break; }
else if (faction == NEIGHBORHOOD_FACTION_HORDE && isHorde)
{
targetMapId = id; break;
}
{ targetMapId = id; break; }
}
if (targetMapId == 0)
+4 -1
View File
@@ -1253,6 +1253,9 @@ enum TrinityStrings
LANG_SHUTDOWN_CANCELLED = 11018,
LANG_YOU_CHANGE_POWER = 11019,
LANG_YOUR_POWER_CHANGED = 11020,
LANG_INVALID_POWER_NAME = 11021
LANG_INVALID_POWER_NAME = 11021,
LANG_SWITCHED_TO_STEADY_FLIGHT = 11022,
LANG_SWITCHED_TO_SKYRIDING = 11023,
LANG_YOU_CANT_FLY_HERE = 11024
};
#endif
@@ -22,6 +22,14 @@ void PerksProgramRequestPurchase::Read()
_worldPacket >> VendorItemID;
}
void PerksProgramRequestCartCheckout::Read()
{
uint32 count = _worldPacket.ReadBits(5);
VendorItemIDs.resize(count);
for (uint32 i = 0; i < count; ++i)
_worldPacket >> VendorItemIDs[i];
}
void PerksProgramSetFrozenVendorItem::Read()
{
_worldPacket >> VendorItemID;
@@ -43,6 +43,15 @@ public:
int32 VendorItemID = 0;
};
class PerksProgramRequestCartCheckout final : public ClientPacket
{
public:
explicit PerksProgramRequestCartCheckout(WorldPacket&& packet) : ClientPacket(CMSG_PERKS_PROGRAM_REQUEST_CART_CHECKOUT, std::move(packet)) { }
void Read() override;
std::vector<int32> VendorItemIDs;
};
class PerksProgramSetFrozenVendorItem final : public ClientPacket
{
public:
+1 -1
View File
@@ -878,7 +878,7 @@ void OpcodeTable::InitializeClientOpcodes()
DEFINE_HANDLER(CMSG_PERFORM_ITEM_INTERACTION, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_PERKS_PROGRAM_GET_RECENT_PURCHASES, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_PERKS_PROGRAM_ITEMS_REFRESHED, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_PERKS_PROGRAM_REQUEST_CART_CHECKOUT, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_PERKS_PROGRAM_REQUEST_CART_CHECKOUT, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePerkProgramRequestCartCheckout);
DEFINE_HANDLER(CMSG_PERKS_PROGRAM_REQUEST_PENDING_REWARDS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePerkProgramPendingReward);
DEFINE_HANDLER(CMSG_PERKS_PROGRAM_REQUEST_PURCHASE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePerkProgramRequestPurchase);
DEFINE_HANDLER(CMSG_PERKS_PROGRAM_REQUEST_REFUND, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
+2
View File
@@ -755,6 +755,7 @@ namespace WorldPackets
class PerksProgramRequestPurchase;
class PerksProgramSetFrozenVendorItem;
class PerksProgramPendingReward;
class PerksProgramRequestCartCheckout;
}
//WowCommunity
@@ -1553,6 +1554,7 @@ class TC_GAME_API WorldSession
//PERKS
void HandleGetPerkProgramRequest(WorldPackets::PerksProgram::PerksProgramStatusRequest& packet);
void HandlePerkProgramRequestPurchase(WorldPackets::PerksProgram::PerksProgramRequestPurchase& packet);
void HandlePerkProgramRequestCartCheckout(WorldPackets::PerksProgram::PerksProgramRequestCartCheckout& packet);
void HandlePerkProgramSetFrozenVendorItem(WorldPackets::PerksProgram::PerksProgramSetFrozenVendorItem& packet);
void HandlePerkProgramPendingReward(WorldPackets::PerksProgram::PerksProgramPendingReward& packet);
@@ -45,6 +45,10 @@ enum DragonridingSpells
// Spell 436854 - Switch Flight Style
class spell_switch_flight : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_SKYRIDING, SPELL_STEADY_FLIGHT, SPELL_VIGOR });
}
void HandleDummy(SpellEffIndex /*effIndex*/)
{
@@ -52,6 +56,16 @@ class spell_switch_flight : public SpellScript
if (!caster)
return;
Player* player = caster->ToPlayer();
if (!player)
return;
// Check if player can fly
if (!player->CanFly())
{
return;
}
bool hadSkyriding = caster->HasAura(SPELL_SKYRIDING);
caster->RemoveAura(SPELL_SKYRIDING);
@@ -59,6 +73,7 @@ class spell_switch_flight : public SpellScript
if (hadSkyriding)
{
// Switch to steady flight (normal flight)
caster->CastSpell(caster, SPELL_STEADY_FLIGHT, true);
caster->RemoveAura(SPELL_VIGOR);
if (caster->IsMounted())
@@ -69,6 +84,7 @@ class spell_switch_flight : public SpellScript
}
else
{
// Switch to skyriding (dragonriding)
caster->CastSpell(caster, SPELL_SKYRIDING, true);
if (!caster->HasAura(SPELL_VIGOR))
caster->CastSpell(caster, SPELL_VIGOR, true);
@@ -77,7 +93,7 @@ class spell_switch_flight : public SpellScript
caster->SetCanAdvFly(true);
caster->SetCanDoubleJump(true);
caster->SetFlightCapabilityID(1, true);
}
}
}
}