diff --git a/sql/updates/characters/master/2026_09_21_03_characters_housing_neighborhood_invite.sql b/sql/updates/characters/master/2026_09_21_03_characters_housing_neighborhood_invite.sql new file mode 100644 index 000000000..726d51747 --- /dev/null +++ b/sql/updates/characters/master/2026_09_21_03_characters_housing_neighborhood_invite.sql @@ -0,0 +1,10 @@ +-- Persist native neighborhood invitations by target character. + +CREATE TABLE IF NOT EXISTS `character_housing_neighborhood_invite` ( + `NeighborhoodGuid` VARCHAR(64) NOT NULL, + `InviteeGuid` BIGINT UNSIGNED NOT NULL, + `InviterGuid` BIGINT UNSIGNED NOT NULL, + `CreatedAt` BIGINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`NeighborhoodGuid`, `InviteeGuid`), + KEY `idx_invitee` (`InviteeGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/sql/updates/characters/master/2026_09_21_04_characters_housing_neighborhood_role.sql b/sql/updates/characters/master/2026_09_21_04_characters_housing_neighborhood_role.sql new file mode 100644 index 000000000..010a65a7a --- /dev/null +++ b/sql/updates/characters/master/2026_09_21_04_characters_housing_neighborhood_role.sql @@ -0,0 +1,11 @@ +-- Persist native neighborhood secondary-owner roles independently from house ownership. + +CREATE TABLE IF NOT EXISTS `character_housing_neighborhood_role` ( + `NeighborhoodGuid` VARCHAR(64) NOT NULL, + `CharacterGuid` BIGINT UNSIGNED NOT NULL, + `Role` TINYINT UNSIGNED NOT NULL DEFAULT 1, + `GrantedBy` BIGINT UNSIGNED NOT NULL, + `GrantedAt` BIGINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`NeighborhoodGuid`, `CharacterGuid`), + KEY `idx_character` (`CharacterGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/sql/updates/characters/master/2026_09_21_05_characters_housing_neighborhood_public.sql b/sql/updates/characters/master/2026_09_21_05_characters_housing_neighborhood_public.sql new file mode 100644 index 000000000..8fd599c43 --- /dev/null +++ b/sql/updates/characters/master/2026_09_21_05_characters_housing_neighborhood_public.sql @@ -0,0 +1,7 @@ +-- Persist the native public/private state used by House Finder. + +ALTER TABLE `housing_neighborhood` + ADD COLUMN `IsPublic` TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER `Name`; + +-- Preserve the pre-migration behavior for neighborhoods already exposed by House Finder. +UPDATE `housing_neighborhood` SET `IsPublic` = 1; diff --git a/sql/updates/characters/master/2026_09_21_06_characters_housing_neighborhood_guild.sql b/sql/updates/characters/master/2026_09_21_06_characters_housing_neighborhood_guild.sql new file mode 100644 index 000000000..3c374cbae --- /dev/null +++ b/sql/updates/characters/master/2026_09_21_06_characters_housing_neighborhood_guild.sql @@ -0,0 +1,5 @@ +-- Persist the native guild ownership used by Housing Services and House Finder. + +ALTER TABLE `housing_neighborhood` + ADD COLUMN `GuildId` INT UNSIGNED NULL DEFAULT NULL AFTER `IsPublic`, + ADD UNIQUE KEY `uk_housing_neighborhood_guild` (`GuildId`); diff --git a/src/server/game/Guilds/Guild.cpp b/src/server/game/Guilds/Guild.cpp index a8a64b29c..bbee89313 100644 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -16,6 +16,7 @@ */ #include "Guild.h" +#include "HousingSessionLifecycle.h" #include "AccountMgr.h" #include "AchievementMgr.h" #include "Bag.h" @@ -1200,6 +1201,7 @@ void Guild::Disband() { // Call scripts before guild data removed from database sScriptMgr->OnGuildDisband(this); + Housing::HandleGuildDisband(GetId(), GetLeaderGUID()); WorldPackets::Guild::GuildEventDisbanded guildEventDisbanded; guildEventDisbanded.Write(); @@ -2988,6 +2990,7 @@ bool Guild::DeleteMember(CharacterDatabaseTransaction trans, ObjectGuid guid, bo } // Call script on remove before member is actually removed from guild (and database) sScriptMgr->OnGuildRemoveMember(this, guid, isDisbanding, isKicked); + Housing::HandleGuildMemberRemoved(GetId(), guid); m_members.erase(guid); diff --git a/src/server/game/Handlers/HousingSessionHandler.cpp b/src/server/game/Handlers/HousingSessionHandler.cpp index 6aa8b2134..2c276c502 100644 --- a/src/server/game/Handlers/HousingSessionHandler.cpp +++ b/src/server/game/Handlers/HousingSessionHandler.cpp @@ -97,6 +97,61 @@ void WorldSession::HandleHousingHouseFinderIgnoreNeighborhood(WorldPackets::Hous Housing::HandleHouseFinderIgnoreNeighborhood(this, packet); } +void WorldSession::HandleInvitePlayerToNeighborhood(WorldPackets::Housing::InvitePlayerToNeighborhood& packet) +{ + Housing::HandleInvitePlayerToNeighborhood(this, packet); +} + +void WorldSession::HandleHousingDeleteAllNeighborhoodInvites(WorldPackets::Housing::HousingDeleteAllNeighborhoodInvites& packet) +{ + Housing::HandleDeleteAllNeighborhoodInvites(this, packet); +} + +void WorldSession::HandleNeighborhoodCancelInvitation(WorldPackets::Housing::NeighborhoodCancelInvitation& packet) +{ + Housing::HandleNeighborhoodCancelInvitation(this, packet); +} + +void WorldSession::HandleHousingSvcsGuildCreateNeighborhood(WorldPackets::Housing::HousingSvcsGuildCreateNeighborhood& packet) +{ + Housing::HandleHousingSvcsGuildCreateNeighborhood(this, packet); +} + +void WorldSession::HandleNeighborhoodSetPublicFlag(WorldPackets::Housing::NeighborhoodSetPublicFlag& packet) +{ + Housing::HandleNeighborhoodSetPublicFlag(this, packet); +} + +void WorldSession::HandleNeighborhoodAddSecondaryOwner(WorldPackets::Housing::NeighborhoodAddSecondaryOwner& packet) +{ + Housing::HandleNeighborhoodAddSecondaryOwner(this, packet); +} + +void WorldSession::HandleNeighborhoodRemoveSecondaryOwner(WorldPackets::Housing::NeighborhoodRemoveSecondaryOwner& packet) +{ + Housing::HandleNeighborhoodRemoveSecondaryOwner(this, packet); +} + +void WorldSession::HandleNeighborhoodGetRoster(WorldPackets::Housing::NeighborhoodGetRoster& packet) +{ + Housing::HandleNeighborhoodGetRoster(this, packet); +} + +void WorldSession::HandleNeighborhoodGetInvites(WorldPackets::Housing::NeighborhoodGetInvites& packet) +{ + Housing::HandleNeighborhoodGetInvites(this, packet); +} + +void WorldSession::HandleNeighborhoodPlayerGetInvite(WorldPackets::Housing::NeighborhoodPlayerGetInvite& packet) +{ + Housing::HandleNeighborhoodPlayerGetInvite(this, packet); +} + +void WorldSession::HandleNeighborhoodPlayerDeclineInvite(WorldPackets::Housing::NeighborhoodPlayerDeclineInvite& packet) +{ + Housing::HandleNeighborhoodPlayerDeclineInvite(this, packet); +} + void WorldSession::HandleHousingGetPlayerHousesInfo(WorldPackets::Housing::GetPlayerHousesInfo& packet) { Housing::HandleGetPlayerHousesInfo(this, packet); diff --git a/src/server/game/Housing/HousingMgr.cpp b/src/server/game/Housing/HousingMgr.cpp index 70accd2d5..66ae3696e 100644 --- a/src/server/game/Housing/HousingMgr.cpp +++ b/src/server/game/Housing/HousingMgr.cpp @@ -434,6 +434,14 @@ namespace Housing owner, houseGuid.ToString(), neighborhoodGuid.ToString(), uint32(oldPlotId), uint32(plotId), plot.ExteriorMapId, purchaseCost, uint32(isMove ? PlotTransactionType::Move : PlotTransactionType::Purchase)); trans->PAppend("DELETE FROM housing_plot_reservation WHERE OwnerGuid = {}", owner); + if (!isMove) + { + // Invitation consumption is part of the same durability boundary as ownership. + // A failed purchase leaves the invitation available for a safe retry. + trans->PAppend( + "DELETE FROM character_housing_neighborhood_invite WHERE NeighborhoodGuid = '{}' AND InviteeGuid = {}", + neighborhoodGuid.ToString(), owner); + } if (purchaseCost) player->SaveInventoryAndGoldToDB(trans); CharacterDatabase.DirectCommitTransaction(trans); diff --git a/src/server/game/Housing/HousingSessionLifecycle.cpp b/src/server/game/Housing/HousingSessionLifecycle.cpp index ec32ebada..7b189f071 100644 --- a/src/server/game/Housing/HousingSessionLifecycle.cpp +++ b/src/server/game/Housing/HousingSessionLifecycle.cpp @@ -5,6 +5,8 @@ #include "HousingSessionLifecycle.h" #include "DatabaseEnv.h" #include "GameTime.h" +#include "Guild.h" +#include "GuildMgr.h" #include "HousingDynamicEntity.h" #include "HousingMgr.h" #include "HousingMapContext.h" @@ -13,6 +15,8 @@ #include "HousingRoomSession.h" #include "Log.h" #include "Map.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" #include "Player.h" #include "WorldSession.h" #include @@ -30,6 +34,99 @@ namespace Housing return static_cast(result); } + bool CanManageNeighborhood(Player const* player, ObjectGuid const& neighborhoodGuid, bool allowSecondaryOwner) + { + if (!player || !IsNeighborhoodGuid(neighborhoodGuid)) + return false; + + QueryResult neighborhood = CharacterDatabase.PQuery( + "SELECT OwnerGuid, GuildId FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1", + neighborhoodGuid.ToString()); + if (!neighborhood) + return false; + + Field* fields = neighborhood->Fetch(); + uint32 const guildId = fields[1].GetUInt32(); + if (guildId == 0 && fields[0].GetUInt64() == player->GetGUID().GetCounter()) + return true; + + if (guildId != 0 && guildId == player->GetGuildId()) + if (Guild* guild = sGuildMgr->GetGuildById(guildId); + guild && guild->GetLeaderGUID() == player->GetGUID()) + return true; + + return allowSecondaryOwner && CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_role WHERE NeighborhoodGuid = '{}' " + "AND CharacterGuid = {} AND Role = 1 LIMIT 1", + neighborhoodGuid.ToString(), player->GetGUID().GetCounter()); + } + + ObjectGuid ResolvePrimaryManagedNeighborhood(Player const* player) + { + if (!player) + return ObjectGuid::Empty; + + HouseState house; + if (sHousingMgr.LoadOwnedHouse(player, ObjectGuid::Empty, house) == Result::Success && + CanManageNeighborhood(player, house.NeighborhoodGuid, false)) + return house.NeighborhoodGuid; + + if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId()); + guild && guild->GetLeaderGUID() == player->GetGUID()) + { + if (QueryResult neighborhood = CharacterDatabase.PQuery( + "SELECT NeighborhoodGuid FROM housing_neighborhood WHERE GuildId = {} LIMIT 1", guild->GetId())) + { + ObjectGuid const guid = ObjectGuid::FromString(neighborhood->Fetch()[0].GetString()); + if (IsNeighborhoodGuid(guid)) + return guid; + } + } + + if (QueryResult neighborhood = CharacterDatabase.PQuery( + "SELECT NeighborhoodGuid FROM housing_neighborhood WHERE OwnerGuid = {} " + "ORDER BY NeighborhoodGuid LIMIT 1", player->GetGUID().GetCounter())) + { + ObjectGuid const guid = ObjectGuid::FromString(neighborhood->Fetch()[0].GetString()); + if (IsNeighborhoodGuid(guid)) + return guid; + } + + return ObjectGuid::Empty; + } + + WorldPackets::Housing::HouseInfo ToPacketHouse(HouseState const& state); + + void BroadcastGuildHouseUpdate(HouseState const& state, bool added) + { + QueryResult neighborhood = CharacterDatabase.PQuery( + "SELECT GuildId FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1", + state.NeighborhoodGuid.ToString()); + if (!neighborhood) + return; + + uint32 const guildId = neighborhood->Fetch()[0].GetUInt32(); + Guild* guild = guildId ? sGuildMgr->GetGuildById(guildId) : nullptr; + if (!guild) + return; + + if (added) + { + WorldPackets::Housing::HousingSvcsGuildAddHouseNotification notification; + notification.House = ToPacketHouse(state); + guild->BroadcastPacket(notification.Write()); + } + else + { + WorldPackets::Housing::HousingSvcsGuildRemoveHouseNotification notification; + notification.House = ToPacketHouse(state); + guild->BroadcastPacket(notification.Write()); + } + + WorldPackets::Housing::HousingHouseFinderForceRefresh refresh; + guild->BroadcastPacket(refresh.Write()); + } + WorldPackets::Housing::HouseInfo ToPacketHouse(HouseState const& state) { WorldPackets::Housing::HouseInfo house; @@ -57,7 +154,7 @@ namespace Housing return false; QueryResult neighborhood = CharacterDatabase.PQuery( - "SELECT OwnerGuid, Name FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1", + "SELECT OwnerGuid, Name, IsPublic, GuildId FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1", neighborhoodGuid.ToString()); if (!neighborhood) return false; @@ -71,7 +168,10 @@ namespace Housing output.OwnerGUID = neighborhoodOwner; output.Name = neighborhoodFields[1].GetString(); - bool authorized = neighborhoodOwner == viewer->GetGUID(); + uint32 const guildId = neighborhoodFields[3].GetUInt32(); + bool authorized = neighborhoodFields[2].GetUInt8() != 0 || + (guildId == 0 && neighborhoodOwner == viewer->GetGUID()) || + (guildId != 0 && guildId == viewer->GetGuildId()); QueryResult houses = CharacterDatabase.PQuery( "SELECT OwnerGuid, HouseGuid, PlotID, HouseSettingFlags FROM character_housing " "WHERE NeighborhoodGuid = '{}' ORDER BY PlotID, HouseGuid LIMIT 64", @@ -265,6 +365,75 @@ namespace Housing } } + void HandleGuildDisband(uint32 guildId, ObjectGuid const& fallbackOwnerGuid) + { + if (guildId == 0 || !fallbackOwnerGuid.IsPlayer()) + return; + + QueryResult neighborhood = CharacterDatabase.PQuery( + "SELECT NeighborhoodGuid FROM housing_neighborhood WHERE GuildId = {} LIMIT 1", guildId); + if (!neighborhood) + return; + + std::string const neighborhoodGuid = neighborhood->Fetch()[0].GetString(); + CharacterDatabase.DirectPExecute( + "DELETE FROM character_housing_neighborhood_role WHERE NeighborhoodGuid = '{}'", + neighborhoodGuid); + CharacterDatabase.DirectPExecute( + "DELETE FROM character_housing_neighborhood_invite WHERE NeighborhoodGuid = '{}'", + neighborhoodGuid); + CharacterDatabase.DirectPExecute( + "UPDATE housing_neighborhood SET GuildId = NULL, OwnerGuid = {}, IsPublic = 0 " + "WHERE NeighborhoodGuid = '{}' AND GuildId = {}", + fallbackOwnerGuid.GetCounter(), neighborhoodGuid, guildId); + + WorldPackets::Housing::HousingHouseFinderForceRefresh refresh; + WorldPacket const* refreshPacket = refresh.Write(); + QueryResult residents = CharacterDatabase.PQuery( + "SELECT DISTINCT OwnerGuid FROM character_housing WHERE NeighborhoodGuid = '{}'", + neighborhoodGuid); + if (residents) + { + do + { + ObjectGuid const residentGuid = ObjectGuid::Create( + residents->Fetch()[0].GetUInt64()); + if (Player* resident = ObjectAccessor::FindConnectedPlayer(residentGuid)) + resident->SendDirectMessage(refreshPacket); + } while (residents->NextRow()); + } + + if (Player* fallbackOwner = ObjectAccessor::FindConnectedPlayer(fallbackOwnerGuid)) + fallbackOwner->SendDirectMessage(refreshPacket); + } + + void HandleGuildMemberRemoved(uint32 guildId, ObjectGuid const& memberGuid) + { + if (guildId == 0 || !memberGuid.IsPlayer()) + return; + + QueryResult neighborhood = CharacterDatabase.PQuery( + "SELECT NeighborhoodGuid FROM housing_neighborhood WHERE GuildId = {} LIMIT 1", guildId); + if (!neighborhood) + return; + + std::string const neighborhoodGuid = neighborhood->Fetch()[0].GetString(); + CharacterDatabase.DirectPExecute( + "DELETE FROM character_housing_neighborhood_role " + "WHERE NeighborhoodGuid = '{}' AND CharacterGuid = {}", + neighborhoodGuid, memberGuid.GetCounter()); + CharacterDatabase.DirectPExecute( + "DELETE FROM character_housing_neighborhood_invite " + "WHERE NeighborhoodGuid = '{}' AND (InviteeGuid = {} OR InviterGuid = {})", + neighborhoodGuid, memberGuid.GetCounter(), memberGuid.GetCounter()); + + if (Player* player = ObjectAccessor::FindConnectedPlayer(memberGuid)) + { + WorldPackets::Housing::HousingHouseFinderForceRefresh refresh; + player->SendDirectMessage(refresh.Write()); + } + } + void HandleGetCurrentHouseInfo(WorldSession* session, WorldPackets::Housing::GetCurrentHouseInfo& /*packet*/) { if (!session) @@ -368,10 +537,7 @@ namespace Housing return; } - QueryResult neighborhood = CharacterDatabase.PQuery( - "SELECT OwnerGuid FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1", - packet.NeighborhoodGUID.ToString()); - if (!neighborhood || neighborhood->Fetch()[0].GetUInt64() != player->GetGUID().GetCounter()) + if (!CanManageNeighborhood(player, packet.NeighborhoodGUID, true)) { session->SendPacket(response.Write()); return; @@ -399,6 +565,527 @@ namespace Housing session->SendPacket(response.Write()); } + void HandleInvitePlayerToNeighborhood(WorldSession* session, + WorldPackets::Housing::InvitePlayerToNeighborhood& packet) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodInviteNameLookupResult lookup; + WorldPackets::Housing::NeighborhoodInviteResidentResponse response; + Player* player = session->GetPlayer(); + if (!player || packet.PlayerName.empty() || packet.PlayerName.size() > 63) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + HouseState house; + if (sHousingMgr.LoadOwnedHouse(player, ObjectGuid::Empty, house) != Result::Success) + { + response.Result = ToPacketResult(Result::HouseNotFound); + session->SendPacket(response.Write()); + return; + } + + if (!CanManageNeighborhood(player, house.NeighborhoodGuid, true)) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + std::string escapedName = packet.PlayerName; + CharacterDatabase.EscapeString(escapedName); + QueryResult target = CharacterDatabase.PQuery( + "SELECT guid FROM characters WHERE name = '{}' LIMIT 1", escapedName); + if (!target) + { + lookup.Result = ToPacketResult(Result::HouseNotFound); + session->SendPacket(lookup.Write()); + response.Result = lookup.Result; + session->SendPacket(response.Write()); + return; + } + + ObjectGuid const inviteeGuid = ObjectGuid::Create(target->Fetch()[0].GetUInt64()); + lookup.PlayerGUID = inviteeGuid; + lookup.Result = ToPacketResult(Result::Success); + session->SendPacket(lookup.Write()); + + response.InviteeGUID = inviteeGuid; + if (inviteeGuid == player->GetGUID()) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + CharacterDatabase.DirectPExecute( + "INSERT INTO character_housing_neighborhood_invite " + "(NeighborhoodGuid, InviteeGuid, InviterGuid, CreatedAt) VALUES ('{}', {}, {}, {}) " + "ON DUPLICATE KEY UPDATE InviterGuid = VALUES(InviterGuid), CreatedAt = VALUES(CreatedAt)", + house.NeighborhoodGuid.ToString(), inviteeGuid.GetCounter(), player->GetGUID().GetCounter(), + uint64(GameTime::GetGameTime())); + + response.Result = CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_invite WHERE NeighborhoodGuid = '{}' " + "AND InviteeGuid = {} LIMIT 1", house.NeighborhoodGuid.ToString(), inviteeGuid.GetCounter()) + ? ToPacketResult(Result::Success) : ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + + if (response.Result == ToPacketResult(Result::Success)) + { + if (Player* invitee = ObjectAccessor::FindConnectedPlayer(inviteeGuid)) + { + WorldPackets::Housing::NeighborhoodInviteNotification notification; + notification.NeighborhoodGUID = house.NeighborhoodGuid; + invitee->SendDirectMessage(notification.Write()); + } + } + } + + void HandleHousingSvcsGuildCreateNeighborhood(WorldSession* session, + WorldPackets::Housing::HousingSvcsGuildCreateNeighborhood& packet) + { + Player* player = session ? session->GetPlayer() : nullptr; + if (!player) + return; + + WorldPackets::Housing::HousingSvcsCreateCharterNeighborhoodResponse response; + response.Result = ToPacketResult(Result::PermissionDenied); + + Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId()); + if (!guild || guild->GetLeaderGUID() != player->GetGUID() || + !ObjectMgr::IsValidCharterName(packet.NeighborhoodName)) + { + session->SendPacket(response.Write()); + return; + } + + uint32 const guildId = guild->GetId(); + if (CharacterDatabase.PQuery("SELECT 1 FROM housing_neighborhood WHERE GuildId = {} LIMIT 1", guildId)) + { + session->SendPacket(response.Write()); + return; + } + + ObjectGuid const neighborhoodGuid = ObjectGuid::Create( + 4, packet.NeighborhoodTypeID, guildId, guildId); + if (!IsNeighborhoodGuid(neighborhoodGuid)) + { + session->SendPacket(response.Write()); + return; + } + + std::string escapedName = packet.NeighborhoodName; + CharacterDatabase.EscapeString(escapedName); + CharacterDatabase.DirectPExecute( + "INSERT IGNORE INTO housing_neighborhood " + "(NeighborhoodGuid, OwnerGuid, Name, Type, Flags, IsPublic, GuildId) " + "VALUES ('{}', {}, '{}', {}, {}, 0, {})", + neighborhoodGuid.ToString(), player->GetGUID().GetCounter(), escapedName, + packet.NeighborhoodTypeID, packet.SecondaryID, guildId); + + QueryResult persisted = CharacterDatabase.PQuery( + "SELECT NeighborhoodGuid FROM housing_neighborhood WHERE GuildId = {} LIMIT 1", guildId); + if (!persisted || ObjectGuid::FromString(persisted->Fetch()[0].GetString()) != neighborhoodGuid) + { + session->SendPacket(response.Write()); + return; + } + + response.Result = ToPacketResult(Result::Success); + response.Neighborhood.NeighborhoodGUID = neighborhoodGuid; + response.Neighborhood.OwnerGUID = player->GetGUID(); + response.Neighborhood.Name = packet.NeighborhoodName; + session->SendPacket(response.Write()); + + WorldPackets::Housing::HousingSvcsGuildCreateNeighborhoodNotification notification; + notification.NeighborhoodGUID = neighborhoodGuid; + notification.Name = packet.NeighborhoodName; + guild->BroadcastPacket(notification.Write()); + + WorldPackets::Housing::HousingHouseFinderForceRefresh refresh; + guild->BroadcastPacket(refresh.Write()); + } + + void HandleNeighborhoodSetPublicFlag(WorldSession* session, + WorldPackets::Housing::NeighborhoodSetPublicFlag& packet) + { + if (!session) + return; + + Player* player = session->GetPlayer(); + if (!player || !IsNeighborhoodGuid(packet.NeighborhoodGUID)) + return; + + if (!CanManageNeighborhood(player, packet.NeighborhoodGUID, false)) + return; + + CharacterDatabase.DirectPExecute( + "UPDATE housing_neighborhood SET IsPublic = {} WHERE NeighborhoodGuid = '{}'", + packet.IsPublic ? 1 : 0, packet.NeighborhoodGUID.ToString()); + QueryResult persisted = CharacterDatabase.PQuery( + "SELECT IsPublic FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1", + packet.NeighborhoodGUID.ToString()); + if (!persisted || (persisted->Fetch()[0].GetUInt8() != 0) != packet.IsPublic) + return; + + WorldPackets::Housing::HousingHouseFinderForceRefresh refresh; + session->SendPacket(refresh.Write()); + } + + void HandleNeighborhoodAddSecondaryOwner(WorldSession* session, + WorldPackets::Housing::NeighborhoodAddSecondaryOwner& packet) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodSecondaryOwnerResponse response( + SMSG_NEIGHBORHOOD_ADD_SECONDARY_OWNER_RESPONSE); + response.PlayerGUID = packet.PlayerGUID; + Player* player = session->GetPlayer(); + if (!player || !packet.PlayerGUID.IsPlayer() || packet.PlayerGUID == player->GetGUID()) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + ObjectGuid const neighborhoodGuid = ResolvePrimaryManagedNeighborhood(player); + if (!IsNeighborhoodGuid(neighborhoodGuid)) + { + response.Result = ToPacketResult(Result::HouseNotFound); + session->SendPacket(response.Write()); + return; + } + if (!CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing WHERE NeighborhoodGuid = '{}' AND OwnerGuid = {} LIMIT 1", + neighborhoodGuid.ToString(), packet.PlayerGUID.GetCounter())) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + CharacterDatabase.DirectPExecute( + "INSERT INTO character_housing_neighborhood_role " + "(NeighborhoodGuid, CharacterGuid, Role, GrantedBy, GrantedAt) VALUES ('{}', {}, 1, {}, {}) " + "ON DUPLICATE KEY UPDATE Role = 1, GrantedBy = VALUES(GrantedBy), GrantedAt = VALUES(GrantedAt)", + neighborhoodGuid.ToString(), packet.PlayerGUID.GetCounter(), player->GetGUID().GetCounter(), + uint64(GameTime::GetGameTime())); + response.Result = CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_role WHERE NeighborhoodGuid = '{}' " + "AND CharacterGuid = {} AND Role = 1 LIMIT 1", neighborhoodGuid.ToString(), packet.PlayerGUID.GetCounter()) + ? ToPacketResult(Result::Success) : ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + if (response.Result == ToPacketResult(Result::Success)) + { + WorldPackets::Housing::NeighborhoodRosterResidentUpdate update; + update.Residents.push_back({ packet.PlayerGUID, 1, true }); + WorldPacket const* updatePacket = update.Write(); + session->SendPacket(updatePacket); + if (Player* target = ObjectAccessor::FindConnectedPlayer(packet.PlayerGUID); target && target != player) + target->SendDirectMessage(updatePacket); + } + } + + void HandleNeighborhoodRemoveSecondaryOwner(WorldSession* session, + WorldPackets::Housing::NeighborhoodRemoveSecondaryOwner& packet) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodSecondaryOwnerResponse response( + SMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER_RESPONSE); + response.PlayerGUID = packet.PlayerGUID; + Player* player = session->GetPlayer(); + if (!player || !packet.PlayerGUID.IsPlayer()) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + ObjectGuid const neighborhoodGuid = ResolvePrimaryManagedNeighborhood(player); + if (!IsNeighborhoodGuid(neighborhoodGuid)) + { + response.Result = ToPacketResult(Result::HouseNotFound); + session->SendPacket(response.Write()); + return; + } + CharacterDatabase.DirectPExecute( + "DELETE FROM character_housing_neighborhood_role WHERE NeighborhoodGuid = '{}' AND CharacterGuid = {}", + neighborhoodGuid.ToString(), packet.PlayerGUID.GetCounter()); + response.Result = CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_role WHERE NeighborhoodGuid = '{}' " + "AND CharacterGuid = {} LIMIT 1", neighborhoodGuid.ToString(), packet.PlayerGUID.GetCounter()) + ? ToPacketResult(Result::PermissionDenied) : ToPacketResult(Result::Success); + session->SendPacket(response.Write()); + if (response.Result == ToPacketResult(Result::Success)) + { + WorldPackets::Housing::NeighborhoodRosterResidentUpdate update; + update.Residents.push_back({ packet.PlayerGUID, 1, false }); + WorldPacket const* updatePacket = update.Write(); + session->SendPacket(updatePacket); + if (Player* target = ObjectAccessor::FindConnectedPlayer(packet.PlayerGUID); target && target != player) + target->SendDirectMessage(updatePacket); + } + } + + void HandleNeighborhoodGetRoster(WorldSession* session, + WorldPackets::Housing::NeighborhoodGetRoster& packet) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodGetRosterResponse response; + response.NeighborhoodGUID = packet.NeighborhoodGUID; + Player* player = session->GetPlayer(); + if (!player || !IsNeighborhoodGuid(packet.NeighborhoodGUID)) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + QueryResult neighborhood = CharacterDatabase.PQuery( + "SELECT OwnerGuid, Name, GuildId FROM housing_neighborhood WHERE NeighborhoodGuid = '{}' LIMIT 1", + packet.NeighborhoodGUID.ToString()); + if (!neighborhood) + { + response.Result = ToPacketResult(Result::HouseNotFound); + session->SendPacket(response.Write()); + return; + } + + Field* neighborhoodFields = neighborhood->Fetch(); + response.OwnerGUID = ObjectGuid::Create(neighborhoodFields[0].GetUInt64()); + response.NeighborhoodName = neighborhoodFields[1].GetString(); + uint32 const guildId = neighborhoodFields[2].GetUInt32(); + bool const isResident = response.OwnerGUID == player->GetGUID() || + (guildId != 0 && guildId == player->GetGuildId()) || CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing WHERE NeighborhoodGuid = '{}' AND OwnerGuid = {} LIMIT 1", + packet.NeighborhoodGUID.ToString(), player->GetGUID().GetCounter()) || CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_role WHERE NeighborhoodGuid = '{}' " + "AND CharacterGuid = {} AND Role = 1 LIMIT 1", + packet.NeighborhoodGUID.ToString(), player->GetGUID().GetCounter()); + if (!response.OwnerGUID.IsPlayer() || !isResident) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + QueryResult members = CharacterDatabase.PQuery( + "SELECT OwnerGuid, HouseGuid, PlotID FROM character_housing " + "WHERE NeighborhoodGuid = '{}' ORDER BY PlotID, OwnerGuid LIMIT 64", + packet.NeighborhoodGUID.ToString()); + if (members) + { + do + { + Field* fields = members->Fetch(); + WorldPackets::Housing::NeighborhoodGetRosterResponse::Member member; + member.PlayerGUID = ObjectGuid::Create(fields[0].GetUInt64()); + member.HouseGUID = ObjectGuid::FromString(fields[1].GetString()); + member.PlotIndex = fields[2].GetUInt8(); + if (!member.PlayerGUID.IsPlayer() || !IsHouseGuid(member.HouseGUID)) + continue; + + if (Player* onlineMember = ObjectAccessor::FindConnectedPlayer(member.PlayerGUID)) + { + member.IsOnline = true; + member.BnetAccountGUID = onlineMember->GetSession()->GetBattlenetAccountGUID(); + } + response.Members.push_back(std::move(member)); + } while (members->NextRow()); + } + + response.Result = ToPacketResult(Result::Success); + session->SendPacket(response.Write()); + } + + void HandleDeleteAllNeighborhoodInvites(WorldSession* session, + WorldPackets::Housing::HousingDeleteAllNeighborhoodInvites& /*packet*/) + { + if (!session) + return; + + WorldPackets::Housing::HousingDeleteAllNeighborhoodInvitesResponse response; + Player* player = session->GetPlayer(); + if (!player) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + uint64 const playerGuid = player->GetGUID().GetCounter(); + CharacterDatabase.DirectPExecute( + "DELETE FROM character_housing_neighborhood_invite WHERE InviteeGuid = {}", playerGuid); + response.Result = CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_invite WHERE InviteeGuid = {} LIMIT 1", playerGuid) + ? ToPacketResult(Result::PermissionDenied) : ToPacketResult(Result::Success); + session->SendPacket(response.Write()); + } + + void HandleNeighborhoodCancelInvitation(WorldSession* session, + WorldPackets::Housing::NeighborhoodCancelInvitation& packet) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodCancelInvitationResponse response; + response.InviteeGUID = packet.InviteeGUID; + Player* player = session->GetPlayer(); + if (!player || !packet.InviteeGUID.IsPlayer() || packet.InviteeGUID == player->GetGUID()) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + uint64 const ownerGuid = player->GetGUID().GetCounter(); + uint64 const inviteeGuid = packet.InviteeGUID.GetCounter(); + uint32 managedGuildId = 0; + if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId()); + guild && guild->GetLeaderGUID() == player->GetGUID()) + managedGuildId = guild->GetId(); + QueryResult ownedInvite = CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_invite i " + "INNER JOIN housing_neighborhood n ON n.NeighborhoodGuid = i.NeighborhoodGuid " + "WHERE (n.OwnerGuid = {} OR n.GuildId = {} OR EXISTS (SELECT 1 FROM character_housing_neighborhood_role r " + "WHERE r.NeighborhoodGuid = i.NeighborhoodGuid AND r.CharacterGuid = {} AND r.Role = 1)) " + "AND i.InviteeGuid = {} LIMIT 1", ownerGuid, managedGuildId, ownerGuid, inviteeGuid); + if (!ownedInvite) + { + response.Result = ToPacketResult(Result::HouseNotFound); + session->SendPacket(response.Write()); + return; + } + + CharacterDatabase.DirectPExecute( + "DELETE i FROM character_housing_neighborhood_invite i " + "INNER JOIN housing_neighborhood n ON n.NeighborhoodGuid = i.NeighborhoodGuid " + "WHERE (n.OwnerGuid = {} OR n.GuildId = {} OR EXISTS (SELECT 1 FROM character_housing_neighborhood_role r " + "WHERE r.NeighborhoodGuid = i.NeighborhoodGuid AND r.CharacterGuid = {} AND r.Role = 1)) " + "AND i.InviteeGuid = {}", ownerGuid, managedGuildId, ownerGuid, inviteeGuid); + response.Result = CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_invite i " + "INNER JOIN housing_neighborhood n ON n.NeighborhoodGuid = i.NeighborhoodGuid " + "WHERE (n.OwnerGuid = {} OR n.GuildId = {} OR EXISTS (SELECT 1 FROM character_housing_neighborhood_role r " + "WHERE r.NeighborhoodGuid = i.NeighborhoodGuid AND r.CharacterGuid = {} AND r.Role = 1)) " + "AND i.InviteeGuid = {} LIMIT 1", ownerGuid, managedGuildId, ownerGuid, inviteeGuid) + ? ToPacketResult(Result::PermissionDenied) : ToPacketResult(Result::Success); + session->SendPacket(response.Write()); + } + + void HandleNeighborhoodGetInvites(WorldSession* session, + WorldPackets::Housing::NeighborhoodGetInvites& /*packet*/) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodGetInvitesResponse response; + Player* player = session->GetPlayer(); + if (!player) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + QueryResult invites = CharacterDatabase.PQuery( + "SELECT i.InviterGuid, i.CreatedAt, COALESCE((SELECT ch.HouseGuid FROM character_housing ch " + "WHERE ch.OwnerGuid = i.InviterGuid AND ch.NeighborhoodGuid = i.NeighborhoodGuid " + "ORDER BY ch.HouseGuid LIMIT 1), '') FROM character_housing_neighborhood_invite i " + "WHERE i.InviteeGuid = {} ORDER BY i.CreatedAt DESC LIMIT 64", + player->GetGUID().GetCounter()); + if (invites) + { + do + { + Field* fields = invites->Fetch(); + WorldPackets::Housing::NeighborhoodInviteEntry invite; + invite.PlayerGUID = ObjectGuid::Create(fields[0].GetUInt64()); + invite.Timestamp = fields[1].GetUInt64(); + invite.HouseGUID = ObjectGuid::FromString(fields[2].GetString()); + if (invite.PlayerGUID.IsPlayer()) + response.Invites.push_back(std::move(invite)); + } while (invites->NextRow()); + } + + response.Result = ToPacketResult(Result::Success); + session->SendPacket(response.Write()); + } + + void HandleNeighborhoodPlayerGetInvite(WorldSession* session, + WorldPackets::Housing::NeighborhoodPlayerGetInvite& /*packet*/) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodPlayerGetInviteResponse response; + Player* player = session->GetPlayer(); + if (!player) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + QueryResult invite = CharacterDatabase.PQuery( + "SELECT i.InviterGuid, i.CreatedAt, COALESCE((SELECT ch.HouseGuid FROM character_housing ch " + "WHERE ch.OwnerGuid = i.InviterGuid AND ch.NeighborhoodGuid = i.NeighborhoodGuid " + "ORDER BY ch.HouseGuid LIMIT 1), '') FROM character_housing_neighborhood_invite i " + "WHERE i.InviteeGuid = {} ORDER BY i.CreatedAt DESC LIMIT 1", + player->GetGUID().GetCounter()); + if (!invite) + { + response.Result = ToPacketResult(Result::HouseNotFound); + session->SendPacket(response.Write()); + return; + } + + Field* fields = invite->Fetch(); + response.Invite.PlayerGUID = ObjectGuid::Create(fields[0].GetUInt64()); + response.Invite.Timestamp = fields[1].GetUInt64(); + response.Invite.HouseGUID = ObjectGuid::FromString(fields[2].GetString()); + response.Result = response.Invite.PlayerGUID.IsPlayer() + ? ToPacketResult(Result::Success) : ToPacketResult(Result::HouseNotFound); + session->SendPacket(response.Write()); + } + + void HandleNeighborhoodPlayerDeclineInvite(WorldSession* session, + WorldPackets::Housing::NeighborhoodPlayerDeclineInvite& packet) + { + if (!session) + return; + + WorldPackets::Housing::NeighborhoodDeclineInvitationResponse response; + response.NeighborhoodGUID = packet.NeighborhoodGUID; + Player* player = session->GetPlayer(); + if (!player || !IsNeighborhoodGuid(packet.NeighborhoodGUID)) + { + response.Result = ToPacketResult(Result::PermissionDenied); + session->SendPacket(response.Write()); + return; + } + + CharacterDatabase.DirectPExecute( + "DELETE FROM character_housing_neighborhood_invite WHERE NeighborhoodGuid = '{}' AND InviteeGuid = {}", + packet.NeighborhoodGUID.ToString(), player->GetGUID().GetCounter()); + + response.Result = CharacterDatabase.PQuery( + "SELECT 1 FROM character_housing_neighborhood_invite WHERE NeighborhoodGuid = '{}' " + "AND InviteeGuid = {} LIMIT 1", packet.NeighborhoodGUID.ToString(), player->GetGUID().GetCounter()) + ? ToPacketResult(Result::PermissionDenied) : ToPacketResult(Result::Success); + session->SendPacket(response.Write()); + } + void HandleGetPlayerHousesInfo(WorldSession* session, WorldPackets::Housing::GetPlayerHousesInfo& /*packet*/) { if (!session) @@ -459,10 +1146,14 @@ namespace Housing { response.House = ToPacketHouse(state); if (moved && hadHouse) + { SynchronizeExteriorMove(player, previousState, state); + BroadcastGuildHouseUpdate(previousState, false); + } else SynchronizeExteriorPlot(player, state, true); SendHouseEntityGraph(session, player, state); + BroadcastGuildHouseUpdate(state, true); } session->SendPacket(response.Write()); @@ -508,6 +1199,8 @@ namespace Housing 6, state.ExteriorMapId, uint32(state.PlotId), player->GetGUID().GetCounter()); SynchronizeExteriorMove(player, previousState, state); SendHouseEntityGraph(session, player, state); + BroadcastGuildHouseUpdate(previousState, false); + BroadcastGuildHouseUpdate(state, true); } session->SendPacket(response.Write()); @@ -524,7 +1217,10 @@ namespace Housing if (Player* player = session->GetPlayer()) { QueryResult neighborhoods = CharacterDatabase.PQuery( - "SELECT NeighborhoodGuid FROM housing_neighborhood ORDER BY NeighborhoodGuid LIMIT 129"); + "SELECT NeighborhoodGuid FROM housing_neighborhood " + "WHERE IsPublic = 1 OR OwnerGuid = {} OR GuildId = {} " + "ORDER BY NeighborhoodGuid LIMIT 129", + player->GetGUID().GetCounter(), player->GetGuildId()); if (neighborhoods) { do @@ -677,6 +1373,7 @@ namespace Housing // before the transaction may be used to remove live entities safely. RemoveLiveHouseEntities(player, previousState, roomGuids, dynamicEntityGuids); SynchronizeExteriorPlot(player, previousState, false); + BroadcastGuildHouseUpdate(previousState, false); } session->SendPacket(response.Write()); diff --git a/src/server/game/Housing/HousingSessionLifecycle.h b/src/server/game/Housing/HousingSessionLifecycle.h index b95892bb4..a9c9d161c 100644 --- a/src/server/game/Housing/HousingSessionLifecycle.h +++ b/src/server/game/Housing/HousingSessionLifecycle.h @@ -7,6 +7,7 @@ #include "Define.h" +class ObjectGuid; class WorldSession; namespace WorldPackets::Housing @@ -16,6 +17,16 @@ namespace WorldPackets::Housing class HouseStatus; class PlayerViewHousesByPlayer; class HousingGetPotentialHouseOwners; + class InvitePlayerToNeighborhood; + class HousingDeleteAllNeighborhoodInvites; + class NeighborhoodCancelInvitation; + class NeighborhoodSetPublicFlag; + class NeighborhoodAddSecondaryOwner; + class NeighborhoodRemoveSecondaryOwner; + class NeighborhoodGetRoster; + class NeighborhoodGetInvites; + class NeighborhoodPlayerGetInvite; + class NeighborhoodPlayerDeclineInvite; class GetPlayerHousesInfo; class NeighborhoodBuyHouse; class NeighborhoodMoveHouse; @@ -33,6 +44,9 @@ namespace Housing // Client-facing adapters for the Housing implementation. Persistence // and ownership validation stay in Housing::Manager; this layer owns only wire // responses plus safe synchronization of live map entities. + TC_GAME_API void HandleGuildDisband(uint32 guildId, ObjectGuid const& fallbackOwnerGuid); + TC_GAME_API void HandleGuildMemberRemoved(uint32 guildId, ObjectGuid const& memberGuid); + TC_GAME_API void HandleGetCurrentHouseInfo(WorldSession* session, WorldPackets::Housing::GetCurrentHouseInfo& packet); TC_GAME_API void HandleGetPlayerPermissions(WorldSession* session, @@ -43,6 +57,28 @@ namespace Housing WorldPackets::Housing::PlayerViewHousesByPlayer& packet); TC_GAME_API void HandleGetPotentialHouseOwners(WorldSession* session, WorldPackets::Housing::HousingGetPotentialHouseOwners& packet); + TC_GAME_API void HandleInvitePlayerToNeighborhood(WorldSession* session, + WorldPackets::Housing::InvitePlayerToNeighborhood& packet); + TC_GAME_API void HandleDeleteAllNeighborhoodInvites(WorldSession* session, + WorldPackets::Housing::HousingDeleteAllNeighborhoodInvites& packet); + TC_GAME_API void HandleNeighborhoodCancelInvitation(WorldSession* session, + WorldPackets::Housing::NeighborhoodCancelInvitation& packet); + TC_GAME_API void HandleHousingSvcsGuildCreateNeighborhood(WorldSession* session, + WorldPackets::Housing::HousingSvcsGuildCreateNeighborhood& packet); + TC_GAME_API void HandleNeighborhoodSetPublicFlag(WorldSession* session, + WorldPackets::Housing::NeighborhoodSetPublicFlag& packet); + TC_GAME_API void HandleNeighborhoodAddSecondaryOwner(WorldSession* session, + WorldPackets::Housing::NeighborhoodAddSecondaryOwner& packet); + TC_GAME_API void HandleNeighborhoodRemoveSecondaryOwner(WorldSession* session, + WorldPackets::Housing::NeighborhoodRemoveSecondaryOwner& packet); + TC_GAME_API void HandleNeighborhoodGetRoster(WorldSession* session, + WorldPackets::Housing::NeighborhoodGetRoster& packet); + TC_GAME_API void HandleNeighborhoodGetInvites(WorldSession* session, + WorldPackets::Housing::NeighborhoodGetInvites& packet); + TC_GAME_API void HandleNeighborhoodPlayerGetInvite(WorldSession* session, + WorldPackets::Housing::NeighborhoodPlayerGetInvite& packet); + TC_GAME_API void HandleNeighborhoodPlayerDeclineInvite(WorldSession* session, + WorldPackets::Housing::NeighborhoodPlayerDeclineInvite& packet); TC_GAME_API void HandleGetPlayerHousesInfo(WorldSession* session, WorldPackets::Housing::GetPlayerHousesInfo& packet); TC_GAME_API void HandleBuyHouse(WorldSession* session, diff --git a/src/server/game/Server/Packets/HousingPackets.cpp b/src/server/game/Server/Packets/HousingPackets.cpp index 782d31071..0ce2e6168 100644 --- a/src/server/game/Server/Packets/HousingPackets.cpp +++ b/src/server/game/Server/Packets/HousingPackets.cpp @@ -121,6 +121,174 @@ namespace WorldPackets::Housing return &_worldPacket; } + void InvitePlayerToNeighborhood::Read() + { + _worldPacket >> SizedString::BitsSize<6>(PlayerName); + _worldPacket >> SizedString::Data(PlayerName); + } + + WorldPacket const* NeighborhoodInviteNameLookupResult::Write() + { + _worldPacket << uint8(Result); + _worldPacket << PlayerGUID; + return &_worldPacket; + } + + WorldPacket const* NeighborhoodInviteResidentResponse::Write() + { + _worldPacket << uint8(Result); + _worldPacket << InviteeGUID; + return &_worldPacket; + } + + WorldPacket const* HousingDeleteAllNeighborhoodInvitesResponse::Write() + { + _worldPacket << uint8(Result); + return &_worldPacket; + } + + WorldPacket const* NeighborhoodCancelInvitationResponse::Write() + { + _worldPacket << uint8(Result); + _worldPacket << InviteeGUID; + return &_worldPacket; + } + + namespace + { + void WriteNeighborhoodInviteEntry(WorldPacket& packet, NeighborhoodInviteEntry const& invite) + { + packet << uint64(invite.Timestamp); + packet << invite.PlayerGUID; + packet << invite.HouseGUID; + packet << uint64(invite.ExtraData); + } + } + + void HousingSvcsGuildCreateNeighborhood::Read() + { + _worldPacket >> NeighborhoodTypeID; + _worldPacket >> SecondaryID; + _worldPacket >> SizedCString::BitsSize<8>(NeighborhoodName); + _worldPacket >> SizedCString::Data(NeighborhoodName); + } + + WorldPacket const* HousingSvcsGuildCreateNeighborhoodNotification::Write() + { + _worldPacket << NeighborhoodGUID; + _worldPacket << uint8(Flag); + uint8 const nameLength = static_cast(std::min(Name.size() + 1, 255)); + _worldPacket << nameLength; + if (nameLength) + _worldPacket.append(Name.c_str(), nameLength); + return &_worldPacket; + } + + WorldPacket const* HousingSvcsGuildAddHouseNotification::Write() + { + WriteHouse(_worldPacket, House); + return &_worldPacket; + } + + WorldPacket const* HousingSvcsGuildRemoveHouseNotification::Write() + { + WriteHouse(_worldPacket, House); + return &_worldPacket; + } + + void NeighborhoodSetPublicFlag::Read() + { + _worldPacket >> NeighborhoodGUID; + _worldPacket >> Bits<1>(IsPublic); + } + + WorldPacket const* NeighborhoodSecondaryOwnerResponse::Write() + { + _worldPacket << PlayerGUID; + _worldPacket << uint8(Result); + return &_worldPacket; + } + + WorldPacket const* NeighborhoodRosterResidentUpdate::Write() + { + _worldPacket << uint32(Residents.size()); + for (Resident const& resident : Residents) + { + _worldPacket << resident.PlayerGUID; + _worldPacket << uint8(resident.UpdateType); + _worldPacket << uint8(resident.IsPrivileged ? 0x80 : 0x00); + } + return &_worldPacket; + } + + WorldPacket const* NeighborhoodGetRosterResponse::Write() + { + _worldPacket << uint8(Result); + _worldPacket << uint32(Members.size()); + _worldPacket << uint32(1); + _worldPacket << NeighborhoodGUID; + _worldPacket << OwnerGUID; + _worldPacket << uint64(0); + _worldPacket << uint64(0); + _worldPacket << uint32(Members.size()); + + uint8 const nameLength = NeighborhoodName.empty() + ? 0 : uint8(std::min(NeighborhoodName.size() + 1, 255)); + _worldPacket << uint8(nameLength); + _worldPacket << uint8(0); + + for (Member const& member : Members) + { + _worldPacket << member.HouseGUID; + _worldPacket << member.PlayerGUID; + _worldPacket << member.BnetAccountGUID; + _worldPacket << uint8(member.PlotIndex); + _worldPacket << uint32(member.JoinTime); + _worldPacket << uint8(0); + } + + if (nameLength > 1) + _worldPacket.append(NeighborhoodName.c_str(), nameLength); + + _worldPacket << uint8(0); + for (Member const& member : Members) + { + _worldPacket << member.PlayerGUID; + _worldPacket << uint8(0); + _worldPacket << uint8(member.IsOnline ? 0x80 : 0x00); + } + return &_worldPacket; + } + + WorldPacket const* NeighborhoodGetInvitesResponse::Write() + { + _worldPacket << uint8(Result); + _worldPacket << uint32(Invites.size()); + for (NeighborhoodInviteEntry const& invite : Invites) + WriteNeighborhoodInviteEntry(_worldPacket, invite); + return &_worldPacket; + } + + WorldPacket const* NeighborhoodPlayerGetInviteResponse::Write() + { + _worldPacket << uint8(Result); + WriteNeighborhoodInviteEntry(_worldPacket, Invite); + return &_worldPacket; + } + + WorldPacket const* NeighborhoodDeclineInvitationResponse::Write() + { + _worldPacket << uint8(Result); + _worldPacket << NeighborhoodGUID; + return &_worldPacket; + } + + WorldPacket const* NeighborhoodInviteNotification::Write() + { + _worldPacket << NeighborhoodGUID; + return &_worldPacket; + } + WorldPacket const* GetPlayerHousesInfoResponse::Write() { _worldPacket << uint32(PlayerHouses.size()); @@ -223,6 +391,13 @@ namespace WorldPackets::Housing } } + WorldPacket const* HousingSvcsCreateCharterNeighborhoodResponse::Write() + { + WriteHouseFinderNeighborhood(_worldPacket, Neighborhood); + _worldPacket << uint8(Result); + return &_worldPacket; + } + WorldPacket const* HousingGetHouseFinderInfoResponse::Write() { // Retail 12.1: one result bit, a raw uint32 count, then full finder records. diff --git a/src/server/game/Server/Packets/HousingPackets.h b/src/server/game/Server/Packets/HousingPackets.h index b7d0ed802..41cdc7041 100644 --- a/src/server/game/Server/Packets/HousingPackets.h +++ b/src/server/game/Server/Packets/HousingPackets.h @@ -166,6 +166,254 @@ namespace WorldPackets::Housing std::vector PotentialOwners; }; + class InvitePlayerToNeighborhood final : public ClientPacket + { + public: + explicit InvitePlayerToNeighborhood(WorldPacket&& packet) : ClientPacket(CMSG_INVITE_PLAYER_TO_NEIGHBORHOOD, std::move(packet)) {} + void Read() override; + + std::string PlayerName; + }; + + class NeighborhoodInviteNameLookupResult final : public ServerPacket + { + public: + NeighborhoodInviteNameLookupResult() : ServerPacket(SMSG_NEIGHBORHOOD_INVITE_NAME_LOOKUP_RESULT, 24) {} + WorldPacket const* Write() override; + + ObjectGuid PlayerGUID; + uint8 Result = 0; + }; + + class NeighborhoodInviteResidentResponse final : public ServerPacket + { + public: + NeighborhoodInviteResidentResponse() : ServerPacket(SMSG_NEIGHBORHOOD_INVITE_RESIDENT_RESPONSE, 24) {} + WorldPacket const* Write() override; + + ObjectGuid InviteeGUID; + uint8 Result = 0; + }; + + class NeighborhoodInviteNotification final : public ServerPacket + { + public: + NeighborhoodInviteNotification() : ServerPacket(SMSG_NEIGHBORHOOD_INVITE_NOTIFICATION, 16) {} + WorldPacket const* Write() override; + + ObjectGuid NeighborhoodGUID; + }; + + struct NeighborhoodInviteEntry + { + uint64 Timestamp = 0; + ObjectGuid PlayerGUID; + ObjectGuid HouseGUID; + uint64 ExtraData = 0; + }; + + class HousingDeleteAllNeighborhoodInvites final : public ClientPacket + { + public: + explicit HousingDeleteAllNeighborhoodInvites(WorldPacket&& packet) : ClientPacket(CMSG_HOUSING_SVCS_DELETE_ALL_NEIGHBORHOOD_INVITES, std::move(packet)) {} + void Read() override {} + }; + + class HousingDeleteAllNeighborhoodInvitesResponse final : public ServerPacket + { + public: + HousingDeleteAllNeighborhoodInvitesResponse() : ServerPacket(SMSG_HOUSING_SVCS_DELETE_ALL_NEIGHBORHOOD_INVITES_RESPONSE, 1) {} + WorldPacket const* Write() override; + uint8 Result = 0; + }; + + class NeighborhoodCancelInvitation final : public ClientPacket + { + public: + explicit NeighborhoodCancelInvitation(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_CANCEL_INVITATION, std::move(packet)) {} + void Read() override { _worldPacket >> InviteeGUID; } + ObjectGuid InviteeGUID; + }; + + class NeighborhoodCancelInvitationResponse final : public ServerPacket + { + public: + NeighborhoodCancelInvitationResponse() : ServerPacket(SMSG_NEIGHBORHOOD_CANCEL_INVITATION_RESPONSE, 24) {} + WorldPacket const* Write() override; + ObjectGuid InviteeGUID; + uint8 Result = 0; + }; + + class HousingSvcsGuildCreateNeighborhood final : public ClientPacket + { + public: + explicit HousingSvcsGuildCreateNeighborhood(WorldPacket&& packet) : ClientPacket(CMSG_HOUSING_SVCS_GUILD_CREATE_NEIGHBORHOOD, std::move(packet)) {} + void Read() override; + uint32 NeighborhoodTypeID = 0; + uint32 SecondaryID = 0; + std::string NeighborhoodName; + }; + + class HousingSvcsGuildCreateNeighborhoodNotification final : public ServerPacket + { + public: + HousingSvcsGuildCreateNeighborhoodNotification() : ServerPacket(SMSG_HOUSING_SVCS_GUILD_CREATE_NEIGHBORHOOD_NOTIFICATION, 32) {} + WorldPacket const* Write() override; + ObjectGuid NeighborhoodGUID; + uint8 Flag = 0; + std::string Name; + }; + + class HousingSvcsGuildAddHouseNotification final : public ServerPacket + { + public: + HousingSvcsGuildAddHouseNotification() : ServerPacket(SMSG_HOUSING_SVCS_GUILD_ADD_HOUSE_NOTIFICATION, 64) {} + WorldPacket const* Write() override; + HouseInfo House; + }; + + class HousingSvcsGuildRemoveHouseNotification final : public ServerPacket + { + public: + HousingSvcsGuildRemoveHouseNotification() : ServerPacket(SMSG_HOUSING_SVCS_GUILD_REMOVE_HOUSE_NOTIFICATION, 64) {} + WorldPacket const* Write() override; + HouseInfo House; + }; + + class NeighborhoodSetPublicFlag final : public ClientPacket + { + public: + explicit NeighborhoodSetPublicFlag(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_SET_PUBLIC_FLAG, std::move(packet)) {} + void Read() override; + ObjectGuid NeighborhoodGUID; + bool IsPublic = false; + }; + + class HousingHouseFinderForceRefresh final : public ServerPacket + { + public: + HousingHouseFinderForceRefresh() : ServerPacket(SMSG_HOUSING_SVCS_HOUSE_FINDER_FORCE_REFRESH, 0) {} + WorldPacket const* Write() override { return &_worldPacket; } + }; + + class NeighborhoodAddSecondaryOwner final : public ClientPacket + { + public: + explicit NeighborhoodAddSecondaryOwner(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_ADD_SECONDARY_OWNER, std::move(packet)) {} + void Read() override { _worldPacket >> PlayerGUID; } + ObjectGuid PlayerGUID; + }; + + class NeighborhoodRemoveSecondaryOwner final : public ClientPacket + { + public: + explicit NeighborhoodRemoveSecondaryOwner(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER, std::move(packet)) {} + void Read() override { _worldPacket >> PlayerGUID; } + ObjectGuid PlayerGUID; + }; + + class NeighborhoodSecondaryOwnerResponse final : public ServerPacket + { + public: + explicit NeighborhoodSecondaryOwnerResponse(OpcodeServer opcode) : ServerPacket(opcode, 24) {} + WorldPacket const* Write() override; + ObjectGuid PlayerGUID; + uint8 Result = 0; + }; + + class NeighborhoodRosterResidentUpdate final : public ServerPacket + { + public: + struct Resident { ObjectGuid PlayerGUID; uint8 UpdateType = 1; bool IsPrivileged = false; }; + NeighborhoodRosterResidentUpdate() : ServerPacket(SMSG_NEIGHBORHOOD_ROSTER_RESIDENT_UPDATE, 32) {} + WorldPacket const* Write() override; + std::vector Residents; + }; + + class NeighborhoodGetRoster final : public ClientPacket + { + public: + explicit NeighborhoodGetRoster(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_GET_ROSTER, std::move(packet)) {} + void Read() override { _worldPacket >> NeighborhoodGUID; } + ObjectGuid NeighborhoodGUID; + }; + + class NeighborhoodGetRosterResponse final : public ServerPacket + { + public: + struct Member + { + ObjectGuid HouseGUID; + ObjectGuid PlayerGUID; + ObjectGuid BnetAccountGUID; + uint8 PlotIndex = 0; + uint32 JoinTime = 0; + bool IsOnline = false; + }; + + NeighborhoodGetRosterResponse() : ServerPacket(SMSG_NEIGHBORHOOD_GET_ROSTER_RESPONSE, 128) {} + WorldPacket const* Write() override; + + ObjectGuid NeighborhoodGUID; + ObjectGuid OwnerGUID; + std::string NeighborhoodName; + std::vector Members; + uint8 Result = 0; + }; + + class NeighborhoodGetInvites final : public ClientPacket + { + public: + explicit NeighborhoodGetInvites(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_GET_INVITES, std::move(packet)) {} + void Read() override {} + }; + + class NeighborhoodPlayerGetInvite final : public ClientPacket + { + public: + explicit NeighborhoodPlayerGetInvite(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_PLAYER_GET_INVITE, std::move(packet)) {} + void Read() override {} + }; + + class NeighborhoodPlayerDeclineInvite final : public ClientPacket + { + public: + explicit NeighborhoodPlayerDeclineInvite(WorldPacket&& packet) : ClientPacket(CMSG_NEIGHBORHOOD_PLAYER_DECLINE_INVITE, std::move(packet)) {} + void Read() override { _worldPacket >> NeighborhoodGUID; } + + ObjectGuid NeighborhoodGUID; + }; + + class NeighborhoodGetInvitesResponse final : public ServerPacket + { + public: + NeighborhoodGetInvitesResponse() : ServerPacket(SMSG_NEIGHBORHOOD_GET_INVITES_RESPONSE, 64) {} + WorldPacket const* Write() override; + + std::vector Invites; + uint8 Result = 0; + }; + + class NeighborhoodPlayerGetInviteResponse final : public ServerPacket + { + public: + NeighborhoodPlayerGetInviteResponse() : ServerPacket(SMSG_NEIGHBORHOOD_PLAYER_GET_INVITE_RESPONSE, 64) {} + WorldPacket const* Write() override; + + NeighborhoodInviteEntry Invite; + uint8 Result = 0; + }; + + class NeighborhoodDeclineInvitationResponse final : public ServerPacket + { + public: + NeighborhoodDeclineInvitationResponse() : ServerPacket(SMSG_NEIGHBORHOOD_DECLINE_INVITATION_RESPONSE, 24) {} + WorldPacket const* Write() override; + + ObjectGuid NeighborhoodGUID; + uint8 Result = 0; + }; + class GetPlayerHousesInfo final : public ClientPacket { public: @@ -278,6 +526,15 @@ namespace WorldPackets::Housing uint8 ExtraFlags = 0; }; + class HousingSvcsCreateCharterNeighborhoodResponse final : public ServerPacket + { + public: + HousingSvcsCreateCharterNeighborhoodResponse() : ServerPacket(SMSG_HOUSING_SVCS_CREATE_CHARTER_NEIGHBORHOOD_RESPONSE, 128) {} + WorldPacket const* Write() override; + HouseFinderNeighborhoodInfo Neighborhood; + uint8 Result = 0; + }; + class HousingGetHouseFinderInfo final : public ClientPacket { public: diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index b580b99c1..fc9a0d88a 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -613,14 +613,14 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_HOUSING_ROOM_SET_DOOR_TYPE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingRoomSetDoorType); DEFINE_HANDLER(CMSG_HOUSING_ROOM_SET_LAYOUT_EDIT_MODE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingRoomSetLayoutEditMode); DEFINE_HANDLER(CMSG_HOUSING_SVCS_ACCEPT_NEIGHBORHOOD_OWNERSHIP, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_HOUSING_SVCS_DELETE_ALL_NEIGHBORHOOD_INVITES, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_HOUSING_SVCS_DELETE_ALL_NEIGHBORHOOD_INVITES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingDeleteAllNeighborhoodInvites); DEFINE_HANDLER(CMSG_HOUSING_SVCS_GET_BNET_FRIEND_NEIGHBORHOODS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_HOUSING_SVCS_GET_HOUSE_FINDER_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingGetHouseFinderInfo); DEFINE_HANDLER(CMSG_HOUSING_SVCS_GET_HOUSE_FINDER_NEIGHBORHOOD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingGetHouseFinderNeighborhood); DEFINE_HANDLER(CMSG_HOUSING_SVCS_GET_PLAYER_HOUSES_INFO, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingGetPlayerHousesInfo); DEFINE_HANDLER(CMSG_HOUSING_SVCS_GET_POTENTIAL_HOUSE_OWNERS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingGetPotentialHouseOwners); DEFINE_HANDLER(CMSG_HOUSING_SVCS_HOUSE_FINDER_IGNORE_NEIGHBORHOOD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingHouseFinderIgnoreNeighborhood); - DEFINE_HANDLER(CMSG_HOUSING_SVCS_GUILD_CREATE_NEIGHBORHOOD, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_HOUSING_SVCS_GUILD_CREATE_NEIGHBORHOOD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingSvcsGuildCreateNeighborhood); DEFINE_HANDLER(CMSG_HOUSING_SVCS_NEIGHBORHOOD_RESERVE_PLOT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingReservePlot); DEFINE_HANDLER(CMSG_HOUSING_SVCS_PLAYER_VIEW_HOUSES_BY_BNET_ACCOUNT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_HOUSING_SVCS_PLAYER_VIEW_HOUSES_BY_PLAYER, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleHousingPlayerViewHousesByPlayer); @@ -636,7 +636,7 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_INSPECT, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleInspectOpcode); DEFINE_HANDLER(CMSG_INSTANCE_ABANDON_VOTE_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInstanceAbandonVoteResponse); DEFINE_HANDLER(CMSG_INSTANCE_LOCK_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInstanceLockResponse); - DEFINE_HANDLER(CMSG_INVITE_PLAYER_TO_NEIGHBORHOOD, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_INVITE_PLAYER_TO_NEIGHBORHOOD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInvitePlayerToNeighborhood); DEFINE_HANDLER(CMSG_ISLAND_QUEUE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleIslandQueue); DEFINE_HANDLER(CMSG_ITEM_PURCHASE_REFUND, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleItemRefund); DEFINE_HANDLER(CMSG_ITEM_TEXT_QUERY, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleItemTextQuery); @@ -810,9 +810,9 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_MOVE_UPDATE_FALL_SPEED, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes); DEFINE_HANDLER(CMSG_MOVE_WATER_WALK_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementAckMessage); DEFINE_HANDLER(CMSG_MYTHIC_PLUS_REQUEST_MAP_STATS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMythicPlusRequestMapStats); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_ADD_SECONDARY_OWNER, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_ADD_SECONDARY_OWNER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodAddSecondaryOwner); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_BUY_HOUSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodBuyHouse); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_CANCEL_INVITATION, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_CANCEL_INVITATION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodCancelInvitation); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_CHARTER_ADD_SIGNATURE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_CHARTER_CREATE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_CHARTER_EDIT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); @@ -820,17 +820,17 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_NEIGHBORHOOD_CHARTER_OPEN_CONFIRMATION_UI, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_CHARTER_SEND_SIGNATURE_REQUEST, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_EVICT_PLOT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_GET_INVITES, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_GET_ROSTER, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_GET_INVITES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodGetInvites); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_GET_ROSTER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodGetRoster); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_INITIATIVE_SERVICE_STATUS_CHECK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodInitiativeServiceStatusCheck); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_INVITE_RESIDENT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_MOVE_HOUSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodMoveHouse); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_OFFER_OWNERSHIP, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_OPEN_CORNERSTONE_UI, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodOpenCornerstoneUI); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_PLAYER_DECLINE_INVITE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_PLAYER_GET_INVITE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_NEIGHBORHOOD_SET_PUBLIC_FLAG, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_PLAYER_DECLINE_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodPlayerDeclineInvite); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_PLAYER_GET_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodPlayerGetInvite); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodRemoveSecondaryOwner); + DEFINE_HANDLER(CMSG_NEIGHBORHOOD_SET_PUBLIC_FLAG, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodSetPublicFlag); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_UPDATE_NAME, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_NEUTRAL_PLAYER_SELECT_FACTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSelectFactionOpcode); DEFINE_HANDLER(CMSG_NEXT_CINEMATIC_CAMERA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNextCinematicCamera); @@ -1796,20 +1796,20 @@ void OpcodeTable::InitializeServerOpcodes() DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_CANCEL_RELINQUISH_HOUSE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_CHANGE_HOUSE_COSMETIC_OWNER, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_CLEAR_PLOT_RESERVATION_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_CREATE_CHARTER_NEIGHBORHOOD_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_DELETE_ALL_NEIGHBORHOOD_INVITES_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_CREATE_CHARTER_NEIGHBORHOOD_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_DELETE_ALL_NEIGHBORHOOD_INVITES_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GET_BNET_FRIEND_NEIGHBORHOODS_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GET_HOUSE_FINDER_INFO_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GET_HOUSE_FINDER_NEIGHBORHOOD_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GET_PLAYER_HOUSES_INFO_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GET_POTENTIAL_HOUSE_OWNERS_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_ADD_HOUSE_NOTIFICATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_ADD_HOUSE_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_APPEND_NEIGHBORHOOD_NOTIFICATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_CREATE_NEIGHBORHOOD_NOTIFICATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_CREATE_NEIGHBORHOOD_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_GET_HOUSING_INFO_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_REMOVE_HOUSE_NOTIFICATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_REMOVE_HOUSE_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_GUILD_RENAME_NEIGHBORHOOD_NOTIFICATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_HOUSE_FINDER_FORCE_REFRESH, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_HOUSE_FINDER_FORCE_REFRESH, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_IGNORE_NEIGHBORHOOD_INVITE_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_NEIGHBORHOOD_OWNERSHIP_TRANSFERRED_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_HOUSING_SVCS_NEIGHBORHOOD_RESERVE_PLOT_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); @@ -2096,30 +2096,30 @@ void OpcodeTable::InitializeServerOpcodes() DEFINE_SERVER_OPCODE_HANDLER(SMSG_MYTHIC_PLUS_CURRENT_AFFIXES, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_MYTHIC_PLUS_NEW_WEEK_RECORD, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_MYTHIC_PLUS_SEASON_DATA, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_ADD_SECONDARY_OWNER_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_ADD_SECONDARY_OWNER_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_BUY_HOUSE_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CANCEL_INVITATION_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CANCEL_INVITATION_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CHARTER_ADD_SIGNATURE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CHARTER_OPEN_CONFIRMATION_UI_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CHARTER_OPEN_UI_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CHARTER_SIGNATURE_REMOVED_NOTIFICATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CHARTER_SIGN_REQUEST, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_CHARTER_UPDATE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_DECLINE_INVITATION_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_DECLINE_INVITATION_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_EVICT_PLAYER, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_EVICT_PLOT_NOTICE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_EVICT_PLOT_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_GET_INVITES_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_GET_ROSTER_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_INVITE_NAME_LOOKUP_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_INVITE_NOTIFICATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_INVITE_RESIDENT_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_GET_INVITES_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_GET_ROSTER_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_INVITE_NAME_LOOKUP_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_INVITE_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_INVITE_RESIDENT_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_MOVE_HOUSE_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_OFFER_OWNERSHIP_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_OPEN_CORNERSTONE_UI_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_PLAYER_GET_INVITE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_ROSTER_RESIDENT_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_PLAYER_GET_INVITE_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_ROSTER_RESIDENT_UPDATE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_UPDATE_NAME_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEUTRAL_PLAYER_FACTION_SELECT_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEW_DATA_BUILD, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index e38f4855e..ef8f5e046 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -642,6 +642,17 @@ namespace WorldPackets class HouseStatus; class PlayerViewHousesByPlayer; class HousingGetPotentialHouseOwners; + class InvitePlayerToNeighborhood; + class HousingDeleteAllNeighborhoodInvites; + class NeighborhoodCancelInvitation; + class HousingSvcsGuildCreateNeighborhood; + class NeighborhoodSetPublicFlag; + class NeighborhoodAddSecondaryOwner; + class NeighborhoodRemoveSecondaryOwner; + class NeighborhoodGetRoster; + class NeighborhoodGetInvites; + class NeighborhoodPlayerGetInvite; + class NeighborhoodPlayerDeclineInvite; class GetPlayerHousesInfo; class HousingBlueprintRequestCollection; class HousingDecorDeleteFromStorage; @@ -1977,6 +1988,17 @@ public: void HandleHousingHouseStatus(WorldPackets::Housing::HouseStatus& packet); void HandleHousingPlayerViewHousesByPlayer(WorldPackets::Housing::PlayerViewHousesByPlayer& packet); void HandleHousingGetPotentialHouseOwners(WorldPackets::Housing::HousingGetPotentialHouseOwners& packet); + void HandleInvitePlayerToNeighborhood(WorldPackets::Housing::InvitePlayerToNeighborhood& packet); + void HandleHousingDeleteAllNeighborhoodInvites(WorldPackets::Housing::HousingDeleteAllNeighborhoodInvites& packet); + void HandleNeighborhoodCancelInvitation(WorldPackets::Housing::NeighborhoodCancelInvitation& packet); + void HandleHousingSvcsGuildCreateNeighborhood(WorldPackets::Housing::HousingSvcsGuildCreateNeighborhood& packet); + void HandleNeighborhoodSetPublicFlag(WorldPackets::Housing::NeighborhoodSetPublicFlag& packet); + void HandleNeighborhoodAddSecondaryOwner(WorldPackets::Housing::NeighborhoodAddSecondaryOwner& packet); + void HandleNeighborhoodRemoveSecondaryOwner(WorldPackets::Housing::NeighborhoodRemoveSecondaryOwner& packet); + void HandleNeighborhoodGetRoster(WorldPackets::Housing::NeighborhoodGetRoster& packet); + void HandleNeighborhoodGetInvites(WorldPackets::Housing::NeighborhoodGetInvites& packet); + void HandleNeighborhoodPlayerGetInvite(WorldPackets::Housing::NeighborhoodPlayerGetInvite& packet); + void HandleNeighborhoodPlayerDeclineInvite(WorldPackets::Housing::NeighborhoodPlayerDeclineInvite& packet); void HandleHousingGetPlayerHousesInfo(WorldPackets::Housing::GetPlayerHousesInfo& packet); void HandleHousingGetHouseFinderInfo(WorldPackets::Housing::HousingGetHouseFinderInfo& packet); void HandleHousingGetHouseFinderNeighborhood(WorldPackets::Housing::HousingGetHouseFinderNeighborhood& packet);