diff --git a/src/game/Chat/Channel.cpp b/src/game/Chat/Channel.cpp index 9c49633e64d..179be44045e 100644 --- a/src/game/Chat/Channel.cpp +++ b/src/game/Chat/Channel.cpp @@ -93,7 +93,7 @@ void Channel::Join(Player* player, const char* password) if (HasFlag(CHANNEL_FLAG_LFG) && sWorld.getConfig(CONFIG_BOOL_CHANNEL_RESTRICTED_LFG)) { - if (player->GetSession()->GetSecurity() == SEC_PLAYER && player->m_lookingForGroup.isEmpty()) + if (player->GetSession()->GetSecurity() == SEC_PLAYER && !player->m_lfgInfo.queued) { MakeNotInLFG(data, m_name); SendToOne(data, guid); diff --git a/src/game/Entities/Player.h b/src/game/Entities/Player.h index ee8eda4414c..c807eee2719 100644 --- a/src/game/Entities/Player.h +++ b/src/game/Entities/Player.h @@ -41,6 +41,7 @@ #include "Server/SQLStorages.h" #include "Loot/LootMgr.h" #include "Cinematics/CinematicMgr.h" +#include "LFG/LFGDefines.h" #include #include @@ -307,61 +308,6 @@ struct EnchantDuration typedef std::list EnchantDurationList; typedef std::list ItemDurationList; -#define MAX_LOOKING_FOR_GROUP_SLOT 3 - -struct LookingForGroupInfo -{ - struct Slot - { - bool empty() const { return (!type || !entry); } - void clear() { entry = 0; } - bool set(uint16 _entry, uint16 _type) { entry = _entry; type = _type; return !empty(); } - bool is(uint16 _entry, uint16 _type) const { return entry == _entry && type == _type; } - bool isAuto() const { return entry && (type == LFG_TYPE_DUNGEON || type == LFG_TYPE_HEROIC_DUNGEON); } - - uint16 entry = 0; - uint16 type = LFG_TYPE_DUNGEON; - }; - - inline void clear() - { - more.clear(); - for (auto& slot : group) - slot.clear(); - } - inline bool isAutoFill() const { return more.isAuto(); } - inline bool isAutoJoin() const - { - for (auto& slot : group) - if (slot.isAuto()) - return true; - return false; - } - inline bool isEmpty() const { return (!isLFM() && !isLFG()); } - inline bool isLFG() const - { - for (auto& slot : group) - if (!slot.empty()) - return true; - return false; - } - inline bool isLFG(uint32 entry, uint32 type, bool autoOnly) const - { - for (auto& slot : group) - if (slot.is(uint16(entry), uint16(type)) && (!autoOnly || slot.isAuto())) - return true; - return false; - } - inline bool isLFG(LookingForGroupInfo const& info, bool autoOnly) const { return isLFG(uint16(info.more.entry), uint16(info.more.type), autoOnly); } - inline bool isLFM() const { return !more.empty(); } - inline bool isLFM(uint32 entry, uint32 type) const { return more.is(uint16(entry), uint16(type)); } - - // bool queued = false; - Slot group[MAX_LOOKING_FOR_GROUP_SLOT]; - Slot more; - std::string comment; -}; - enum RaidGroupError { ERR_RAID_GROUP_NONE = 0, @@ -2187,7 +2133,7 @@ class Player : public Unit void RemoveAtLoginFlag(AtLoginFlags f, bool in_db_also = false); static bool ValidateAppearance(uint8 race, uint8 class_, uint8 gender, uint8 hairID, uint8 hairColor, uint8 faceID, uint8 facialHair, uint8 skinColor, bool create = false); - LookingForGroupInfo m_lookingForGroup; + LfgPlayerInfo m_lfgInfo; // Temporarily removed pet cache uint32 GetTemporaryUnsummonedPetNumber() const { return m_temporaryUnsummonedPetNumber; } diff --git a/src/game/LFG/LFGDefines.h b/src/game/LFG/LFGDefines.h new file mode 100644 index 00000000000..1b3feae8534 --- /dev/null +++ b/src/game/LFG/LFGDefines.h @@ -0,0 +1,52 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LFG_DEFINES_H +#define _LFG_DEFINES_H + +#include "Common.h" + +#define MAX_LOOKING_FOR_GROUP_SLOT 3 + +enum LfgType : uint32 +{ + LFG_TYPE_NONE = 0, + LFG_TYPE_DUNGEON = 1, + LFG_TYPE_RAID = 2, + LFG_TYPE_QUEST = 3, + LFG_TYPE_ZONE = 4, + LFG_TYPE_HEROIC_DUNGEON = 5 +}; + +enum class MeetingstoneFailedStatus : uint8 +{ + MEETINGSTONE_FAIL_NONE = 0, // custom, not to be sent + MEETINGSTONE_FAIL_PARTYLEADER = 1, + MEETINGSTONE_FAIL_FULL_GROUP = 2, + MEETINGSTONE_FAIL_RAID_GROUP = 3, +}; + +struct LfgPlayerInfo +{ + std::string comment; + bool autojoin; + bool autofill; + bool queued; // cached async information +}; + +#endif \ No newline at end of file diff --git a/src/game/LFG/LFGHandler.cpp b/src/game/LFG/LFGHandler.cpp index b818e4375d4..50a799d7da8 100644 --- a/src/game/LFG/LFGHandler.cpp +++ b/src/game/LFG/LFGHandler.cpp @@ -20,463 +20,93 @@ #include "Globals/ObjectMgr.h" #include "Log/Log.h" #include "World/World.h" +#include "LFG/LFGDefines.h" +#include "LFG/LFGQueue.h" - -static inline void LookingForGroupMakeMeetingStoneQueueLeftFor(WorldPacket& data, uint32 entry) -{ - data.Initialize(SMSG_MEETINGSTONE_SETQUEUE); - data << uint32(entry); - data << uint8(0x00); -} - -static inline void LookingForGroupMakeMeetingStoneQueueJoinedFor(WorldPacket& data, uint32 entry) -{ - data.Initialize(SMSG_MEETINGSTONE_SETQUEUE); - data << uint32(entry); - data << uint8(0x01); -} - -static inline void LookingForGroupMakeMeetingStoneQueueMatchedFor(WorldPacket& data, uint32 entry, bool asLeader = false) -{ - data.Initialize(SMSG_MEETINGSTONE_SETQUEUE); - data << uint32(entry); - data << uint8(0x05); - data << uint8(asLeader); -} - -static inline void LookingForGroupMakeMeetingStoneMemberAdded(WorldPacket& data, ObjectGuid guid) -{ - data.Initialize(SMSG_MEETINGSTONE_SETQUEUE, 8); - data << guid; -} - -static inline const Player* LookingForGroupGetCurrentLeader(Player* _this) -{ - if (Group* group = _this->GetGroup()) - { - if (!group->IsBattleGroup() && !group->IsFull() && !group->IsLeader(_this->GetObjectGuid())) - { - for (const GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) - { - if (Player* member = itr->getSource()) - { - if (group->IsLeader(member->GetObjectGuid())) - return member; - } - } - } - } - return _this; -} - -static inline void LookingForGroupUpdateChannelStatus(Player* _this) -{ - const Player* leader = LookingForGroupGetCurrentLeader(_this); - - if (leader->m_lookingForGroup.isEmpty()) - { - if (sWorld.getConfig(CONFIG_BOOL_CHANNEL_RESTRICTED_LFG) && _this->GetSession()->GetSecurity() == SEC_PLAYER) - _this->LeaveLFGChannel(); - } -} - -static inline bool LookingForGroupUpdateQueueStatus(Player* _this, WorldSession* _session) +void WorldSession::HandleLfgSetAutoJoinOpcode(WorldPacket& /*recv_data*/) { - Group* _group = _this->GetGroup(); - - const bool autojoin = (_this->m_lookingForGroup.isAutoJoin() && _session->LookingForGroup_auto_join); - const bool autofill = (_this->m_lookingForGroup.isAutoFill() && _session->LookingForGroup_auto_add); - const bool notraid = (!_group || (!_group->IsRaidGroup() && _group->IsLeader(_this->GetObjectGuid()))); - - const bool status = ((autojoin && !_group) || (autofill && notraid)); - - if (_session->LookingForGroup_queue == status) - return false; - - _session->LookingForGroup_queue = status; - - // Sync client's UI queue status - - const Player* pov = LookingForGroupGetCurrentLeader(_this); - const bool queued = pov->GetSession()->LookingForGroup_queue; - auto const& info = pov->m_lookingForGroup; - - WorldPacket data(SMSG_LFG_UPDATE_QUEUED, 1); - data << uint8(queued); + DEBUG_LOG("CMSG_LFG_SET_AUTOJOIN"); - WorldPacket message; - if (queued) - LookingForGroupMakeMeetingStoneQueueJoinedFor(message, info.more.entry); - else - LookingForGroupMakeMeetingStoneQueueLeftFor(message, info.more.entry); + _player->m_lfgInfo.autojoin = true; - if (!_group || !notraid) + sWorld.GetLFGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid()](LFGQueue* queue) { - _session->SendPacket(data); + queue->SetAutoJoin(playerGuid, true); + }); - if (info.isLFM()) - _session->SendPacket(message); - } - else - { - _group->BroadcastPacket(data, true); + uint8 result = uint8(MeetingstoneFailedStatus::MEETINGSTONE_FAIL_NONE); - if (info.isLFM()) - _group->BroadcastPacket(message, true); - } - return true; + WorldPacket data(SMSG_MEETINGSTONE_JOINFAILED, 1); + data << uint8(result); + _player->GetSession()->SendPacket(data); } -static inline void LookingForGroupUpdateUI(Player* _this, WorldSession* _session, bool completed = false) +void WorldSession::HandleLfgClearAutoJoinOpcode(WorldPacket& /*recv_data*/) { - Group* _group = _this->GetGroup(); + DEBUG_LOG("CMSG_LFG_CLEAR_AUTOJOIN"); - if (!_group || !_group->IsLeader(_this->GetObjectGuid())) - { - if (completed) - _session->SendMeetingStoneComplete(); - else - _session->SendLFGUpdate(); + _player->m_lfgInfo.autojoin = false; - LookingForGroupUpdateChannelStatus(_this); - return; - } - - for (const GroupReference* itr = _group->GetFirstMember(); itr; itr = itr->next()) + sWorld.GetLFGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid()](LFGQueue* queue) { - if (Player* member = itr->getSource()) - { - if (completed) - member->GetSession()->SendMeetingStoneComplete(); - else - member->GetSession()->SendLFGUpdate(); - - LookingForGroupUpdateChannelStatus(member); - } - } -} - -static inline void LookingForGroupUpdate(Player* _this, WorldSession* _session, bool completed = false) -{ - LookingForGroupUpdateQueueStatus(_this, _session); - LookingForGroupUpdateUI(_this, _session, completed); -} - -static inline void LookingForGroupClearLFG(Player* _this, WorldSession* _session) -{ - if (!_this->m_lookingForGroup.isLFG()) - return; - - // Unset LFG serverside and force client to update - for (int i = 0; i < MAX_LOOKING_FOR_GROUP_SLOT; ++i) - _this->m_lookingForGroup.group[i].clear(); - - LookingForGroupUpdate(_this, _session); -} - -static inline void LookingForGroupClearLFM(Player* _this, WorldSession* _session) -{ - if (!_this->m_lookingForGroup.isLFM()) - return; - - // Unset LFM serverside and force client to update - _this->m_lookingForGroup.more.clear(); - - LookingForGroupUpdate(_this, _session); + queue->SetAutoJoin(playerGuid, false); + }); } -static inline void LookingForGroupClear(Player* _this, WorldSession* _session, bool completed = false) -{ - // Unset all serverside and force client to update - _this->m_lookingForGroup.clear(); - LookingForGroupUpdate(_this, _session, completed); -} - -static inline bool LookingForGroupAddMemberFor(Player* _this, WorldSession* _session, Player* member, uint32 entry, Group*& _group) +void WorldSession::HandleLfmSetAutoFillOpcode(WorldPacket& /*recv_data*/) { - const bool create = !_group; - - if (create) - { - _group = new Group; - - if (!_group->Create(_this->GetObjectGuid(), _this->GetName())) - { - delete _group; - _group = nullptr; - return false; - } - - sObjectMgr.AddGroup(_group); - } - - const ObjectGuid _guid = member->GetObjectGuid(); - - if (!_group->AddMember(_guid, member->GetName())) - return false; - - WorldPacket data; - - if (create) - { - LookingForGroupMakeMeetingStoneQueueMatchedFor(data, entry, true); - _session->SendPacket(data); - } - - LookingForGroupMakeMeetingStoneMemberAdded(data, _guid); - _group->BroadcastPacket(data, true, -1, _guid); - - WorldSession* session = member->GetSession(); - - LookingForGroupMakeMeetingStoneQueueMatchedFor(data, entry); - session->SendPacket(data); - - LookingForGroupClear(member, session); - - if (_group->IsFull()) - { - LookingForGroupClear(_this, _this->GetSession(), true); - return false; - } - - return true; -} + DEBUG_LOG("CMSG_LFM_SET_AUTOFILL"); -static void LookingForGroupTryJoin(Player* _player, bool initial = false) -{ - WorldSession* _session = _player->GetSession(); + MeetingstoneFailedStatus result = MeetingstoneFailedStatus::MEETINGSTONE_FAIL_NONE; - // skip not can autojoin cases and player group case if (Group* _group = _player->GetGroup()) { - if (_group->IsBattleGroup() || _group->IsFull() || !_group->IsLeader(_player->GetObjectGuid())) - LookingForGroupClear(_player, _session); + if (_group->IsRaidGroup()) + result = MeetingstoneFailedStatus::MEETINGSTONE_FAIL_RAID_GROUP; + else if (!_group->IsLeader(_player->GetObjectGuid())) + result = MeetingstoneFailedStatus::MEETINGSTONE_FAIL_PARTYLEADER; else - LookingForGroupClearLFG(_player, _session); + result = MeetingstoneFailedStatus::MEETINGSTONE_FAIL_FULL_GROUP; } - LookingForGroupUpdateQueueStatus(_player, _session); - - if (!_session->LookingForGroup_queue || !_player->m_lookingForGroup.isLFG()) - return; - - bool attempted = false; - - // TODO: Guard Player Map - HashMapHolder::MapType const& players = sObjectAccessor.GetPlayers(); - for (HashMapHolder::MapType::const_iterator iter = players.begin(); iter != players.end(); ++iter) + if (result == MeetingstoneFailedStatus::MEETINGSTONE_FAIL_NONE) { - Player* plr = iter->second; - - // skip enemies and self - if (!plr || plr == _player || plr->GetTeam() != _player->GetTeam()) - continue; - - WorldSession* session = plr->GetSession(); - - // skip players not in world or reconnecting - if (!plr->IsInWorld() || session->IsOffline()) - continue; - - Group* grp = plr->GetGroup(); - - // skip players in not compatinle groups and dequeue them if discovered - if (grp && (grp->IsBattleGroup() || grp->IsFull() || !grp->IsLeader(_player->GetObjectGuid()))) + _player->m_lfgInfo.autofill = true; + sWorld.GetLFGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid()](LFGQueue* queue) { - LookingForGroupClear(plr, session); - continue; - } - - LookingForGroupUpdateQueueStatus(plr, session); - - // skip not queued - if (!session->LookingForGroup_queue || plr->m_lookingForGroup.more.empty()) - continue; - - // skip not fitting slots - if (!_player->m_lookingForGroup.isLFG(plr->m_lookingForGroup, true)) - continue; - - Group* result = grp; - - // stop at join success - if (LookingForGroupAddMemberFor(plr, session, _player, plr->m_lookingForGroup.more.entry, result)) - break; - else if (!grp) - attempted = true; - // group is full or cant join for other reasons, continue - } - - if (!initial && attempted && _session->LookingForGroup_queue) - _session->SendMeetingStoneInProgress(); -} - -static void LookingForGroupTryFill(Player* _player, bool initial = false) -{ - WorldSession* _session = _player->GetSession(); - - Group* _group = _player->GetGroup(); - - // skip non auto-join slot or player in a battleground, not group leader, group is full cases - if (_group && (_group->IsBattleGroup() || _group->IsFull() || !_group->IsLeader(_player->GetObjectGuid()))) - { - LookingForGroupClear(_player, _session); - return; - } - - LookingForGroupUpdateQueueStatus(_player, _session); - - if (!_session->LookingForGroup_queue || _player->m_lookingForGroup.more.empty()) - return; - - bool attempted = false; - - // TODO: Guard Player map - HashMapHolder::MapType const& players = sObjectAccessor.GetPlayers(); - for (HashMapHolder::MapType::const_iterator iter = players.begin(); iter != players.end(); ++iter) - { - Player* plr = iter->second; - - // skip enemies and self - if (!plr || plr == _player || plr->GetTeam() != _player->GetTeam()) - continue; - - WorldSession* session = plr->GetSession(); - - // skip players not in world or reconnecting - if (!plr->IsInWorld() || session->IsOffline()) - continue; - - // skip players in groups, dequeue and remove them from LFG if discovered - if (Group* grp = plr->GetGroup()) - { - if (grp->IsBattleGroup() || grp->IsFull() || !grp->IsLeader(plr->GetObjectGuid())) - LookingForGroupClear(plr, session); - else - LookingForGroupClearLFG(plr, session); - continue; - } - - LookingForGroupUpdateQueueStatus(plr, session); - - // skip not queued - if (!session->LookingForGroup_queue || !plr->m_lookingForGroup.isLFG()) - continue; - - // skip not fitting slots - if (!plr->m_lookingForGroup.isLFG(_player->m_lookingForGroup, true)) - continue; - - Group* result = _group; - - // stop at false result (full?) - if (!LookingForGroupAddMemberFor(_player, _session, plr, _player->m_lookingForGroup.more.entry, result)) - { - attempted = true; - break; - } - // group is not full yet, continue - } - - if (!initial && attempted && _session->LookingForGroup_queue) - _session->SendMeetingStoneInProgress(); -} - -static inline void LookingForGroupSetAutoJoin(Player* _player, WorldSession* _session, bool enabled) -{ - if (LookingForGroupUpdateQueueStatus(_player, _session)) - { - if (enabled) - LookingForGroupTryJoin(_player, true); - return; - } - - if (!enabled) - return; - - uint8 result = 0x00; // No message - - WorldPacket data(SMSG_MEETINGSTONE_JOINFAILED, 1); - data << uint8(result); - _session->SendPacket(data); -} - -static inline void LookingForGroupSetAutoFill(Player* _player, WorldSession* _session, bool enabled) -{ - if (LookingForGroupUpdateQueueStatus(_player, _session)) - { - if (enabled) - LookingForGroupTryFill(_player, true); - return; - } - - if (!enabled) - return; - - uint8 result = 0x00; // No message - - if (Group* _group = _player->GetGroup()) - { - if (_group->IsRaidGroup()) - result = 0x03; // NO_RAID_GROUP - else if (!_group->IsLeader(_player->GetObjectGuid())) - result = 0x01; // MUST_BE_LEADER - else - result = 0x02; // No message, failure for other reason related to groups? + queue->SetAutoFill(playerGuid, true); + }); } WorldPacket data(SMSG_MEETINGSTONE_JOINFAILED, 1); data << uint8(result); - _session->SendPacket(data); -} - -void WorldSession::HandleLfgSetAutoJoinOpcode(WorldPacket& /*recv_data*/) -{ - DEBUG_LOG("CMSG_LFG_SET_AUTOJOIN"); - - LookingForGroup_auto_join = true; - - if (!_player) // needed because STATUS_AUTHED - return; - - LookingForGroupSetAutoJoin(_player, _player->GetSession(), true); -} - -void WorldSession::HandleLfgClearAutoJoinOpcode(WorldPacket& /*recv_data*/) -{ - DEBUG_LOG("CMSG_LFG_CLEAR_AUTOJOIN"); - - LookingForGroup_auto_join = false; - - LookingForGroupSetAutoJoin(_player, _player->GetSession(), false); -} - -void WorldSession::HandleLfmSetAutoFillOpcode(WorldPacket& /*recv_data*/) -{ - DEBUG_LOG("CMSG_LFM_SET_AUTOFILL"); - - LookingForGroup_auto_add = true; - - if (!_player) // needed because STATUS_AUTHED - return; - - LookingForGroupSetAutoFill(_player, _player->GetSession(), true); + _player->GetSession()->SendPacket(data); } void WorldSession::HandleLfmClearAutoFillOpcode(WorldPacket& /*recv_data*/) { DEBUG_LOG("CMSG_LFM_CLEAR_AUTOFILL"); - LookingForGroup_auto_add = false; + _player->m_lfgInfo.autofill = false; - LookingForGroupSetAutoFill(_player, _player->GetSession(), false); + sWorld.GetLFGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid()](LFGQueue* queue) + { + queue->SetAutoFill(playerGuid, false); + }); } void WorldSession::HandleLfgClearOpcode(WorldPacket& /*recv_data */) { DEBUG_LOG("CMSG_CLEAR_LOOKING_FOR_GROUP"); - LookingForGroupClearLFG(_player, this); + ObjectGuid leaderGuid = _player->GetObjectGuid(); + if (Group* group = _player->GetGroup()) + leaderGuid = group->GetLeaderGuid(); + + sWorld.GetLFGQueue().GetMessager().AddMessage([leaderGuid, playerGuid = _player->GetObjectGuid()](LFGQueue* queue) + { + queue->StopLookingForGroup(leaderGuid, playerGuid); + }); } void WorldSession::HandleSetLfgOpcode(WorldPacket& recv_data) @@ -490,39 +120,51 @@ void WorldSession::HandleSetLfgOpcode(WorldPacket& recv_data) if (slot >= MAX_LOOKING_FOR_GROUP_SLOT) return; + LFGPlayerQueueInfo info; + + ObjectGuid leaderGuid = _player->GetObjectGuid(); if (Group* _group = _player->GetGroup()) { - const Player* pov = LookingForGroupGetCurrentLeader(_player); - - if (pov != _player && pov->m_lookingForGroup.isLFM()) + if (Group* group = _player->GetGroup()) { - WorldPacket data(SMSG_LFG_LEADER_IS_LFM); - SendPacket(data); - return; + if (!group->IsBattleGroup() && !group->IsFull() && !group->IsLeader(_player->GetObjectGuid())) + { + leaderGuid = group->GetLeaderGuid(); + for (const GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + if (Player* member = itr->getSource()) + { + if (!group->IsLeader(member->GetObjectGuid())) + { + info.members.push_back(LFGGroupQueueInfo(member->GetObjectGuid(), member->GetLevel())); + } + } + } + } } - - SendLFGUpdate(); - return; } uint16 entry = (data & 0xFFFF); uint16 type = ((data >> 24) & 0xFFFF); - DEBUG_LOG("LFG set: looknumber %u, temp %X, type %u, entry %u", slot, data, type, entry); - _player->m_lookingForGroup.group[slot].set(entry, type); - LookingForGroupClearLFM(_player, this); - - LookingForGroupUpdateQueueStatus(_player, this); + info.leaderGuid = leaderGuid; + info.group[slot].set(entry, type); - if (LookingForGroup_auto_join) - LookingForGroupTryJoin(_player); + DEBUG_LOG("LFG set: looknumber %u, temp %X, type %u, entry %u", slot, data, type, entry); + sWorld.GetLFGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid(), info](LFGQueue* queue) + { + queue->StartLookingForGroup(info, playerGuid); + }); } void WorldSession::HandleLfmClearOpcode(WorldPacket& /*recv_data */) { DEBUG_LOG("CMSG_CLEAR_LOOKING_FOR_MORE"); - LookingForGroupClearLFM(_player, this); + sWorld.GetLFGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid()](LFGQueue* queue) + { + queue->StopLookingForMore(playerGuid); + }); } void WorldSession::HandleSetLfmOpcode(WorldPacket& recv_data) @@ -533,32 +175,46 @@ void WorldSession::HandleSetLfmOpcode(WorldPacket& recv_data) recv_data >> data; + LFGPlayerQueueInfo info; + + ObjectGuid leaderGuid = _player->GetObjectGuid(); if (Group* _group = _player->GetGroup()) { - ObjectGuid _guid = _player->GetObjectGuid(); - - if (_group->IsBattleGroup() || _group->IsFull() || !_group->IsLeader(_guid)) + if (Group* group = _player->GetGroup()) { - SendLFGUpdate(); - return; + if (!group->IsBattleGroup() && !group->IsFull() && !group->IsLeader(_player->GetObjectGuid())) + { + leaderGuid = group->GetLeaderGuid(); + for (const GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + if (Player* member = itr->getSource()) + { + if (!group->IsLeader(member->GetObjectGuid())) + { + info.members.push_back(LFGGroupQueueInfo(member->GetObjectGuid(), member->GetLevel())); + } + } + } + } + else + { + SendLFGUpdate(); + return; + } } } uint16 entry = (data & 0xFFFF); uint16 type = ((data >> 24) & 0xFFFF); - DEBUG_LOG("LFM set: temp %u, zone %u, type %u", data, entry, type); - _player->m_lookingForGroup.more.set(entry, type); - LookingForGroupClearLFG(_player, this); - - // TODO broadcast LFM preference to party as well instead of full ui update? - LookingForGroupUpdateUI(_player, this); + info.leaderGuid = leaderGuid; + info.more.set(entry, type); - LookingForGroup_queue = false; // FIXME: this is to cause requeue message - LookingForGroupUpdateQueueStatus(_player, this); - - if (LookingForGroup_auto_add) - LookingForGroupTryFill(_player); + DEBUG_LOG("LFM set: temp %u, zone %u, type %u", data, entry, type); + sWorld.GetLFGQueue().GetMessager().AddMessage([objectGuid = _player->GetObjectGuid(), info](LFGQueue* queue) + { + queue->StartLookingForMore(objectGuid, info); + }); } void WorldSession::HandleSetLfgCommentOpcode(WorldPacket& recv_data) @@ -569,7 +225,11 @@ void WorldSession::HandleSetLfgCommentOpcode(WorldPacket& recv_data) recv_data >> comment; - _player->m_lookingForGroup.comment = comment; + sWorld.GetLFGQueue().GetMessager().AddMessage([objectGuid = _player->GetObjectGuid(), comment = comment](LFGQueue* queue) + { + queue->SetComment(objectGuid, comment); + }); + DEBUG_LOG("LFG comment %s", comment.c_str()); } @@ -581,13 +241,10 @@ void WorldSession::HandleLFGListQuery(WorldPacket& recv_data) recv_data >> type >> entry >> unk; DEBUG_LOG("MSG_LOOKING_FOR_GROUP: type %u, entry %u, unk %u", type, entry, unk); - if (LookingForGroup_auto_add) - LookingForGroupTryFill(_player); - - if (LookingForGroup_auto_join) - LookingForGroupTryJoin(_player); - - SendLFGListQueryResponse(LfgType(type), entry); + sWorld.GetLFGQueue().GetMessager().AddMessage([objectGuid = _player->GetObjectGuid(), team = _player->GetTeam(), type, entry](LFGQueue* queue) + { + queue->SendLFGListQueryResponse(objectGuid, team, LfgType(type), entry); + }); } void WorldSession::SendMeetingStoneComplete() @@ -602,135 +259,31 @@ void WorldSession::SendMeetingStoneInProgress() SendPacket(data); } -void WorldSession::SendLFGListQueryResponse(LfgType type, uint32 entry) -{ - // start prepare packet - WorldPacket data(MSG_LOOKING_FOR_GROUP); - data << uint32(type); // type - data << uint32(entry); // entry from LFGDungeons.dbc - data << uint32(0); // displayed players count, placeholder - data << uint32(0); // found players count, placeholder - - uint32 displayed = 0; - uint32 found = 0; - - // TODO: Guard Player map - HashMapHolder::MapType const& players = sObjectAccessor.GetPlayers(); - for (const auto& i : players) - { - Player* plr = i.second; - - if (!plr || plr->GetTeam() != _player->GetTeam()) - continue; - - if (!plr->IsInWorld() || plr->GetSession()->IsOffline()) - continue; - - if (!plr->m_lookingForGroup.isLFG(entry, type, false) && !plr->m_lookingForGroup.isLFM(entry, type)) - continue; - - const Group* grp = plr->GetGroup(); - - if (grp && (grp->IsBattleGroup() || grp->IsFull() || !grp->IsLeader(plr->GetObjectGuid()))) - continue; - - ++found; - - // Client hardcoded limitation on amount of players sent in the packet handler and UI: - if (found > 50) - continue; - - ++displayed; - - const bool isLFM = plr->m_lookingForGroup.isLFM(entry, type); - - data << plr->GetPackGUID(); // packed guid - data << uint32(plr->GetLevel()); // level - data << uint32(plr->GetZoneId()); // current zone - data << uint8(isLFM); // 0x00 - LFG, 0x01 - LFM - - if (isLFM) - { - data << uint32(plr->m_lookingForGroup.more.entry | (plr->m_lookingForGroup.more.type << 24)); - data << uint32(0x1000000); - data << uint32(0x1000000); - } - else - { - for (uint8 j = 0; j < MAX_LOOKING_FOR_GROUP_SLOT; ++j) - data << uint32(plr->m_lookingForGroup.group[j].entry | (plr->m_lookingForGroup.group[j].type << 24)); - } - - data << plr->m_lookingForGroup.comment; - - data << uint32(0); // other group members count, placeholder - - if (grp) - { - const size_t offset = (data.wpos() - 4); // other group members count, offset - uint32 count = 0; // other group members count - - for (const GroupReference* itr = grp->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - if (const Player* member = itr->getSource()) - { - if (member->GetObjectGuid() != plr->GetObjectGuid()) - { - data << member->GetPackGUID(); // packed guid - data << uint32(member->GetLevel()); // player level - ++count; - } - } - } - - data.put(offset, count); // other group members count, fill the placeholder - } - } - - // fill count placeholders - data.put(4 + 4, displayed); - data.put(4 + 4 + 4, found); - - SendPacket(data); -} - void WorldSession::SendLFGUpdate() { - const Player* pov = LookingForGroupGetCurrentLeader(_player); - auto const& info = pov->m_lookingForGroup; - const bool queued = pov->GetSession()->LookingForGroup_queue; - const bool isLFM = info.isLFM(); - - WorldPacket data(SMSG_LFG_UPDATE); - data << uint8(queued); - data << uint8(info.isLFG()); - data << uint8(isLFM); - if (isLFM) - data << uint32(info.more.entry | (info.more.type << 24)); - SendPacket(data); + ObjectGuid leaderGuid = _player->GetObjectGuid(); + if (Group* group = _player->GetGroup()) + leaderGuid = group->GetLeaderGuid(); + sWorld.GetLFGQueue().GetMessager().AddMessage([leaderGuid, objectGuid = _player->GetObjectGuid()](LFGQueue* queue) + { + queue->SendLFGUpdate(leaderGuid, objectGuid); + }); } void WorldSession::SendLFGUpdateLFG() { // Syncs player's own LFG UI selection with what we send here - auto const& selection = _player->m_lookingForGroup.group; - - WorldPacket data(SMSG_LFG_UPDATE_LFG, (4 * MAX_LOOKING_FOR_GROUP_SLOT)); - for (uint8 i = 0; i < MAX_LOOKING_FOR_GROUP_SLOT; ++i) - data << uint32(selection[i].entry | (selection[i].type << 24)); - SendPacket(data); + sWorld.GetLFGQueue().GetMessager().AddMessage([objectGuid = _player->GetObjectGuid()](LFGQueue* queue) + { + queue->SendLFGUpdateLFG(objectGuid); + }); } void WorldSession::SendLFGUpdateLFM() { // Syncs player's own LFG UI selection with what we send here - auto const& info = _player->m_lookingForGroup; - const bool isLFM = info.isLFM(); - auto const& selection = _player->m_lookingForGroup.more; - - WorldPacket data(SMSG_LFG_UPDATE_LFM); - data << uint8(isLFM); - if (isLFM) - data << uint32(selection.entry | (selection.type << 24)); - SendPacket(data); + sWorld.GetLFGQueue().GetMessager().AddMessage([objectGuid = _player->GetObjectGuid()](LFGQueue* queue) + { + queue->SendLFGUpdateLFM(objectGuid); + }); } diff --git a/src/game/LFG/LFGMgr.cpp b/src/game/LFG/LFGMgr.cpp new file mode 100644 index 00000000000..5e665fa0236 --- /dev/null +++ b/src/game/LFG/LFGMgr.cpp @@ -0,0 +1,20 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "LFG/LFGMgr.h" + diff --git a/src/game/LFG/LFGMgr.h b/src/game/LFG/LFGMgr.h new file mode 100644 index 00000000000..d6035b303ba --- /dev/null +++ b/src/game/LFG/LFGMgr.h @@ -0,0 +1,27 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MANGOSSERVER_LFGMGR_H +#define MANGOSSERVER_LFGMGR_H + +#include "LFG/LFGDefines.h" +#include + + + +#endif \ No newline at end of file diff --git a/src/game/LFG/LFGQueue.cpp b/src/game/LFG/LFGQueue.cpp new file mode 100644 index 00000000000..4cd96053cf7 --- /dev/null +++ b/src/game/LFG/LFGQueue.cpp @@ -0,0 +1,615 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "LFG/LFGQueue.h" +#include "World/World.h" + +void LFGQueue::Update() +{ + TimePoint previously = sWorld.GetCurrentClockTime(); + while (!World::IsStopped()) + { + GetMessager().Execute(this); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + }; +} + +void LFGQueue::SetComment(ObjectGuid playerGuid, std::string const& comment) +{ + m_queuedPlayers[playerGuid].comment = comment; +} + +void LFGQueue::SetAutoFill(ObjectGuid playerGuid, bool state) +{ + auto itr = m_queuedPlayers.find(playerGuid); + if (itr == m_queuedPlayers.end()) + return; + + itr->second.autoFill = state; +} + +void LFGQueue::SetAutoJoin(ObjectGuid playerGuid, bool state) +{ + auto itr = m_queuedPlayers.find(playerGuid); + if (itr == m_queuedPlayers.end()) + return; + + itr->second.autoJoin = state; +} + +void LFGQueue::StartLookingForMore(ObjectGuid playerGuid, LFGPlayerQueueInfo info) +{ + auto itr = m_queuedPlayers.find(info.leaderGuid); + if (itr != m_queuedPlayers.end()) + { + sWorld.GetMessager().AddMessage([leaderGuid = info.leaderGuid, playerGuid](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + + if (leaderGuid != playerGuid) + { + WorldPacket data(SMSG_LFG_LEADER_IS_LFM); + player->GetSession()->SendPacket(data); + } + }); + + SendLFGUpdate(info.leaderGuid, playerGuid); + return; + } + + m_queuedPlayers[info.leaderGuid] = info; + + // TODO broadcast LFM preference to party as well instead of full ui update? + GroupUpdateUI(playerGuid, false); + GroupUpdateQueueStatus(playerGuid, playerGuid); + + TryFill(playerGuid, true); +} + +void LFGQueue::StopLookingForMore(ObjectGuid playerGuid) +{ + auto itr = m_queuedPlayers.find(playerGuid); + if (itr == m_queuedPlayers.end()) + return; + + m_queuedPlayers.erase(itr); +} + +void LFGQueue::StartLookingForGroup(LFGPlayerQueueInfo info, ObjectGuid invokerPlayer) +{ + auto itr = m_queuedPlayers.find(info.leaderGuid); + if (itr != m_queuedPlayers.end()) + { + sWorld.GetMessager().AddMessage([leaderGuid = info.leaderGuid, invokerPlayer](World* world) + { + Player* player = sObjectMgr.GetPlayer(invokerPlayer); + + if (leaderGuid != invokerPlayer) + { + WorldPacket data(SMSG_LFG_LEADER_IS_LFM); + player->GetSession()->SendPacket(data); + } + }); + + SendLFGUpdate(info.leaderGuid, invokerPlayer); + return; + } + + m_queuedPlayers[info.leaderGuid] = info; + + SendLFGUpdate(info.leaderGuid, invokerPlayer); + + GroupUpdateQueueStatus(info.leaderGuid, invokerPlayer); + + TryJoin(info.leaderGuid, true); +} + +void LFGQueue::StopLookingForGroup(ObjectGuid leaderGuid, ObjectGuid playerGuid) +{ + auto itr = m_queuedPlayers.find(leaderGuid); + bool success = false; + if (itr != m_queuedPlayers.end()) + { + m_queuedPlayers.erase(itr); + } + + SendLFGUpdate(leaderGuid, playerGuid); + + // TODO: Send out packets +} + +void LFGQueue::TryJoin(ObjectGuid playerGuid, bool initial) +{ + auto itr = m_queuedPlayers.find(playerGuid); + if (itr == m_queuedPlayers.end() || itr->second.autoJoin == false) + return; + + auto& info = itr->second; + + bool attempted = false; + + for (auto& queueData : m_queuedPlayers) + { + // skip not fitting slots + if (!info.isLFG(queueData.second, true)) + continue; + + // stop at join success + if (AddMember(queueData.second, info, queueData.second.more.entry)) + break; + else if (info.members.empty()) + attempted = true; + } + + if (!initial && attempted) + { + sWorld.GetMessager().AddMessage([playerGuid](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + + player->GetSession()->SendMeetingStoneInProgress(); + }); + } +} + +void LFGQueue::TryFill(ObjectGuid leaderGuid, bool initial) +{ + auto itr = m_queuedPlayers.find(leaderGuid); + if (itr == m_queuedPlayers.end() || itr->second.autoFill == false) + return; + + auto& info = itr->second; + + bool attempted = false; + + for (auto& queueData : m_queuedPlayers) + { + // skip not fitting slots + if (!queueData.second.isLFG(info, true)) + continue; + + // stop at false result (full?) + if (!AddMember(info, queueData.second, info.more.entry)) + { + attempted = true; + break; + } + } + + if (!initial && attempted) + { + sWorld.GetMessager().AddMessage([leaderGuid](World* world) + { + Player* player = sObjectMgr.GetPlayer(leaderGuid); + + player->GetSession()->SendMeetingStoneInProgress(); + }); + } +} + +bool LFGQueue::AddMember(LFGPlayerQueueInfo& info, LFGPlayerQueueInfo& playerInfo, uint32 entry) +{ + const bool create = info.members.empty(); + + ObjectGuid playerGuid = playerInfo.leaderGuid; + + info.pendingMembers.push_back(playerGuid); + + playerInfo.pendingTransfer = true; + + sWorld.GetMessager().AddMessage([leaderGuid = info.leaderGuid, playerGuid, entry](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + Player* leader = sObjectMgr.GetPlayer(leaderGuid); + Group* grp = leader->GetGroup(); + + Group* group = player->GetGroup(); + if (group && group != grp) + group->RemoveMember(player->GetObjectGuid(), 0); + + bool created = false; + if (!grp) + { + grp = new Group(); + if (!grp->Create(leader->GetObjectGuid(), leader->GetName())) + { + delete grp; + grp = nullptr; + world->GetLFGQueue().GetMessager().AddMessage([leaderGuid, playerGuid](LFGQueue* queue) + { + queue->RemovePendingJoin(leaderGuid, playerGuid); + }); + return; + } + + created = true; + ObjectGuid gguid = grp->GetObjectGuid(); + sObjectMgr.AddGroup(grp); + bool result = grp->AddMember(player->GetObjectGuid(), player->GetName()); + MANGOS_ASSERT(result); // should never fail - group was empty + } + else if (group != grp) + { + if (!grp->AddMember(player->GetObjectGuid(), player->GetName())) + { + world->GetLFGQueue().GetMessager().AddMessage([leaderGuid, playerGuid](LFGQueue* queue) + { + queue->RemovePendingJoin(leaderGuid, playerGuid); + }); + return; + } + } + + WorldPacket data; + + if (created) + { + GroupMakeMeetingStoneQueueMatchedFor(data, entry, true); + leader->GetSession()->SendPacket(data); + } + + GroupMakeMeetingStoneMemberAdded(data, player->GetObjectGuid()); + grp->BroadcastPacket(data, true, -1, player->GetObjectGuid()); + + GroupMakeMeetingStoneQueueMatchedFor(data, entry); + player->GetSession()->SendPacket(data); + + if (grp->IsFull()) + { + // completely clear from lfg + world->GetLFGQueue().GetMessager().AddMessage([leaderGuid, playerGuid](LFGQueue* queue) + { + queue->PendingJoinSuccess(leaderGuid, playerGuid, true); + }); + } + else + { + // only clear pending transfer and merge infos + world->GetLFGQueue().GetMessager().AddMessage([leaderGuid, playerGuid](LFGQueue* queue) + { + queue->PendingJoinSuccess(leaderGuid, playerGuid, false); + }); + } + }); + + return true; +} + +void LFGQueue::RemovePendingJoin(ObjectGuid leaderGuid, ObjectGuid playerGuid) +{ + auto leaderItr = m_queuedPlayers.find(leaderGuid); + if (leaderItr != m_queuedPlayers.end()) + { + leaderItr->second.pendingMembers.erase(std::remove(leaderItr->second.pendingMembers.begin(), leaderItr->second.pendingMembers.end(), playerGuid), leaderItr->second.pendingMembers.end()); + } + auto playerItr = m_queuedPlayers.find(playerGuid); + if (playerItr != m_queuedPlayers.end()) + { + playerItr->second.pendingTransfer = false; + } +} + +void LFGQueue::PendingJoinSuccess(ObjectGuid leaderGuid, ObjectGuid playerGuid, bool full) +{ + auto leaderItr = m_queuedPlayers.find(leaderGuid); + auto playerItr = m_queuedPlayers.find(playerGuid); + if (leaderItr != m_queuedPlayers.end()) + { + leaderItr->second.pendingMembers.erase(std::remove(leaderItr->second.pendingMembers.begin(), leaderItr->second.pendingMembers.end(), playerGuid), leaderItr->second.pendingMembers.end()); + if (playerItr != m_queuedPlayers.end()) + { + leaderItr->second.members.emplace_back(playerGuid, playerItr->second.level); + m_queuedPlayers.erase(playerItr); + } + } + else if (playerItr != m_queuedPlayers.end()) // async nature + { + playerItr->second.pendingTransfer = false; + } + + if (full && leaderItr != m_queuedPlayers.end()) + m_queuedPlayers.erase(leaderItr); + + GroupUpdate(leaderGuid, playerGuid, full); +} + +void LFGQueue::SendLFGUpdate(ObjectGuid leaderGuid, ObjectGuid playerGuid) const +{ + auto itr = m_queuedPlayers.find(leaderGuid); + bool queued = true; + bool lfm = false; + bool lfg = false; + uint32 data = 0; + if (itr == m_queuedPlayers.end()) + queued = false; + else + { + auto& info = itr->second; + lfm = info.isLFM(); + lfg = info.isLFG(); + data = uint32(info.more.entry | (info.more.type << 24)); + } + + WorldPacket response(SMSG_LFG_UPDATE); + response << uint8(queued); + response << uint8(lfg); + response << uint8(lfm); + if (lfm) + response << data; + + sWorld.GetMessager().AddMessage([playerGuid, response](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + + player->GetSession()->SendPacket(response); + }); +} + +void LFGQueue::SendLFGUpdateLFG(ObjectGuid playerGuid) const +{ + auto itr = m_queuedPlayers.find(playerGuid); + uint32 data[3]; + memset(data, 0, sizeof(data)); + if (itr != m_queuedPlayers.end()) + for (uint8 i = 0; i < MAX_LOOKING_FOR_GROUP_SLOT; ++i) + data[i] = uint32(itr->second.group[i].entry | (itr->second.group[i].type << 24)); + + WorldPacket response(SMSG_LFG_UPDATE_LFG, (4 * MAX_LOOKING_FOR_GROUP_SLOT)); + for (uint8 i = 0; i < MAX_LOOKING_FOR_GROUP_SLOT; ++i) + response << data[i]; + + sWorld.GetMessager().AddMessage([playerGuid, response](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + + player->GetSession()->SendPacket(response); + }); +} + +void LFGQueue::SendLFGUpdateLFM(ObjectGuid playerGuid) const +{ + auto itr = m_queuedPlayers.find(playerGuid); + bool lfm = false; + uint32 data = 0; + if (itr != m_queuedPlayers.end()) + { + auto const& info = itr->second; + lfm = info.isLFM(); + data = uint32(info.more.entry | (info.more.type << 24)); + } + + WorldPacket response(SMSG_LFG_UPDATE_LFM); + response << uint8(lfm); + if (lfm) + response << data; + + sWorld.GetMessager().AddMessage([playerGuid, response](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + + player->GetSession()->SendPacket(response); + }); +} + +void LFGQueue::SendLFGListQueryResponse(ObjectGuid playerGuid, Team playerTeam, LfgType type, uint32 entry) const +{ + WorldPacket response(MSG_LOOKING_FOR_GROUP); + response << uint32(type); // type + response << uint32(entry); // entry from LFGDungeons.dbc + response << uint32(0); // displayed players count, placeholder + response << uint32(0); // found players count, placeholder + + uint32 displayed = 0; + uint32 found = 0; + + for (const auto& data : m_queuedPlayers) + { + auto const& info = data.second; + if (!info.team != playerTeam) + continue; + + if (!info.isLeader || info.full) + continue; + + ++found; + + // Client hardcoded limitation on amount of players sent in the packet handler and UI: + if (found > 50) + continue; + + ++displayed; + + const bool isLFM = info.isLFM(entry, type); + + response << data.first.WriteAsPacked(); // packed guid + response << uint32(info.level); // level + response << uint32(info.zoneId); // current zone + response << uint8(isLFM); // 0x00 - LFG, 0x01 - LFM + + if (isLFM) + { + response << uint32(info.more.entry | (info.more.type << 24)); + response << uint32(0x1000000); + response << uint32(0x1000000); + } + else + { + for (uint8 j = 0; j < MAX_LOOKING_FOR_GROUP_SLOT; ++j) + response << uint32(info.group[j].entry | (info.group[j].type << 24)); + } + + response << info.comment; + + response << uint32(0); // other group members count, placeholder + + if (!info.members.empty()) + { + const size_t offset = (response.wpos() - 4); // other group members count, offset + uint32 count = 0; // other group members count + + for (auto& member : info.members) + { + if (member.partyMember != data.first) + { + response << member.partyMember.WriteAsPacked(); // packed guid + response << uint32(member.level); // player level + ++count; + } + } + + response.put(offset, count); // other group members count, fill the placeholder + } + } + + // fill count placeholders + response.put(4 + 4, displayed); + response.put(4 + 4 + 4, found); + + sWorld.GetMessager().AddMessage([playerGuid, response](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + + player->GetSession()->SendPacket(response); + }); +} + +void LFGQueue::GroupUpdate(ObjectGuid playerGuid, ObjectGuid leaderGuid, bool completed) +{ + GroupUpdateQueueStatus(playerGuid, leaderGuid); + GroupUpdateUI(leaderGuid, completed); +} + +bool LFGQueue::GroupUpdateQueueStatus(ObjectGuid playerGuid, ObjectGuid leaderGuid) +{ + bool lfm = false; + std::vector members; + auto itr = m_queuedPlayers.find(leaderGuid); + if (itr == m_queuedPlayers.end()) + return false; + auto& info = itr->second; + lfm = info.isLFM(); + for (auto& member : info.members) + members.push_back(member.partyMember); + + const bool autojoin = info.isAutoJoin(); + const bool autofill = info.isAutoFill(); + + const bool status = ((autojoin && info.members.empty()) || (autofill && !info.members.empty())); + + if (info.status == status) + return false; + + info.status = status; + + // Sync client's UI queue status + const bool queued = itr != m_queuedPlayers.end(); + + + WorldPacket data(SMSG_LFG_UPDATE_QUEUED, 1); + data << uint8(queued); + + WorldPacket message; + if (queued) + GroupMakeMeetingStoneQueueJoinedFor(message, info.more.entry); + else + GroupMakeMeetingStoneQueueLeftFor(message, info.more.entry); + + sWorld.GetMessager().AddMessage([leaderGuid, data, message, lfm, members](World* world) + { + Player* player = sObjectMgr.GetPlayer(leaderGuid); + + player->GetSession()->SendPacket(data); + + if (lfm) + player->GetSession()->SendPacket(message); + + for (auto& member : members) // TODO: Ignore players in bg here? + { + player->GetSession()->SendPacket(data); + + if (lfm) + player->GetSession()->SendPacket(message); + } + }); + return true; +} + +void LFGQueue::GroupUpdateUI(ObjectGuid leaderGuid, bool completed) +{ + auto itr = m_queuedPlayers.find(leaderGuid); + if (itr == m_queuedPlayers.end()) + return; + + auto& info = itr->second; + std::vector members; + for (auto& member : info.members) + members.push_back(member.partyMember); + + sWorld.GetMessager().AddMessage([leaderGuid, completed, members](World* world) + { + Player* player = sObjectMgr.GetPlayer(leaderGuid); + + if (completed) + player->GetSession()->SendMeetingStoneComplete(); + else + player->GetSession()->SendLFGUpdate(); + + if (sWorld.getConfig(CONFIG_BOOL_CHANNEL_RESTRICTED_LFG) && player->GetSession()->GetSecurity() == SEC_PLAYER) + player->LeaveLFGChannel(); + + for (auto& member : members) + { + if (completed) + player->GetSession()->SendMeetingStoneComplete(); + else + player->GetSession()->SendLFGUpdate(); + + if (sWorld.getConfig(CONFIG_BOOL_CHANNEL_RESTRICTED_LFG) && player->GetSession()->GetSecurity() == SEC_PLAYER) + player->LeaveLFGChannel(); + } + }); +} + +void LFGQueue::GroupMakeMeetingStoneQueueJoinedFor(WorldPacket& message, uint16 entry) +{ + message.Initialize(SMSG_MEETINGSTONE_SETQUEUE); + message << uint32(entry); + message << uint8(0x01); +} + +void LFGQueue::GroupMakeMeetingStoneQueueLeftFor(WorldPacket& message, uint16 entry) +{ + message.Initialize(SMSG_MEETINGSTONE_SETQUEUE); + message << uint32(entry); + message << uint8(0x00); +} + +void LFGQueue::GroupMakeMeetingStoneQueueMatchedFor(WorldPacket& message, uint32 entry, bool asLeader) +{ + message.Initialize(SMSG_MEETINGSTONE_SETQUEUE); + message << uint32(entry); + message << uint8(0x05); + message << uint8(asLeader); +} + +void LFGQueue::GroupMakeMeetingStoneMemberAdded(WorldPacket& data, ObjectGuid guid) +{ + data.Initialize(SMSG_MEETINGSTONE_SETQUEUE, 8); + data << guid; +} diff --git a/src/game/LFG/LFGQueue.h b/src/game/LFG/LFGQueue.h new file mode 100644 index 00000000000..b780d8e43cc --- /dev/null +++ b/src/game/LFG/LFGQueue.h @@ -0,0 +1,145 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LFG_QUEUE_H +#define _LFG_QUEUE_H + +#include "LFG/LFGDefines.h" + +struct LFGGroupQueueInfo +{ + ObjectGuid partyMember; + uint32 level; + + LFGGroupQueueInfo(ObjectGuid partyMember, uint32 level) : partyMember(partyMember), level(level) {} +}; + +struct LFGPlayerQueueInfo +{ + struct Slot + { + bool empty() const { return (!type || !entry); } + void clear() { entry = 0; } + bool set(uint16 _entry, uint16 _type) { entry = _entry; type = _type; return !empty(); } + bool is(uint16 _entry, uint16 _type) const { return entry == _entry && type == _type; } + bool isAuto() const { return entry && (type == LFG_TYPE_DUNGEON || type == LFG_TYPE_HEROIC_DUNGEON); } + + uint16 entry = 0; + uint16 type = LFG_TYPE_DUNGEON; + }; + + inline void clear() + { + more.clear(); + for (auto& slot : group) + slot.clear(); + } + inline bool isAutoFill() const { return more.isAuto(); } + inline bool isAutoJoin() const + { + for (auto& slot : group) + if (slot.isAuto()) + return true; + return false; + } + inline bool isEmpty() const { return (!isLFM() && !isLFG()); } + inline bool isLFG() const + { + for (auto& slot : group) + if (!slot.empty()) + return true; + return false; + } + inline bool isLFG(uint32 entry, uint32 type, bool autoOnly) const + { + for (auto& slot : group) + if (slot.is(uint16(entry), uint16(type)) && (!autoOnly || slot.isAuto())) + return true; + return false; + } + inline bool isLFG(LFGPlayerQueueInfo const& info, bool autoOnly) const { return isLFG(uint16(info.more.entry), uint16(info.more.type), autoOnly); } + inline bool isLFM() const { return !more.empty(); } + inline bool isLFM(uint32 entry, uint32 type) const { return more.is(uint16(entry), uint16(type)); } + + // bool queued = false; + Slot group[MAX_LOOKING_FOR_GROUP_SLOT]; + Slot more; + std::string comment; + Team team = TEAM_NONE; + bool isLeader = true; + bool full = false; + uint32 level = 0; + uint32 zoneId = 0; + bool status = false; + ObjectGuid leaderGuid; + + bool autoFill; + bool autoJoin; + + bool pendingTransfer = false; + + std::vector members; + std::vector pendingMembers; +}; + +class LFGQueue +{ + public: + void Update(); + + void SetComment(ObjectGuid playerGuid, std::string const& comment); + void SetAutoFill(ObjectGuid playerGuid, bool state); + void SetAutoJoin(ObjectGuid playerGuid, bool state); + + void StartLookingForMore(ObjectGuid playerGuid, LFGPlayerQueueInfo info); + void StopLookingForMore(ObjectGuid playerGuid); + + void StartLookingForGroup(LFGPlayerQueueInfo info, ObjectGuid invokerPlayer); + void StopLookingForGroup(ObjectGuid leaderGuid, ObjectGuid playerGuid); + + void TryJoin(ObjectGuid playerGuid, bool initial); + void TryFill(ObjectGuid leaderGuid, bool initial); + + bool AddMember(LFGPlayerQueueInfo& info, LFGPlayerQueueInfo& playerInfo, uint32 entry); + + void RemovePendingJoin(ObjectGuid leaderGuid, ObjectGuid playerGuid); + void PendingJoinSuccess(ObjectGuid leaderGuid, ObjectGuid playerGuid, bool full); + + void SendLFGUpdate(ObjectGuid leaderGuid, ObjectGuid playerGuid) const; + void SendLFGUpdateLFG(ObjectGuid playerGuid) const; + void SendLFGUpdateLFM(ObjectGuid playerGuid) const; + void SendLFGListQueryResponse(ObjectGuid playerGuid, Team playerTeam, LfgType type, uint32 entry) const; + + void GroupUpdate(ObjectGuid playerGuid, ObjectGuid leaderGuid, bool completed = false); + bool GroupUpdateQueueStatus(ObjectGuid playerGuid, ObjectGuid leaderGuid); + void GroupUpdateUI(ObjectGuid leaderGuid, bool completed = false); + static void GroupMakeMeetingStoneQueueJoinedFor(WorldPacket& message, uint16 entry); + static void GroupMakeMeetingStoneQueueLeftFor(WorldPacket& message, uint16 entry); + static void GroupMakeMeetingStoneQueueMatchedFor(WorldPacket& message, uint32 entry, bool asLeader = false); + static void GroupMakeMeetingStoneMemberAdded(WorldPacket& data, ObjectGuid guid); + + Messager& GetMessager() { return m_messager; } + + private: + typedef std::map QueuedPlayersMap; + QueuedPlayersMap m_queuedPlayers; + + Messager m_messager; +}; + +#endif \ No newline at end of file diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index 31974baf79f..929efc755cb 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -97,7 +97,7 @@ bool WorldSessionFilter::Process(WorldPacket const& packet) const /// WorldSession constructor WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, std::string accountName, uint32 accountFlags, uint32 recruitingFriend, bool isARecruiter) : - LookingForGroup_auto_join(false), LookingForGroup_auto_add(true), m_muteTime(mute_time), + m_muteTime(mute_time), _player(nullptr), m_socket(sock ? sock->shared_from_this() : nullptr), m_requestSocket(nullptr), m_localAddress("127.0.0.1"), m_sessionState(WORLD_SESSION_STATE_CREATED), _security(sec), _accountId(id), m_expansion(expansion), m_accountName(accountName), m_accountFlags(accountFlags), m_clientOS(CLIENT_OS_UNKNOWN), m_clientPlatform(CLIENT_PLATFORM_UNKNOWN), m_gameBuild(0), m_accountMaxLevel(0), m_orderCounter(0), m_lastAnticheatUpdate(0), m_anticheat(nullptr), diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index 7c37f93163e..9c5210e26bc 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -123,16 +123,6 @@ enum PartyResult ERR_INVITE_RESTRICTED = 13, }; -enum LfgType : uint32 -{ - LFG_TYPE_NONE = 0, - LFG_TYPE_DUNGEON = 1, - LFG_TYPE_RAID = 2, - LFG_TYPE_QUEST = 3, - LFG_TYPE_ZONE = 4, - LFG_TYPE_HEROIC_DUNGEON = 5 -}; - enum ChatRestrictionType { ERR_CHAT_RESTRICTED = 0, @@ -421,12 +411,8 @@ class WorldSession // Looking For Group // TRUE values set by client sending CMSG_LFG_SET_AUTOJOIN and CMSG_LFM_CLEAR_AUTOFILL before player login - bool LookingForGroup_auto_join = false; - bool LookingForGroup_auto_add = false; - bool LookingForGroup_queue = false; void SendMeetingStoneInProgress(); void SendMeetingStoneComplete(); - void SendLFGListQueryResponse(LfgType type, uint32 entry); void SendLFGUpdate(); void SendLFGUpdateLFG(); void SendLFGUpdateLFM(); diff --git a/src/game/World/World.cpp b/src/game/World/World.cpp index 04eb7775b81..4cff78db5de 100644 --- a/src/game/World/World.cpp +++ b/src/game/World/World.cpp @@ -158,6 +158,8 @@ World::~World() VMAP::VMapFactory::clear(); MMAP::MMapFactory::clear(); + + m_lfgQueueThread.join(); } /// Cleanups before world stop @@ -2876,3 +2878,11 @@ void World::LoadWorldSafeLocs() const sWorldSafeLocsStore.Load(true); sLog.outString(">> Loaded %u world safe locs", sWorldSafeLocsStore.GetRecordCount()); } + +void World::StartLFGQueueThread() +{ + m_lfgQueueThread = std::thread([&]() + { + m_lfgQueue.Update(); + }); +} diff --git a/src/game/World/World.h b/src/game/World/World.h index c4de5ee227e..c61e74f7e11 100644 --- a/src/game/World/World.h +++ b/src/game/World/World.h @@ -30,6 +30,7 @@ #include "Entities/Object.h" #include "Multithreading/Messager.h" #include "Globals/GraveyardManager.h" +#include "LFG/LFGQueue.h" #include #include @@ -678,6 +679,9 @@ class World GraveyardManager& GetGraveyardManager() { return m_graveyardManager; } void SendGMTextFlags(uint32 accountFlag, int32 stringId, std::string type, const char* message); + + LFGQueue& GetLFGQueue() { return m_lfgQueue; } + void StartLFGQueueThread(); protected: void _UpdateGameTime(); // callback for UpdateRealmCharacters @@ -805,6 +809,10 @@ class World std::array, MAX_CLASSES> m_onlineClasses; GraveyardManager m_graveyardManager; + + // Housing this here but logically it is completely asynchronous - TODO: Separate this and unify with BG queue + LFGQueue m_lfgQueue; + std::thread m_lfgQueueThread; }; extern uint32 realmID; diff --git a/src/mangosd/Master.cpp b/src/mangosd/Master.cpp index 896d7699055..69b4cf7dd75 100644 --- a/src/mangosd/Master.cpp +++ b/src/mangosd/Master.cpp @@ -149,6 +149,8 @@ int Master::Run() LoginDatabase.DirectPExecute("UPDATE realmlist SET realmflags = realmflags & ~(%u), population = 0, realmbuilds = '%s' WHERE id = '%u'", REALM_FLAG_OFFLINE, builds.c_str(), realmID); } + sWorld.StartLFGQueueThread(); + MaNGOS::Thread* cliThread = nullptr; #ifdef _WIN32