trading post
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user