Housing: Fix post-purchase dashboard with sniff-verified packet formats
Correct multiple packet format mismatches preventing the housing dashboard from populating after buying a plot: - Fix HouseStatusResponse wire format: 3 PackedGUIDs + uint32 (was 4 + 2 uint8s) - Fix PlayerHousesInfoResponse to use JamCurrentHouseInfo with correct field mapping: OwnerGuid=HouseGUID, SecondaryOwnerGuid=PlotGUID, PlotGuid=NeighborhoodGUID, Flags=PlotIndex, HouseTypeId=32 - Add PlotGUID generation (HighGuid::Housing subType=2) to Housing class - Send FirstTimeDecorAcquisition packets for starter decor items after purchase - Send 2x UpdateHousesLevelFavor with 36-byte format after purchase - Fix owner permissions to 0xE0 (bits 5,6,7) instead of 0xFF - Add serverside spell 1266097 for cornerstone UILink click handling - Fix cornerstone GO interaction to cast spell instead of direct handler call - Add createTime tracking to Housing for optional HouseId field - Register housing spell script and new opcode handlers
This commit is contained in:
@@ -805,8 +805,8 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_INS_CHARACTER_BANK_TAB_SETTINGS, "INSERT INTO character_bank_tab_settings (characterGuid, tabId, name, icon, description, depositFlags) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
|
||||
//WowCommunity
|
||||
// Housing
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING, "SELECT houseId, neighborhoodGuid, plotIndex, houseLevel, favor, settingsFlags, exteriorLocked, houseSize, houseType FROM character_housing WHERE guid = ?", CONNECTION_ASYNC);
|
||||
// Housing
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_HOUSING, "SELECT houseId, neighborhoodGuid, plotIndex, houseLevel, favor, settingsFlags, exteriorLocked, houseSize, houseType, createTime FROM character_housing WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING, "INSERT INTO character_housing (guid, houseId, neighborhoodGuid, plotIndex, houseLevel, favor, settingsFlags, exteriorLocked, houseSize, houseType, createTime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UNIX_TIMESTAMP())", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_CHARACTER_HOUSING, "INSERT INTO character_housing (guid, houseId, neighborhoodGuid, plotIndex, houseLevel, favor, settingsFlags, createTime) VALUES (?, ?, ?, ?, ?, ?, ?, UNIX_TIMESTAMP())", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHARACTER_HOUSING, "DELETE FROM character_housing WHERE guid = ?", CONNECTION_ASYNC);
|
||||
|
||||
@@ -5184,10 +5184,10 @@ struct RoomComponentEntry
|
||||
struct RoomComponentOptionEntry
|
||||
{
|
||||
uint32 ID;
|
||||
int32 RoomComponentID;
|
||||
uint8 Type;
|
||||
int32 ModelFileDataID;
|
||||
uint8 SubType;
|
||||
int32 ModelFileDataID;
|
||||
int32 RoomComponentID;
|
||||
int32 MeshStyleFilterID;
|
||||
int32 HouseThemeID;
|
||||
int32 Flags;
|
||||
|
||||
@@ -3385,6 +3385,14 @@ void GameObject::Use(Unit* user, bool ignoreCastInProgress /*= false*/)
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::Use(GAMEOBJECT_TYPE_UI_LINK): entry={} guid={} "
|
||||
"UILinkType={} PlayerInteractionType={} spell={} player={}",
|
||||
GetEntry(), GetGUID().ToString(),
|
||||
GetGOInfo()->UILink.UILinkType,
|
||||
GetGOInfo()->UILink.PlayerInteractionType,
|
||||
GetGOInfo()->UILink.spell,
|
||||
player->GetGUID().ToString());
|
||||
|
||||
if (GetGOInfo()->UILink.PlayerInteractionType)
|
||||
{
|
||||
WorldPackets::NPC::NPCInteractionOpenResult npcInteraction;
|
||||
@@ -3393,8 +3401,23 @@ void GameObject::Use(Unit* user, bool ignoreCastInProgress /*= false*/)
|
||||
npcInteraction.Success = true;
|
||||
player->SendDirectMessage(npcInteraction.Write());
|
||||
|
||||
if (uint32 spellId = GetGOInfo()->UILink.spell)
|
||||
TC_LOG_DEBUG("housing", " -> Sent SMSG_NPC_INTERACTION_OPEN_RESULT: npc={} interactionType={} success=true",
|
||||
GetGUID().ToString(), GetGOInfo()->UILink.PlayerInteractionType);
|
||||
|
||||
uint32 spellId = GetGOInfo()->UILink.spell;
|
||||
|
||||
// Per-plot cornerstone GOs from DB2 CASC data have spell=0 in their
|
||||
// template. The master template (entry 457142) has Data8=1266097 but
|
||||
// the actual per-plot entries do not. Fall back to the known spell
|
||||
// for CornerstoneInteraction (type 70).
|
||||
if (!spellId && GetGOInfo()->UILink.PlayerInteractionType == 70)
|
||||
spellId = 1266097; // [DNT] Trigger Convo for Unowned Plot
|
||||
|
||||
if (spellId)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", " -> Casting spell {} on player", spellId);
|
||||
player->CastSpell(player, spellId, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4698,4 +4721,9 @@ void GameObject::InitHousingCornerstoneData(uint64 cost, int32 plotIndex)
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FJamHousingCornerstone_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingCornerstoneData));
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingCornerstoneData: entry={} guid={} cost={} plotIndex={} "
|
||||
"isInWorld={} fragmentCount={} updateableCount={}",
|
||||
GetEntry(), GetGUID().ToString(), cost, plotIndex,
|
||||
IsInWorld(), m_entityFragments.Count, m_entityFragments.UpdateableCount);
|
||||
}
|
||||
|
||||
@@ -1990,7 +1990,20 @@ GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid const& guid) const
|
||||
return nullptr;
|
||||
|
||||
if (!go->IsWithinDistInMap(this))
|
||||
{
|
||||
// Debug: log interaction failures for housing cornerstones (type 48 = UI_LINK)
|
||||
if (go->GetGoType() == GAMEOBJECT_TYPE_UI_LINK)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "Player::GetGameObjectIfCanInteractWith FAILED (distance/phase): "
|
||||
"player={} go entry={} guid={} displayId={} dist={:.1f} "
|
||||
"inMap={} inPhase={} atInteractDist={}",
|
||||
GetGUID().ToString(), go->GetEntry(), go->GetGUID().ToString(),
|
||||
go->GetGOInfo()->displayId, GetExactDist(go),
|
||||
go->IsInMap(this), go->InSamePhase(this),
|
||||
go->IsAtInteractDistance(this));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
@@ -24794,8 +24807,28 @@ void Player::SendInitialPacketsBeforeAddToMap()
|
||||
if (HousingMap* housingMap = dynamic_cast<HousingMap*>(GetMap()))
|
||||
if (Neighborhood* neighborhood = housingMap->GetNeighborhood())
|
||||
worldServerInfo.NeighborhoodGUID = neighborhood->GetGuid();
|
||||
//WowCommunity
|
||||
SendDirectMessage(worldServerInfo.Write());
|
||||
|
||||
WorldPacket const* wsiPkt = worldServerInfo.Write();
|
||||
SendDirectMessage(wsiPkt);
|
||||
|
||||
TC_LOG_ERROR("housing", "=== SMSG_WORLD_SERVER_INFO (login) ===\n"
|
||||
" DifficultyID={}, IsTournament={}, XRealmPvp={}, BlockExit={}\n"
|
||||
" HouseGUID: {} (lo={:016X} hi={:016X})\n"
|
||||
" HouseOwnerAccountGUID: {} (lo={:016X} hi={:016X})\n"
|
||||
" HouseCosmeticOwnerGUID: {} (lo={:016X} hi={:016X})\n"
|
||||
" NeighborhoodGUID: {} (lo={:016X} hi={:016X})\n"
|
||||
" Packet size={} bytes",
|
||||
worldServerInfo.DifficultyID, worldServerInfo.IsTournamentRealm,
|
||||
worldServerInfo.XRealmPvpAlert, worldServerInfo.BlockExitingLoadingScreen,
|
||||
worldServerInfo.HouseGUID.ToString(),
|
||||
worldServerInfo.HouseGUID.GetRawValue(0), worldServerInfo.HouseGUID.GetRawValue(1),
|
||||
worldServerInfo.HouseOwnerAccountGUID.ToString(),
|
||||
worldServerInfo.HouseOwnerAccountGUID.GetRawValue(0), worldServerInfo.HouseOwnerAccountGUID.GetRawValue(1),
|
||||
worldServerInfo.HouseCosmeticOwnerGUID.ToString(),
|
||||
worldServerInfo.HouseCosmeticOwnerGUID.GetRawValue(0), worldServerInfo.HouseCosmeticOwnerGUID.GetRawValue(1),
|
||||
worldServerInfo.NeighborhoodGUID.ToString(),
|
||||
worldServerInfo.NeighborhoodGUID.GetRawValue(0), worldServerInfo.NeighborhoodGUID.GetRawValue(1),
|
||||
wsiPkt->size());
|
||||
|
||||
// Spell modifiers
|
||||
SendSpellModifiers();
|
||||
@@ -24950,13 +24983,11 @@ void Player::SendInitialPacketsAfterAddToMap()
|
||||
if (housing)
|
||||
{
|
||||
statusResponse.HouseGuid = housing->GetHouseGuid();
|
||||
statusResponse.OwnerBNetGuid = GetSession()->GetBattlenetAccountGUID();
|
||||
statusResponse.OwnerPlayerGuid = GetGUID();
|
||||
statusResponse.HouseStatus = 1; // Has house
|
||||
statusResponse.PlotIndex = housing->GetPlotIndex();
|
||||
statusResponse.StatusFlags = 0;
|
||||
statusResponse.HouseTemplateGuid = ObjectGuid::Create<HighGuid::Housing>(3, 0, 7, 0);
|
||||
statusResponse.PlotGuid = housing->GetPlotGuid();
|
||||
statusResponse.Status = 0;
|
||||
}
|
||||
// No house: all fields stay at defaults (empty GUIDs, HouseStatus=0, PlotIndex=0xFF).
|
||||
// No house: all fields stay at defaults (empty GUIDs, Status=0).
|
||||
SendDirectMessage(statusResponse.Write());
|
||||
|
||||
// Populate NeighborhoodMirrorData on the Account entity so the
|
||||
@@ -24989,6 +25020,27 @@ void Player::SendInitialPacketsAfterAddToMap()
|
||||
}
|
||||
}
|
||||
|
||||
// Proactively send the neighborhood name response BEFORE the roster.
|
||||
// The client's NeighborhoodState singleton initializes all four display
|
||||
// flags (+572..+575) to 1. Flag +574 is only cleared when the
|
||||
// JamCliNeighborhoodName DataCache already contains the neighborhood
|
||||
// name. By sending this packet first, we pre-populate that cache so
|
||||
// the roster response's display function finds the name resolved.
|
||||
{
|
||||
WorldPackets::Housing::QueryNeighborhoodNameResponse nameResponse;
|
||||
nameResponse.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
nameResponse.Allow = true;
|
||||
nameResponse.Name = neighborhood->GetName();
|
||||
SendDirectMessage(nameResponse.Write());
|
||||
|
||||
TC_LOG_ERROR("housing", "=== SMSG_QUERY_NEIGHBORHOOD_NAME_RESPONSE (0x460012) [login-preload] ===\n"
|
||||
" Allow={}, Name='{}' (len={})\n"
|
||||
" NeighborhoodGuid: {} (lo={:016X} hi={:016X})",
|
||||
nameResponse.Allow, nameResponse.Name, nameResponse.Name.size(),
|
||||
nameResponse.NeighborhoodGuid.ToString(),
|
||||
nameResponse.NeighborhoodGuid.GetRawValue(0), nameResponse.NeighborhoodGuid.GetRawValue(1));
|
||||
}
|
||||
|
||||
// Proactively send the roster so the client has plot occupancy data
|
||||
// for the map without needing to request it.
|
||||
WorldPackets::Neighborhood::NeighborhoodGetRosterResponse rosterResponse;
|
||||
@@ -25009,7 +25061,19 @@ void Player::SendInitialPacketsAfterAddToMap()
|
||||
data.HouseGuid = plotInfo->HouseGuid;
|
||||
rosterResponse.Members.push_back(data);
|
||||
}
|
||||
SendDirectMessage(rosterResponse.Write());
|
||||
WorldPacket const* loginRosterPkt = rosterResponse.Write();
|
||||
SendDirectMessage(loginRosterPkt);
|
||||
|
||||
// Debug: log raw GUID bytes to verify roster packet populates handler context correctly
|
||||
TC_LOG_ERROR("housing", "=== SMSG_NEIGHBORHOOD_GET_ROSTER_RESPONSE (0x5C0012) [login] ===\n"
|
||||
" GroupNeighborhoodGuid: {} (lo={:016X} hi={:016X})\n"
|
||||
" GroupOwnerGuid: {} (lo={:016X} hi={:016X})\n"
|
||||
" NeighborhoodName='{}', Members={}, Packet size={} bytes",
|
||||
rosterResponse.GroupNeighborhoodGuid.ToString(),
|
||||
rosterResponse.GroupNeighborhoodGuid.GetRawValue(0), rosterResponse.GroupNeighborhoodGuid.GetRawValue(1),
|
||||
rosterResponse.GroupOwnerGuid.ToString(),
|
||||
rosterResponse.GroupOwnerGuid.GetRawValue(0), rosterResponse.GroupOwnerGuid.GetRawValue(1),
|
||||
rosterResponse.NeighborhoodName, rosterResponse.Members.size(), loginRosterPkt->size());
|
||||
|
||||
TC_LOG_INFO("housing", "Player {} entered neighborhood map {} - sent HouseStatus + roster + NeighborhoodMirrorData (Neighborhood: '{}' {}, Members: {}, Plots: {}, HasHouse: {})",
|
||||
GetGUID().ToString(), GetMapId(), neighborhood->GetName(), neighborhood->GetGuid().ToString(),
|
||||
|
||||
@@ -29,10 +29,41 @@
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "CharacterCache.h"
|
||||
#include "SocialMgr.h"
|
||||
|
||||
// ============================================================
|
||||
// Decline Neighborhood Invites
|
||||
// ============================================================
|
||||
namespace
|
||||
{
|
||||
std::string HexDumpPacket(WorldPacket const* packet, size_t maxBytes = 128)
|
||||
{
|
||||
if (!packet || packet->size() == 0)
|
||||
return "(empty)";
|
||||
size_t len = std::min(packet->size(), maxBytes);
|
||||
std::string result;
|
||||
result.reserve(len * 3 + 32);
|
||||
uint8 const* raw = packet->data();
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
if (i > 0 && i % 32 == 0)
|
||||
result += "\n ";
|
||||
else if (i > 0)
|
||||
result += ' ';
|
||||
result += fmt::format("{:02X}", raw[i]);
|
||||
}
|
||||
if (len < packet->size())
|
||||
result += fmt::format(" ...({} more)", packet->size() - len);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GuidHex(ObjectGuid const& guid)
|
||||
{
|
||||
return fmt::format("lo={:016X} hi={:016X}", guid.GetRawValue(0), guid.GetRawValue(1));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Decline Neighborhood Invites
|
||||
// ============================================================
|
||||
|
||||
void WorldSession::HandleDeclineNeighborhoodInvites(WorldPackets::Housing::DeclineNeighborhoodInvites const& declineNeighborhoodInvites)
|
||||
{
|
||||
@@ -119,11 +150,9 @@ void WorldSession::HandleHouseInteriorLeaveHouse(WorldPackets::Housing::HouseInt
|
||||
// Send updated house status to acknowledge the interior exit
|
||||
WorldPackets::Housing::HousingHouseStatusResponse response;
|
||||
response.HouseGuid = housing->GetHouseGuid();
|
||||
response.OwnerBNetGuid = GetBattlenetAccountGUID();
|
||||
response.OwnerPlayerGuid = player->GetGUID();
|
||||
response.HouseStatus = 1; // House active, not in interior
|
||||
response.PlotIndex = housing->GetPlotIndex();
|
||||
response.StatusFlags = 0;
|
||||
response.HouseTemplateGuid = ObjectGuid::Create<HighGuid::Housing>(3, 0, 7, 0); // static template GUID
|
||||
response.PlotGuid = housing->GetPlotGuid();
|
||||
response.Status = 0;
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSE_INTERIOR_LEAVE_HOUSE: Player {} leaving house interior",
|
||||
@@ -1122,14 +1151,24 @@ void WorldSession::HandleHousingSvcsPlayerViewHousesByBnetAccount(WorldPackets::
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
// BNet account house lookup - uses the same player neighborhood query for now
|
||||
// In a complete implementation this would query all characters on the BNet account
|
||||
// Find all neighborhoods where the queried BNet account has a plot (owns a house)
|
||||
std::vector<Neighborhood*> neighborhoods = sNeighborhoodMgr.GetNeighborhoodsByBnetAccount(housingSvcsPlayerViewHousesByBnetAccount.BnetAccountGuid);
|
||||
|
||||
WorldPackets::Housing::HousingSvcsPlayerViewHousesResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Neighborhoods.reserve(neighborhoods.size());
|
||||
for (Neighborhood const* neighborhood : neighborhoods)
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsPlayerViewHousesResponse::NeighborhoodInfoData info;
|
||||
info.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
info.Name = neighborhood->GetName();
|
||||
info.MapID = neighborhood->GetNeighborhoodMapID();
|
||||
response.Neighborhoods.push_back(std::move(info));
|
||||
}
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_SVCS_PLAYER_VIEW_HOUSES_BY_BNET_ACCOUNT BnetAccountGuid: {}",
|
||||
housingSvcsPlayerViewHousesByBnetAccount.BnetAccountGuid.ToString());
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_SVCS_PLAYER_VIEW_HOUSES_BY_BNET_ACCOUNT BnetAccountGuid: {}, FoundNeighborhoods: {}",
|
||||
housingSvcsPlayerViewHousesByBnetAccount.BnetAccountGuid.ToString(), uint32(neighborhoods.size()));
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingSvcsGetPlayerHousesInfo(WorldPackets::Housing::HousingSvcsGetPlayerHousesInfo const& /*housingSvcsGetPlayerHousesInfo*/)
|
||||
@@ -1143,11 +1182,19 @@ void WorldSession::HandleHousingSvcsGetPlayerHousesInfo(WorldPackets::Housing::H
|
||||
WorldPackets::Housing::HousingSvcsGetPlayerHousesInfoResponse response;
|
||||
for (Housing const* housing : player->GetAllHousings())
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsGetPlayerHousesInfoResponse::HouseInfoData info;
|
||||
info.HouseGuid = housing->GetHouseGuid();
|
||||
info.NeighborhoodGuid = housing->GetNeighborhoodGuid();
|
||||
info.PlotIndex = housing->GetPlotIndex();
|
||||
info.Level = static_cast<uint8>(housing->GetLevel());
|
||||
WorldPackets::Housing::JamCurrentHouseInfo info;
|
||||
// Sniff-verified: OwnerGuid=HouseGUID, SecondaryOwnerGuid=PlotGUID, PlotGuid=NeighborhoodGUID
|
||||
info.OwnerGuid = housing->GetHouseGuid();
|
||||
info.SecondaryOwnerGuid = housing->GetPlotGuid();
|
||||
info.PlotGuid = housing->GetNeighborhoodGuid();
|
||||
info.Flags = housing->GetPlotIndex();
|
||||
info.HouseTypeId = 32;
|
||||
// Set creation timestamp if available (sniff shows StatusFlags=0x80 with uint64 timestamp)
|
||||
if (housing->GetCreateTime())
|
||||
{
|
||||
info.StatusFlags = 0x80;
|
||||
info.HouseId = static_cast<uint64>(housing->GetCreateTime());
|
||||
}
|
||||
response.Houses.push_back(info);
|
||||
}
|
||||
SendPacket(response.Write());
|
||||
@@ -1220,10 +1267,9 @@ void WorldSession::HandleHousingSvcsStartTutorial(WorldPackets::Housing::Housing
|
||||
return;
|
||||
|
||||
// Step 1: Find or create a tutorial neighborhood for the player's faction.
|
||||
// The tutorial assigns the player to a system-generated neighborhood so they
|
||||
// have a place to buy a plot and acquire a house.
|
||||
Neighborhood* neighborhood = sNeighborhoodMgr.FindOrCreateTutorialNeighborhood(
|
||||
player->GetGUID(), player->GetTeam());
|
||||
// The tutorial only needs a neighborhood to exist so the map instance can be
|
||||
// created. It does NOT grant membership ? that happens when the player buys a plot.
|
||||
Neighborhood* neighborhood = sNeighborhoodMgr.FindOrCreatePublicNeighborhood(player->GetTeam());
|
||||
|
||||
if (neighborhood)
|
||||
{
|
||||
@@ -1252,15 +1298,31 @@ void WorldSession::HandleHousingSvcsStartTutorial(WorldPackets::Housing::Housing
|
||||
|
||||
// Step 2: Auto-accept the "My First Home" quest (91863) so the player can
|
||||
// progress through the tutorial by interacting with the steward NPC.
|
||||
// Skip if already completed (account-wide warband quest) or already in quest log.
|
||||
static constexpr uint32 QUEST_MY_FIRST_HOME = 91863;
|
||||
if (Quest const* quest = sObjectMgr->GetQuestTemplate(QUEST_MY_FIRST_HOME))
|
||||
{
|
||||
if (player->CanAddQuest(quest, true))
|
||||
QuestStatus status = player->GetQuestStatus(QUEST_MY_FIRST_HOME);
|
||||
if (status == QUEST_STATUS_NONE)
|
||||
{
|
||||
player->AddQuestAndCheckCompletion(quest, nullptr);
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_SVCS_START_TUTORIAL: Auto-accepted quest {} for player {}",
|
||||
// Quest not in log and not yet rewarded ? safe to add
|
||||
if (player->CanAddQuest(quest, true))
|
||||
{
|
||||
player->AddQuestAndCheckCompletion(quest, nullptr);
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_SVCS_START_TUTORIAL: Auto-accepted quest {} for player {}",
|
||||
QUEST_MY_FIRST_HOME, player->GetGUID().ToString());
|
||||
}
|
||||
}
|
||||
else if (status == QUEST_STATUS_REWARDED)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "CMSG_HOUSING_SVCS_START_TUTORIAL: Quest {} already completed (warband) for player {}, skipping",
|
||||
QUEST_MY_FIRST_HOME, player->GetGUID().ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "CMSG_HOUSING_SVCS_START_TUTORIAL: Quest {} already in log (status {}) for player {}, skipping",
|
||||
QUEST_MY_FIRST_HOME, uint32(status), player->GetGUID().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Teleport the player to the housing neighborhood via faction-specific spell.
|
||||
@@ -1468,14 +1530,51 @@ void WorldSession::HandleHousingSvcsGetBnetFriendNeighborhoods(WorldPackets::Hou
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
// BNet friend neighborhood lookup requires BNet social integration
|
||||
// For now, acknowledge the request
|
||||
PlayerSocial* social = player->GetSocial();
|
||||
if (!social)
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsGetBnetFriendNeighborhoodsResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Build response by iterating all neighborhoods and checking if any plot owner
|
||||
// is on the requesting player's friend list. Each player can own at most one
|
||||
// house per faction (2 houses max), so results are naturally bounded.
|
||||
WorldPackets::Housing::HousingSvcsGetBnetFriendNeighborhoodsResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
|
||||
std::vector<Neighborhood*> allNeighborhoods = sNeighborhoodMgr.GetAllNeighborhoods();
|
||||
for (Neighborhood const* neighborhood : allNeighborhoods)
|
||||
{
|
||||
for (auto const& plot : neighborhood->GetPlots())
|
||||
{
|
||||
if (!plot.IsOccupied() || plot.OwnerGuid.IsEmpty())
|
||||
continue;
|
||||
|
||||
// Check if the plot owner is a friend of the requesting player
|
||||
if (!social->HasFriend(plot.OwnerGuid))
|
||||
continue;
|
||||
|
||||
// Resolve the friend's character name
|
||||
std::string friendName;
|
||||
if (!sCharacterCache->GetCharacterNameByGuid(plot.OwnerGuid, friendName))
|
||||
continue;
|
||||
|
||||
WorldPackets::Housing::HousingSvcsGetBnetFriendNeighborhoodsResponse::BnetFriendNeighborhoodData data;
|
||||
data.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
data.FriendName = std::move(friendName);
|
||||
data.MapID = neighborhood->GetNeighborhoodMapID();
|
||||
response.Neighborhoods.push_back(std::move(data));
|
||||
break; // Only list each neighborhood once per friend check
|
||||
}
|
||||
}
|
||||
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_SVCS_GET_BNET_FRIEND_NEIGHBORHOODS BnetAccountGuid: {}",
|
||||
housingSvcsGetBnetFriendNeighborhoods.BnetAccountGuid.ToString());
|
||||
TC_LOG_INFO("housing", "CMSG_HOUSING_SVCS_GET_BNET_FRIEND_NEIGHBORHOODS BnetAccountGuid: {}, FriendNeighborhoods: {}",
|
||||
housingSvcsGetBnetFriendNeighborhoods.BnetAccountGuid.ToString(), uint32(response.Neighborhoods.size()));
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingSvcsDeleteAllNeighborhoodInvites(WorldPackets::Housing::HousingSvcsDeleteAllNeighborhoodInvites const& /*housingSvcsDeleteAllNeighborhoodInvites*/)
|
||||
@@ -1512,22 +1611,16 @@ void WorldSession::HandleHousingHouseStatus(WorldPackets::Housing::HousingHouseS
|
||||
if (housing)
|
||||
{
|
||||
response.HouseGuid = housing->GetHouseGuid();
|
||||
response.OwnerBNetGuid = GetBattlenetAccountGUID();
|
||||
response.OwnerPlayerGuid = player->GetGUID();
|
||||
response.HouseStatus = 1; // Active
|
||||
response.PlotIndex = housing->GetPlotIndex();
|
||||
response.StatusFlags = 0;
|
||||
response.HouseTemplateGuid = ObjectGuid::Create<HighGuid::Housing>(3, 0, 7, 0);
|
||||
response.PlotGuid = housing->GetPlotGuid();
|
||||
response.Status = 0;
|
||||
}
|
||||
// No house: all fields stay at defaults (empty GUIDs, HouseStatus=0, PlotIndex=0xFF).
|
||||
// Neighborhood context is provided separately via SMSG_HOUSING_GET_CURRENT_HOUSE_INFO_RESPONSE.
|
||||
// No house: all fields stay at defaults (empty GUIDs, Status=0).
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", ">>> CMSG_HOUSING_HOUSE_STATUS received");
|
||||
TC_LOG_INFO("housing", "<<< SMSG_HOUSING_HOUSE_STATUS_RESPONSE sent (HouseStatus: {}, PlotIndex: {}, StatusFlags: {}, HouseGuid: {}, "
|
||||
"OwnerBNetGuid: {}, OwnerPlayerGuid: {})",
|
||||
response.HouseStatus, response.PlotIndex, response.StatusFlags,
|
||||
response.HouseGuid.ToString(), response.OwnerBNetGuid.ToString(),
|
||||
response.OwnerPlayerGuid.ToString());
|
||||
TC_LOG_INFO("housing", "<<< SMSG_HOUSING_HOUSE_STATUS_RESPONSE sent (HouseGuid: {}, PlotGuid: {}, Status: {})",
|
||||
response.HouseGuid.ToString(), response.PlotGuid.ToString(), response.Status);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetPlayerPermissions(WorldPackets::Housing::HousingGetPlayerPermissions const& housingGetPlayerPermissions)
|
||||
@@ -1552,7 +1645,9 @@ void WorldSession::HandleHousingGetPlayerPermissions(WorldPackets::Housing::Hous
|
||||
if (targetGuid == player->GetGUID())
|
||||
{
|
||||
// House owner gets full permissions
|
||||
response.PermissionFlags = 0xFFFF;
|
||||
// Sniff-verified: owner permissions are 0xE0 (bits 5,6,7)
|
||||
response.ResultCode = 0;
|
||||
response.PermissionFlags = 0xE0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1590,17 +1685,19 @@ void WorldSession::HandleHousingGetPlayerPermissions(WorldPackets::Housing::Hous
|
||||
if (flags & HOUSE_SETTING_PLOT_ACCESS_PARTY)
|
||||
permissions |= HOUSE_SETTING_PLOT_ACCESS_PARTY;
|
||||
|
||||
response.PermissionFlags = static_cast<uint16>(permissions);
|
||||
response.ResultCode = 0; // Success
|
||||
response.PermissionFlags = static_cast<uint8>(permissions & 0xFF);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.ResultCode = 0;
|
||||
response.PermissionFlags = 0;
|
||||
}
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", "<<< SMSG_HOUSING_GET_PLAYER_PERMISSIONS_RESPONSE sent (HouseGuid: {}, PermissionFlags: 0x{:04X})",
|
||||
response.HouseGuid.ToString(), response.PermissionFlags);
|
||||
TC_LOG_INFO("housing", "<<< SMSG_HOUSING_GET_PLAYER_PERMISSIONS_RESPONSE sent (HouseGuid: {}, ResultCode: {}, PermissionFlags: 0x{:02X})",
|
||||
response.HouseGuid.ToString(), response.ResultCode, response.PermissionFlags);
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingGetCurrentHouseInfo(WorldPackets::Housing::HousingGetCurrentHouseInfo const& housingGetCurrentHouseInfo)
|
||||
@@ -1616,25 +1713,26 @@ void WorldSession::HandleHousingGetCurrentHouseInfo(WorldPackets::Housing::Housi
|
||||
WorldPackets::Housing::HousingGetCurrentHouseInfoResponse response;
|
||||
if (housing)
|
||||
{
|
||||
response.HouseGuid = housing->GetHouseGuid();
|
||||
response.OwnerPlayerGuid = player->GetGUID();
|
||||
response.NeighborhoodGuid = housing->GetNeighborhoodGuid();
|
||||
response.PlotIndex = housing->GetPlotIndex();
|
||||
response.HouseProperties = housing->GetSettingsFlags() & 0xFF; // Packed property bits
|
||||
response.HouseLevel = static_cast<uint8>(housing->GetLevel());
|
||||
// Sniff-verified: JamCurrentHouseInfo fields carry HouseGuid, PlotGuid, NeighborhoodGuid
|
||||
response.HouseInfo.OwnerGuid = housing->GetHouseGuid();
|
||||
response.HouseInfo.SecondaryOwnerGuid = housing->GetPlotGuid();
|
||||
response.HouseInfo.PlotGuid = housing->GetNeighborhoodGuid();
|
||||
response.HouseInfo.Flags = housing->GetPlotIndex();
|
||||
response.HouseInfo.HouseTypeId = 32; // sniff value: 0x20 = default house type
|
||||
response.HouseInfo.StatusFlags = 0;
|
||||
}
|
||||
else if (HousingMap* housingMap = dynamic_cast<HousingMap*>(player->GetMap()))
|
||||
{
|
||||
// No house, but on a housing map ? provide the neighborhood GUID so
|
||||
// the client knows which neighborhood it's viewing (enables purchase UI).
|
||||
// Do NOT set OwnerPlayerGuid ? the player doesn't own anything yet.
|
||||
// No house, but on a housing map ? provide the neighborhood GUID
|
||||
// in the PlotGuid field (3rd GUID) so the client knows the context.
|
||||
if (Neighborhood* neighborhood = housingMap->GetNeighborhood())
|
||||
response.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
response.HouseInfo.PlotGuid = neighborhood->GetGuid();
|
||||
}
|
||||
response.ResponseFlags = 0;
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", "<<< SMSG_HOUSING_GET_CURRENT_HOUSE_INFO_RESPONSE sent (HasHouse: {}, HouseGuid: {}, NeighborhoodGuid: {})",
|
||||
housing ? "yes" : "no", response.HouseGuid.ToString(), response.NeighborhoodGuid.ToString());
|
||||
TC_LOG_INFO("housing", "<<< SMSG_HOUSING_GET_CURRENT_HOUSE_INFO_RESPONSE sent (HasHouse: {}, HouseGuid: {})",
|
||||
housing ? "yes" : "no", response.HouseInfo.OwnerGuid.ToString());
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingResetKioskMode(WorldPackets::Housing::HousingResetKioskMode const& /*housingResetKioskMode*/)
|
||||
@@ -1666,21 +1764,31 @@ void WorldSession::HandleQueryNeighborhoodInfo(WorldPackets::Housing::QueryNeigh
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::QueryNeighborhoodNameResponse response;
|
||||
response.NeighborhoodGuid = queryNeighborhoodInfo.NeighborhoodGuid;
|
||||
|
||||
Neighborhood const* neighborhood = sNeighborhoodMgr.ResolveNeighborhood(queryNeighborhoodInfo.NeighborhoodGuid, player);
|
||||
if (neighborhood)
|
||||
{
|
||||
// Use the canonical neighborhood GUID, not the client's (which may be a GO GUID or empty)
|
||||
response.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
response.Allow = true;
|
||||
response.Name = neighborhood->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.NeighborhoodGuid = queryNeighborhoodInfo.NeighborhoodGuid;
|
||||
response.Allow = false;
|
||||
}
|
||||
|
||||
SendPacket(response.Write());
|
||||
WorldPacket const* namePkt = response.Write();
|
||||
SendPacket(namePkt);
|
||||
|
||||
TC_LOG_INFO("housing", "CMSG_QUERY_NEIGHBORHOOD_INFO NeighborhoodGuid: {}, Found: {}",
|
||||
queryNeighborhoodInfo.NeighborhoodGuid.ToString(), response.Allow);
|
||||
TC_LOG_ERROR("housing", "=== SMSG_QUERY_NEIGHBORHOOD_NAME_RESPONSE (0x460012) ===\n"
|
||||
" Allow={}, Name='{}' (len={})\n"
|
||||
" NeighborhoodGuid: {} ({})\n"
|
||||
" Packet size={} bytes, hex:\n {}",
|
||||
response.Allow, response.Name, response.Name.size(),
|
||||
response.NeighborhoodGuid.ToString(), GuidHex(response.NeighborhoodGuid),
|
||||
namePkt->size(), HexDumpPacket(namePkt));
|
||||
}
|
||||
|
||||
void WorldSession::HandleInvitePlayerToNeighborhood(WorldPackets::Housing::InvitePlayerToNeighborhood const& invitePlayerToNeighborhood)
|
||||
@@ -1693,7 +1801,7 @@ void WorldSession::HandleInvitePlayerToNeighborhood(WorldPackets::Housing::Invit
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteResidentResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -1701,7 +1809,7 @@ void WorldSession::HandleInvitePlayerToNeighborhood(WorldPackets::Housing::Invit
|
||||
if (!neighborhood->IsManager(player->GetGUID()) && !neighborhood->IsOwner(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteResidentResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -1709,8 +1817,7 @@ void WorldSession::HandleInvitePlayerToNeighborhood(WorldPackets::Housing::Invit
|
||||
HousingResult result = neighborhood->InviteResident(player->GetGUID(), invitePlayerToNeighborhood.PlayerGuid);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteResidentResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.NeighborhoodGuid = invitePlayerToNeighborhood.NeighborhoodGuid;
|
||||
response.Result = static_cast<uint8>(result);
|
||||
response.InviteeGuid = invitePlayerToNeighborhood.PlayerGuid;
|
||||
SendPacket(response.Write());
|
||||
|
||||
@@ -1721,8 +1828,6 @@ void WorldSession::HandleInvitePlayerToNeighborhood(WorldPackets::Housing::Invit
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteNotification notification;
|
||||
notification.NeighborhoodGuid = invitePlayerToNeighborhood.NeighborhoodGuid;
|
||||
notification.InviterGuid = player->GetGUID();
|
||||
notification.NeighborhoodName = neighborhood->GetName();
|
||||
invitee->SendDirectMessage(notification.Write());
|
||||
}
|
||||
}
|
||||
@@ -1762,3 +1867,39 @@ void WorldSession::HandleGuildGetOthersOwnedHouses(WorldPackets::Housing::GuildG
|
||||
TC_LOG_INFO("housing", "CMSG_GUILD_GET_OTHERS_OWNED_HOUSES PlayerGuid: {}, FoundNeighborhoods: {}",
|
||||
guildGetOthersOwnedHouses.PlayerGuid.ToString(), uint32(neighborhoods.size()));
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Photo Sharing Authorization
|
||||
// ============================================================
|
||||
|
||||
void WorldSession::HandleHousingPhotoSharingCompleteAuthorization(WorldPackets::Housing::HousingPhotoSharingCompleteAuthorization const& /*packet*/)
|
||||
{
|
||||
Player* player = GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
// Photo sharing authorization grants permission for the player's house photos
|
||||
// to be shared with other players through the housing social features.
|
||||
WorldPackets::Housing::HousingPhotoSharingAuthorizationResult response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_HOUSING_PHOTO_SHARING_COMPLETE_AUTHORIZATION Player: {}",
|
||||
player->GetGUID().ToString());
|
||||
}
|
||||
|
||||
void WorldSession::HandleHousingPhotoSharingClearAuthorization(WorldPackets::Housing::HousingPhotoSharingClearAuthorization const& /*packet*/)
|
||||
{
|
||||
Player* player = GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
// Clears any existing photo sharing authorization for the player's house,
|
||||
// revoking permission for photos to be shared publicly.
|
||||
WorldPackets::Housing::HousingPhotoSharingAuthorizationClearedResult response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_HOUSING_PHOTO_SHARING_CLEAR_AUTHORIZATION Player: {}",
|
||||
player->GetGUID().ToString());
|
||||
}
|
||||
|
||||
@@ -34,9 +34,38 @@
|
||||
#include "Player.h"
|
||||
#include "World.h"
|
||||
|
||||
// ============================================================
|
||||
// Neighborhood Charter System
|
||||
// ============================================================
|
||||
namespace
|
||||
{
|
||||
std::string HexDumpPacket(WorldPacket const* packet, size_t maxBytes = 128)
|
||||
{
|
||||
if (!packet || packet->size() == 0)
|
||||
return "(empty)";
|
||||
size_t len = std::min(packet->size(), maxBytes);
|
||||
std::string result;
|
||||
result.reserve(len * 3 + 32);
|
||||
uint8 const* raw = packet->data();
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
if (i > 0 && i % 32 == 0)
|
||||
result += "\n ";
|
||||
else if (i > 0)
|
||||
result += ' ';
|
||||
result += fmt::format("{:02X}", raw[i]);
|
||||
}
|
||||
if (len < packet->size())
|
||||
result += fmt::format(" ...({} more)", packet->size() - len);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GuidHex(ObjectGuid const& guid)
|
||||
{
|
||||
return fmt::format("lo={:016X} hi={:016X}", guid.GetRawValue(0), guid.GetRawValue(1));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Neighborhood Charter System
|
||||
// ============================================================
|
||||
|
||||
void WorldSession::HandleNeighborhoodCharterOpenConfirmationUI(WorldPackets::Neighborhood::NeighborhoodCharterOpenConfirmationUI const& /*neighborhoodCharterOpenConfirmationUI*/)
|
||||
{
|
||||
@@ -50,7 +79,7 @@ void WorldSession::HandleNeighborhoodCharterOpenConfirmationUI(WorldPackets::Nei
|
||||
// Client requests to open the charter creation confirmation UI
|
||||
// The client handles UI display; server acknowledges readiness
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterOpenConfirmationUIResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Sent NeighborhoodCharterOpenConfirmationUIResponse (SUCCESS) to player {}",
|
||||
@@ -71,7 +100,7 @@ void WorldSession::HandleNeighborhoodCharterCreate(WorldPackets::Neighborhood::N
|
||||
if (neighborhoodCharterCreate.Name.empty() || neighborhoodCharterCreate.Name.length() > HOUSING_MAX_NAME_LENGTH)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_INVALID_NEIGHBORHOOD_NAME);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_INVALID_NEIGHBORHOOD_NAME);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", "HandleNeighborhoodCharterCreate: Invalid name length for player {}",
|
||||
@@ -96,7 +125,7 @@ void WorldSession::HandleNeighborhoodCharterCreate(WorldPackets::Neighborhood::N
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Player {} created neighborhood charter '{}' (ID: {}, MapID: {})",
|
||||
@@ -118,7 +147,7 @@ void WorldSession::HandleNeighborhoodCharterEdit(WorldPackets::Neighborhood::Nei
|
||||
if (neighborhoodCharterEdit.Name.empty() || neighborhoodCharterEdit.Name.length() > HOUSING_MAX_NAME_LENGTH)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_INVALID_NEIGHBORHOOD_NAME);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_INVALID_NEIGHBORHOOD_NAME);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_INFO("housing", "HandleNeighborhoodCharterEdit: Invalid name length for player {}",
|
||||
@@ -144,7 +173,7 @@ void WorldSession::HandleNeighborhoodCharterEdit(WorldPackets::Neighborhood::Nei
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Player {} edited neighborhood charter '{}' (ID: {}, MapID: {})",
|
||||
@@ -171,7 +200,7 @@ void WorldSession::HandleNeighborhoodCharterFinalize(WorldPackets::Neighborhood:
|
||||
if (!charterResult)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCharterFinalize: No charter found for player {}",
|
||||
@@ -187,7 +216,7 @@ void WorldSession::HandleNeighborhoodCharterFinalize(WorldPackets::Neighborhood:
|
||||
if (!charter.LoadFromDB(charterResult, sigResult))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_DB_ERROR);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_DB_ERROR);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCharterFinalize: Failed to load charter {} from DB",
|
||||
@@ -199,7 +228,7 @@ void WorldSession::HandleNeighborhoodCharterFinalize(WorldPackets::Neighborhood:
|
||||
if (!charter.HasEnoughSignatures())
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_MORE_SIGNATURES_NEEDED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_MORE_SIGNATURES_NEEDED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCharterFinalize: Charter {} has only {}/{} signatures",
|
||||
@@ -223,7 +252,7 @@ void WorldSession::HandleNeighborhoodCharterFinalize(WorldPackets::Neighborhood:
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Player {} finalized charter '{}', created neighborhood {}",
|
||||
@@ -233,7 +262,7 @@ void WorldSession::HandleNeighborhoodCharterFinalize(WorldPackets::Neighborhood:
|
||||
else
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterUpdateResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_DB_ERROR);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_DB_ERROR);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Player {} failed to finalize charter '{}' - neighborhood creation failed",
|
||||
@@ -261,7 +290,7 @@ void WorldSession::HandleNeighborhoodCharterAddSignature(WorldPackets::Neighborh
|
||||
if (!charterResult)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterAddSignatureResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCharterAddSignature: Charter {} not found in DB",
|
||||
@@ -277,7 +306,7 @@ void WorldSession::HandleNeighborhoodCharterAddSignature(WorldPackets::Neighborh
|
||||
if (!charter.LoadFromDB(charterResult, sigResult))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterAddSignatureResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_DB_ERROR);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_DB_ERROR);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCharterAddSignature: Failed to load charter {} from DB",
|
||||
@@ -289,7 +318,7 @@ void WorldSession::HandleNeighborhoodCharterAddSignature(WorldPackets::Neighborh
|
||||
if (!charter.AddSignature(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterAddSignatureResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCharterAddSignature: Player {} could not sign charter {}",
|
||||
@@ -298,7 +327,7 @@ void WorldSession::HandleNeighborhoodCharterAddSignature(WorldPackets::Neighborh
|
||||
}
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterAddSignatureResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
response.CharterGuid = neighborhoodCharterAddSignature.CharterGuid;
|
||||
response.SignerGuid = player->GetGUID();
|
||||
SendPacket(response.Write());
|
||||
@@ -336,7 +365,7 @@ void WorldSession::HandleNeighborhoodCharterSendSignatureRequest(WorldPackets::N
|
||||
|
||||
// Acknowledge to the requester that the signature request was sent
|
||||
WorldPackets::Neighborhood::NeighborhoodCharterAddSignatureResponse ackResponse;
|
||||
ackResponse.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
ackResponse.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(ackResponse.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Player {} requested signature from {} for charter {}",
|
||||
@@ -357,7 +386,7 @@ void WorldSession::HandleNeighborhoodUpdateName(WorldPackets::Neighborhood::Neig
|
||||
if (!housing)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -371,7 +400,7 @@ void WorldSession::HandleNeighborhoodUpdateName(WorldPackets::Neighborhood::Neig
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodUpdateName: Neighborhood {} not found",
|
||||
@@ -383,7 +412,7 @@ void WorldSession::HandleNeighborhoodUpdateName(WorldPackets::Neighborhood::Neig
|
||||
if (!neighborhood->IsOwner(player->GetGUID()) && !neighborhood->IsManager(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodUpdateName: Player {} lacks permission for neighborhood {}",
|
||||
@@ -395,7 +424,7 @@ void WorldSession::HandleNeighborhoodUpdateName(WorldPackets::Neighborhood::Neig
|
||||
if (neighborhoodUpdateName.NewName.empty() || neighborhoodUpdateName.NewName.length() > HOUSING_MAX_NAME_LENGTH)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_INVALID_NEIGHBORHOOD_NAME);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_INVALID_NEIGHBORHOOD_NAME);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodUpdateName: Invalid name length");
|
||||
@@ -414,15 +443,13 @@ void WorldSession::HandleNeighborhoodUpdateName(WorldPackets::Neighborhood::Neig
|
||||
memberPlayer->SendDirectMessage(invalidate.Write());
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameNotification nameNotification;
|
||||
nameNotification.NeighborhoodGuid = neighborhoodGuid;
|
||||
nameNotification.NewName = neighborhoodUpdateName.NewName;
|
||||
memberPlayer->SendDirectMessage(nameNotification.Write());
|
||||
}
|
||||
}
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.NeighborhoodGuid = neighborhoodGuid;
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
// Send guild rename notification if player is in a guild
|
||||
@@ -452,7 +479,7 @@ void WorldSession::HandleNeighborhoodSetPublicFlag(WorldPackets::Neighborhood::N
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodSetPublicFlag: Neighborhood {} not found",
|
||||
@@ -464,7 +491,7 @@ void WorldSession::HandleNeighborhoodSetPublicFlag(WorldPackets::Neighborhood::N
|
||||
if (!neighborhood->IsOwner(player->GetGUID()) && !neighborhood->IsManager(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodSetPublicFlag: Player {} lacks permission for neighborhood {}",
|
||||
@@ -475,7 +502,7 @@ void WorldSession::HandleNeighborhoodSetPublicFlag(WorldPackets::Neighborhood::N
|
||||
neighborhood->SetPublic(neighborhoodSetPublicFlag.IsPublic);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodUpdateNameResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Neighborhood {} set to {} by player {}",
|
||||
@@ -494,7 +521,7 @@ void WorldSession::HandleNeighborhoodAddSecondaryOwner(WorldPackets::Neighborhoo
|
||||
if (!housing)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodAddSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -508,7 +535,7 @@ void WorldSession::HandleNeighborhoodAddSecondaryOwner(WorldPackets::Neighborhoo
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodAddSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodAddSecondaryOwner: Neighborhood {} not found",
|
||||
@@ -520,7 +547,7 @@ void WorldSession::HandleNeighborhoodAddSecondaryOwner(WorldPackets::Neighborhoo
|
||||
if (!neighborhood->IsOwner(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodAddSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodAddSecondaryOwner: Player {} is not owner of neighborhood {}",
|
||||
@@ -531,9 +558,8 @@ void WorldSession::HandleNeighborhoodAddSecondaryOwner(WorldPackets::Neighborhoo
|
||||
HousingResult result = neighborhood->AddManager(neighborhoodAddSecondaryOwner.PlayerGuid);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodAddSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.NeighborhoodGuid = neighborhoodGuid;
|
||||
response.PlayerGuid = neighborhoodAddSecondaryOwner.PlayerGuid;
|
||||
response.Result = static_cast<uint8>(result);
|
||||
SendPacket(response.Write());
|
||||
|
||||
// Broadcast roster update and refresh manager mirror data for all online members
|
||||
@@ -582,7 +608,7 @@ void WorldSession::HandleNeighborhoodRemoveSecondaryOwner(WorldPackets::Neighbor
|
||||
if (!housing)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodRemoveSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -596,7 +622,7 @@ void WorldSession::HandleNeighborhoodRemoveSecondaryOwner(WorldPackets::Neighbor
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodRemoveSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodRemoveSecondaryOwner: Neighborhood {} not found",
|
||||
@@ -608,7 +634,7 @@ void WorldSession::HandleNeighborhoodRemoveSecondaryOwner(WorldPackets::Neighbor
|
||||
if (!neighborhood->IsOwner(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodRemoveSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodRemoveSecondaryOwner: Player {} is not owner of neighborhood {}",
|
||||
@@ -619,9 +645,8 @@ void WorldSession::HandleNeighborhoodRemoveSecondaryOwner(WorldPackets::Neighbor
|
||||
HousingResult result = neighborhood->RemoveManager(neighborhoodRemoveSecondaryOwner.PlayerGuid);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodRemoveSecondaryOwnerResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.NeighborhoodGuid = neighborhoodGuid;
|
||||
response.PlayerGuid = neighborhoodRemoveSecondaryOwner.PlayerGuid;
|
||||
response.Result = static_cast<uint8>(result);
|
||||
SendPacket(response.Write());
|
||||
|
||||
// Broadcast roster update and refresh manager mirror data for all online members
|
||||
@@ -670,7 +695,7 @@ void WorldSession::HandleNeighborhoodInviteResident(WorldPackets::Neighborhood::
|
||||
if (!housing)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteResidentResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -684,7 +709,7 @@ void WorldSession::HandleNeighborhoodInviteResident(WorldPackets::Neighborhood::
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteResidentResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodInviteResident: Neighborhood {} not found",
|
||||
@@ -696,7 +721,7 @@ void WorldSession::HandleNeighborhoodInviteResident(WorldPackets::Neighborhood::
|
||||
if (!neighborhood->IsOwner(player->GetGUID()) && !neighborhood->IsManager(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteResidentResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodInviteResident: Player {} lacks permission for neighborhood {}",
|
||||
@@ -707,8 +732,7 @@ void WorldSession::HandleNeighborhoodInviteResident(WorldPackets::Neighborhood::
|
||||
HousingResult result = neighborhood->InviteResident(player->GetGUID(), neighborhoodInviteResident.PlayerGuid);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteResidentResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.NeighborhoodGuid = neighborhoodGuid;
|
||||
response.Result = static_cast<uint8>(result);
|
||||
response.InviteeGuid = neighborhoodInviteResident.PlayerGuid;
|
||||
SendPacket(response.Write());
|
||||
|
||||
@@ -719,8 +743,6 @@ void WorldSession::HandleNeighborhoodInviteResident(WorldPackets::Neighborhood::
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodInviteNotification notification;
|
||||
notification.NeighborhoodGuid = neighborhoodGuid;
|
||||
notification.InviterGuid = player->GetGUID();
|
||||
notification.NeighborhoodName = neighborhood->GetName();
|
||||
invitee->SendDirectMessage(notification.Write());
|
||||
}
|
||||
}
|
||||
@@ -740,7 +762,7 @@ void WorldSession::HandleNeighborhoodCancelInvitation(WorldPackets::Neighborhood
|
||||
if (!housing)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCancelInvitationResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -754,7 +776,7 @@ void WorldSession::HandleNeighborhoodCancelInvitation(WorldPackets::Neighborhood
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCancelInvitationResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCancelInvitation: Neighborhood {} not found",
|
||||
@@ -766,7 +788,7 @@ void WorldSession::HandleNeighborhoodCancelInvitation(WorldPackets::Neighborhood
|
||||
if (!neighborhood->IsOwner(player->GetGUID()) && !neighborhood->IsManager(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodCancelInvitationResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodCancelInvitation: Player {} lacks permission for neighborhood {}",
|
||||
@@ -777,8 +799,7 @@ void WorldSession::HandleNeighborhoodCancelInvitation(WorldPackets::Neighborhood
|
||||
HousingResult result = neighborhood->CancelInvitation(neighborhoodCancelInvitation.InviteeGuid);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodCancelInvitationResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.NeighborhoodGuid = neighborhoodGuid;
|
||||
response.Result = static_cast<uint8>(result);
|
||||
response.InviteeGuid = neighborhoodCancelInvitation.InviteeGuid;
|
||||
SendPacket(response.Write());
|
||||
|
||||
@@ -800,7 +821,7 @@ void WorldSession::HandleNeighborhoodPlayerDeclineInvite(WorldPackets::Neighborh
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodDeclineInvitationResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodPlayerDeclineInvite: Neighborhood {} not found",
|
||||
@@ -811,7 +832,7 @@ void WorldSession::HandleNeighborhoodPlayerDeclineInvite(WorldPackets::Neighborh
|
||||
HousingResult result = neighborhood->DeclineInvitation(player->GetGUID());
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodDeclineInvitationResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.Result = static_cast<uint8>(result);
|
||||
response.NeighborhoodGuid = neighborhoodPlayerDeclineInvite.NeighborhoodGuid;
|
||||
SendPacket(response.Write());
|
||||
|
||||
@@ -848,15 +869,14 @@ void WorldSession::HandleNeighborhoodPlayerGetInvite(WorldPackets::Neighborhood:
|
||||
WorldPackets::Neighborhood::NeighborhoodPlayerGetInviteResponse response;
|
||||
if (foundNeighborhood && foundInvite)
|
||||
{
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.NeighborhoodGuid = foundNeighborhood->GetGuid();
|
||||
response.InviterGuid = foundInvite->InviterGuid;
|
||||
response.InviteTime = foundInvite->InviteTime;
|
||||
response.NeighborhoodName = foundNeighborhood->GetName();
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
response.Entry.Timestamp = foundInvite->InviteTime;
|
||||
response.Entry.PlayerGuid = foundInvite->InviterGuid;
|
||||
response.Entry.HouseGuid = foundNeighborhood->GetGuid();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
}
|
||||
SendPacket(response.Write());
|
||||
|
||||
@@ -878,7 +898,7 @@ void WorldSession::HandleNeighborhoodGetInvites(WorldPackets::Neighborhood::Neig
|
||||
if (!housing)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodGetInvitesResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -888,7 +908,7 @@ void WorldSession::HandleNeighborhoodGetInvites(WorldPackets::Neighborhood::Neig
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodGetInvitesResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodGetInvites: Neighborhood {} not found",
|
||||
@@ -900,7 +920,7 @@ void WorldSession::HandleNeighborhoodGetInvites(WorldPackets::Neighborhood::Neig
|
||||
if (!neighborhood->IsOwner(player->GetGUID()) && !neighborhood->IsManager(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodGetInvitesResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodGetInvites: Player {} lacks permission for neighborhood {}",
|
||||
@@ -911,15 +931,15 @@ void WorldSession::HandleNeighborhoodGetInvites(WorldPackets::Neighborhood::Neig
|
||||
std::vector<Neighborhood::PendingInvite> const& invites = neighborhood->GetPendingInvites();
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodGetInvitesResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
response.Invites.reserve(invites.size());
|
||||
for (auto const& invite : invites)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodGetInvitesResponse::InviteData data;
|
||||
data.InviteeGuid = invite.InviteeGuid;
|
||||
data.InviterGuid = invite.InviterGuid;
|
||||
data.InviteTime = invite.InviteTime;
|
||||
response.Invites.push_back(data);
|
||||
WorldPackets::Housing::JamNeighborhoodRosterEntry entry;
|
||||
entry.Timestamp = invite.InviteTime;
|
||||
entry.PlayerGuid = invite.InviteeGuid;
|
||||
entry.HouseGuid = ObjectGuid::Empty; // invitees don't have houses yet
|
||||
response.Invites.push_back(entry);
|
||||
}
|
||||
SendPacket(response.Write());
|
||||
|
||||
@@ -933,14 +953,15 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
TC_LOG_INFO("housing", "CMSG_NEIGHBORHOOD_BUY_HOUSE NeighborhoodGuid: {}, PlotIndex: {}",
|
||||
neighborhoodBuyHouse.NeighborhoodGuid.ToString(), neighborhoodBuyHouse.PlotIndex);
|
||||
TC_LOG_INFO("housing", "CMSG_NEIGHBORHOOD_BUY_HOUSE NeighborhoodGuid: {}, RawPlotIndex: {}, PlotGuid: {}",
|
||||
neighborhoodBuyHouse.NeighborhoodGuid.ToString(), neighborhoodBuyHouse.PlotIndex,
|
||||
neighborhoodBuyHouse.PlotGuid.ToString());
|
||||
|
||||
Neighborhood* neighborhood = sNeighborhoodMgr.ResolveNeighborhood(neighborhoodBuyHouse.NeighborhoodGuid, player);
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodBuyHouseResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodBuyHouse: Neighborhood {} not found",
|
||||
@@ -948,23 +969,49 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be a member of the neighborhood
|
||||
// Resolve the actual DB2 PlotIndex from the cornerstone GO entry.
|
||||
// The client may send a different value in the PlotIndex field (array index vs DB2 PlotIndex),
|
||||
// so we always resolve from the GO GUID which is reliable.
|
||||
uint32 cornerstoneGoEntry = neighborhoodBuyHouse.NeighborhoodGuid.GetEntry();
|
||||
uint32 neighborhoodMapId = neighborhood->GetNeighborhoodMapID();
|
||||
NeighborhoodPlotData const* plotData = sHousingMgr.GetPlotByCornerstoneEntry(neighborhoodMapId, cornerstoneGoEntry);
|
||||
if (!plotData)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "HandleNeighborhoodBuyHouse: No plot found for CornerstoneGO entry {} on map {}",
|
||||
cornerstoneGoEntry, neighborhoodMapId);
|
||||
WorldPackets::Neighborhood::NeighborhoodBuyHouseResponse response;
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_GENERIC_FAILURE);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 resolvedPlotIndex = static_cast<uint8>(plotData->PlotIndex);
|
||||
TC_LOG_INFO("housing", "HandleNeighborhoodBuyHouse: Resolved CornerstoneGO {} -> PlotIndex {} (raw was {})",
|
||||
cornerstoneGoEntry, resolvedPlotIndex, neighborhoodBuyHouse.PlotIndex);
|
||||
|
||||
// Auto-join neighborhood if not already a member ? buying a plot implies joining
|
||||
if (!neighborhood->IsMember(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodBuyHouseResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
HousingResult joinResult = neighborhood->AddResident(player->GetGUID());
|
||||
if (joinResult != HOUSING_RESULT_SUCCESS)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodBuyHouseResponse response;
|
||||
response.Result = static_cast<uint8>(joinResult);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodBuyHouse: Player {} is not a member of neighborhood {}",
|
||||
player->GetGUID().ToString(), neighborhoodBuyHouse.NeighborhoodGuid.ToString());
|
||||
return;
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodBuyHouse: Failed to add player {} to neighborhood {} (result {})",
|
||||
player->GetGUID().ToString(), neighborhoodBuyHouse.NeighborhoodGuid.ToString(), static_cast<uint32>(joinResult));
|
||||
return;
|
||||
}
|
||||
TC_LOG_INFO("housing", "HandleNeighborhoodBuyHouse: Auto-added player {} as resident of neighborhood '{}'",
|
||||
player->GetGUID().ToString(), neighborhood->GetName());
|
||||
}
|
||||
|
||||
// Must not already own a house in this neighborhood
|
||||
if (player->GetHousingForNeighborhood(neighborhoodBuyHouse.NeighborhoodGuid))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodBuyHouseResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_INVALID_HOUSE);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_INVALID_HOUSE);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodBuyHouse: Player {} already has a house in neighborhood {}",
|
||||
@@ -972,15 +1019,15 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
return;
|
||||
}
|
||||
|
||||
HousingResult result = neighborhood->PurchasePlot(player->GetGUID(), neighborhoodBuyHouse.PlotIndex);
|
||||
HousingResult result = neighborhood->PurchasePlot(player->GetGUID(), resolvedPlotIndex);
|
||||
if (result == HOUSING_RESULT_SUCCESS)
|
||||
{
|
||||
player->CreateHousing(neighborhoodBuyHouse.NeighborhoodGuid, neighborhoodBuyHouse.PlotIndex);
|
||||
player->CreateHousing(neighborhoodBuyHouse.NeighborhoodGuid, resolvedPlotIndex);
|
||||
|
||||
// Update the PlotInfo with the newly created HouseGuid and Battle.net account GUID
|
||||
if (Housing const* housing = player->GetHousing())
|
||||
{
|
||||
neighborhood->UpdatePlotHouseInfo(neighborhoodBuyHouse.PlotIndex,
|
||||
neighborhood->UpdatePlotHouseInfo(resolvedPlotIndex,
|
||||
housing->GetHouseGuid(), GetBattlenetAccountGUID());
|
||||
}
|
||||
|
||||
@@ -988,14 +1035,67 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
static constexpr uint32 NPC_KILL_CREDIT_BUY_HOME = 248858;
|
||||
player->KilledMonsterCredit(NPC_KILL_CREDIT_BUY_HOME);
|
||||
|
||||
// Retail sequence: FirstTimeDecorAcquisition ? BuyHouseResponse ? LevelFavor updates
|
||||
|
||||
// 1. Send starter decor acquisition notifications (sniff: 7-8 packets before buy response)
|
||||
std::vector<uint32> starterDecorIds = sHousingMgr.GetStarterDecorIds();
|
||||
for (uint32 decorId : starterDecorIds)
|
||||
{
|
||||
WorldPackets::Housing::HousingFirstTimeDecorAcquisition decorAcq;
|
||||
decorAcq.DecorEntryID = decorId;
|
||||
SendPacket(decorAcq.Write());
|
||||
}
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodBuyHouse: Sent {} FirstTimeDecorAcquisition packets",
|
||||
uint32(starterDecorIds.size()));
|
||||
|
||||
// 2. Build buy response with JamCurrentHouseInfo matching retail sniff format
|
||||
WorldPackets::Neighborhood::NeighborhoodBuyHouseResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.NeighborhoodGuid = neighborhoodBuyHouse.NeighborhoodGuid;
|
||||
response.PlotIndex = neighborhoodBuyHouse.PlotIndex;
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_SUCCESS);
|
||||
if (Housing const* h = player->GetHousing())
|
||||
response.HouseGuid = h->GetHouseGuid();
|
||||
{
|
||||
// Sniff-verified: OwnerGuid=HouseGUID, SecondaryOwnerGuid=PlotGUID, PlotGuid=NeighborhoodGUID
|
||||
response.HouseInfo.OwnerGuid = h->GetHouseGuid();
|
||||
response.HouseInfo.SecondaryOwnerGuid = h->GetPlotGuid();
|
||||
response.HouseInfo.PlotGuid = neighborhood->GetGuid();
|
||||
response.HouseInfo.Flags = resolvedPlotIndex;
|
||||
response.HouseInfo.HouseTypeId = 32; // sniff value: 0x20
|
||||
response.HouseInfo.StatusFlags = 0;
|
||||
}
|
||||
SendPacket(response.Write());
|
||||
|
||||
// 3. Send level/favor updates (sniff: always 2 packets after buy response)
|
||||
if (Housing const* h = player->GetHousing())
|
||||
{
|
||||
// Packet 1: Initial level assignment (prev=-1/-1, new=level 1, next level cost=910)
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsUpdateHousesLevelFavor levelFavor;
|
||||
levelFavor.Type = 0;
|
||||
levelFavor.PreviousFavor = -1;
|
||||
levelFavor.PreviousLevel = -1;
|
||||
levelFavor.NewLevel = 1;
|
||||
levelFavor.Field4 = 0;
|
||||
levelFavor.HouseGuid = h->GetHouseGuid();
|
||||
levelFavor.PreviousLevelId = -1;
|
||||
levelFavor.NextLevelFavorCost = 910; // sniff value: 0x038E
|
||||
levelFavor.Flags = 0x8000;
|
||||
SendPacket(levelFavor.Write());
|
||||
}
|
||||
// Packet 2: Favor state (prev favor=910, level=1, no next level info)
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsUpdateHousesLevelFavor levelFavor;
|
||||
levelFavor.Type = 0;
|
||||
levelFavor.PreviousFavor = 910;
|
||||
levelFavor.PreviousLevel = 1;
|
||||
levelFavor.NewLevel = 1;
|
||||
levelFavor.Field4 = 0;
|
||||
levelFavor.HouseGuid = h->GetHouseGuid();
|
||||
levelFavor.PreviousLevelId = -1;
|
||||
levelFavor.NextLevelFavorCost = -1;
|
||||
levelFavor.Flags = 0x8000;
|
||||
SendPacket(levelFavor.Write());
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast roster update to other neighborhood members
|
||||
for (auto const& member : neighborhood->GetMembers())
|
||||
{
|
||||
@@ -1004,7 +1104,7 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
if (Player* memberPlayer = ObjectAccessor::FindPlayer(member.PlayerGuid))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodRosterResidentUpdate rosterUpdate;
|
||||
rosterUpdate.Residents.push_back({ player->GetGUID(), uint16(neighborhoodBuyHouse.PlotIndex) });
|
||||
rosterUpdate.Residents.push_back({ player->GetGUID(), uint16(resolvedPlotIndex) });
|
||||
memberPlayer->SendDirectMessage(rosterUpdate.Write());
|
||||
}
|
||||
}
|
||||
@@ -1022,11 +1122,11 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
|
||||
// Mark the plot Cornerstone as owned (GOState 1 = READY)
|
||||
if (HousingMap* housingMap = dynamic_cast<HousingMap*>(player->GetMap()))
|
||||
housingMap->SetPlotOwnershipState(neighborhoodBuyHouse.PlotIndex, true);
|
||||
housingMap->SetPlotOwnershipState(resolvedPlotIndex, true);
|
||||
|
||||
TC_LOG_DEBUG("housing", "Player {} purchased plot {} in neighborhood {}",
|
||||
player->GetGUID().ToString(), neighborhoodBuyHouse.PlotIndex,
|
||||
neighborhoodBuyHouse.NeighborhoodGuid.ToString());
|
||||
TC_LOG_DEBUG("housing", "Player {} purchased plot {} in neighborhood '{}'",
|
||||
player->GetGUID().ToString(), resolvedPlotIndex,
|
||||
neighborhood->GetName());
|
||||
|
||||
// Check if neighborhoods need expansion after plot purchase
|
||||
sNeighborhoodMgr.CheckAndExpandNeighborhoods();
|
||||
@@ -1034,7 +1134,7 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
else
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodBuyHouseResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.Result = static_cast<uint8>(result);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodBuyHouse: PurchasePlot result: {} for player {}",
|
||||
@@ -1055,7 +1155,7 @@ void WorldSession::HandleNeighborhoodMoveHouse(WorldPackets::Neighborhood::Neigh
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodMoveHouseResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodMoveHouse: Neighborhood {} not found",
|
||||
@@ -1079,9 +1179,15 @@ void WorldSession::HandleNeighborhoodMoveHouse(WorldPackets::Neighborhood::Neigh
|
||||
HousingResult result = neighborhood->MoveHouse(player->GetGUID(), targetPlotIndex);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodMoveHouseResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.NeighborhoodGuid = neighborhoodMoveHouse.NeighborhoodGuid;
|
||||
response.NewPlotIndex = targetPlotIndex;
|
||||
response.Result = static_cast<uint8>(result);
|
||||
if (result == HOUSING_RESULT_SUCCESS)
|
||||
{
|
||||
response.HouseInfo.OwnerGuid = player->GetGUID();
|
||||
response.HouseInfo.PlotGuid = neighborhoodMoveHouse.PlotGuid;
|
||||
if (Housing const* housing = player->GetHousing())
|
||||
response.HouseInfo.HouseTypeId = 0; // default house type
|
||||
}
|
||||
response.MoveTransactionGuid = ObjectGuid::Empty; // transaction tracking GUID
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "MoveHouse result: {} to plot {} in neighborhood {}",
|
||||
@@ -1101,12 +1207,12 @@ void WorldSession::HandleNeighborhoodOpenCornerstoneUI(WorldPackets::Neighborhoo
|
||||
Neighborhood* neighborhood = sNeighborhoodMgr.ResolveNeighborhood(neighborhoodOpenCornerstoneUI.NeighborhoodGuid, player);
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodOpenCornerstoneUIResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodOpenCornerstoneUI: Neighborhood {} not found",
|
||||
neighborhoodOpenCornerstoneUI.NeighborhoodGuid.ToString());
|
||||
// No result code in this packet ? just send with PurchaseStatus=0, client won't show buy button
|
||||
WorldPackets::Neighborhood::NeighborhoodOpenCornerstoneUIResponse response;
|
||||
response.PlotIndex = neighborhoodOpenCornerstoneUI.PlotIndex;
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1119,7 +1225,7 @@ void WorldSession::HandleNeighborhoodOpenCornerstoneUI(WorldPackets::Neighborhoo
|
||||
|
||||
for (NeighborhoodPlotData const* plot : plots)
|
||||
{
|
||||
if (plot->PlotIndex == neighborhoodOpenCornerstoneUI.PlotIndex)
|
||||
if (plot->PlotIndex == static_cast<int32>(neighborhoodOpenCornerstoneUI.PlotIndex))
|
||||
{
|
||||
plotCost = plot->Cost;
|
||||
plotFound = true;
|
||||
@@ -1129,26 +1235,65 @@ void WorldSession::HandleNeighborhoodOpenCornerstoneUI(WorldPackets::Neighborhoo
|
||||
|
||||
if (!plotFound)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodOpenCornerstoneUIResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_RPC_FAILURE);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_ERROR("housing", "HandleNeighborhoodOpenCornerstoneUI: PlotIndex {} not found in neighborhood map {}",
|
||||
neighborhoodOpenCornerstoneUI.PlotIndex, neighborhoodMapId);
|
||||
WorldPackets::Neighborhood::NeighborhoodOpenCornerstoneUIResponse response;
|
||||
response.PlotIndex = neighborhoodOpenCornerstoneUI.PlotIndex;
|
||||
response.NeighborhoodName = neighborhood->GetName();
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodOpenCornerstoneUIResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
response.NeighborhoodGuid = neighborhoodOpenCornerstoneUI.NeighborhoodGuid;
|
||||
response.PlotGuid = ObjectGuid::Empty; // Unclaimed plot ? no owner
|
||||
response.PlotIndex = static_cast<uint8>(neighborhoodOpenCornerstoneUI.PlotIndex);
|
||||
response.Cost = plotCost;
|
||||
response.NeighborhoodName = neighborhood->GetName();
|
||||
SendPacket(response.Write());
|
||||
// Pre-send neighborhood name response to populate the JamCliNeighborhoodName
|
||||
// DataCache. Flag +574 in the display function checks whether the TLS
|
||||
// NeighborhoodGuid is resolved in the DataCache. Sending this immediately
|
||||
// before the cornerstone response ensures the cache entry exists.
|
||||
{
|
||||
WorldPackets::Housing::QueryNeighborhoodNameResponse nameResp;
|
||||
nameResp.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
nameResp.Allow = true;
|
||||
nameResp.Name = neighborhood->GetName();
|
||||
SendPacket(nameResp.Write());
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodOpenCornerstoneUI: Player {} opened cornerstone UI for plot {} (cost: {}) in neighborhood '{}'",
|
||||
player->GetGUID().ToString(), neighborhoodOpenCornerstoneUI.PlotIndex, plotCost, neighborhood->GetName());
|
||||
// Build cornerstone UI response ? wire format verified against retail 12.0.1 build 65940
|
||||
WorldPackets::Neighborhood::NeighborhoodOpenCornerstoneUIResponse response;
|
||||
response.PlotIndex = neighborhoodOpenCornerstoneUI.PlotIndex;
|
||||
// PlotOwnerGuid (?Buffer+40): Controls flag +573. Empty triggers zero-check path
|
||||
// which clears +573. Non-Housing GUIDs (Player type 2) succeed entity name lookup.
|
||||
response.PlotOwnerGuid = ObjectGuid::Empty;
|
||||
// NeighborhoodGuid (?Buffer+56): Controls flag +574 via JamCliNeighborhoodName DataCache.
|
||||
// Client looks up neighborhood name in the cache using this GUID as the key.
|
||||
// Must match the GUID in the pre-sent QueryNeighborhoodNameResponse above,
|
||||
// otherwise the cache lookup fails and the name shows as <?>.
|
||||
response.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
response.Cost = plotCost;
|
||||
// PurchaseStatus (?Buffer+80): HousingResult enum value
|
||||
// Value 73 = PlotReserved error code, which shows "Plot already reserved"
|
||||
// Value 0 = Success (no error), allows the purchase UI to render normally
|
||||
response.PurchaseStatus = 0;
|
||||
response.CornerstoneGuid = ObjectGuid::Empty;
|
||||
// CanPurchase bit is 0 in ALL retail packets (both owned and unclaimed purchasable)
|
||||
// The actual "can buy" signal is PurchaseStatus==73. Setting CanPurchase=true
|
||||
// causes client to show "Plot already reserved" message.
|
||||
response.CanPurchase = false;
|
||||
response.NeighborhoodName = neighborhood->GetName();
|
||||
WorldPacket const* pkt = response.Write();
|
||||
SendPacket(pkt);
|
||||
|
||||
TC_LOG_ERROR("housing", "=== SMSG_NEIGHBORHOOD_OPEN_CORNERSTONE_UI_RESPONSE (0x5C000A) ===\n"
|
||||
" PlotIndex={}, Cost={}, PurchaseStatus={}, CanPurchase={}, IsPlotOwned={}\n"
|
||||
" PlotOwnerGuid: {} ({})\n"
|
||||
" NeighborhoodGuid: {} ({})\n"
|
||||
" CornerstoneGuid: {} ({})\n"
|
||||
" NeighborhoodName='{}' (len={})\n"
|
||||
" Packet size={} bytes, hex:\n {}",
|
||||
response.PlotIndex, response.Cost, uint32(response.PurchaseStatus), response.CanPurchase, response.IsPlotOwned,
|
||||
response.PlotOwnerGuid.ToString(), GuidHex(response.PlotOwnerGuid),
|
||||
response.NeighborhoodGuid.ToString(), GuidHex(response.NeighborhoodGuid),
|
||||
response.CornerstoneGuid.ToString(), GuidHex(response.CornerstoneGuid),
|
||||
response.NeighborhoodName, response.NeighborhoodName.size(),
|
||||
pkt->size(), HexDumpPacket(pkt));
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodOfferOwnership(WorldPackets::Neighborhood::NeighborhoodOfferOwnership const& neighborhoodOfferOwnership)
|
||||
@@ -1161,7 +1306,7 @@ void WorldSession::HandleNeighborhoodOfferOwnership(WorldPackets::Neighborhood::
|
||||
if (!housing)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodOfferOwnershipResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_HOUSE_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
@@ -1175,7 +1320,7 @@ void WorldSession::HandleNeighborhoodOfferOwnership(WorldPackets::Neighborhood::
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodOfferOwnershipResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodOfferOwnership: Neighborhood {} not found",
|
||||
@@ -1187,7 +1332,7 @@ void WorldSession::HandleNeighborhoodOfferOwnership(WorldPackets::Neighborhood::
|
||||
if (!neighborhood->IsOwner(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodOfferOwnershipResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodOfferOwnership: Player {} is not owner of neighborhood {}",
|
||||
@@ -1198,9 +1343,7 @@ void WorldSession::HandleNeighborhoodOfferOwnership(WorldPackets::Neighborhood::
|
||||
HousingResult result = neighborhood->TransferOwnership(neighborhoodOfferOwnership.NewOwnerGuid);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodOfferOwnershipResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.NeighborhoodGuid = neighborhoodGuid;
|
||||
response.NewOwnerGuid = neighborhoodOfferOwnership.NewOwnerGuid;
|
||||
response.Result = static_cast<uint8>(result);
|
||||
SendPacket(response.Write());
|
||||
|
||||
// Notify the new owner and broadcast to all members
|
||||
@@ -1247,7 +1390,7 @@ void WorldSession::HandleNeighborhoodGetRoster(WorldPackets::Neighborhood::Neigh
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodGetRosterResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodGetRoster: Neighborhood {} not found",
|
||||
@@ -1281,15 +1424,35 @@ void WorldSession::HandleNeighborhoodGetRoster(WorldPackets::Neighborhood::Neigh
|
||||
data.PlayerGuid = member.PlayerGuid;
|
||||
data.PlotIndex = member.PlotIndex;
|
||||
data.JoinTime = member.JoinTime;
|
||||
data.IsOnline = ObjectAccessor::FindPlayer(member.PlayerGuid) != nullptr;
|
||||
if (member.PlotIndex != INVALID_PLOT_INDEX)
|
||||
if (Neighborhood::PlotInfo const* plotInfo = neighborhood->GetPlotInfo(member.PlotIndex))
|
||||
data.HouseGuid = plotInfo->HouseGuid;
|
||||
response.Members.push_back(data);
|
||||
}
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "Neighborhood {} roster: {} members sent",
|
||||
neighborhoodGetRoster.NeighborhoodGuid.ToString(), uint32(members.size()));
|
||||
// Pre-send neighborhood name response to populate JamCliNeighborhoodName DataCache.
|
||||
// The roster UI resolves the neighborhood name via GroupNeighborhoodGuid cache lookup.
|
||||
{
|
||||
WorldPackets::Housing::QueryNeighborhoodNameResponse nameResp;
|
||||
nameResp.NeighborhoodGuid = neighborhood->GetGuid();
|
||||
nameResp.Allow = true;
|
||||
nameResp.Name = neighborhood->GetName();
|
||||
SendPacket(nameResp.Write());
|
||||
}
|
||||
|
||||
WorldPacket const* rosterPkt = response.Write();
|
||||
SendPacket(rosterPkt);
|
||||
|
||||
TC_LOG_ERROR("housing", "=== SMSG_NEIGHBORHOOD_GET_ROSTER_RESPONSE (0x5C0012) [handler] ===\n"
|
||||
" Result={}, Members={}, NeighborhoodName='{}'\n"
|
||||
" GroupNeighborhoodGuid: {} ({})\n"
|
||||
" GroupOwnerGuid: {} ({})\n"
|
||||
" Packet size={} bytes, hex:\n {}",
|
||||
uint32(response.Result), response.Members.size(), response.NeighborhoodName,
|
||||
response.GroupNeighborhoodGuid.ToString(), GuidHex(response.GroupNeighborhoodGuid),
|
||||
response.GroupOwnerGuid.ToString(), GuidHex(response.GroupOwnerGuid),
|
||||
rosterPkt->size(), HexDumpPacket(rosterPkt, 256));
|
||||
}
|
||||
|
||||
void WorldSession::HandleNeighborhoodEvictPlot(WorldPackets::Neighborhood::NeighborhoodEvictPlot const& neighborhoodEvictPlot)
|
||||
@@ -1305,7 +1468,7 @@ void WorldSession::HandleNeighborhoodEvictPlot(WorldPackets::Neighborhood::Neigh
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodEvictPlotResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodEvictPlot: Neighborhood {} not found",
|
||||
@@ -1317,7 +1480,7 @@ void WorldSession::HandleNeighborhoodEvictPlot(WorldPackets::Neighborhood::Neigh
|
||||
if (!neighborhood->IsOwner(player->GetGUID()) && !neighborhood->IsManager(player->GetGUID()))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodEvictPlotResponse response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
response.Result = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HandleNeighborhoodEvictPlot: Player {} lacks permission for neighborhood {}",
|
||||
@@ -1337,9 +1500,8 @@ void WorldSession::HandleNeighborhoodEvictPlot(WorldPackets::Neighborhood::Neigh
|
||||
HousingResult result = neighborhood->EvictPlayer(evictedPlayerGuid);
|
||||
|
||||
WorldPackets::Neighborhood::NeighborhoodEvictPlotResponse response;
|
||||
response.Result = static_cast<uint32>(result);
|
||||
response.Result = static_cast<uint8>(result);
|
||||
response.NeighborhoodGuid = neighborhoodEvictPlot.NeighborhoodGuid;
|
||||
response.PlotGuid = plotGuid;
|
||||
SendPacket(response.Write());
|
||||
|
||||
// Send eviction notice to the evicted player and broadcast roster update
|
||||
@@ -1354,6 +1516,7 @@ void WorldSession::HandleNeighborhoodEvictPlot(WorldPackets::Neighborhood::Neigh
|
||||
if (Player* evictedPlayer = ObjectAccessor::FindPlayer(evictedPlayerGuid))
|
||||
{
|
||||
WorldPackets::Neighborhood::NeighborhoodEvictPlotNotice notice;
|
||||
notice.PlotId = neighborhoodEvictPlot.PlotIndex;
|
||||
notice.NeighborhoodGuid = neighborhoodEvictPlot.NeighborhoodGuid;
|
||||
notice.PlotGuid = plotGuid;
|
||||
evictedPlayer->SendDirectMessage(notice.Write());
|
||||
@@ -1404,13 +1567,23 @@ void WorldSession::HandleGetAvailableInitiativeRequest(WorldPackets::Neighborhoo
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_GET_AVAILABLE_INITIATIVE_REQUEST NeighborhoodGuid: {}",
|
||||
getAvailableInitiativeRequest.NeighborhoodGuid.ToString());
|
||||
Neighborhood* neighborhood = sNeighborhoodMgr.ResolveNeighborhood(getAvailableInitiativeRequest.NeighborhoodGuid, player);
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Housing::GetPlayerInitiativeInfoResult response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Respond with empty initiative list for now
|
||||
// Initiative info is driven by DB2 data (NeighborhoodInitiative.db2) which the client
|
||||
// already has. This response confirms the server acknowledges the initiative query.
|
||||
WorldPackets::Housing::GetPlayerInitiativeInfoResult response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_GET_AVAILABLE_INITIATIVE_REQUEST NeighborhoodGuid: {}, Player: {}",
|
||||
getAvailableInitiativeRequest.NeighborhoodGuid.ToString(), player->GetGUID().ToString());
|
||||
}
|
||||
|
||||
void WorldSession::HandleGetInitiativeActivityLogRequest(WorldPackets::Neighborhood::GetInitiativeActivityLogRequest const& getInitiativeActivityLogRequest)
|
||||
@@ -1419,11 +1592,21 @@ void WorldSession::HandleGetInitiativeActivityLogRequest(WorldPackets::Neighborh
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_GET_INITIATIVE_ACTIVITY_LOG_REQUEST NeighborhoodGuid: {}",
|
||||
getInitiativeActivityLogRequest.NeighborhoodGuid.ToString());
|
||||
Neighborhood* neighborhood = sNeighborhoodMgr.ResolveNeighborhood(getInitiativeActivityLogRequest.NeighborhoodGuid, player);
|
||||
if (!neighborhood)
|
||||
{
|
||||
WorldPackets::Housing::GetInitiativeActivityLogResult response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_NEIGHBORHOOD_NOT_FOUND);
|
||||
SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// Respond with empty activity log
|
||||
// Activity log entries are tracked per-neighborhood. The response confirms the query
|
||||
// and the client uses cached initiative data from DB2 to display the log.
|
||||
WorldPackets::Housing::GetInitiativeActivityLogResult response;
|
||||
response.Result = static_cast<uint32>(HOUSING_RESULT_SUCCESS);
|
||||
SendPacket(response.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "CMSG_GET_INITIATIVE_ACTIVITY_LOG_REQUEST NeighborhoodGuid: {}, Player: {}",
|
||||
getInitiativeActivityLogRequest.NeighborhoodGuid.ToString(), player->GetGUID().ToString());
|
||||
}
|
||||
|
||||
@@ -34,12 +34,34 @@
|
||||
#include "Transport.h"
|
||||
#include "World.h"
|
||||
|
||||
#include "Neighborhood.h"
|
||||
#include "NeighborhoodMgr.h"
|
||||
|
||||
void WorldSession::BuildNameQueryData(ObjectGuid guid, WorldPackets::Query::NameCacheLookupResult& lookupData)
|
||||
{
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
|
||||
lookupData.Player = guid;
|
||||
|
||||
// Housing GUIDs (HighGuid::Housing, type 55) are resolved via HouseData
|
||||
// instead of the player name cache. The client's NameCacheLookupResult
|
||||
// structure includes an Optional<HouseLookupData> field for this purpose.
|
||||
if (guid.GetHigh() == HighGuid::Housing)
|
||||
{
|
||||
Neighborhood const* neighborhood = sNeighborhoodMgr.GetNeighborhood(guid);
|
||||
if (neighborhood && !neighborhood->GetName().empty())
|
||||
{
|
||||
lookupData.Result = RESPONSE_SUCCESS;
|
||||
lookupData.HouseData.emplace();
|
||||
lookupData.HouseData->Guid = guid;
|
||||
// GetName() returns const std::string& with stable lifetime
|
||||
lookupData.HouseData->Name = neighborhood->GetName();
|
||||
}
|
||||
else
|
||||
lookupData.Result = RESPONSE_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
|
||||
lookupData.Data.emplace();
|
||||
if (lookupData.Data->Initialize(guid, player))
|
||||
lookupData.Result = RESPONSE_SUCCESS; // name known
|
||||
@@ -51,7 +73,14 @@ void WorldSession::HandleQueryPlayerNames(WorldPackets::Query::QueryPlayerNames&
|
||||
{
|
||||
WorldPackets::Query::QueryPlayerNamesResponse response;
|
||||
for (ObjectGuid guid : queryPlayerNames.Players)
|
||||
{
|
||||
// Log Housing GUID queries for debugging neighborhood name display
|
||||
if (guid.GetHigh() == HighGuid::Housing)
|
||||
TC_LOG_ERROR("housing", "CMSG_QUERY_PLAYER_NAMES: Client queried Housing GUID {} ? resolving via HouseData",
|
||||
guid.ToString());
|
||||
|
||||
BuildNameQueryData(guid, response.Players.emplace_back());
|
||||
}
|
||||
|
||||
SendPacket(response.Write());
|
||||
}
|
||||
|
||||
@@ -211,6 +211,13 @@ void WorldSession::HandleGameObjectUseOpcode(WorldPackets::GameObject::GameObjUs
|
||||
|
||||
obj->Use(GetPlayer());
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Debug: Log failed GO interaction attempt for housing diagnostics
|
||||
TC_LOG_DEBUG("housing", "HandleGameObjectUseOpcode: GetGameObjectIfCanInteractWith returned null "
|
||||
"for guid={} player={}", packet.Guid.ToString(), GetPlayer()->GetGUID().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleGameobjectReportUse(WorldPackets::GameObject::GameObjReportUse& packet)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "Account.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "GameTime.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "HousingPackets.h"
|
||||
#include "Log.h"
|
||||
@@ -59,6 +60,7 @@ bool Housing::LoadFromDB(PreparedQueryResult housing, PreparedQueryResult decor,
|
||||
_exteriorLocked = fields[6].GetUInt8() != 0;
|
||||
_houseSize = fields[7].GetUInt8();
|
||||
_houseType = fields[8].GetUInt32();
|
||||
_createTime = fields[9].GetUInt32();
|
||||
|
||||
// Load placed decor
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
|
||||
@@ -326,6 +328,7 @@ HousingResult Housing::Create(ObjectGuid neighborhoodGuid, uint8 plotIndex)
|
||||
_exteriorLocked = false;
|
||||
_houseSize = HOUSING_FIXTURE_SIZE_SMALL;
|
||||
_houseType = 0;
|
||||
_createTime = static_cast<uint32>(GameTime::GetGameTime());
|
||||
|
||||
// Generate a new house guid using the owner's low guid as a base
|
||||
_houseGuid = ObjectGuid::Create<HighGuid::Housing>(/*subType*/ 3, /*arg1*/ sRealmList->GetCurrentRealmId().Realm, /*arg2*/ 7, _owner->GetGUID().GetCounter());
|
||||
@@ -337,6 +340,16 @@ HousingResult Housing::Create(ObjectGuid neighborhoodGuid, uint8 plotIndex)
|
||||
return HOUSING_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ObjectGuid Housing::GetPlotGuid() const
|
||||
{
|
||||
// Deterministic PlotGUID: subType=2 encodes neighborhood + plot index
|
||||
return ObjectGuid::Create<HighGuid::Housing>(
|
||||
/*subType*/ 2,
|
||||
/*arg1*/ sRealmList->GetCurrentRealmId().Realm,
|
||||
/*arg2*/ _plotIndex,
|
||||
_neighborhoodGuid.GetCounter());
|
||||
}
|
||||
|
||||
void Housing::Delete()
|
||||
{
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
@@ -1184,9 +1197,15 @@ void Housing::AddLevel(uint32 amount)
|
||||
if (_owner && _owner->GetSession())
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsUpdateHousesLevelFavor levelUpdate;
|
||||
levelUpdate.Type = 0;
|
||||
levelUpdate.PreviousFavor = static_cast<int32>(_level > 1 ? _favor : -1);
|
||||
levelUpdate.PreviousLevel = static_cast<int32>(_level - amount);
|
||||
levelUpdate.NewLevel = static_cast<int32>(_level);
|
||||
levelUpdate.Field4 = 0;
|
||||
levelUpdate.HouseGuid = _houseGuid;
|
||||
levelUpdate.Level = _level;
|
||||
levelUpdate.Favor = _favor64;
|
||||
levelUpdate.PreviousLevelId = -1;
|
||||
levelUpdate.NextLevelFavorCost = -1;
|
||||
levelUpdate.Flags = 0x8000;
|
||||
_owner->SendDirectMessage(levelUpdate.Write());
|
||||
}
|
||||
}
|
||||
@@ -1205,9 +1224,15 @@ void Housing::AddFavor(uint64 amount)
|
||||
if (_owner && _owner->GetSession())
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsUpdateHousesLevelFavor favorUpdate;
|
||||
favorUpdate.Type = 0;
|
||||
favorUpdate.PreviousFavor = static_cast<int32>(_favor);
|
||||
favorUpdate.PreviousLevel = static_cast<int32>(_level);
|
||||
favorUpdate.NewLevel = static_cast<int32>(_level);
|
||||
favorUpdate.Field4 = 0;
|
||||
favorUpdate.HouseGuid = _houseGuid;
|
||||
favorUpdate.Level = _level;
|
||||
favorUpdate.Favor = _favor64;
|
||||
favorUpdate.PreviousLevelId = -1;
|
||||
favorUpdate.NextLevelFavorCost = -1;
|
||||
favorUpdate.Flags = 0x8000;
|
||||
_owner->SendDirectMessage(favorUpdate.Write());
|
||||
}
|
||||
}
|
||||
@@ -1219,6 +1244,7 @@ void Housing::OnQuestCompleted(uint32 questId)
|
||||
uint32 nextLevelQuestId = sHousingMgr.GetQuestForLevel(_level + 1);
|
||||
if (nextLevelQuestId > 0 && nextLevelQuestId == questId)
|
||||
{
|
||||
uint32 previousLevel = _level;
|
||||
_level++;
|
||||
TC_LOG_DEBUG("housing", "Housing::OnQuestCompleted: Player {} house leveled up to {} (quest {}) in house {}",
|
||||
_owner->GetName(), _level, questId, _houseGuid.ToString());
|
||||
@@ -1229,9 +1255,15 @@ void Housing::OnQuestCompleted(uint32 questId)
|
||||
if (_owner && _owner->GetSession())
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsUpdateHousesLevelFavor levelUpdate;
|
||||
levelUpdate.Type = 0;
|
||||
levelUpdate.PreviousFavor = static_cast<int32>(_favor);
|
||||
levelUpdate.PreviousLevel = static_cast<int32>(previousLevel);
|
||||
levelUpdate.NewLevel = static_cast<int32>(_level);
|
||||
levelUpdate.Field4 = 0;
|
||||
levelUpdate.HouseGuid = _houseGuid;
|
||||
levelUpdate.Level = _level;
|
||||
levelUpdate.Favor = _favor64;
|
||||
levelUpdate.PreviousLevelId = -1;
|
||||
levelUpdate.NextLevelFavorCost = -1;
|
||||
levelUpdate.Flags = 0x8000;
|
||||
_owner->SendDirectMessage(levelUpdate.Write());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,9 @@ public:
|
||||
Player* GetOwner() const { return _owner; }
|
||||
ObjectGuid GetHouseGuid() const { return _houseGuid; }
|
||||
ObjectGuid GetNeighborhoodGuid() const { return _neighborhoodGuid; }
|
||||
ObjectGuid GetPlotGuid() const;
|
||||
uint8 GetPlotIndex() const { return _plotIndex; }
|
||||
uint32 GetCreateTime() const { return _createTime; }
|
||||
uint32 GetLevel() const { return _level; }
|
||||
uint32 GetFavor() const { return _favor; }
|
||||
uint32 GetSettingsFlags() const { return _settingsFlags; }
|
||||
@@ -197,6 +199,7 @@ private:
|
||||
bool _exteriorLocked = false;
|
||||
uint8 _houseSize = HOUSING_FIXTURE_SIZE_SMALL;
|
||||
uint32 _houseType = 0;
|
||||
uint32 _createTime = 0;
|
||||
|
||||
// WeightCost-based budget tracking
|
||||
uint32 _interiorDecorWeightUsed = 0;
|
||||
|
||||
@@ -94,7 +94,7 @@ void HousingMap::SpawnPlotGameObjects()
|
||||
LoadGrid(x, y);
|
||||
|
||||
// Retail always uses CornerstoneGameObjectID (457142) for ALL plots.
|
||||
// Ownership state is communicated via GOState: 0 (ACTIVE) = ForSale, 1 (READY) = Owned.
|
||||
// Ownership state via GOState: 0 (ACTIVE) = Owned/Claimed, 1 (READY) = ForSale sign.
|
||||
Neighborhood::PlotInfo const* plotInfo = _neighborhood->GetPlotInfo(static_cast<uint8>(plot->PlotIndex));
|
||||
uint32 goEntry = static_cast<uint32>(plot->CornerstoneGameObjectID);
|
||||
bool isOwned = plotInfo && !plotInfo->OwnerGuid.IsEmpty();
|
||||
@@ -115,8 +115,8 @@ void HousingMap::SpawnPlotGameObjects()
|
||||
float rotZ = plot->CornerstoneRotation[2];
|
||||
QuaternionData rot = QuaternionData::fromEulerAnglesZYX(rotZ, plot->CornerstoneRotation[1], plot->CornerstoneRotation[0]);
|
||||
|
||||
// Retail sniff: ForSale = GOState 0 (ACTIVE), Owned = GOState 1 (READY)
|
||||
GOState plotState = isOwned ? GO_STATE_READY : GO_STATE_ACTIVE;
|
||||
// GOState 0 (ACTIVE) = Owned/Claimed cornerstone, GOState 1 (READY) = ForSale sign
|
||||
GOState plotState = isOwned ? GO_STATE_ACTIVE : GO_STATE_READY;
|
||||
|
||||
Position pos(x, y, z, rotZ);
|
||||
GameObject* go = GameObject::CreateGameObject(goEntry, this, pos, rot, 255, plotState);
|
||||
@@ -233,8 +233,8 @@ void HousingMap::SetPlotOwnershipState(uint8 plotIndex, bool owned)
|
||||
return;
|
||||
|
||||
// Toggle GOState on the existing Cornerstone GO.
|
||||
// Retail sniff: GOState 0 (ACTIVE) = ForSale, GOState 1 (READY) = Owned.
|
||||
GOState newState = owned ? GO_STATE_READY : GO_STATE_ACTIVE;
|
||||
// GOState 0 (ACTIVE) = Owned/Claimed cornerstone, GOState 1 (READY) = ForSale sign
|
||||
GOState newState = owned ? GO_STATE_ACTIVE : GO_STATE_READY;
|
||||
|
||||
auto itr = _plotGameObjects.find(plotIndex);
|
||||
if (itr != _plotGameObjects.end())
|
||||
@@ -304,9 +304,9 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
|
||||
}
|
||||
|
||||
// Do NOT auto-add the player as a neighborhood member here.
|
||||
// Membership is granted when the player buys a plot or is invited.
|
||||
// Auto-adding causes the client to resolve neighborhoodOwnerType as
|
||||
// Self instead of None, which prevents the "For Sale" Cornerstone UI.
|
||||
// Membership is granted when the player buys a plot or is invited.
|
||||
// Auto-adding causes the client to resolve neighborhoodOwnerType as
|
||||
// Self instead of None, which prevents the "For Sale" Cornerstone UI.
|
||||
|
||||
// Track player housing if they own a house in this neighborhood
|
||||
Housing* housing = player->GetHousingForNeighborhood(_neighborhood->GetGuid());
|
||||
@@ -319,17 +319,22 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
|
||||
// Send neighborhood context so the client can call SetViewingNeighborhood()
|
||||
// and enable Cornerstone purchase UI interaction
|
||||
WorldPackets::Housing::HousingGetCurrentHouseInfoResponse houseInfo;
|
||||
houseInfo.NeighborhoodGuid = _neighborhood->GetGuid();
|
||||
if (housing)
|
||||
{
|
||||
houseInfo.HouseGuid = housing->GetHouseGuid();
|
||||
houseInfo.OwnerPlayerGuid = player->GetGUID();
|
||||
houseInfo.PlotIndex = housing->GetPlotIndex();
|
||||
houseInfo.HouseProperties = housing->GetSettingsFlags() & 0xFF;
|
||||
houseInfo.HouseLevel = static_cast<uint8>(housing->GetLevel());
|
||||
// Sniff-verified: OwnerGuid=HouseGUID, SecondaryOwnerGuid=PlotGUID, PlotGuid=NeighborhoodGUID
|
||||
houseInfo.HouseInfo.OwnerGuid = housing->GetHouseGuid();
|
||||
houseInfo.HouseInfo.SecondaryOwnerGuid = housing->GetPlotGuid();
|
||||
houseInfo.HouseInfo.PlotGuid = housing->GetNeighborhoodGuid();
|
||||
houseInfo.HouseInfo.Flags = housing->GetPlotIndex();
|
||||
houseInfo.HouseInfo.HouseTypeId = 32;
|
||||
houseInfo.HouseInfo.StatusFlags = 0;
|
||||
}
|
||||
// No house: OwnerPlayerGuid stays empty. Only NeighborhoodGuid is set
|
||||
// so the client knows which neighborhood it's viewing.
|
||||
else if (_neighborhood)
|
||||
{
|
||||
// No house ? provide neighborhood GUID in PlotGuid field for purchase UI context
|
||||
houseInfo.HouseInfo.PlotGuid = _neighborhood->GetGuid();
|
||||
}
|
||||
houseInfo.ResponseFlags = 0;
|
||||
player->SendDirectMessage(houseInfo.Write());
|
||||
|
||||
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: Sent neighborhood context to player {} (neighborhood='{}', hasHouse={})",
|
||||
|
||||
@@ -382,6 +382,19 @@ std::vector<NeighborhoodPlotData const*> HousingMgr::GetPlotsForMap(uint32 neigh
|
||||
return {};
|
||||
}
|
||||
|
||||
NeighborhoodPlotData const* HousingMgr::GetPlotByCornerstoneEntry(uint32 neighborhoodMapId, uint32 cornerstoneGoEntry) const
|
||||
{
|
||||
auto itr = _plotsByMap.find(neighborhoodMapId);
|
||||
if (itr == _plotsByMap.end())
|
||||
return nullptr;
|
||||
|
||||
for (NeighborhoodPlotData const* plot : itr->second)
|
||||
if (static_cast<uint32>(plot->CornerstoneGameObjectID) == cornerstoneGoEntry)
|
||||
return plot;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string HousingMgr::GenerateNeighborhoodName(uint32 neighborhoodMapId) const
|
||||
{
|
||||
auto itr = _nameGenByMap.find(neighborhoodMapId);
|
||||
@@ -487,6 +500,20 @@ uint32 HousingMgr::GetRoomWeightCost(uint32 roomEntryId) const
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint32> HousingMgr::GetStarterDecorIds() const
|
||||
{
|
||||
std::vector<uint32> result;
|
||||
for (auto const& [id, decor] : _houseDecorStore)
|
||||
{
|
||||
if (decor.StartingQuantity > 0)
|
||||
{
|
||||
for (int32 i = 0; i < decor.StartingQuantity; ++i)
|
||||
result.push_back(id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HousingResult HousingMgr::ValidateDecorPlacement(uint32 decorId, Position const& pos, uint32 houseLevel) const
|
||||
{
|
||||
HouseDecorData const* decorEntry = GetHouseDecorData(decorId);
|
||||
|
||||
@@ -239,6 +239,8 @@ public:
|
||||
|
||||
// Neighborhood plot lookups
|
||||
std::vector<NeighborhoodPlotData const*> GetPlotsForMap(uint32 neighborhoodMapId) const;
|
||||
// Find a plot by its cornerstone GO entry within a specific neighborhood map
|
||||
NeighborhoodPlotData const* GetPlotByCornerstoneEntry(uint32 neighborhoodMapId, uint32 cornerstoneGoEntry) const;
|
||||
|
||||
// Check if a world MapID corresponds to a neighborhood map
|
||||
bool IsNeighborhoodWorldMap(uint32 mapId) const;
|
||||
@@ -265,6 +267,9 @@ public:
|
||||
uint32 GetRoomDoorCount(uint32 roomEntryId) const;
|
||||
std::vector<RoomDoorInfo> const* GetRoomDoors(uint32 roomWmoDataId) const;
|
||||
|
||||
// Starter decor (items granted on first house purchase)
|
||||
std::vector<uint32> GetStarterDecorIds() const;
|
||||
|
||||
// Validation
|
||||
HousingResult ValidateDecorPlacement(uint32 decorId, Position const& pos, uint32 houseLevel) const;
|
||||
|
||||
|
||||
@@ -42,7 +42,10 @@ bool Neighborhood::LoadFromDB(PreparedQueryResult neighborhood, PreparedQueryRes
|
||||
// _guid is already set in constructor
|
||||
_name = fields[1].GetString();
|
||||
_neighborhoodMapID = fields[2].GetUInt32();
|
||||
_ownerGuid = ObjectGuid::Create<HighGuid::Player>(fields[3].GetUInt64());
|
||||
{
|
||||
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();
|
||||
|
||||
@@ -271,6 +271,15 @@ Neighborhood* NeighborhoodMgr::GetNeighborhoodByOwner(ObjectGuid ownerGuid)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Neighborhood*> NeighborhoodMgr::GetAllNeighborhoods() const
|
||||
{
|
||||
std::vector<Neighborhood*> result;
|
||||
result.reserve(_neighborhoods.size());
|
||||
for (auto const& [guid, neighborhood] : _neighborhoods)
|
||||
result.push_back(neighborhood.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Neighborhood*> NeighborhoodMgr::GetPublicNeighborhoods() const
|
||||
{
|
||||
std::vector<Neighborhood*> result;
|
||||
@@ -293,6 +302,23 @@ std::vector<Neighborhood*> NeighborhoodMgr::GetNeighborhoodsForPlayer(ObjectGuid
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Neighborhood*> NeighborhoodMgr::GetNeighborhoodsByBnetAccount(ObjectGuid bnetAccountGuid) const
|
||||
{
|
||||
std::vector<Neighborhood*> result;
|
||||
for (auto const& [guid, neighborhood] : _neighborhoods)
|
||||
{
|
||||
for (auto const& plot : neighborhood->GetPlots())
|
||||
{
|
||||
if (plot.IsOccupied() && plot.OwnerBnetGuid == bnetAccountGuid)
|
||||
{
|
||||
result.push_back(neighborhood.get());
|
||||
break; // Only add each neighborhood once
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string NeighborhoodMgr::GetNeighborhoodName(ObjectGuid neighborhoodGuid) const
|
||||
{
|
||||
Neighborhood const* neighborhood = GetNeighborhood(neighborhoodGuid);
|
||||
@@ -312,25 +338,14 @@ Neighborhood* NeighborhoodMgr::FindNeighborhoodWithPendingInvite(ObjectGuid play
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Neighborhood* NeighborhoodMgr::FindOrCreateTutorialNeighborhood(ObjectGuid playerGuid, uint32 teamId)
|
||||
Neighborhood* NeighborhoodMgr::FindOrCreatePublicNeighborhood(uint32 teamId)
|
||||
{
|
||||
// Check if player already belongs to a neighborhood
|
||||
std::vector<Neighborhood*> existing = GetNeighborhoodsForPlayer(playerGuid);
|
||||
if (!existing.empty())
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "FindOrCreateTutorialNeighborhood: Player {} already in neighborhood '{}'",
|
||||
playerGuid.ToString(), existing[0]->GetName());
|
||||
return existing[0];
|
||||
}
|
||||
|
||||
// Determine the correct NeighborhoodMapID for the player's faction
|
||||
// NeighborhoodMap flags: bit 0 = Alliance, bit 1 = Horde, bit 2 = CanSystemGenerate
|
||||
// Determine the correct NeighborhoodMapID for the faction
|
||||
uint32 targetMapId = 0;
|
||||
int32 factionRestriction = NEIGHBORHOOD_FACTION_NONE;
|
||||
|
||||
for (auto const& [id, data] : sHousingMgr.GetAllNeighborhoodMapData())
|
||||
{
|
||||
int32 flags = data.UiMapID; // UiMapID stores DB2 FactionRestriction/Flags field
|
||||
int32 flags = data.UiMapID;
|
||||
bool isAlliance = (flags & 0x1) != 0;
|
||||
bool isHorde = (flags & 0x2) != 0;
|
||||
bool canSystemGenerate = (flags & 0x4) != 0;
|
||||
@@ -338,51 +353,40 @@ Neighborhood* NeighborhoodMgr::FindOrCreateTutorialNeighborhood(ObjectGuid playe
|
||||
if (!canSystemGenerate)
|
||||
continue;
|
||||
|
||||
if (teamId == ALLIANCE && isAlliance)
|
||||
if ((teamId == ALLIANCE && isAlliance) || (teamId == HORDE && isHorde))
|
||||
{
|
||||
targetMapId = id;
|
||||
factionRestriction = NEIGHBORHOOD_FACTION_ALLIANCE;
|
||||
break;
|
||||
}
|
||||
else if (teamId == HORDE && isHorde)
|
||||
{
|
||||
targetMapId = id;
|
||||
factionRestriction = NEIGHBORHOOD_FACTION_HORDE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetMapId == 0)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "FindOrCreateTutorialNeighborhood: No system-generatable NeighborhoodMap found for team {}",
|
||||
teamId);
|
||||
TC_LOG_ERROR("housing", "FindOrCreatePublicNeighborhood: No system-generatable NeighborhoodMap found for team {}", teamId);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Look for an existing public neighborhood on the target map with available plots
|
||||
// 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.
|
||||
// Force-run it now as a fallback, then retry.
|
||||
TC_LOG_WARN("housing", "FindOrCreatePublicNeighborhood: No public neighborhood for map {}, running EnsurePublicNeighborhoods", targetMapId);
|
||||
EnsurePublicNeighborhoods();
|
||||
|
||||
return FindPublicNeighborhoodForMap(targetMapId);
|
||||
}
|
||||
|
||||
Neighborhood* NeighborhoodMgr::FindPublicNeighborhoodForMap(uint32 neighborhoodMapId) const
|
||||
{
|
||||
for (auto const& [guid, neighborhood] : _neighborhoods)
|
||||
{
|
||||
if (neighborhood->GetNeighborhoodMapID() == targetMapId && neighborhood->IsPublic())
|
||||
{
|
||||
// Ensure the player is a member (they may not be if they didn't create this neighborhood)
|
||||
neighborhood->AddResident(playerGuid);
|
||||
|
||||
TC_LOG_DEBUG("housing", "FindOrCreateTutorialNeighborhood: Found existing public neighborhood '{}' for player {}",
|
||||
neighborhood->GetName(), playerGuid.ToString());
|
||||
if (neighborhood->GetNeighborhoodMapID() == neighborhoodMapId && neighborhood->IsPublic())
|
||||
return neighborhood.get();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new public, system-generated neighborhood with the player as owner
|
||||
std::string name = sHousingMgr.GenerateNeighborhoodName(targetMapId);
|
||||
Neighborhood* neighborhood = CreateNeighborhood(playerGuid, name, targetMapId, factionRestriction, /*isPublic*/ true);
|
||||
if (neighborhood)
|
||||
{
|
||||
TC_LOG_INFO("housing", "FindOrCreateTutorialNeighborhood: Created tutorial neighborhood '{}' (map {}) for player {}",
|
||||
name, targetMapId, playerGuid.ToString());
|
||||
}
|
||||
|
||||
return neighborhood;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NeighborhoodMgr::EnsurePublicNeighborhoods()
|
||||
|
||||
@@ -54,13 +54,18 @@ public:
|
||||
|
||||
// Queries
|
||||
Neighborhood* GetNeighborhoodByOwner(ObjectGuid ownerGuid);
|
||||
std::vector<Neighborhood*> GetAllNeighborhoods() const;
|
||||
std::vector<Neighborhood*> GetPublicNeighborhoods() const;
|
||||
std::vector<Neighborhood*> GetNeighborhoodsForPlayer(ObjectGuid playerGuid) const;
|
||||
std::vector<Neighborhood*> GetNeighborhoodsByBnetAccount(ObjectGuid bnetAccountGuid) const;
|
||||
std::string GetNeighborhoodName(ObjectGuid neighborhoodGuid) const;
|
||||
Neighborhood* FindNeighborhoodWithPendingInvite(ObjectGuid playerGuid);
|
||||
|
||||
// Tutorial support
|
||||
Neighborhood* FindOrCreateTutorialNeighborhood(ObjectGuid playerGuid, uint32 teamId);
|
||||
// Find or create a public neighborhood for a faction (no membership changes)
|
||||
Neighborhood* FindOrCreatePublicNeighborhood(uint32 teamId);
|
||||
|
||||
// Find a public neighborhood on the given map (for visitors, no membership change)
|
||||
Neighborhood* FindPublicNeighborhoodForMap(uint32 neighborhoodMapId) const;
|
||||
|
||||
// Expansion
|
||||
void CheckAndExpandNeighborhoods();
|
||||
|
||||
@@ -288,12 +288,14 @@ Map* MapManager::CreateMap(uint32 mapId, Player* player, Optional<uint32> lfgDun
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-assign if not already a member
|
||||
// If the player isn't a member of any neighborhood on this map,
|
||||
// find an existing public neighborhood for map rendering only.
|
||||
// Do NOT auto-add the player as a member ? membership is only
|
||||
// granted through the tutorial flow, buying a plot, or being invited.
|
||||
if (!neighborhood)
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "MapManager::CreateMap: No existing membership, auto-assigning tutorial neighborhood");
|
||||
neighborhood = sNeighborhoodMgr.FindOrCreateTutorialNeighborhood(
|
||||
player->GetGUID(), player->GetTeam());
|
||||
TC_LOG_DEBUG("housing", "MapManager::CreateMap: No existing membership, finding public neighborhood for viewing");
|
||||
neighborhood = sNeighborhoodMgr.FindPublicNeighborhoodForMap(neighborhoodMapId);
|
||||
}
|
||||
|
||||
if (!neighborhood)
|
||||
|
||||
@@ -355,18 +355,20 @@ namespace WorldPackets::Housing
|
||||
|
||||
WorldPacket const* QueryNeighborhoodNameResponse::Write()
|
||||
{
|
||||
// Wire format verified against retail 12.0.1 build 65940 (opcode 0x460012)
|
||||
// Retail: [PackedGUID] [0x80] [uint8 len] [string bytes]
|
||||
// Byte after GUID: bit 7 = Allow, bits 6-0 = zero padding (NOT a length field)
|
||||
// The 7-bit field was incorrectly used as SizedString::BitsSize<7> before,
|
||||
// causing the client to read NameLen bytes before the uint8 prefix, shifting everything.
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << Bits<1>(Allow);
|
||||
_worldPacket.FlushBits();
|
||||
|
||||
if (Allow)
|
||||
{
|
||||
_worldPacket << SizedString::BitsSize<7>(Name);
|
||||
_worldPacket.FlushBits();
|
||||
|
||||
_worldPacket << SizedString::Data(Name);
|
||||
_worldPacket << uint8(Name.size());
|
||||
_worldPacket.WriteString(Name);
|
||||
}
|
||||
else
|
||||
_worldPacket.FlushBits();
|
||||
|
||||
return &_worldPacket;
|
||||
}
|
||||
@@ -669,17 +671,15 @@ namespace WorldPackets::Housing
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
static void WriteJamCurrentHouseInfo(WorldPacket& packet, JamCurrentHouseInfo const& info);
|
||||
|
||||
WorldPacket const* HousingSvcsGetPlayerHousesInfoResponse::Write()
|
||||
{
|
||||
// Sniff-verified: uint32 Count + uint8 Unknown + JamCurrentHouseInfo per house
|
||||
_worldPacket << uint32(Houses.size());
|
||||
_worldPacket << uint8(Unknown);
|
||||
for (auto const& house : Houses)
|
||||
{
|
||||
_worldPacket << house.HouseGuid;
|
||||
_worldPacket << house.NeighborhoodGuid;
|
||||
_worldPacket << uint8(house.PlotIndex);
|
||||
_worldPacket << uint8(house.Level);
|
||||
}
|
||||
WriteJamCurrentHouseInfo(_worldPacket, house);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
@@ -708,9 +708,16 @@ namespace WorldPackets::Housing
|
||||
|
||||
WorldPacket const* HousingSvcsUpdateHousesLevelFavor::Write()
|
||||
{
|
||||
// Sniff-verified (36 bytes): uint8 + 4x int32 + PackedGUID + 2x int32 + uint16
|
||||
_worldPacket << uint8(Type);
|
||||
_worldPacket << int32(PreviousFavor);
|
||||
_worldPacket << int32(PreviousLevel);
|
||||
_worldPacket << int32(NewLevel);
|
||||
_worldPacket << int32(Field4);
|
||||
_worldPacket << HouseGuid;
|
||||
_worldPacket << uint32(Level);
|
||||
_worldPacket << uint64(Favor);
|
||||
_worldPacket << int32(PreviousLevelId);
|
||||
_worldPacket << int32(NextLevelFavorCost);
|
||||
_worldPacket << uint16(Flags);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
@@ -870,26 +877,46 @@ namespace WorldPackets::Housing
|
||||
// Housing General SMSG Responses (0x55xxxx)
|
||||
// ============================================================
|
||||
|
||||
// Helper: Write JamCurrentHouseInfo struct to packet (IDA sub_7FF6F6E0A170)
|
||||
static void WriteJamCurrentHouseInfo(WorldPacket& packet, JamCurrentHouseInfo const& info)
|
||||
{
|
||||
packet << info.OwnerGuid;
|
||||
packet << info.SecondaryOwnerGuid;
|
||||
packet << info.PlotGuid;
|
||||
packet << uint8(info.Flags);
|
||||
packet << uint32(info.HouseTypeId);
|
||||
uint8 statusFlags = info.StatusFlags;
|
||||
if (info.HouseId)
|
||||
statusFlags |= 0x80;
|
||||
packet << uint8(statusFlags);
|
||||
if (info.HouseId)
|
||||
packet << uint64(*info.HouseId);
|
||||
}
|
||||
|
||||
// Helper: Write JamNeighborhoodRosterEntry struct to packet (IDA sub_7FF6F6E0A460)
|
||||
static void WriteJamNeighborhoodRosterEntry(WorldPacket& packet, JamNeighborhoodRosterEntry const& entry)
|
||||
{
|
||||
packet << uint64(entry.Timestamp);
|
||||
packet << entry.PlayerGuid;
|
||||
packet << entry.HouseGuid;
|
||||
packet << uint64(entry.ExtraData);
|
||||
}
|
||||
|
||||
WorldPacket const* HousingHouseStatusResponse::Write()
|
||||
{
|
||||
// Sniff-verified (0x550000): 3x PackedGUID + uint32 Status
|
||||
_worldPacket << HouseGuid;
|
||||
_worldPacket << OwnerBNetGuid;
|
||||
_worldPacket << OwnerPlayerGuid;
|
||||
_worldPacket << uint16(HouseStatus);
|
||||
_worldPacket << uint8(PlotIndex);
|
||||
_worldPacket << uint8(StatusFlags);
|
||||
_worldPacket << HouseTemplateGuid;
|
||||
_worldPacket << PlotGuid;
|
||||
_worldPacket << uint32(Status);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* HousingGetCurrentHouseInfoResponse::Write()
|
||||
{
|
||||
_worldPacket << HouseGuid;
|
||||
_worldPacket << OwnerPlayerGuid;
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << uint8(PlotIndex);
|
||||
_worldPacket << uint8(HouseProperties);
|
||||
_worldPacket << uint8(HouseLevel);
|
||||
_worldPacket << uint32(Reserved);
|
||||
// IDA 12.0 verified (0x550001): JamCurrentHouseInfo + uint8 ResponseFlags
|
||||
WriteJamCurrentHouseInfo(_worldPacket, HouseInfo);
|
||||
_worldPacket << uint8(ResponseFlags);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
@@ -905,14 +932,17 @@ namespace WorldPackets::Housing
|
||||
|
||||
WorldPacket const* HousingGetPlayerPermissionsResponse::Write()
|
||||
{
|
||||
// IDA 12.0 verified (0x550006): PackedGUID + uint8 ResultCode + uint8 Permissions
|
||||
_worldPacket << HouseGuid;
|
||||
_worldPacket << uint16(PermissionFlags);
|
||||
_worldPacket << uint8(ResultCode);
|
||||
_worldPacket << uint8(PermissionFlags);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* HousingResetKioskModeResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
// IDA 12.0 verified (0x550007): single uint8
|
||||
_worldPacket << uint8(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
@@ -979,6 +1009,18 @@ namespace WorldPackets::Housing
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* HousingPhotoSharingAuthorizationResult::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* HousingPhotoSharingAuthorizationClearedResult::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
} // namespace WorldPackets::Housing
|
||||
|
||||
// ============================================================
|
||||
@@ -1171,136 +1213,144 @@ namespace WorldPackets::Neighborhood
|
||||
|
||||
WorldPacket const* NeighborhoodUpdateNameResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C0003): single uint8
|
||||
_worldPacket << uint8(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodUpdateNameNotification::Write()
|
||||
{
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << SizedString::BitsSize<7>(NewName);
|
||||
_worldPacket.FlushBits();
|
||||
_worldPacket << SizedString::Data(NewName);
|
||||
// IDA 12.0 verified (0x5C0004): uint8(nameLen) + bytes[nameLen]
|
||||
uint8 nameLen = NewName.empty() ? 0 : static_cast<uint8>(NewName.size() + 1);
|
||||
_worldPacket << uint8(nameLen);
|
||||
if (nameLen > 0)
|
||||
_worldPacket.append(reinterpret_cast<uint8 const*>(NewName.c_str()), nameLen);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodAddSecondaryOwnerResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C0006): PackedGUID + uint8 Result
|
||||
_worldPacket << PlayerGuid;
|
||||
_worldPacket << uint8(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodRemoveSecondaryOwnerResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C0007): PackedGUID + uint8 Result
|
||||
_worldPacket << PlayerGuid;
|
||||
_worldPacket << uint8(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodBuyHouseResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << HouseGuid;
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << uint8(PlotIndex);
|
||||
// IDA 12.0 verified (0x5C0008): JamCurrentHouseInfo + uint8 Result
|
||||
Housing::WriteJamCurrentHouseInfo(_worldPacket, HouseInfo);
|
||||
_worldPacket << uint8(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodMoveHouseResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << uint8(NewPlotIndex);
|
||||
// IDA 12.0 verified (0x5C0009): JamCurrentHouseInfo + PackedGUID + uint8 Result
|
||||
Housing::WriteJamCurrentHouseInfo(_worldPacket, HouseInfo);
|
||||
_worldPacket << MoveTransactionGuid;
|
||||
_worldPacket << uint8(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodOpenCornerstoneUIResponse::Write()
|
||||
{
|
||||
// Wire format verified against IDA client deserializer sub_7FF6F6E3E200 (12.0, opcode 0x5C000A)
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid; // PackedGUID ? neighborhood identity
|
||||
_worldPacket << PlotGuid; // PackedGUID ? plot owner (empty = unclaimed "For Sale")
|
||||
_worldPacket << uint64(Cost);
|
||||
_worldPacket << uint8(PlotIndex);
|
||||
// Wire format verified against retail 12.0.1 build 65940 packet captures (Alliance + Horde)
|
||||
// IDA deserializer sub_7FF6F6E3E200: uint32?+32, GUID?+40, GUID?+56, uint64?+72, uint8?+80, GUID?+128
|
||||
// Fixed fields
|
||||
_worldPacket << uint32(PlotIndex); // Echoed from CMSG (NOT a result code)
|
||||
_worldPacket << PlotOwnerGuid; // ?+40: Player GUID when owned, Empty when unclaimed
|
||||
_worldPacket << NeighborhoodGuid; // ?+56: Housing GUID when owned, Empty when unclaimed
|
||||
_worldPacket << uint64(Cost); // ?+72: Purchase price
|
||||
_worldPacket << uint8(PurchaseStatus); // ?+80: 73=purchasable, 0=not. Client checks ==73
|
||||
_worldPacket << CornerstoneGuid; // ?+128: Cornerstone game object
|
||||
|
||||
// Length-prefixed string: uint32 byteCount (including null terminator) + bytes
|
||||
// Client reader sub_7FF6F97E62B0 skips read when length <= 1
|
||||
uint32 nameLen = NeighborhoodName.empty() ? 0 : static_cast<uint32>(NeighborhoodName.size() + 1);
|
||||
_worldPacket << uint32(nameLen);
|
||||
if (nameLen > 0)
|
||||
_worldPacket.append(reinterpret_cast<uint8 const*>(NeighborhoodName.c_str()), nameLen);
|
||||
// Bit-packed section: 1 bool + 8-bit nameLen + 6 bools = 15 bits = 2 bytes
|
||||
// Retail uses NUL-terminated CString: NameLen includes the NUL byte
|
||||
_worldPacket << Bits<1>(IsPlotOwned);
|
||||
_worldPacket << SizedCString::BitsSize<8>(NeighborhoodName);
|
||||
_worldPacket << OptionalInit(AlternatePrice);
|
||||
_worldPacket << Bits<1>(CanPurchase);
|
||||
_worldPacket.WriteBit(false); // HasOptionalStruct ? not yet implemented
|
||||
_worldPacket << Bits<1>(HasResidents);
|
||||
_worldPacket << OptionalInit(StatusValue);
|
||||
_worldPacket << Bits<1>(IsInitiative);
|
||||
_worldPacket.FlushBits();
|
||||
|
||||
// Variable-length data (order matches client deserialization)
|
||||
// Optional struct data would go here if HasOptionalStruct was set
|
||||
_worldPacket << SizedCString::Data(NeighborhoodName);
|
||||
|
||||
if (AlternatePrice)
|
||||
_worldPacket << uint64(*AlternatePrice);
|
||||
|
||||
if (StatusValue)
|
||||
_worldPacket << uint32(*StatusValue);
|
||||
|
||||
_worldPacket << uint8(0); // OptionalFieldBits ? no optional sub-structs
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodInviteResidentResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C000B): uint8 Result + PackedGUID
|
||||
_worldPacket << uint8(Result);
|
||||
_worldPacket << InviteeGuid;
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodCancelInvitationResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C000C): uint8 Result + PackedGUID
|
||||
_worldPacket << uint8(Result);
|
||||
_worldPacket << InviteeGuid;
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodDeclineInvitationResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
// IDA 12.0 verified (0x5C000D): uint8 Result + PackedGUID
|
||||
_worldPacket << uint8(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodPlayerGetInviteResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << InviterGuid;
|
||||
_worldPacket << uint32(InviteTime);
|
||||
_worldPacket << SizedString::BitsSize<7>(NeighborhoodName);
|
||||
_worldPacket.FlushBits();
|
||||
_worldPacket << SizedString::Data(NeighborhoodName);
|
||||
// IDA 12.0 verified (0x5C000E): uint8 Result + JamNeighborhoodRosterEntry(48 bytes)
|
||||
_worldPacket << uint8(Result);
|
||||
Housing::WriteJamNeighborhoodRosterEntry(_worldPacket, Entry);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodGetInvitesResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
// IDA 12.0 verified (0x5C000F): uint8 Result + uint32 Count + RosterEntry[Count]
|
||||
_worldPacket << uint8(Result);
|
||||
_worldPacket << uint32(Invites.size());
|
||||
for (auto const& invite : Invites)
|
||||
{
|
||||
_worldPacket << invite.InviteeGuid;
|
||||
_worldPacket << invite.InviterGuid;
|
||||
_worldPacket << uint32(invite.InviteTime);
|
||||
}
|
||||
Housing::WriteJamNeighborhoodRosterEntry(_worldPacket, invite);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodInviteNotification::Write()
|
||||
{
|
||||
// IDA 12.0 verified (0x5C0010): single PackedGUID
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << InviterGuid;
|
||||
_worldPacket << SizedString::BitsSize<7>(NeighborhoodName);
|
||||
_worldPacket.FlushBits();
|
||||
_worldPacket << SizedString::Data(NeighborhoodName);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodOfferOwnershipResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << NewOwnerGuid;
|
||||
// IDA 12.0 verified (0x5C0011): single uint8 Result
|
||||
_worldPacket << uint8(Result);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
@@ -1361,7 +1411,8 @@ namespace WorldPackets::Neighborhood
|
||||
{
|
||||
_worldPacket << member.PlayerGuid; // PackedGUID
|
||||
_worldPacket << uint8(0); // Status field 1
|
||||
_worldPacket << uint8(0); // Status field 2 (only bit 7 used by client)
|
||||
// Status field 2: bit 7 (0x80) = online flag, checked by client UI for roster display
|
||||
_worldPacket << uint8(member.IsOnline ? 0x80 : 0x00);
|
||||
}
|
||||
|
||||
// Step 6: Optional trailing GUID (skipped since MainFlags.bit7 = 0)
|
||||
@@ -1382,24 +1433,24 @@ namespace WorldPackets::Neighborhood
|
||||
|
||||
WorldPacket const* NeighborhoodInviteNameLookupResult::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
// IDA 12.0 verified (0x5C0014): uint8 Result + PackedGUID
|
||||
_worldPacket << uint8(Result);
|
||||
_worldPacket << PlayerGuid;
|
||||
_worldPacket << SizedString::BitsSize<7>(PlayerName);
|
||||
_worldPacket.FlushBits();
|
||||
_worldPacket << SizedString::Data(PlayerName);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodEvictPlotResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
// IDA 12.0 verified (0x5C0015): uint8 Result + PackedGUID
|
||||
_worldPacket << uint8(Result);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << PlotGuid;
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* NeighborhoodEvictPlotNotice::Write()
|
||||
{
|
||||
// IDA 12.0 verified (0x5C0016): uint32 + PackedGUID + PackedGUID
|
||||
_worldPacket << uint32(PlotId);
|
||||
_worldPacket << NeighborhoodGuid;
|
||||
_worldPacket << PlotGuid;
|
||||
return &_worldPacket;
|
||||
|
||||
@@ -27,6 +27,33 @@
|
||||
|
||||
namespace WorldPackets::Housing
|
||||
{
|
||||
// ============================================================
|
||||
// Shared JAM Structs (verified against IDA 12.0 deserializers)
|
||||
// ============================================================
|
||||
|
||||
// JamCurrentHouseInfo ? sub_7FF6F6E0A170 (80 bytes, used by 0x550001, 0x5C0008, 0x5C0009)
|
||||
// Wire order: PackedGUID + PackedGUID + PackedGUID + uint8 + uint32 + uint8(bit7=has_optional) + [optional uint64]
|
||||
struct JamCurrentHouseInfo
|
||||
{
|
||||
ObjectGuid OwnerGuid;
|
||||
ObjectGuid SecondaryOwnerGuid;
|
||||
ObjectGuid PlotGuid;
|
||||
uint8 Flags = 0;
|
||||
uint32 HouseTypeId = 0;
|
||||
uint8 StatusFlags = 0; // bit 7 = has optional HouseId
|
||||
Optional<uint64> HouseId;
|
||||
};
|
||||
|
||||
// JamNeighborhoodRosterEntry ? sub_7FF6F6E0A460 (48 bytes, used by 0x5C000E, 0x5C000F)
|
||||
// Wire order: uint64 + PackedGUID + PackedGUID + uint64
|
||||
struct JamNeighborhoodRosterEntry
|
||||
{
|
||||
uint64 Timestamp = 0;
|
||||
ObjectGuid PlayerGuid;
|
||||
ObjectGuid HouseGuid;
|
||||
uint64 ExtraData = 0;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// House Exterior System (0x2Exxxx)
|
||||
// ============================================================
|
||||
@@ -590,6 +617,26 @@ namespace WorldPackets::Housing
|
||||
Optional<ObjectGuid> PlayerGuid;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Photo Sharing Authorization (0x40019x)
|
||||
// ============================================================
|
||||
|
||||
class HousingPhotoSharingCompleteAuthorization final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
explicit HousingPhotoSharingCompleteAuthorization(WorldPacket&& packet) : ClientPacket(CMSG_HOUSING_PHOTO_SHARING_COMPLETE_AUTHORIZATION, std::move(packet)) {}
|
||||
|
||||
void Read() override {}
|
||||
};
|
||||
|
||||
class HousingPhotoSharingClearAuthorization final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
explicit HousingPhotoSharingClearAuthorization(WorldPacket&& packet) : ClientPacket(CMSG_HOUSING_PHOTO_SHARING_CLEAR_AUTHORIZATION, std::move(packet)) {}
|
||||
|
||||
void Read() override {}
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Other Housing CMSG
|
||||
// ============================================================
|
||||
@@ -1029,16 +1076,9 @@ namespace WorldPackets::Housing
|
||||
HousingSvcsGetPlayerHousesInfoResponse() : ServerPacket(SMSG_HOUSING_SVCS_GET_PLAYER_HOUSES_INFO_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
// Wire format (sniff-confirmed, 5 bytes minimum):
|
||||
// uint32 HouseCount + uint8 Unknown
|
||||
struct HouseInfoData
|
||||
{
|
||||
ObjectGuid HouseGuid;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint8 PlotIndex = 0;
|
||||
uint8 Level = 0;
|
||||
};
|
||||
std::vector<HouseInfoData> Houses;
|
||||
// Wire format (sniff-verified): uint32 Count + uint8 Unknown + JamCurrentHouseInfo per house
|
||||
// JamCurrentHouseInfo fields mapped to: HouseGuid, PlotGuid, NeighborhoodGuid, PlotIndex, HouseType, StatusFlags+Timestamp
|
||||
std::vector<JamCurrentHouseInfo> Houses;
|
||||
uint8 Unknown = 0;
|
||||
};
|
||||
|
||||
@@ -1072,9 +1112,17 @@ namespace WorldPackets::Housing
|
||||
public:
|
||||
HousingSvcsUpdateHousesLevelFavor() : ServerPacket(SMSG_HOUSING_SVCS_UPDATE_HOUSES_LEVEL_FAVOR) {}
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
// Sniff-verified (36 bytes): uint8 + 4x int32 + PackedGUID + 2x int32 + uint16
|
||||
uint8 Type = 0;
|
||||
int32 PreviousFavor = -1;
|
||||
int32 PreviousLevel = -1;
|
||||
int32 NewLevel = 1;
|
||||
int32 Field4 = 0;
|
||||
ObjectGuid HouseGuid;
|
||||
uint32 Level = 0;
|
||||
uint64 Favor = 0;
|
||||
int32 PreviousLevelId = -1;
|
||||
int32 NextLevelFavorCost = -1;
|
||||
uint16 Flags = 0x8000;
|
||||
};
|
||||
|
||||
class HousingSvcsGuildAddHouseNotification final : public ServerPacket
|
||||
@@ -1259,15 +1307,12 @@ namespace WorldPackets::Housing
|
||||
HousingHouseStatusResponse() : ServerPacket(SMSG_HOUSING_HOUSE_STATUS_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
// Wire format (sniff-confirmed, 30 bytes):
|
||||
// PackedGUID HouseGUID + PackedGUID OwnerBNetGUID + PackedGUID OwnerPlayerGUID
|
||||
// + uint16 HouseStatus + uint8 PlotIndex + uint8 StatusFlags
|
||||
// Wire format (sniff-verified, 0x550000):
|
||||
// PackedGUID HouseGuid + PackedGUID HouseTemplateGuid + PackedGUID PlotGuid + uint32 Status
|
||||
ObjectGuid HouseGuid;
|
||||
ObjectGuid OwnerBNetGuid;
|
||||
ObjectGuid OwnerPlayerGuid;
|
||||
uint16 HouseStatus = 0; // 0 = no house, 1 = active
|
||||
uint8 PlotIndex = 0xFF; // INVALID_PLOT_INDEX
|
||||
uint8 StatusFlags = 0;
|
||||
ObjectGuid HouseTemplateGuid;
|
||||
ObjectGuid PlotGuid;
|
||||
uint32 Status = 0;
|
||||
};
|
||||
|
||||
class HousingGetCurrentHouseInfoResponse final : public ServerPacket
|
||||
@@ -1276,16 +1321,10 @@ namespace WorldPackets::Housing
|
||||
HousingGetCurrentHouseInfoResponse() : ServerPacket(SMSG_HOUSING_GET_CURRENT_HOUSE_INFO_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
// Wire format (sniff-confirmed, 33 bytes):
|
||||
// PackedGUID HouseGUID + PackedGUID OwnerPlayerGUID + PackedGUID NeighborhoodGUID
|
||||
// + uint8 PlotIndex + uint8 HouseProperties + uint8 HouseLevel + uint32 Reserved
|
||||
ObjectGuid HouseGuid;
|
||||
ObjectGuid OwnerPlayerGuid;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint8 PlotIndex = 0;
|
||||
uint8 HouseProperties = 0;
|
||||
uint8 HouseLevel = 0;
|
||||
uint32 Reserved = 0;
|
||||
// Wire format (IDA 12.0 verified, 0x550001):
|
||||
// JamCurrentHouseInfo + uint8 ResponseFlags
|
||||
JamCurrentHouseInfo HouseInfo;
|
||||
uint8 ResponseFlags = 0;
|
||||
};
|
||||
|
||||
class HousingExportHouseResponse final : public ServerPacket
|
||||
@@ -1304,10 +1343,11 @@ namespace WorldPackets::Housing
|
||||
HousingGetPlayerPermissionsResponse() : ServerPacket(SMSG_HOUSING_GET_PLAYER_PERMISSIONS_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
// Wire format (sniff-confirmed, 12 bytes):
|
||||
// PackedGUID HouseGUID + uint16 PermissionFlags
|
||||
// Wire format (IDA 12.0 verified, 0x550006):
|
||||
// PackedGUID + uint8 ResultCode + uint8 Permissions(bits 5,6,7)
|
||||
ObjectGuid HouseGuid;
|
||||
uint16 PermissionFlags = 0;
|
||||
uint8 ResultCode = 0;
|
||||
uint8 PermissionFlags = 0; // bit7=houseEditingPermitted, bit6=plotEntryPermitted, bit5=houseEntryPermitted
|
||||
};
|
||||
|
||||
class HousingResetKioskModeResponse final : public ServerPacket
|
||||
@@ -1315,7 +1355,7 @@ namespace WorldPackets::Housing
|
||||
public:
|
||||
HousingResetKioskModeResponse() : ServerPacket(SMSG_HOUSING_RESET_KIOSK_MODE_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
uint8 Result = 0; // IDA 12.0 verified (0x550007): single uint8
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
@@ -1397,6 +1437,26 @@ namespace WorldPackets::Housing
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Photo Sharing SMSG Responses (0x42037x)
|
||||
// ============================================================
|
||||
|
||||
class HousingPhotoSharingAuthorizationResult final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
HousingPhotoSharingAuthorizationResult() : ServerPacket(SMSG_HOUSING_PHOTO_SHARING_AUTHORIZATION_RESULT) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
};
|
||||
|
||||
class HousingPhotoSharingAuthorizationClearedResult final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
HousingPhotoSharingAuthorizationClearedResult() : ServerPacket(SMSG_HOUSING_PHOTO_SHARING_AUTHORIZATION_CLEARED_RESULT) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
};
|
||||
}
|
||||
|
||||
namespace WorldPackets::Neighborhood
|
||||
@@ -1717,8 +1777,7 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodUpdateNameResponse() : ServerPacket(SMSG_NEIGHBORHOOD_UPDATE_NAME_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint8 Result = 0; // IDA 12.0 verified (0x5C0003): single uint8
|
||||
};
|
||||
|
||||
class NeighborhoodUpdateNameNotification final : public ServerPacket
|
||||
@@ -1726,7 +1785,7 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodUpdateNameNotification() : ServerPacket(SMSG_NEIGHBORHOOD_UPDATE_NAME_NOTIFICATION) {}
|
||||
WorldPacket const* Write() override;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C0004): uint8(nameLen) + bytes[nameLen]
|
||||
std::string NewName;
|
||||
};
|
||||
|
||||
@@ -1735,9 +1794,9 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodAddSecondaryOwnerResponse() : ServerPacket(SMSG_NEIGHBORHOOD_ADD_SECONDARY_OWNER_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C0006): PackedGUID + uint8 Result
|
||||
ObjectGuid PlayerGuid;
|
||||
uint8 Result = 0;
|
||||
};
|
||||
|
||||
class NeighborhoodRemoveSecondaryOwnerResponse final : public ServerPacket
|
||||
@@ -1745,9 +1804,9 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodRemoveSecondaryOwnerResponse() : ServerPacket(SMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C0007): PackedGUID + uint8 Result
|
||||
ObjectGuid PlayerGuid;
|
||||
uint8 Result = 0;
|
||||
};
|
||||
|
||||
class NeighborhoodBuyHouseResponse final : public ServerPacket
|
||||
@@ -1755,10 +1814,9 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodBuyHouseResponse() : ServerPacket(SMSG_NEIGHBORHOOD_BUY_HOUSE_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid HouseGuid;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint8 PlotIndex = 0;
|
||||
// IDA 12.0 verified (0x5C0008): JamCurrentHouseInfo + uint8 Result
|
||||
Housing::JamCurrentHouseInfo HouseInfo;
|
||||
uint8 Result = 0;
|
||||
};
|
||||
|
||||
class NeighborhoodMoveHouseResponse final : public ServerPacket
|
||||
@@ -1766,9 +1824,10 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodMoveHouseResponse() : ServerPacket(SMSG_NEIGHBORHOOD_MOVE_HOUSE_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
uint8 NewPlotIndex = 0;
|
||||
// IDA 12.0 verified (0x5C0009): JamCurrentHouseInfo + PackedGUID + uint8 Result
|
||||
Housing::JamCurrentHouseInfo HouseInfo;
|
||||
ObjectGuid MoveTransactionGuid;
|
||||
uint8 Result = 0;
|
||||
};
|
||||
|
||||
class NeighborhoodOpenCornerstoneUIResponse final : public ServerPacket
|
||||
@@ -1776,12 +1835,22 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodOpenCornerstoneUIResponse() : ServerPacket(SMSG_NEIGHBORHOOD_OPEN_CORNERSTONE_UI_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
ObjectGuid PlotGuid; // Plot owner GUID (empty for unclaimed "For Sale" plots)
|
||||
uint64 Cost = 0;
|
||||
uint8 PlotIndex = 0;
|
||||
std::string NeighborhoodName; // Neighborhood name displayed in Cornerstone UI
|
||||
|
||||
// Wire format verified against retail 12.0.1 build 65940 packet captures
|
||||
// IDA deserializer sub_7FF6F6E3E200: uint32?+32, GUID?+40, GUID?+56, uint64?+72, uint8?+80, GUID?+128
|
||||
uint32 PlotIndex = 0; // Echoed from CMSG (NOT a result code)
|
||||
ObjectGuid PlotOwnerGuid; // ?Buffer+40: Player GUID when owned, Empty when unclaimed
|
||||
ObjectGuid NeighborhoodGuid; // ?Buffer+56: Housing GUID when owned, Empty when unclaimed
|
||||
uint64 Cost = 0; // ?Buffer+72: Purchase price (0 if owned or free)
|
||||
uint8 PurchaseStatus = 0; // ?Buffer+80: 73 (0x49) = purchasable, 0 = not. Client checks ==73
|
||||
ObjectGuid CornerstoneGuid; // ?Buffer+128: Cornerstone game object GUID
|
||||
bool IsPlotOwned = false; // Whether this plot has an owner
|
||||
bool CanPurchase = false; // Whether the player can purchase this plot
|
||||
bool HasResidents = false; // Whether the plot has residents
|
||||
bool IsInitiative = false; // Initiative-related flag
|
||||
Optional<uint64> AlternatePrice; // Alternate/discounted price
|
||||
Optional<uint32> StatusValue; // Additional status value
|
||||
std::string NeighborhoodName; // NUL-terminated CString in wire format
|
||||
};
|
||||
|
||||
class NeighborhoodInviteResidentResponse final : public ServerPacket
|
||||
@@ -1789,8 +1858,8 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodInviteResidentResponse() : ServerPacket(SMSG_NEIGHBORHOOD_INVITE_RESIDENT_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C000B): uint8 Result + PackedGUID
|
||||
uint8 Result = 0;
|
||||
ObjectGuid InviteeGuid;
|
||||
};
|
||||
|
||||
@@ -1799,8 +1868,8 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodCancelInvitationResponse() : ServerPacket(SMSG_NEIGHBORHOOD_CANCEL_INVITATION_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
// IDA 12.0 verified (0x5C000C): uint8 Result + PackedGUID
|
||||
uint8 Result = 0;
|
||||
ObjectGuid InviteeGuid;
|
||||
};
|
||||
|
||||
@@ -1809,7 +1878,8 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodDeclineInvitationResponse() : ServerPacket(SMSG_NEIGHBORHOOD_DECLINE_INVITATION_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
// IDA 12.0 verified (0x5C000D): uint8 Result + PackedGUID
|
||||
uint8 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
};
|
||||
|
||||
@@ -1818,11 +1888,9 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodPlayerGetInviteResponse() : ServerPacket(SMSG_NEIGHBORHOOD_PLAYER_GET_INVITE_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
ObjectGuid InviterGuid;
|
||||
std::string NeighborhoodName;
|
||||
uint32 InviteTime = 0;
|
||||
// IDA 12.0 verified (0x5C000E): uint8 Result + JamNeighborhoodRosterEntry(48 bytes)
|
||||
uint8 Result = 0;
|
||||
Housing::JamNeighborhoodRosterEntry Entry;
|
||||
};
|
||||
|
||||
class NeighborhoodGetInvitesResponse final : public ServerPacket
|
||||
@@ -1830,15 +1898,9 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodGetInvitesResponse() : ServerPacket(SMSG_NEIGHBORHOOD_GET_INVITES_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
|
||||
struct InviteData
|
||||
{
|
||||
ObjectGuid InviteeGuid;
|
||||
ObjectGuid InviterGuid;
|
||||
uint32 InviteTime = 0;
|
||||
};
|
||||
std::vector<InviteData> Invites;
|
||||
// IDA 12.0 verified (0x5C000F): uint8 Result + uint32 Count + JamNeighborhoodRosterEntry[Count]
|
||||
uint8 Result = 0;
|
||||
std::vector<Housing::JamNeighborhoodRosterEntry> Invites;
|
||||
};
|
||||
|
||||
class NeighborhoodInviteNotification final : public ServerPacket
|
||||
@@ -1846,9 +1908,8 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodInviteNotification() : ServerPacket(SMSG_NEIGHBORHOOD_INVITE_NOTIFICATION) {}
|
||||
WorldPacket const* Write() override;
|
||||
// IDA 12.0 verified (0x5C0010): single PackedGUID
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
ObjectGuid InviterGuid;
|
||||
std::string NeighborhoodName;
|
||||
};
|
||||
|
||||
class NeighborhoodOfferOwnershipResponse final : public ServerPacket
|
||||
@@ -1856,9 +1917,8 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodOfferOwnershipResponse() : ServerPacket(SMSG_NEIGHBORHOOD_OFFER_OWNERSHIP_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
ObjectGuid NewOwnerGuid;
|
||||
// IDA 12.0 verified (0x5C0011): single uint8 Result
|
||||
uint8 Result = 0;
|
||||
};
|
||||
|
||||
class NeighborhoodGetRosterResponse final : public ServerPacket
|
||||
@@ -1875,6 +1935,7 @@ namespace WorldPackets::Neighborhood
|
||||
ObjectGuid BnetAccountGuid; // Usually empty
|
||||
uint8 PlotIndex = 0xFF; // INVALID_PLOT_INDEX
|
||||
uint32 JoinTime = 0;
|
||||
bool IsOnline = false; // Controls status byte 2 bit 7 in roster UI
|
||||
};
|
||||
std::vector<RosterMemberData> Members;
|
||||
|
||||
@@ -1904,8 +1965,8 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodInviteNameLookupResult() : ServerPacket(SMSG_NEIGHBORHOOD_INVITE_NAME_LOOKUP_RESULT) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
std::string PlayerName;
|
||||
// IDA 12.0 verified (0x5C0014): uint8 Result + PackedGUID
|
||||
uint8 Result = 0;
|
||||
ObjectGuid PlayerGuid;
|
||||
};
|
||||
|
||||
@@ -1914,9 +1975,9 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodEvictPlotResponse() : ServerPacket(SMSG_NEIGHBORHOOD_EVICT_PLOT_RESPONSE) {}
|
||||
WorldPacket const* Write() override;
|
||||
uint32 Result = 0;
|
||||
// IDA 12.0 verified (0x5C0015): uint8 Result + PackedGUID
|
||||
uint8 Result = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
ObjectGuid PlotGuid;
|
||||
};
|
||||
|
||||
class NeighborhoodEvictPlotNotice final : public ServerPacket
|
||||
@@ -1924,6 +1985,8 @@ namespace WorldPackets::Neighborhood
|
||||
public:
|
||||
NeighborhoodEvictPlotNotice() : ServerPacket(SMSG_NEIGHBORHOOD_EVICT_PLOT_NOTICE) {}
|
||||
WorldPacket const* Write() override;
|
||||
// IDA 12.0 verified (0x5C0016): uint32 + PackedGUID + PackedGUID
|
||||
uint32 PlotId = 0;
|
||||
ObjectGuid NeighborhoodGuid;
|
||||
ObjectGuid PlotGuid;
|
||||
};
|
||||
|
||||
@@ -584,8 +584,8 @@ void OpcodeTable::InitializeClientOpcodes()
|
||||
DEFINE_HANDLER(CMSG_HOUSING_GET_CURRENT_HOUSE_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingGetCurrentHouseInfo);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_GET_PLAYER_PERMISSIONS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingGetPlayerPermissions);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_HOUSE_STATUS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingHouseStatus);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_PHOTO_SHARING_CLEAR_AUTHORIZATION, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_PHOTO_SHARING_COMPLETE_AUTHORIZATION, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_PHOTO_SHARING_CLEAR_AUTHORIZATION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingPhotoSharingClearAuthorization);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_PHOTO_SHARING_COMPLETE_AUTHORIZATION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingPhotoSharingCompleteAuthorization);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_RESET_KIOSK_MODE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingResetKioskMode);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_ROOM_ADD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingRoomAdd);
|
||||
DEFINE_HANDLER(CMSG_HOUSING_ROOM_APPLY_COMPONENT_MATERIALS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingRoomApplyComponentMaterials);
|
||||
|
||||
@@ -487,6 +487,8 @@ namespace WorldPackets
|
||||
class InvitePlayerToNeighborhood;
|
||||
class GuildGetOthersOwnedHouses;
|
||||
class HouseExteriorLock;
|
||||
class HousingPhotoSharingCompleteAuthorization;
|
||||
class HousingPhotoSharingClearAuthorization;
|
||||
class HousingFixtureSetHouseSize;
|
||||
class HousingFixtureSetHouseType;
|
||||
}
|
||||
@@ -1621,6 +1623,10 @@ class TC_GAME_API WorldSession
|
||||
void HandleHousingSvcsGetBnetFriendNeighborhoods(WorldPackets::Housing::HousingSvcsGetBnetFriendNeighborhoods const& housingSvcsGetBnetFriendNeighborhoods);
|
||||
void HandleHousingSvcsDeleteAllNeighborhoodInvites(WorldPackets::Housing::HousingSvcsDeleteAllNeighborhoodInvites const& housingSvcsDeleteAllNeighborhoodInvites);
|
||||
|
||||
// Housing - Photo Sharing
|
||||
void HandleHousingPhotoSharingCompleteAuthorization(WorldPackets::Housing::HousingPhotoSharingCompleteAuthorization const& packet);
|
||||
void HandleHousingPhotoSharingClearAuthorization(WorldPackets::Housing::HousingPhotoSharingClearAuthorization const& packet);
|
||||
|
||||
// Housing - Misc
|
||||
void HandleHousingHouseStatus(WorldPackets::Housing::HousingHouseStatus const& housingHouseStatus);
|
||||
void HandleHousingGetCurrentHouseInfo(WorldPackets::Housing::HousingGetCurrentHouseInfo const& housingGetCurrentHouseInfo);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "GameObject.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "SpellScript.h"
|
||||
|
||||
enum HousingCornerstoneSpells
|
||||
{
|
||||
SPELL_TRIGGER_CONVO_UNOWNED_PLOT = 1266097
|
||||
};
|
||||
|
||||
// 1266097 - [DNT] Trigger Convo for Unowned Plot
|
||||
// Cast by Cornerstone GO (entry 457142, type UILink) when a player clicks it.
|
||||
// The SMSG_NPC_INTERACTION_OPEN_RESULT with CornerstoneInteraction (type 70) is
|
||||
// already sent by the UILink Use() handler before this spell fires.
|
||||
// This dummy effect provides server-side validation and logging.
|
||||
class spell_housing_trigger_convo_unowned_plot : public SpellScript
|
||||
{
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandleDummy(SpellEffIndex /*effIndex*/) const
|
||||
{
|
||||
Player* caster = GetCaster()->ToPlayer();
|
||||
if (!caster)
|
||||
return;
|
||||
|
||||
TC_LOG_DEBUG("housing", "spell_housing_trigger_convo_unowned_plot: Spell {} fired for player {} ({})",
|
||||
GetSpellInfo()->Id, caster->GetName(), caster->GetGUID().ToString());
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHit += SpellEffectFn(spell_housing_trigger_convo_unowned_plot::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_housing_spell_scripts()
|
||||
{
|
||||
RegisterSpellScript(spell_housing_trigger_convo_unowned_plot);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ void AddSC_warrior_spell_scripts();
|
||||
void AddSC_quest_spell_scripts();
|
||||
void AddSC_item_spell_scripts();
|
||||
void AddSC_azerite_item_spell_scripts();
|
||||
void AddSC_housing_spell_scripts();
|
||||
|
||||
// The name of this function should match:
|
||||
// void Add${NameOfDirectory}Scripts()
|
||||
@@ -55,4 +56,5 @@ void AddSpellsScripts()
|
||||
AddSC_quest_spell_scripts();
|
||||
AddSC_item_spell_scripts();
|
||||
AddSC_azerite_item_spell_scripts();
|
||||
AddSC_housing_spell_scripts();
|
||||
}
|
||||
|
||||
@@ -25,27 +25,30 @@
|
||||
enum HousingTutorialData
|
||||
{
|
||||
// Quest: "My First Home" (91863) kill credit NPCs
|
||||
NPC_KILL_CREDIT_GREET_STEWARD = 249851,
|
||||
NPC_KILL_CREDIT_ASK_STEWARD = 248857,
|
||||
NPC_KILL_CREDIT_GREET_STEWARD = 249851,
|
||||
NPC_KILL_CREDIT_ASK_STEWARD = 248857,
|
||||
|
||||
// Gossip actions
|
||||
GOSSIP_ACTION_ASK_TO_JOIN = 1001,
|
||||
GOSSIP_ACTION_ASK_TO_JOIN = 1001,
|
||||
};
|
||||
|
||||
// Lyssabel Dawnpetal (233063) / Tocho (233708) — Housing tutorial steward NPCs.
|
||||
// Lyssabel Dawnpetal (233063) / Tocho (233708) ? Housing tutorial steward NPCs.
|
||||
// When the player interacts with the steward during the "My First Home" quest (91863),
|
||||
// the gossip grants quest kill credits for greeting the steward and asking them to join.
|
||||
struct npc_housing_steward : public CreatureAI
|
||||
{
|
||||
npc_housing_steward(Creature* creature) : CreatureAI(creature) { }
|
||||
npc_housing_steward(Creature* creature) : CreatureAI(creature) {}
|
||||
|
||||
void UpdateAI(uint32 /*diff*/) override { }
|
||||
void UpdateAI(uint32 /*diff*/) override {}
|
||||
|
||||
bool OnGossipHello(Player* player) override
|
||||
{
|
||||
// Grant "Greet the steward" kill credit (quest objective 0)
|
||||
// Grant "Greet the steward" kill credit (quest objective 0: MONSTER 249851)
|
||||
player->KilledMonsterCredit(NPC_KILL_CREDIT_GREET_STEWARD);
|
||||
|
||||
// Satisfy "Talk to Lyssabel/Tocho" objective (quest objective 1/2: TALKTO with NPC entry)
|
||||
player->TalkedToCreature(me->GetEntry(), me->GetGUID());
|
||||
|
||||
// Show gossip menu with option to ask the steward to join the neighborhood
|
||||
InitGossipMenuFor(player, 0);
|
||||
if (me->IsQuestGiver())
|
||||
@@ -55,8 +58,8 @@ struct npc_housing_steward : public CreatureAI
|
||||
GOSSIP_SENDER_MAIN, GOSSIP_ACTION_ASK_TO_JOIN);
|
||||
SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, me->GetGUID());
|
||||
|
||||
TC_LOG_DEBUG("housing", "npc_housing_steward: Player {} greeted steward {} (kill credit {})",
|
||||
player->GetGUID().ToString(), me->GetEntry(), NPC_KILL_CREDIT_GREET_STEWARD);
|
||||
TC_LOG_DEBUG("housing", "npc_housing_steward: Player {} greeted steward {} (kill credit {}, talkto {})",
|
||||
player->GetGUID().ToString(), me->GetEntry(), NPC_KILL_CREDIT_GREET_STEWARD, me->GetEntry());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user