Housing: Fix initiative packet parsing, opcode collisions, and tutorial CVar timing
- Fix CMSG_INITIATIVE_REPORT_PROGRESS ByteBufferException: packet only contains a packed NeighborhoodGuid (sniff-verified 7 bytes), not three extra uint32 fields. Handler now just sends initiative info back. - Fix 4 SMSG initiative opcode collisions (0x420369-0x42036C) with existing opcodes (CATALOG_SHOP_OBTAIN_LICENSE, MIRROR_VARS, SET_INSTANCE_LEAVER, UNSET_INSTANCE_LEAVER). Reassigned to 0x420380-0x420383. - Fix tutorial mode still blocking editor: CVar injection in Player::LoadFromDB happened after the client already fetched account data during auth. Added SendAccountDataTimes() after SetAccountData() to force the client to re-fetch the updated GLOBAL_CONFIG_CACHE with housingTutorialsEnabled=0 and closedInfoFramesAccountWide bits. - Reset FHousingStorage_C populated flag on every edit mode entry so the Account VALUES_UPDATE always carries the full storage map. - Remove FHousingDecorActor_C fragment from decor MeshObjects (sniff analysis confirmed fragment 28 is not present on any retail entity). - Use DROP+CREATE instead of CREATE IF NOT EXISTS in initiative SQL to prevent stale schema from persisting silently.
This commit is contained in:
@@ -181,13 +181,18 @@ void MeshObject::InitHousingDecorData(ObjectGuid decorGuid, ObjectGuid houseGuid
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingDecor_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingDecorData));
|
||||
|
||||
// Retail sniff-verified: decor MeshObjects have exactly these fragments:
|
||||
// [CGObject(2), FMeshObjectData_C(19), FHousingDecor_C(20),
|
||||
// FMirroredPositionData_C(31), Tag_MeshObject(221)]
|
||||
// FHousingDecorActor_C (28) is NOT present on any retail entity.
|
||||
|
||||
// Retail sniff: HasDecor movement block flag is NEVER set (always False).
|
||||
// The room entity GUID is already in the FHousingDecor_C fragment's AttachParentGUID field.
|
||||
// Do NOT set m_updateFlag.Decor here — it adds an extra movement block field
|
||||
// Do NOT set m_updateFlag.Decor here ? it adds an extra movement block field
|
||||
// that the client does not expect.
|
||||
_decorRoomEntityGUID = roomEntityGuid;
|
||||
|
||||
TC_LOG_DEBUG("housing", "MeshObject::InitHousingDecorData: guid={} decorGuid={} houseGuid={} flags={} roomEntity={} (Decor flag=ON)",
|
||||
TC_LOG_DEBUG("housing", "MeshObject::InitHousingDecorData: guid={} decorGuid={} houseGuid={} flags={} roomEntity={} (FHousingDecor_C ON)",
|
||||
GetGUID().ToString(), decorGuid.ToString(), houseGuid.ToString(), flags, roomEntityGuid.ToString());
|
||||
}
|
||||
|
||||
@@ -256,7 +261,7 @@ void MeshObject::InitHousingRoomData(ObjectGuid houseGuid, int32 houseRoomID,
|
||||
|
||||
// Retail sniff: HasRoom movement block flag is NEVER set (always False).
|
||||
// The house GUID is already in the FHousingRoom_C fragment data.
|
||||
// Do NOT set m_updateFlag.Room here — it adds an extra movement block field
|
||||
// Do NOT set m_updateFlag.Room here ? it adds an extra movement block field
|
||||
// that the client does not expect.
|
||||
_roomHouseGUID = houseGuid;
|
||||
|
||||
@@ -295,7 +300,7 @@ void MeshObject::InitHousingRoomComponentData(ObjectGuid roomGuid,
|
||||
}
|
||||
|
||||
// Set Geobox (axis-aligned bounding box) on MeshObjectData.
|
||||
// Sniff-verified: ALL retail room component meshes have Geobox (-35,-30,-1.01)→(35,30,125.01).
|
||||
// Sniff-verified: ALL retail room component meshes have Geobox (-35,-30,-1.01)?(35,30,125.01).
|
||||
// The client uses this box for its OutsidePlotBounds collision check.
|
||||
{
|
||||
auto meshData = m_values.ModifyValue(&MeshObject::m_meshObjectData);
|
||||
@@ -323,7 +328,7 @@ void MeshObject::InitHousingRoomComponentData(ObjectGuid roomGuid,
|
||||
|
||||
TC_LOG_ERROR("housing", "MeshObject::InitHousingRoomComponentData: guid={} roomGuid={} "
|
||||
"compOptionID={} compID={} compType={} themeID={} "
|
||||
"geobox=({:.2f},{:.2f},{:.2f})→({:.2f},{:.2f},{:.2f})",
|
||||
"geobox=({:.2f},{:.2f},{:.2f})?({:.2f},{:.2f},{:.2f})",
|
||||
GetGUID().ToString(), roomGuid.ToString(),
|
||||
roomComponentOptionID, roomComponentID, roomComponentType, houseThemeID,
|
||||
geoboxMinX, geoboxMinY, geoboxMinZ, geoboxMaxX, geoboxMaxY, geoboxMaxZ);
|
||||
|
||||
@@ -18925,6 +18925,10 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
|
||||
if (configModified)
|
||||
{
|
||||
GetSession()->SetAccountData(GLOBAL_CONFIG_CACHE, GameTime::GetGameTime(), configData);
|
||||
// Re-send account data timestamps so the client detects the newer timestamp
|
||||
// and re-fetches GLOBAL_CONFIG_CACHE. Without this, the client uses the stale
|
||||
// data it fetched during auth (before LoadFromDB modified it).
|
||||
GetSession()->SendAccountDataTimes(GetGUID(), GLOBAL_CACHE_MASK);
|
||||
TC_LOG_DEBUG("housing", "Player::LoadFromDB: Injected housing tutorial CVars into GLOBAL_CONFIG_CACHE for account {}",
|
||||
GetSession()->GetAccountId());
|
||||
}
|
||||
|
||||
@@ -496,10 +496,14 @@ void WorldSession::HandleHousingDecorSetEditMode(WorldPackets::Housing::HousingD
|
||||
player->SetUnitFlag2(UNIT_FLAG2_NO_ACTIONS);
|
||||
player->ReplaceAllSilencedSchoolMask(SPELL_SCHOOL_MASK_ALL);
|
||||
|
||||
// 4. Populate FHousingStorage_C on the Account entity if not done yet.
|
||||
// 4. Populate FHousingStorage_C on the Account entity.
|
||||
// The client correlates MeshObject FHousingDecor_C.DecorGUID with entries in
|
||||
// FHousingStorage_C to build its placed decor list for the targeting system.
|
||||
// Without this, the client has no decor to target and selection is impossible.
|
||||
// Reset the populated flag so storage entries are re-pushed on every edit mode
|
||||
// entry ? the client may clear its decor list when exiting editor mode, so we
|
||||
// must ensure the Account VALUES_UPDATE always carries the full storage map.
|
||||
housing->ResetStoragePopulated();
|
||||
housing->PopulateCatalogStorageEntries();
|
||||
|
||||
// 4b. Refresh budget values on the HousingPlayerHouseEntity so the client
|
||||
|
||||
@@ -2016,8 +2016,8 @@ void WorldSession::HandleInitiativeReportProgress(WorldPackets::Neighborhood::In
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_INITIATIVE_REPORT_PROGRESS NeighborhoodGuid: {} InitiativeID: {} TaskID: {} Delta: {} Player: {}",
|
||||
packet.NeighborhoodGuid.ToString(), packet.InitiativeID, packet.TaskID, packet.ProgressDelta, player->GetGUID().ToString());
|
||||
TC_LOG_DEBUG("housing", "CMSG_INITIATIVE_REPORT_PROGRESS NeighborhoodGuid: {} Player: {}",
|
||||
packet.NeighborhoodGuid.ToString(), player->GetGUID().ToString());
|
||||
|
||||
Neighborhood* neighborhood = sNeighborhoodMgr.ResolveNeighborhood(packet.NeighborhoodGuid, player);
|
||||
if (!neighborhood)
|
||||
@@ -2030,20 +2030,7 @@ void WorldSession::HandleInitiativeReportProgress(WorldPackets::Neighborhood::In
|
||||
|
||||
uint64 nhGuid = neighborhood->GetGuid().GetCounter();
|
||||
|
||||
// Validate that the initiative and task exist
|
||||
ActiveInitiative* active = sInitiativeManager.GetActiveInitiative(nhGuid);
|
||||
if (!active || active->InitiativeID != packet.InitiativeID)
|
||||
{
|
||||
WorldPackets::Housing::GetPlayerInitiativeInfoResult response;
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply progress and send updated task info
|
||||
if (packet.ProgressDelta > 0)
|
||||
sInitiativeManager.UpdateTaskProgress(nhGuid, packet.InitiativeID, packet.TaskID, packet.ProgressDelta, player);
|
||||
|
||||
// Client is requesting initiative info for this neighborhood ? send current state
|
||||
sInitiativeManager.SendPlayerInitiativeInfo(this, nhGuid);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -221,9 +221,10 @@ public:
|
||||
// Direct access to placed decor map (for GO spawning)
|
||||
std::unordered_map<ObjectGuid, PlacedDecor> const& GetPlacedDecorMap() const { return _placedDecor; }
|
||||
bool IsStoragePopulated() const { return _storagePopulated; }
|
||||
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();
|
||||
|
||||
@@ -263,7 +264,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
|
||||
|
||||
@@ -2781,12 +2781,8 @@ namespace WorldPackets::Neighborhood
|
||||
void InitiativeReportProgress::Read()
|
||||
{
|
||||
_worldPacket >> NeighborhoodGuid;
|
||||
_worldPacket >> InitiativeID;
|
||||
_worldPacket >> TaskID;
|
||||
_worldPacket >> ProgressDelta;
|
||||
|
||||
TC_LOG_DEBUG("network.opcode", "CMSG_INITIATIVE_REPORT_PROGRESS NeighborhoodGuid: {} InitiativeID: {} TaskID: {} ProgressDelta: {}",
|
||||
NeighborhoodGuid.ToString(), InitiativeID, TaskID, ProgressDelta);
|
||||
TC_LOG_DEBUG("network.opcode", "CMSG_INITIATIVE_REPORT_PROGRESS NeighborhoodGuid: {}",
|
||||
NeighborhoodGuid.ToString());
|
||||
}
|
||||
|
||||
void GetInitiativeClaimRewardRequest::Read()
|
||||
|
||||
@@ -2864,9 +2864,6 @@ namespace WorldPackets::Neighborhood
|
||||
InitiativeReportProgress(WorldPacket&& packet) : ClientPacket(CMSG_INITIATIVE_REPORT_PROGRESS, std::move(packet)) {}
|
||||
void Read() override;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint32 InitiativeID = 0;
|
||||
uint32 TaskID = 0;
|
||||
uint32 ProgressDelta = 0;
|
||||
};
|
||||
|
||||
class GetInitiativeClaimRewardRequest final : public ClientPacket
|
||||
|
||||
@@ -1776,14 +1776,14 @@ enum OpcodeServer : uint32
|
||||
SMSG_HOUSING_SVC_REQUEST_PLAYER_RELOAD_DATA = 0x540020,
|
||||
SMSG_INITIALIZE_FACTIONS = 0x4201CC,
|
||||
SMSG_INITIAL_SETUP = 0x420014,
|
||||
SMSG_INITIATIVE_CHEST_RESULT = 0x420369,
|
||||
SMSG_INITIATIVE_CHEST_RESULT = 0x420380,
|
||||
SMSG_INITIATIVE_COMPLETE = 0x420363,
|
||||
SMSG_INITIATIVE_MILESTONE_UPDATE = 0x42036A,
|
||||
SMSG_INITIATIVE_POINTS_UPDATE = 0x42036B,
|
||||
SMSG_INITIATIVE_MILESTONE_UPDATE = 0x420381,
|
||||
SMSG_INITIATIVE_POINTS_UPDATE = 0x420382,
|
||||
SMSG_INITIATIVE_REWARD_AVAILABLE = 0x420368,
|
||||
SMSG_INITIATIVE_SERVICE_STATUS = 0x420361,
|
||||
SMSG_INITIATIVE_TASK_COMPLETE = 0x420362,
|
||||
SMSG_INITIATIVE_TRACKED_UPDATED = 0x42036C,
|
||||
SMSG_INITIATIVE_TRACKED_UPDATED = 0x420383,
|
||||
SMSG_INITIATIVE_UPDATE_STATUS = 0x42036D,
|
||||
SMSG_INIT_WORLD_STATES = 0x4201EE,
|
||||
SMSG_INSPECT_RESULT = 0x4200D9,
|
||||
|
||||
Reference in New Issue
Block a user