Housing: fix Visit teleport for offline owners + spawn 4 Group B mirrors per plot
Visit teleport (TELEPORT_TO_PLOT, TeleportType=5): CanVisitorAccess() returned false whenever the plot owner was offline because the helper takes Player const* and aborts on null. Result: the client received PERMISSION_DENIED and never teleported, even on plots whose settings were "ANYONE". Fix: add CanVisitorAccessPlot(visitor, ownerGuid, settingsFlags, isInterior) that resolves friend / guild / neighborhood relationships from the visitor side + sCharacterCache, so the check works for offline owners. Settings come from a new PlotInfo::HouseSettingsFlags mirrored from character_housing.settingsFlags at neighborhood preload, refreshed when Housing::SaveSettings runs while the owner is online. TeleportToPlot now passes the persisted plot settings into the new helper instead of bailing on a null Player*. Group B Entity mirrors: Audit 2026-04-21 (FINDINGS.md sec 1.2) found retail emits 4 per-piece Entity mirrors per plot - one FMirroredPositionData_C-only mirror attached to each visible exterior fixture MeshObject (Base/Roof/Door/ Window - ExteriorComponent Type 9/10/11/12). We were emitting one, anchored to the root only. _houseMeshMirrorEntities is now a vector<unique_ptr<HousingMirrorEntity>> per plot. SpawnHouseForPlot iterates the plot's MeshObjects, and pairs one Group B mirror per fixture-tier mesh. MakeHouseMeshMirrorGuid now packs (bnetId << 16) | (plot << 8) | piece so each mirror has a unique GUID. Player::BuildCreateUpdateBlockForPlayer iterates the new GetHouseMeshMirrors() list so all per-piece mirrors land in the initial UPDATE_OBJECT bundle. Schema: CHAR_SEL_NEIGHBORHOOD_MEMBERS extended with ch.settingsFlags so Neighborhood::LoadFromDB can populate PlotInfo::HouseSettingsFlags for every member's plot at startup.
This commit is contained in:
@@ -884,7 +884,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_PUBLIC, "UPDATE neighborhoods SET isPublic = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_NEIGHBORHOOD_NAME, "UPDATE neighborhoods SET name = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_NEIGHBORHOOD, "DELETE FROM neighborhoods WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_MEMBERS, "SELECT nm.playerGuid, nm.role, nm.joinTime, nm.plotIndex, ch.houseId, c.account, ch.houseLevel, ch.favor, ch.houseName, ch.houseType FROM neighborhood_members nm LEFT JOIN character_housing ch ON nm.playerGuid = ch.guid LEFT JOIN characters c ON nm.playerGuid = c.guid WHERE nm.neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_NEIGHBORHOOD_MEMBERS, "SELECT nm.playerGuid, nm.role, nm.joinTime, nm.plotIndex, ch.houseId, c.account, ch.houseLevel, ch.favor, ch.houseName, ch.houseType, ch.settingsFlags FROM neighborhood_members nm LEFT JOIN character_housing ch ON nm.playerGuid = ch.guid LEFT JOIN characters c ON nm.playerGuid = c.guid WHERE nm.neighborhoodGuid = ?", CONNECTION_SYNCH);
|
||||
// Owner-keyed batch fetches used to preload all occupied-plot exterior and
|
||||
// interior spawn data at neighborhood init, so houses render for every plot
|
||||
// regardless of whether the owner is online. Filtered by the neighborhood's
|
||||
|
||||
@@ -3713,8 +3713,10 @@ void Player::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) c
|
||||
m->BuildCreateUpdateBlockForPlayer(data, target);
|
||||
++mirrorCount;
|
||||
}
|
||||
// Group B mesh mirror (untagged, AttachParent=MeshObject).
|
||||
if (HousingMirrorEntity* bm = hmap->GetHouseMeshMirror(plot.PlotIndex))
|
||||
// Group B per-piece mirrors (untagged, AttachParent=fixture
|
||||
// MeshObject). Retail emits one per visible exterior fixture
|
||||
// (Base/Roof/Door/Window — typically 4 per plot).
|
||||
for (HousingMirrorEntity* bm : hmap->GetHouseMeshMirrors(plot.PlotIndex))
|
||||
{
|
||||
bm->BuildCreateUpdateBlockForPlayer(data, target);
|
||||
++mirrorCount;
|
||||
@@ -3739,7 +3741,7 @@ void Player::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) c
|
||||
{
|
||||
if (HousingMirrorEntity* ownMirror = hmap->GetHouseMirror(ownPlotIndex))
|
||||
ownMirror->BuildCreateUpdateBlockForPlayer(data, target);
|
||||
if (HousingMirrorEntity* ownMeshMirror = hmap->GetHouseMeshMirror(ownPlotIndex))
|
||||
for (HousingMirrorEntity* ownMeshMirror : hmap->GetHouseMeshMirrors(ownPlotIndex))
|
||||
ownMeshMirror->BuildCreateUpdateBlockForPlayer(data, target);
|
||||
}
|
||||
|
||||
|
||||
@@ -3527,18 +3527,20 @@ void WorldSession::HandleHousingSvcsTeleportToPlot(WorldPackets::Housing::Housin
|
||||
|
||||
if (targetPlot)
|
||||
{
|
||||
// Per-house access check: verify visitor has permission to access this plot
|
||||
// Per-house access check: verify visitor has permission to access this plot.
|
||||
// Owner can be offline — fall back to the persisted plotInfo->HouseSettingsFlags
|
||||
// (mirrored from character_housing.settingsFlags at neighborhood preload).
|
||||
if (!neighborhood->IsMember(player->GetGUID()))
|
||||
{
|
||||
Neighborhood::PlotInfo const* plotInfo = neighborhood->GetPlotInfo(static_cast<uint8>(plotIndex));
|
||||
if (plotInfo && plotInfo->IsOccupied())
|
||||
{
|
||||
Player* ownerPlayer = ObjectAccessor::FindPlayer(plotInfo->OwnerGuid);
|
||||
uint32 settingsFlags = HOUSE_SETTING_DEFAULT;
|
||||
if (ownerPlayer && ownerPlayer->GetHousing())
|
||||
settingsFlags = ownerPlayer->GetHousing()->GetSettingsFlags();
|
||||
uint32 settingsFlags = (ownerPlayer && ownerPlayer->GetHousing())
|
||||
? ownerPlayer->GetHousing()->GetSettingsFlags()
|
||||
: plotInfo->HouseSettingsFlags;
|
||||
|
||||
if (!sHousingMgr.CanVisitorAccess(player, ownerPlayer, settingsFlags, false))
|
||||
if (!sHousingMgr.CanVisitorAccessPlot(player, plotInfo->OwnerGuid, settingsFlags, false))
|
||||
{
|
||||
WorldPackets::Housing::HousingSvcsNotifyPermissionsFailure denied;
|
||||
denied.FailureType = static_cast<uint8>(HOUSING_RESULT_PERMISSION_DENIED);
|
||||
|
||||
@@ -2556,6 +2556,11 @@ void Housing::SaveSettings(uint32 settingsFlags)
|
||||
stmt->setUInt64(1, _owner->GetGUID().GetCounter());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
|
||||
// Mirror onto the in-memory neighborhood plot so visitor permission checks
|
||||
// (CanVisitorAccessPlot) work correctly when the owner is offline.
|
||||
if (Neighborhood* nbh = sNeighborhoodMgr.GetNeighborhood(_neighborhoodGuid))
|
||||
nbh->UpdatePlotSettingsFlags(_owner->GetGUID(), _settingsFlags);
|
||||
|
||||
SyncUpdateFields();
|
||||
|
||||
TC_LOG_DEBUG("housing", "Housing::SaveSettings: Player {} updated house settings to {} in house {}",
|
||||
|
||||
@@ -2012,14 +2012,23 @@ GameObject* HousingMap::SpawnHouseForPlot(uint8 plotIndex, Position const* custo
|
||||
_houseMirrorEntities[plotIndex] = std::move(mirror);
|
||||
}
|
||||
|
||||
// Group B Entity mirror — FMirroredPositionData_C only (no tags),
|
||||
// AttachParent = exterior-root MeshObject (not the room). Sniff
|
||||
// idx 9984 shows 4 Group B mirrors alongside the 4 Group A ones.
|
||||
// Purpose: pure spatial anchor off the house's visible mesh.
|
||||
// Lookup: first MeshObject in this plot's list whose FHousingFixture_C
|
||||
// ExteriorComponentType==9 (Base) and no AttachParent (i.e. a root).
|
||||
// Group B Entity mirrors — FMirroredPositionData_C only (no tags),
|
||||
// one per visible exterior fixture MeshObject (Base/Roof/Door/Window —
|
||||
// ExteriorComponent Type 9/10/11/12). AttachParent = the piece's own
|
||||
// MeshObject. Sniff idx 9984 shows 4 Group B mirrors per plot, one
|
||||
// anchored to each fixture mesh. Without these per-piece anchors the
|
||||
// client lacks spatial hooks for door-hover detection and expert-mode
|
||||
// placement preview off non-root meshes.
|
||||
{
|
||||
ObjectGuid exteriorRootGuid;
|
||||
std::vector<std::unique_ptr<HousingMirrorEntity>>& mirrors = _houseMeshMirrorEntities[plotIndex];
|
||||
mirrors.clear();
|
||||
uint32 const bnetId = static_cast<uint32>(plotInfo->OwnerBnetGuid.GetCounter());
|
||||
uint8 pieceIndex = 0;
|
||||
QuaternionData identity;
|
||||
identity.x = identity.y = identity.z = 0.0f;
|
||||
identity.w = 1.0f;
|
||||
Position const localPos(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
auto meshItr = _meshObjects.find(plotIndex);
|
||||
if (meshItr != _meshObjects.end())
|
||||
{
|
||||
@@ -2029,35 +2038,35 @@ GameObject* HousingMap::SpawnHouseForPlot(uint8 plotIndex, Position const* custo
|
||||
if (!mesh || !mesh->m_housingFixtureData.has_value())
|
||||
continue;
|
||||
UF::HousingFixtureData const& fd = *mesh->m_housingFixtureData;
|
||||
if (uint8(fd.ExteriorComponentType) == 9 && fd.AttachParentGUID->IsEmpty())
|
||||
{
|
||||
exteriorRootGuid = meshGuid;
|
||||
break;
|
||||
}
|
||||
uint8 const compType = uint8(fd.ExteriorComponentType);
|
||||
// Pair Group B mirrors with the visible fixture types only:
|
||||
// 9=Base, 10=Roof, 11=Door, 12=Window. Other component types
|
||||
// (decorative subpieces, hooks) don't get retail-side mirrors.
|
||||
if (compType < 9 || compType > 12)
|
||||
continue;
|
||||
|
||||
ObjectGuid mirrorGuid = MakeHouseMeshMirrorGuid(plotIndex, bnetId, pieceIndex);
|
||||
auto mirror = std::make_unique<HousingMirrorEntity>(this, mirrorGuid);
|
||||
mirror->InitPositionData(meshGuid,
|
||||
localPos, identity, /*scale*/ 1.0f, /*attachmentFlags*/ 3,
|
||||
/*isExteriorRoot*/ false);
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnHouseForPlot: spawned Group B mirror[{}] {} "
|
||||
"for plot {} (attach={} [mesh type={}])",
|
||||
pieceIndex, mirrorGuid.ToString(), plotIndex, meshGuid.ToString(), compType);
|
||||
mirrors.push_back(std::move(mirror));
|
||||
++pieceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exteriorRootGuid.IsEmpty())
|
||||
if (mirrors.empty())
|
||||
{
|
||||
uint32 bnetId = static_cast<uint32>(plotInfo->OwnerBnetGuid.GetCounter());
|
||||
ObjectGuid meshMirrorGuid = MakeHouseMeshMirrorGuid(plotIndex, bnetId);
|
||||
auto meshMirror = std::make_unique<HousingMirrorEntity>(this, meshMirrorGuid);
|
||||
Position const localPos(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
QuaternionData identity;
|
||||
identity.x = identity.y = identity.z = 0.0f;
|
||||
identity.w = 1.0f;
|
||||
meshMirror->InitPositionData(exteriorRootGuid,
|
||||
localPos, identity, /*scale*/ 1.0f, /*attachmentFlags*/ 3,
|
||||
/*isExteriorRoot*/ false);
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnHouseForPlot: spawned Group B mesh mirror {} for plot {} "
|
||||
"(attach={} [mesh root])",
|
||||
meshMirrorGuid.ToString(), plotIndex, exteriorRootGuid.ToString());
|
||||
_houseMeshMirrorEntities[plotIndex] = std::move(meshMirror);
|
||||
TC_LOG_WARN("housing", "HousingMap::SpawnHouseForPlot: no fixture MeshObjects found for plot {}; "
|
||||
"Group B mirrors skipped (client spatial anchors off house meshes will be missing)", plotIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
TC_LOG_WARN("housing", "HousingMap::SpawnHouseForPlot: no exterior-root MeshObject found for plot {}; "
|
||||
"Group B mirror skipped (client spatial anchor off house mesh will be missing)", plotIndex);
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnHouseForPlot: emitted {} Group B mirrors for plot {}",
|
||||
mirrors.size(), plotIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3143,7 +3152,11 @@ ObjectGuid HousingMap::MakeHouseMirrorGuid(uint8 plotIndex, uint32 bnetAccountId
|
||||
HousingMirrorEntity* HousingMap::GetHouseMeshMirror(uint8 plotIndex) const
|
||||
{
|
||||
auto itr = _houseMeshMirrorEntities.find(plotIndex);
|
||||
return itr != _houseMeshMirrorEntities.end() ? itr->second.get() : nullptr;
|
||||
if (itr == _houseMeshMirrorEntities.end() || itr->second.empty())
|
||||
return nullptr;
|
||||
// Returns the first (root-piece) Group B mirror for legacy callers that
|
||||
// only need any anchor; per-piece access goes via _houseMeshMirrorEntities.
|
||||
return itr->second.front().get();
|
||||
}
|
||||
|
||||
ObjectGuid HousingMap::GetHouseMeshMirrorGuid(uint8 plotIndex) const
|
||||
@@ -3153,12 +3166,27 @@ ObjectGuid HousingMap::GetHouseMeshMirrorGuid(uint8 plotIndex) const
|
||||
return ObjectGuid::Empty;
|
||||
}
|
||||
|
||||
ObjectGuid HousingMap::MakeHouseMeshMirrorGuid(uint8 plotIndex, uint32 bnetAccountId) const
|
||||
std::vector<HousingMirrorEntity*> HousingMap::GetHouseMeshMirrors(uint8 plotIndex) const
|
||||
{
|
||||
std::vector<HousingMirrorEntity*> result;
|
||||
auto itr = _houseMeshMirrorEntities.find(plotIndex);
|
||||
if (itr == _houseMeshMirrorEntities.end())
|
||||
return result;
|
||||
result.reserve(itr->second.size());
|
||||
for (auto const& mirror : itr->second)
|
||||
result.push_back(mirror.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
ObjectGuid HousingMap::MakeHouseMeshMirrorGuid(uint8 plotIndex, uint32 bnetAccountId, uint8 pieceIndex /*= 0*/) const
|
||||
{
|
||||
// Distinct synthetic entry from Group A (37361) so Group A/B guids never collide.
|
||||
// Same counter packing: (bnetId << 8) | plotIndex.
|
||||
// Counter packs the (bnet, plot, piece) triple so each per-piece Group B
|
||||
// mirror has a unique GUID across the realm: (bnetId << 16) | (plot << 8) | piece.
|
||||
constexpr uint32 HOUSING_MESH_MIRROR_ENTRY = 37362;
|
||||
uint64 counter = (static_cast<uint64>(bnetAccountId) << 8) | static_cast<uint64>(plotIndex);
|
||||
uint64 counter = (static_cast<uint64>(bnetAccountId) << 16)
|
||||
| (static_cast<uint64>(plotIndex) << 8)
|
||||
| static_cast<uint64>(pieceIndex);
|
||||
return ObjectGuid::Create<HighGuid::Entity>(GetId(), HOUSING_MESH_MIRROR_ENTRY, counter);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,16 +95,20 @@ public:
|
||||
// and bnet owner are known from NeighborhoodMirror data.
|
||||
ObjectGuid MakeHouseMirrorGuid(uint8 plotIndex, uint32 bnetAccountId) const;
|
||||
|
||||
// "Group B" mesh-level mirror. Retail pairs each Group A (house-exterior
|
||||
// root) Entity mirror with a SECOND untagged FMirroredPositionData_C
|
||||
// Entity mirror whose AttachParent is the exterior root MeshObject (not
|
||||
// the room / Housing/2). Sniff idx 9984: 4 Group A (44-byte values) +
|
||||
// 4 Group B (52-byte values, AttachParent=MeshObject serialises longer).
|
||||
// Without Group B the client's spatial index for the house-exterior mesh
|
||||
// is missing, which breaks AoE/icon resolution off the MeshObject root.
|
||||
// "Group B" per-piece mesh-level mirrors. Retail pairs EACH visible
|
||||
// exterior fixture MeshObject (Base/Roof/Door/Window — ExteriorComponent
|
||||
// Type 9/10/11/12) with one untagged FMirroredPositionData_C Entity
|
||||
// mirror whose AttachParent is the piece's MeshObject. Sniff idx 9984:
|
||||
// 4 Group A (44-byte values) + 4 Group B (52-byte values, AttachParent=
|
||||
// MeshObject serialises longer). Without these per-piece anchors the
|
||||
// client's spatial index is missing finer-grained hooks (door-hover
|
||||
// detection, expert-mode placement preview off non-root meshes).
|
||||
HousingMirrorEntity* GetHouseMeshMirror(uint8 plotIndex) const;
|
||||
ObjectGuid GetHouseMeshMirrorGuid(uint8 plotIndex) const;
|
||||
ObjectGuid MakeHouseMeshMirrorGuid(uint8 plotIndex, uint32 bnetAccountId) const;
|
||||
ObjectGuid MakeHouseMeshMirrorGuid(uint8 plotIndex, uint32 bnetAccountId, uint8 pieceIndex = 0) const;
|
||||
// Full list of per-piece Group B mirrors for this plot (one per fixture
|
||||
// MeshObject of Type 9/10/11/12). Returns empty if no house spawned.
|
||||
std::vector<HousingMirrorEntity*> GetHouseMeshMirrors(uint8 plotIndex) const;
|
||||
|
||||
// Lightweight Housing/2 identity room entity (HighGuid::Housing subType=2,
|
||||
// objectType=18) — the authoritative room. Retail-verified architecture:
|
||||
@@ -212,9 +216,10 @@ private:
|
||||
// 1:1 with the exterior root MeshObject.
|
||||
std::unordered_map<uint8, std::unique_ptr<HousingMirrorEntity>> _houseMirrorEntities;
|
||||
|
||||
// "Group B" mesh-level Entity mirror, attached to the exterior root
|
||||
// MeshObject (Type=9). 1:1 with the house root mesh; co-spawned/despawned.
|
||||
std::unordered_map<uint8, std::unique_ptr<HousingMirrorEntity>> _houseMeshMirrorEntities;
|
||||
// "Group B" per-piece mesh-level Entity mirrors, one per visible exterior
|
||||
// fixture MeshObject (Type 9/10/11/12). Co-spawned with the house and
|
||||
// despawned together. Vector since retail emits 4 of these per plot.
|
||||
std::unordered_map<uint8, std::vector<std::unique_ptr<HousingMirrorEntity>>> _houseMeshMirrorEntities;
|
||||
|
||||
// Lightweight Housing/2 identity room GUID per plot. The entity itself is
|
||||
// a WorldObject owned by the map's object store; we only track its GUID.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "HousingMgr.h"
|
||||
#include "CharacterCache.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "DB2Structure.h"
|
||||
#include "GameObjectData.h"
|
||||
@@ -24,6 +25,8 @@
|
||||
#include "Housing.h"
|
||||
#include "Log.h"
|
||||
#include "Neighborhood.h"
|
||||
#include "NeighborhoodMgr.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "RaceMask.h"
|
||||
@@ -834,6 +837,69 @@ std::vector<std::pair<uint32, int32>> HousingMgr::GetStarterDecorWithQuantities(
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HousingMgr::CanVisitorAccessPlot(Player const* visitor, ObjectGuid ownerGuid, uint32 settingsFlags, bool isInterior) const
|
||||
{
|
||||
if (!visitor || ownerGuid.IsEmpty())
|
||||
return false;
|
||||
|
||||
if (visitor->GetGUID() == ownerGuid)
|
||||
return true;
|
||||
|
||||
uint32 anyoneFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_ANYONE : HOUSE_SETTING_PLOT_ACCESS_ANYONE;
|
||||
uint32 neighborsFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_NEIGHBORS : HOUSE_SETTING_PLOT_ACCESS_NEIGHBORS;
|
||||
uint32 guildFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_GUILD : HOUSE_SETTING_PLOT_ACCESS_GUILD;
|
||||
uint32 friendsFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_FRIENDS : HOUSE_SETTING_PLOT_ACCESS_FRIENDS;
|
||||
uint32 partyFlag = isInterior ? HOUSE_SETTING_HOUSE_ACCESS_PARTY : HOUSE_SETTING_PLOT_ACCESS_PARTY;
|
||||
|
||||
uint32 accessMask = isInterior
|
||||
? (HOUSE_SETTING_HOUSE_ACCESS_ANYONE | HOUSE_SETTING_HOUSE_ACCESS_NEIGHBORS |
|
||||
HOUSE_SETTING_HOUSE_ACCESS_GUILD | HOUSE_SETTING_HOUSE_ACCESS_FRIENDS | HOUSE_SETTING_HOUSE_ACCESS_PARTY)
|
||||
: (HOUSE_SETTING_PLOT_ACCESS_ANYONE | HOUSE_SETTING_PLOT_ACCESS_NEIGHBORS |
|
||||
HOUSE_SETTING_PLOT_ACCESS_GUILD | HOUSE_SETTING_PLOT_ACCESS_FRIENDS | HOUSE_SETTING_PLOT_ACCESS_PARTY);
|
||||
|
||||
if ((settingsFlags & accessMask) == 0)
|
||||
return true; // No restrictions configured — open to all
|
||||
|
||||
if (settingsFlags & anyoneFlag)
|
||||
return true;
|
||||
|
||||
Player* ownerPlayer = ObjectAccessor::FindPlayer(ownerGuid);
|
||||
|
||||
if (settingsFlags & partyFlag)
|
||||
{
|
||||
// Party requires both online — same Group instance.
|
||||
if (ownerPlayer && visitor->GetGroup() && visitor->GetGroup() == ownerPlayer->GetGroup())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (settingsFlags & guildFlag)
|
||||
{
|
||||
ObjectGuid::LowType ownerGuildId = ownerPlayer
|
||||
? ownerPlayer->GetGuildId()
|
||||
: sCharacterCache->GetCharacterGuildIdByGuid(ownerGuid);
|
||||
if (ownerGuildId != 0 && visitor->GetGuildId() == ownerGuildId)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (settingsFlags & friendsFlag)
|
||||
{
|
||||
// Friends are mutual on retail — visitor's social manager has the same record.
|
||||
if (visitor->GetSocial() && visitor->GetSocial()->HasFriend(ownerGuid))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (settingsFlags & neighborsFlag)
|
||||
{
|
||||
// Both are residents of the same neighborhood. Works offline because
|
||||
// neighborhood membership is stored on Neighborhood objects, not Player.
|
||||
for (Neighborhood const* nbh : sNeighborhoodMgr.GetNeighborhoodsForPlayer(ownerGuid))
|
||||
if (nbh->IsMember(visitor->GetGUID()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HousingMgr::CanVisitorAccess(Player const* visitor, Player const* owner, uint32 settingsFlags, bool isInterior) const
|
||||
{
|
||||
if (!visitor || !owner)
|
||||
|
||||
@@ -370,6 +370,11 @@ public:
|
||||
// accessMask = HOUSE_SETTING_HOUSE_ACCESS_* for interior, HOUSE_SETTING_PLOT_ACCESS_* for exterior
|
||||
bool CanVisitorAccess(Player const* visitor, Player const* owner, uint32 settingsFlags, bool isInterior) const;
|
||||
|
||||
// Same as CanVisitorAccess but works when owner is offline — uses CharacterCache + the
|
||||
// visitor's own social/group/guild/neighborhood data to resolve friend/party/guild/neighbor
|
||||
// relationships symmetrically. Settings come from the persisted plotInfo->HouseSettingsFlags.
|
||||
bool CanVisitorAccessPlot(Player const* visitor, ObjectGuid ownerGuid, uint32 settingsFlags, bool isInterior) const;
|
||||
|
||||
// Validation
|
||||
HousingResult ValidateDecorPlacement(uint32 decorId, Position const& pos, uint32 houseLevel) const;
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ bool Neighborhood::LoadFromDB(PreparedQueryResult neighborhood, PreparedQueryRes
|
||||
{
|
||||
Field* memberFields = members->Fetch();
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8 9
|
||||
// SELECT nm.playerGuid, nm.role, nm.joinTime, nm.plotIndex, ch.houseId, c.account, ch.houseLevel, ch.favor, ch.houseName, ch.houseType
|
||||
// 0 1 2 3 4 5 6 7 8 9 10
|
||||
// SELECT nm.playerGuid, nm.role, nm.joinTime, nm.plotIndex, ch.houseId, c.account, ch.houseLevel, ch.favor, ch.houseName, ch.houseType, ch.settingsFlags
|
||||
// FROM neighborhood_members nm LEFT JOIN character_housing ch ON nm.playerGuid = ch.guid
|
||||
// LEFT JOIN characters c ON nm.playerGuid = c.guid
|
||||
// WHERE nm.neighborhoodGuid = ?
|
||||
@@ -116,6 +116,8 @@ bool Neighborhood::LoadFromDB(PreparedQueryResult neighborhood, PreparedQueryRes
|
||||
_plots[member.PlotIndex].HouseName = memberFields[8].GetString();
|
||||
if (!memberFields[9].IsNull())
|
||||
_plots[member.PlotIndex].HouseType = memberFields[9].GetUInt32();
|
||||
if (!memberFields[10].IsNull())
|
||||
_plots[member.PlotIndex].HouseSettingsFlags = memberFields[10].GetUInt32();
|
||||
|
||||
TC_LOG_INFO("housing", "Neighborhood::LoadFromDB plot[{}] owner={} lvl={} favor={} name='{}' "
|
||||
"(ch.houseLevel.IsNull={} ch.favor.IsNull={} ch.houseName.IsNull={})",
|
||||
@@ -1083,6 +1085,20 @@ void Neighborhood::UpdatePlotHouseInfo(uint8 plotIndex, ObjectGuid houseGuid, Ob
|
||||
plotIndex, houseGuid.ToString(), ownerBnetGuid.ToString(), _name);
|
||||
}
|
||||
|
||||
void Neighborhood::UpdatePlotSettingsFlags(ObjectGuid ownerGuid, uint32 settingsFlags)
|
||||
{
|
||||
for (PlotInfo& plot : _plots)
|
||||
{
|
||||
if (plot.IsOccupied() && plot.OwnerGuid == ownerGuid)
|
||||
{
|
||||
plot.HouseSettingsFlags = settingsFlags;
|
||||
TC_LOG_DEBUG("housing", "Neighborhood::UpdatePlotSettingsFlags: plot {} owner {} settings=0x{:X} in '{}'",
|
||||
plot.PlotIndex, ownerGuid.ToString(), settingsFlags, _name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HousingResult Neighborhood::MoveHouse(ObjectGuid sourcePlotOwner, uint8 newPlotIndex)
|
||||
{
|
||||
if (newPlotIndex >= MAX_NEIGHBORHOOD_PLOTS)
|
||||
|
||||
@@ -85,6 +85,11 @@ public:
|
||||
// the owner is currently online.
|
||||
std::vector<Housing::Room> Rooms;
|
||||
|
||||
// Mirrored from character_housing.settingsFlags so visitor permission
|
||||
// checks (CanVisitorAccess) work when the plot owner is offline.
|
||||
// Refreshed when an online owner mutates their Housing settings.
|
||||
uint32 HouseSettingsFlags = 0;
|
||||
|
||||
bool IsOccupied() const { return PlotIndex != INVALID_PLOT_INDEX; }
|
||||
};
|
||||
|
||||
@@ -154,6 +159,7 @@ public:
|
||||
// Plot management
|
||||
HousingResult PurchasePlot(ObjectGuid playerGuid, uint8 plotIndex);
|
||||
void UpdatePlotHouseInfo(uint8 plotIndex, ObjectGuid houseGuid, ObjectGuid ownerBnetGuid);
|
||||
void UpdatePlotSettingsFlags(ObjectGuid ownerGuid, uint32 settingsFlags);
|
||||
HousingResult MoveHouse(ObjectGuid sourcePlotOwner, uint8 newPlotIndex);
|
||||
void SetPlotAreaTriggerGuid(uint8 plotIndex, ObjectGuid atGuid);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user