diff --git a/src/game/Object/ObjectMgr.h b/src/game/Object/ObjectMgr.h index ae20aa7b9..41b0cd153 100644 --- a/src/game/Object/ObjectMgr.h +++ b/src/game/Object/ObjectMgr.h @@ -708,6 +708,28 @@ class ObjectMgr return NULL; } + DungeonFinderRequirements const* GetDungeonFinderRequirements(uint32 mapId, uint32 difficulty) const + { + DungeonFinderRequirementsMap::const_iterator itr = mDungeonFinderRequirementsMap.find(MAKE_PAIR32(mapId, difficulty)); + if (itr != mDungeonFinderRequirementsMap.end()) + return &itr->second; + return NULL; + } + + DungeonFinderRewards const* GetDungeonFinderRewards(uint32 level) const + { + DungeonFinderRewardsMap::const_iterator itr = mDungeonFinderRewardsMap.find(level); + if (itr != mDungeonFinderRewardsMap.end()) + { + return &itr->second; + } + return NULL; + } + + DungeonFinderRequirementsMap const& GetDungeonFinderRequirementsMap() const { return mDungeonFinderRequirementsMap; } + DungeonFinderRewardsMap const& GetDungeonFinderRewardsMap() const { return mDungeonFinderRewardsMap; } + DungeonFinderItemsMap const& GetDungeonFinderItemsMap() const { return mDungeonFinderItemsMap; } + // Static wrappers for various accessors static GameObjectInfo const* GetGameObjectInfo(uint32 id); ///< Wrapper for sGOStorage.LookupEntry static Player* GetPlayer(const char* name); ///< Wrapper for ObjectAccessor::FindPlayerByName diff --git a/src/game/Server/DBCStores.h b/src/game/Server/DBCStores.h index 064f0bdb8..2dc34e493 100644 --- a/src/game/Server/DBCStores.h +++ b/src/game/Server/DBCStores.h @@ -180,6 +180,7 @@ extern DBCStorage sItemRandomPropertiesStore; extern DBCStorage sItemRandomSuffixStore; extern DBCStorage sItemReforgeStore; extern DBCStorage sItemSetStore; +extern DBCStorage sLfgDungeonsStore; extern DBCStorage sLiquidTypeStore; extern DBCStorage sLockStore; extern DBCStorage sMailTemplateStore; diff --git a/src/game/Server/DBCStructure.h b/src/game/Server/DBCStructure.h index e03f3e6a5..e0e41629d 100644 --- a/src/game/Server/DBCStructure.h +++ b/src/game/Server/DBCStructure.h @@ -1249,26 +1249,28 @@ struct ItemSetEntry uint32 required_skill_value; // 36 m_requiredSkillRank }; -/*struct LfgDungeonsEntry +struct LfgDungeonsEntry { - m_ID - m_name_lang - m_minLevel - m_maxLevel - m_target_level - m_target_level_min - m_target_level_max - m_mapID - m_difficulty - m_flags - m_typeID - m_faction - m_textureFilename - m_expansionLevel - m_order_index - m_group_id - m_description_lang -};*/ + uint32 ID; // 0 m_ID + char* name[16]; // 1-16 m_name_lang + uint32 minLevel; // 18 m_minLevel + uint32 maxLevel; // 19 m_maxLevel + uint32 targetLevel; // 20 m_target_level + uint32 targetLevelMin; // 21 m_target_level_min + uint32 targetLevelMax; // 22 m_target_level_max + int32 mapID; // 23 m_mapID + uint32 difficulty; // 24 m_difficulty + uint32 flags; // 25 m_flags + uint32 typeID; // 26 m_typeID + //uint32 faction; // 27 m_faction + //char* textureFilename; // 28 m_textureFilename + uint32 expansionLevel; // 29 m_expansionLevel + uint32 orderIndex; // 30 m_order_index + uint32 groupID; // 31 m_group_id + //char* description[16]; // 32-49 m_Description_lang + + uint32 Entry() const { return ID + ((uint8)typeID << 24); } +}; /*struct LfgDungeonGroupEntry { diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index 0e96ccdc9..4bffef049 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -34,6 +34,7 @@ #include "ObjectGuid.h" #include "AuctionHouseMgr.h" #include "Item.h" +#include "LFGMgr.h" struct ItemPrototype; struct AuctionEntry; @@ -140,28 +141,9 @@ enum PartyResult ERR_PARTY_LFG_TELEPORT_IN_COMBAT = 30 }; -enum LfgJoinResult -{ - ERR_LFG_OK = 0x00, - ERR_LFG_ROLE_CHECK_FAILED = 0x01, - ERR_LFG_GROUP_FULL = 0x02, - ERR_LFG_NO_LFG_OBJECT = 0x04, - ERR_LFG_NO_SLOTS_PLAYER = 0x05, - ERR_LFG_NO_SLOTS_PARTY = 0x06, - ERR_LFG_MISMATCHED_SLOTS = 0x07, - ERR_LFG_PARTY_PLAYERS_FROM_DIFFERENT_REALMS = 0x08, - ERR_LFG_MEMBERS_NOT_PRESENT = 0x09, - ERR_LFG_GET_INFO_TIMEOUT = 0x0A, - ERR_LFG_INVALID_SLOT = 0x0B, - ERR_LFG_DESERTER_PLAYER = 0x0C, - ERR_LFG_DESERTER_PARTY = 0x0D, - ERR_LFG_RANDOM_COOLDOWN_PLAYER = 0x0E, - ERR_LFG_RANDOM_COOLDOWN_PARTY = 0x0F, - ERR_LFG_TOO_MANY_MEMBERS = 0x10, - ERR_LFG_CANT_USE_DUNGEONS = 0x11, - ERR_LFG_ROLE_CHECK_FAILED2 = 0x12, -}; - +/* + * these have been moved to LFGMgr.h for dev21 + * delete from here once all is good with the move enum LfgUpdateType { LFG_UPDATE_JOIN = 5, @@ -178,6 +160,7 @@ enum LfgType LFG_TYPE_HEROIC_DUNGEON = 5, LFG_TYPE_RANDOM_DUNGEON = 6 }; +*/ enum ChatRestrictionType { @@ -255,8 +238,20 @@ class WorldSession void SendNotification(int32 string_id, ...); void SendPetNameInvalid(uint32 error, const std::string& name, DeclinedName* declinedName); void SendLfgSearchResults(LfgType type, uint32 entry); - void SendLfgJoinResult(LfgJoinResult result); - void SendLfgUpdate(bool isGroup, LfgUpdateType updateType, uint32 id); + + // void SendLfgJoinResult(LfgJoinResult result); // delete this if the below proves to work + void SendLfgJoinResult(LfgJoinResult result, LFGState state, partyForbidden const& lockedDungeons); + + // void SendLfgUpdate(bool isGroup, LfgUpdateType updateType, uint32 id); // delete this if the below proves to work + void SendLfgUpdate(bool isGroup, LFGPlayerStatus status); + void SendLfgQueueStatus(LFGQueueStatus const& status); + void SendLfgRoleCheckUpdate(LFGRoleCheck const& roleCheck); + void SendLfgRoleChosen(uint64 rawGuid, uint8 roles); + void SendLfgProposalUpdate(LFGProposal const& proposal); + void SendLfgTeleportError(uint8 error); + void SendLfgRewards(LFGRewards const& rewards); + void SendLfgBootUpdate(LFGBoot const& boot); + void SendPartyResult(PartyOperation operation, const std::string& member, PartyResult res); void SendGuildInvite(Player* player, bool alreadyInGuild = false); void SendGroupInvite(Player* player, bool alreadyInGroup = false); diff --git a/src/game/WorldHandlers/Group.h b/src/game/WorldHandlers/Group.h index aa7708957..a90765be4 100644 --- a/src/game/WorldHandlers/Group.h +++ b/src/game/WorldHandlers/Group.h @@ -255,13 +255,14 @@ class Group void SetLootThreshold(ItemQualities threshold) { m_lootThreshold = threshold; } void Disband(bool hideDestroy = false); - // properties accessories + // properties accessors uint32 GetId() const { return m_Id; } ObjectGuid GetObjectGuid() const { return ObjectGuid(HIGHGUID_GROUP, GetId()); } bool IsFull() const { return (m_groupType == GROUPTYPE_NORMAL) ? (m_memberSlots.size() >= MAX_GROUP_SIZE) : (m_memberSlots.size() >= MAX_RAID_SIZE); } GroupType GetGroupType() const { return m_groupType; } bool isRaidGroup() const { return m_groupType & GROUPTYPE_RAID; } bool isBGGroup() const { return m_bgGroup != NULL; } + bool isLFGGroup() const { return m_groupType & GROUPTYPE_LFD; } bool IsCreated() const { return GetMembersCount() > 0; } ObjectGuid GetLeaderGuid() const { return m_leaderGuid; } const char* GetLeaderName() const { return m_leaderName.c_str(); } @@ -270,6 +271,7 @@ class Group ItemQualities GetLootThreshold() const { return m_lootThreshold; } // member manipulation methods + void SetAsLfgGroup() { m_groupType = GroupType(m_groupType | GROUPTYPE_LFD); } bool IsMember(ObjectGuid guid) const { return _getMemberCSlot(guid) != m_memberSlots.end(); } bool IsLeader(ObjectGuid guid) const { return GetLeaderGuid() == guid; } ObjectGuid GetMemberGuid(const std::string& name) diff --git a/src/game/WorldHandlers/LFGHandler.cpp b/src/game/WorldHandlers/LFGHandler.cpp index 36ab777a3..91194002a 100644 --- a/src/game/WorldHandlers/LFGHandler.cpp +++ b/src/game/WorldHandlers/LFGHandler.cpp @@ -23,6 +23,7 @@ */ #include "WorldSession.h" +#include "LFGMgr.h" #include "Log.h" #include "Player.h" #include "WorldPacket.h" @@ -222,6 +223,9 @@ void WorldSession::SendLfgSearchResults(LfgType type, uint32 entry) SendPacket(&data); } +/* + * pre dev21 version + * delete this if the one below it proves to be good (chucky) void WorldSession::SendLfgJoinResult(LfgJoinResult result) { WorldPacket data(SMSG_LFG_JOIN_RESULT, 0); @@ -246,7 +250,41 @@ void WorldSession::SendLfgJoinResult(LfgJoinResult result) SendPacket(&data); } +*/ +void WorldSession::SendLfgJoinResult(LfgJoinResult result, LFGState state, partyForbidden const& lockedDungeons) +{ + uint32 packetSize = 0; + for (partyForbidden::const_iterator it = lockedDungeons.begin(); it != lockedDungeons.end(); ++it) + packetSize += 12 + uint32(it->second.size()) * 8; + + WorldPacket data(SMSG_LFG_JOIN_RESULT, packetSize); + data << uint32(result); + data << uint32(state); + + if (!lockedDungeons.empty()) + { + for (partyForbidden::const_iterator it = lockedDungeons.begin(); it != lockedDungeons.end(); ++it) + { + dungeonForbidden dungeonInfo = it->second; + + data << uint64(it->first); // object guid of player + data << uint32(dungeonInfo.size()); // amount of their locked dungeons + + for (dungeonForbidden::iterator itr = dungeonInfo.begin(); itr != dungeonInfo.end(); ++itr) + { + data << uint32(itr->first); // dungeon entry + data << uint32(itr->second); // reason for dungeon being forbidden/locked + } + } + } + + SendPacket(&data); +} + +/* + * new version hass been added below for dev21 + * delete this once the new one proves to work (chucky) void WorldSession::SendLfgUpdate(bool isGroup, LfgUpdateType updateType, uint32 id) { WorldPacket data(isGroup ? SMSG_LFG_UPDATE_PARTY : SMSG_LFG_UPDATE_PLAYER, 0); @@ -276,3 +314,258 @@ void WorldSession::SendLfgUpdate(bool isGroup, LfgUpdateType updateType, uint32 } SendPacket(&data); } +*/ + + +/* + * The following functions were added for dev21, teken from Two + * If tey prove to work, then delete/alter this comment (chucky) + */ + +void WorldSession::SendLfgUpdate(bool isGroup, LFGPlayerStatus status) +{ + uint8 dungeonSize = uint8(status.dungeonList.size()); + + bool isQueued = false, joinLFG = false; + + switch (status.updateType) + { + case LFG_UPDATE_JOIN: + case LFG_UPDATE_ADDED_TO_QUEUE: + isQueued = true; + case LFG_UPDATE_PROPOSAL_BEGIN: + if (isGroup) + joinLFG = true; + break; + case LFG_UPDATE_STATUS: + isQueued = (status.state == LFG_STATE_QUEUED); + + if (isGroup) + joinLFG = (status.state != LFG_STATE_ROLECHECK) && (status.state != LFG_STATE_NONE); + break; + default: + break; + } + + WorldPacket data(isGroup ? SMSG_LFG_UPDATE_PARTY : SMSG_LFG_UPDATE_PLAYER); + data << uint8(status.updateType); + + data << uint8(dungeonSize > 0); + + if (dungeonSize) + { + if (isGroup) + data << uint8(joinLFG); + data << uint8(isQueued); + data << uint8(0); + data << uint8(0); + + if (isGroup) + { + for (uint32 i = 0; i < 3; ++i) + data << uint8(0); + } + + data << uint8(dungeonSize); + for (std::set::iterator it = status.dungeonList.begin(); it != status.dungeonList.end(); ++it) + data << uint32(*it); + + data << status.comment; + } + SendPacket(&data); +} + +void WorldSession::SendLfgQueueStatus(LFGQueueStatus const& status) +{ + WorldPacket data(SMSG_LFG_QUEUE_STATUS, 31); + + data << uint32(status.dungeonID); + data << int32(status.playerAvgWaitTime); + data << int32(status.avgWaitTime); + data << int32(status.tankAvgWaitTime); + data << int32(status.healerAvgWaitTime); + data << int32(status.dpsAvgWaitTime); + data << uint8(status.neededTanks); + data << uint8(status.neededHeals); + data << uint8(status.neededDps); + data << uint32(status.timeSpentInQueue); + + SendPacket(&data); +} + +void WorldSession::SendLfgRoleCheckUpdate(LFGRoleCheck const& roleCheck) +{ + WorldPacket data(SMSG_LFG_ROLE_CHECK_UPDATE); + + data << uint32(roleCheck.state); + data << uint8(roleCheck.state == LFG_ROLECHECK_INITIALITING); + + std::set dungeons; + if (roleCheck.randomDungeonID) + dungeons.insert(roleCheck.randomDungeonID); + else + dungeons = roleCheck.dungeonList; + + data << uint8(dungeons.size()); + if (!dungeons.empty()) + for (std::set::iterator it = dungeons.begin(); it != dungeons.end(); ++it) + data << uint32(sLFGMgr.GetDungeonEntry(*it)); + + data << uint8(roleCheck.currentRoles.size()); + if (!roleCheck.currentRoles.empty()) + { + ObjectGuid leaderGuid = ObjectGuid(roleCheck.leaderGuidRaw); + uint8 leaderRoles = roleCheck.currentRoles.find(leaderGuid)->second; + Player* pLeader = ObjectAccessor::FindPlayer(leaderGuid); + + data << uint64(leaderGuid.GetRawValue()); + data << uint8(leaderRoles > 0); + data << uint32(leaderRoles); + data << uint8(pLeader->getLevel()); + + for (roleMap::const_iterator rItr = roleCheck.currentRoles.begin(); rItr != roleCheck.currentRoles.end(); ++rItr) + { + if (rItr->first == leaderGuid) + continue; // exclude the leader + + ObjectGuid plrGuid = rItr->first; + + Player* pPlayer = ObjectAccessor::FindPlayer(plrGuid); + + data << uint64(plrGuid.GetRawValue()); + data << uint8(rItr->second > 0); + data << uint32(rItr->second); + data << uint8(pPlayer->getLevel()); + } + } + + SendPacket(&data); +} + +void WorldSession::SendLfgRoleChosen(uint64 rawGuid, uint8 roles) +{ + WorldPacket data(SMSG_ROLE_CHOSEN, 13); + data << uint64(rawGuid); + data << uint8(roles > 0); + data << uint32(roles); + SendPacket(&data); +} + +void WorldSession::SendLfgProposalUpdate(LFGProposal const& proposal) +{ + Player* pPlayer = GetPlayer(); + ObjectGuid plrGuid = pPlayer->GetObjectGuid(); + ObjectGuid plrGroupGuid = proposal.groups.find(plrGuid)->second; + + uint32 dungeonEntry = sLFGMgr.GetDungeonEntry(proposal.dungeonID); + bool showProposal = !proposal.isNew && proposal.groupRawGuid == plrGroupGuid.GetRawValue(); + + WorldPacket data(SMSG_LFG_PROPOSAL_UPDATE, 15 + (9 * proposal.currentRoles.size())); + + data << uint32(dungeonEntry); // Dungeon Entry + data << uint8(proposal.state); // Proposal state + data << uint32(proposal.id); // ID of proposal + data << uint32(proposal.encounters); // Encounters done + data << uint8(showProposal); // Show or hide proposal window [todo-this] + data << uint8(proposal.currentRoles.size()); // Size of group + + for (playerGroupMap::const_iterator it = proposal.groups.begin(); it != proposal.groups.end(); ++it) + { + ObjectGuid grpPlrGuid = it->first; + uint8 grpPlrRole = proposal.currentRoles.find(grpPlrGuid)->second; + LFGProposalAnswer grpPlrAnswer = proposal.answers.find(grpPlrGuid)->second; + + data << uint32(grpPlrRole); // Player's role + data << uint8(grpPlrGuid == plrGuid); // Is this player me? + + if (it->second != 0) + { + data << uint8(it->second == ObjectGuid(proposal.groupRawGuid)); // Is player in the proposed group? + data << uint8(it->second == plrGroupGuid); // Is player in the same group as myself? + } + else + { + data << uint8(0); + data << uint8(0); + } + + data << uint8(grpPlrAnswer != LFG_ANSWER_PENDING); // Has the player selected an answer? + data << uint8(grpPlrAnswer == LFG_ANSWER_AGREE); // Has the player agreed to do the dungeon? + } + SendPacket(&data); +} + +void WorldSession::SendLfgTeleportError(uint8 error) +{ + DEBUG_LOG("SMSG_LFG_TELEPORT_DENIED"); + WorldPacket data(SMSG_LFG_TELEPORT_DENIED, 4); + data << uint32(error); + SendPacket(&data); +} + +void WorldSession::SendLfgRewards(LFGRewards const& rewards) +{ + DEBUG_LOG("SMSG_LFG_PLAYER_REWARD"); + + WorldPacket data(SMSG_LFG_PLAYER_REWARD, 42); + data << uint32(rewards.randomDungeonEntry); + data << uint32(rewards.groupDungeonEntry); + data << uint8(rewards.hasDoneDaily); + data << uint32(1); + data << uint32(rewards.moneyReward); + data << uint32(rewards.expReward); + data << uint32(0); + data << uint32(0); + if (rewards.itemID != 0) + { + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(rewards.itemID); + if (pProto) + { + data << uint8(1); + data << uint32(rewards.itemID); + data << uint32(pProto->DisplayInfoID); + data << uint32(rewards.itemAmount); + } + } + else + data << uint8(0); + SendPacket(&data); +} + +void WorldSession::SendLfgBootUpdate(LFGBoot const& boot) +{ + DEBUG_LOG("SMSG_LFG_BOOT_PLAYER"); + + ObjectGuid plrGuid = GetPlayer()->GetObjectGuid(); + LFGProposalAnswer plrAnswer = boot.answers.find(plrGuid)->second; + + uint32 voteCount = 0, yayCount = 0; + for (proposalAnswerMap::const_iterator it = boot.answers.begin(); it != boot.answers.end(); ++it) + { + if (it->second != LFG_ANSWER_PENDING) + { + ++voteCount; + if (it->second == LFG_ANSWER_AGREE) + ++yayCount; + } + } + + uint32 timeLeft = uint8(((boot.startTime + LFG_TIME_BOOT) - time(NULL)) / 1000); + + WorldPacket data(SMSG_LFG_BOOT_PLAYER, 27 + boot.reason.length()); + + data << uint8(boot.inProgress); // Is boot still ongoing? + data << uint8(plrAnswer != LFG_ANSWER_PENDING); // Did this player vote yet? + data << uint8(plrAnswer == LFG_ANSWER_AGREE); // Did this player agree to boot them? + data << uint64(boot.playerVotedOn.GetRawValue()); // Potentially booted player's objectguid value + data << uint32(voteCount); // Number of players who've voted so far + data << uint32(yayCount); // Number of players who've voted against the plr so far + data << uint32(timeLeft); // Time left in seconds + data << uint32(REQUIRED_VOTES_FOR_BOOT); // Number of votes needed to win + data << boot.reason.c_str(); // Reason given for booting + + SendPacket(&data); +} + + + diff --git a/src/game/WorldHandlers/LFGMgr.cpp b/src/game/WorldHandlers/LFGMgr.cpp new file mode 100644 index 000000000..ad786f915 --- /dev/null +++ b/src/game/WorldHandlers/LFGMgr.cpp @@ -0,0 +1,1958 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2016 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +#include "DBCEnums.h" +#include "DBCStores.h" +#include "DBCStructure.h" +#include "GameEventMgr.h" +#include "Group.h" +#include "LFGMgr.h" +#include "Object.h" +#include "Player.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "SharedDefines.h" +#include "WorldSession.h" + +INSTANTIATE_SINGLETON_1(LFGMgr); + +LFGMgr::LFGMgr() +{ + m_proposalId = 0; +} + +LFGMgr::~LFGMgr() +{ + m_dailyAny.clear(); + m_dailyTBCHeroic.clear(); + m_dailyLKNormal.clear(); + m_dailyLKHeroic.clear(); + + m_playerData.clear(); + m_queueSet.clear(); + + m_playerStatusMap.clear(); + m_groupStatusMap.clear(); + m_groupSet.clear(); + m_proposalMap.clear(); + + m_roleCheckMap.clear(); + + m_bootStatusMap.clear(); + + m_tankWaitTime.clear(); + m_healerWaitTime.clear(); + m_dpsWaitTime.clear(); + m_avgWaitTime.clear(); +} + +void LFGMgr::Update() +{ + //todo: remove old queues, proposals & boot votes + + // remove old role checks + RemoveOldRoleChecks(); + + // go through a waitTimeMap::iterator for each wait map and update times based on player count + for (waitTimeMap::iterator tankItr = m_tankWaitTime.begin(); tankItr != m_tankWaitTime.end(); ++tankItr) + { + LFGWait waitInfo = tankItr->second; + if (waitInfo.doAverage) + { + int32 lastTime = waitInfo.previousTime; + int32 thisTime = waitInfo.time; + + // average of the two join times + waitInfo.time = (thisTime + lastTime) / 2; + + // now set what was just the current wait time to the previous time for a later calculation + waitInfo.previousTime = thisTime; + waitInfo.doAverage = false; + + tankItr->second = waitInfo; + } + } + for (waitTimeMap::iterator healItr = m_healerWaitTime.begin(); healItr != m_healerWaitTime.end(); ++healItr) + { + LFGWait waitInfo = healItr->second; + if (waitInfo.doAverage) + { + int32 lastTime = waitInfo.previousTime; + int32 thisTime = waitInfo.time; + + // average of the two join times + waitInfo.time = (thisTime + lastTime) / 2; + + // now set what was just the current wait time to the previous time for a later calculation + waitInfo.previousTime = thisTime; + waitInfo.doAverage = false; + + healItr->second = waitInfo; + } + } + for (waitTimeMap::iterator dpsItr = m_dpsWaitTime.begin(); dpsItr != m_dpsWaitTime.end(); ++dpsItr) + { + LFGWait waitInfo = dpsItr->second; + if (waitInfo.doAverage) + { + int32 lastTime = waitInfo.previousTime; + int32 thisTime = waitInfo.time; + + // average of the two join times + waitInfo.time = (thisTime + lastTime) / 2; + + // now set what was just the current wait time to the previous time for a later calculation + waitInfo.previousTime = thisTime; + waitInfo.doAverage = false; + + dpsItr->second = waitInfo; + } + } + for (waitTimeMap::iterator avgItr = m_avgWaitTime.begin(); avgItr != m_avgWaitTime.end(); ++avgItr) + { + LFGWait waitInfo = avgItr->second; + if (waitInfo.doAverage) + { + int32 lastTime = waitInfo.previousTime; + int32 thisTime = waitInfo.time; + + // average of the two join times + waitInfo.time = (thisTime + lastTime) / 2; + + // now set what was just the current wait time to the previous time for a later calculation + waitInfo.previousTime = thisTime; + waitInfo.doAverage = false; + + avgItr->second = waitInfo; + } + } + + // Queue System + FindQueueMatches(); + SendQueueStatus(); +} + +void LFGMgr::JoinLFG(uint32 roles, std::set dungeons, std::string comments, Player* plr) +{ + // Todo: + // - see if any of this code/information can be put into a generalized class for other use + // - look into splitting this into 2 fns- one for player case, one for group + Group* pGroup = plr->GetGroup(); + ObjectGuid guid = (pGroup) ? pGroup->GetObjectGuid() : plr->GetObjectGuid(); + uint32 randomDungeonID; // used later if random dungeon has been chosen + + LFGPlayers* currentInfo = GetPlayerOrPartyData(guid); + + // check if we actually have info on the player/group right now + if (currentInfo) + { + bool groupCurrentlyInDungeon = pGroup && pGroup->isLFGGroup() && currentInfo->currentState != LFG_STATE_FINISHED_DUNGEON; + + // are they already queued? + if (currentInfo->currentState == LFG_STATE_QUEUED) + { + // remove from that queue so they can later join this one + queueSet::iterator qItr = m_queueSet.find(guid); + if (qItr != m_queueSet.end()) + m_queueSet.erase(qItr); + // note: do we need to send a packet telling them the current queue is over? + } + + // are they already in a dungeon? + if (groupCurrentlyInDungeon) + { + std::set currentDungeon = currentInfo->dungeonList; + + dungeons.clear(); + dungeons.insert(*currentDungeon.begin()); // they should only have 1 dungeon in the map + } + } + + // used for upcoming checks + bool isRandom = false; + bool isRaid = false; + bool isDungeon = false; + + LfgJoinResult result = GetJoinResult(plr); + if (result == ERR_LFG_OK) + { + // additional checks on dungeon selection + for (std::set::iterator it = dungeons.begin(); it != dungeons.end(); ++it) + { + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(*it); + switch (dungeon->typeID) + { + case LFG_TYPE_RANDOM_DUNGEON: + if (dungeons.size() > 1) + result = ERR_LFG_INVALID_SLOT; + else + isRandom = true; + case LFG_TYPE_DUNGEON: + case LFG_TYPE_HEROIC_DUNGEON: + if (isRaid) + result = ERR_LFG_MISMATCHED_SLOTS; + isDungeon = true; + break; + case LFG_TYPE_RAID: + if (isDungeon) + result = ERR_LFG_MISMATCHED_SLOTS; + isRaid = true; + break; + default: // one of the other types + result = ERR_LFG_INVALID_SLOT; + break; + } + } + } + + // since our join result may have just changed, check it again + if (result == ERR_LFG_OK) + { + if (isRandom) + { + // store the current dungeon id (replaced into the dungeon set later) + randomDungeonID = *dungeons.begin(); + // fetch all dungeons with our groupID and add to set + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(*dungeons.begin()); + + if (dungeon) + { + uint32 group = dungeon->groupID; + + for (uint32 id = 0; id < sLfgDungeonsStore.GetNumRows(); ++id) + { + LfgDungeonsEntry const* dungeonList = sLfgDungeonsStore.LookupEntry(id); + if (dungeonList) + { + if (dungeonList->groupID == group) + dungeons.insert(dungeonList->ID); // adding to set + } + } + } + else + result = ERR_LFG_NO_LFG_OBJECT; + } + } + + partyForbidden partyLockedDungeons; + if (result == ERR_LFG_OK) + { + // do FindRandomDungeonsNotForPlayer for the plr or whole group + if (pGroup) + { + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* pGroupPlr = itr->getSource()) + { + ObjectGuid plrGuid = pGroupPlr->GetObjectGuid(); + + dungeonForbidden lockedDungeons = FindRandomDungeonsNotForPlayer(pGroupPlr); + partyLockedDungeons[plrGuid] = lockedDungeons; + + for (dungeonForbidden::iterator it = lockedDungeons.begin(); it != lockedDungeons.end(); ++it) + { + uint32 dungeonID = (it->first & 0x00FFFFFF); + + std::set::iterator setItr = dungeons.find(dungeonID); + if (setItr != dungeons.end()) + dungeons.erase(*setItr); + } + } + } + } + else + { + dungeonForbidden lockedDungeons = FindRandomDungeonsNotForPlayer(plr); + partyLockedDungeons[guid] = lockedDungeons; + + for (dungeonForbidden::iterator it = lockedDungeons.begin(); it != lockedDungeons.end(); ++it) + { + uint32 dungeonID = (it->first & 0x00FFFFFF); + + std::set::iterator setItr = dungeons.find(dungeonID); + if (setItr != dungeons.end()) + dungeons.erase(*setItr); + } + } + + if (!dungeons.empty()) + partyLockedDungeons.clear(); + else + result = (pGroup) ? ERR_LFG_NO_SLOTS_PARTY : ERR_LFG_NO_SLOTS_PLAYER; + } + + // If our result is not ERR_LFG_OK, send join result now with err message + if (result != ERR_LFG_OK) + { + plr->GetSession()->SendLfgJoinResult(result, LFG_STATE_NONE, partyLockedDungeons); + return; + } + + if (pGroup) + { + ObjectGuid leaderGuid = pGroup->GetLeaderGuid(); + + LFGRoleCheck roleCheck; + roleCheck.state = LFG_ROLECHECK_INITIALITING; + roleCheck.dungeonList = dungeons; + roleCheck.randomDungeonID = randomDungeonID; + roleCheck.leaderGuidRaw = leaderGuid.GetRawValue(); + roleCheck.waitForRoleTime = time_t(time(NULL) + LFG_TIME_ROLECHECK); + + m_roleCheckMap[guid] = roleCheck; + + // place original dungeon ID back in the set + if (isRandom) + { + dungeons.clear(); + dungeons.insert(randomDungeonID); + } + + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* pGroupPlr = itr->getSource()) + { + LFGPlayerStatus overallStatus(LFG_STATE_NONE, LFG_UPDATE_JOIN, dungeons, comments); + + pGroupPlr->GetSession()->SendLfgUpdate(true, overallStatus); + overallStatus.state = LFG_STATE_ROLECHECK; + + ObjectGuid plrGuid = pGroupPlr->GetObjectGuid(); + roleCheck.currentRoles[plrGuid] = 0; + + m_playerStatusMap[plrGuid] = overallStatus; + } + } + // used later if they enter the queue + LFGPlayers groupInfo(LFG_STATE_NONE, dungeons, roleCheck.currentRoles, comments, false, time(NULL), 0, 0, 0); + m_playerData[guid] = groupInfo; + + PerformRoleCheck(plr, pGroup, (uint8)roles); + } + else + { + // place original dungeon ID back in the set + if (isRandom) + { + dungeons.clear(); + dungeons.insert(randomDungeonID); + } + + // set up a role map and then an lfgplayer struct + roleMap playerRole; + playerRole[guid] = (uint8)roles; + + LFGPlayers playerInfo(LFG_STATE_QUEUED, dungeons, playerRole, comments, false, time(NULL), 0, 0, 0); + m_playerData[guid] = playerInfo; + + // set up a status struct for client requests/updates + LFGPlayerStatus plrStatus; + plrStatus.updateType = LFG_UPDATE_JOIN; + plrStatus.state = LFG_STATE_NONE; + plrStatus.dungeonList = dungeons; + plrStatus.comment = comments; + + // Send information back to the client + plr->GetSession()->SendLfgJoinResult(result, LFG_STATE_NONE, partyLockedDungeons); + plr->GetSession()->SendLfgUpdate(false, plrStatus); + + plrStatus.state = LFG_STATE_QUEUED; + m_playerStatusMap[guid] = plrStatus; + AddToQueue(guid); + } +} + +void LFGMgr::LeaveLFG(Player* plr, bool isGroup) +{ + if (isGroup) + { + Group* pGroup = plr->GetGroup(); + ObjectGuid grpGuid = pGroup->GetObjectGuid(); + + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* pGroupPlr = itr->getSource()) + { + ObjectGuid grpPlrGuid = pGroupPlr->GetObjectGuid(); + + LFGPlayerStatus grpPlrStatus = GetPlayerStatus(grpPlrGuid); + switch (grpPlrStatus.state) + { + case LFG_STATE_PROPOSAL: + case LFG_STATE_QUEUED: + grpPlrStatus.updateType = LFG_UPDATE_LEAVE; + grpPlrStatus.state = LFG_STATE_NONE; + SendLfgUpdate(grpPlrGuid, grpPlrStatus, true); + break; + case LFG_STATE_ROLECHECK: + PerformRoleCheck(NULL, pGroup, 0); + break; + //todo: other state cases after they get implemented + } + + m_playerData.erase(grpPlrGuid); + m_playerStatusMap.erase(grpPlrGuid); + } + } + + m_queueSet.erase(grpGuid); + m_playerData.erase(grpGuid); + } + else + { + ObjectGuid plrGuid = plr->GetObjectGuid(); + + LFGPlayerStatus plrStatus = GetPlayerStatus(plrGuid); + switch (plrStatus.state) + { + case LFG_STATE_PROPOSAL: + case LFG_STATE_QUEUED: + plrStatus.updateType = LFG_UPDATE_LEAVE; + plrStatus.state = LFG_STATE_NONE; + SendLfgUpdate(plrGuid, plrStatus, false); + break; + // do other states after being implemented, if applicable for a single plr + } + + m_queueSet.erase(plrGuid); + m_playerData.erase(plrGuid); + m_playerStatusMap.erase(plrGuid); + } + +} + +LFGPlayers* LFGMgr::GetPlayerOrPartyData(ObjectGuid guid) +{ + playerData::iterator it = m_playerData.find(guid); + if (it != m_playerData.end()) + return &(it->second); + else + return NULL; +} + +LFGProposal* LFGMgr::GetProposalData(uint32 proposalID) +{ + proposalMap::iterator it = m_proposalMap.find(proposalID); + if (it != m_proposalMap.end()) + return &(it->second); + else + return NULL; +} + +LfgJoinResult LFGMgr::GetJoinResult(Player* plr) +{ + LfgJoinResult result; + Group* pGroup = plr->GetGroup(); + + /* Reasons for not entering: + * Deserter spell + * Dungeon finder cooldown + * In a battleground + * In an arena + * Queued for battleground + * Too many members in group + * Group member disconnected + * Group member too low/high level + * Any group member cannot enter for x reason any other player can't + */ + + if (plr->HasAura(LFG_DESERTER_SPELL)) + result = ERR_LFG_DESERTER_PLAYER; + else if (plr->InBattleGround() || plr->InBattleGroundQueue() || plr->InArena()) + result = ERR_LFG_CANT_USE_DUNGEONS; + else if (plr->HasAura(LFG_COOLDOWN_SPELL)) + result = ERR_LFG_RANDOM_COOLDOWN_PLAYER; + + if (pGroup) + { + if (pGroup->GetMembersCount() > 5) + result = ERR_LFG_TOO_MANY_MEMBERS; + else + { + uint8 currentMemberCount = 0; + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* pGroupPlr = itr->getSource()) + { + // check if the group members are level 15+ to use finder + if (pGroupPlr->getLevel() < 15) + result = ERR_LFG_CANT_USE_DUNGEONS; + else if (pGroupPlr->HasAura(LFG_DESERTER_SPELL)) + result = ERR_LFG_DESERTER_PARTY; + else if (pGroupPlr->InBattleGround() || pGroupPlr->InBattleGroundQueue() || pGroupPlr->InArena()) + result = ERR_LFG_CANT_USE_DUNGEONS; + else if (pGroupPlr->HasAura(LFG_COOLDOWN_SPELL)) + result = ERR_LFG_RANDOM_COOLDOWN_PARTY; + else + result = ERR_LFG_OK; + + ++currentMemberCount; + } + } + + if (result == ERR_LFG_OK && currentMemberCount != pGroup->GetMembersCount()) + result = ERR_LFG_MEMBERS_NOT_PRESENT; + } + } + else + result = ERR_LFG_OK; + + return result; +} + +LFGPlayerStatus LFGMgr::GetPlayerStatus(ObjectGuid guid) +{ + LFGPlayerStatus status; + + playerStatusMap::iterator it = m_playerStatusMap.find(guid); + if (it != m_playerStatusMap.end()) + status = it->second; + + return status; +} + +void LFGMgr::SetPlayerComment(ObjectGuid guid, std::string comment) +{ + LFGPlayerStatus status = GetPlayerStatus(guid); + status.comment = comment; + + m_playerStatusMap[guid] = status; +} + +void LFGMgr::SetPlayerState(ObjectGuid guid, LFGState state) +{ + LFGPlayerStatus status = GetPlayerStatus(guid); + status.state = state; + + m_playerStatusMap[guid] = status; +} + +void LFGMgr::SetPlayerUpdateType(ObjectGuid guid, LfgUpdateType updateType) +{ + LFGPlayerStatus status = GetPlayerStatus(guid); + status.updateType = updateType; + + m_playerStatusMap[guid] = status; +} + +ItemRewards LFGMgr::GetDungeonItemRewards(uint32 dungeonId, DungeonTypes type) +{ + ItemRewards rewards; + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(dungeonId); + if (dungeon) + { + uint32 minLevel = dungeon->minLevel; + uint32 maxLevel = dungeon->maxLevel; + uint32 avgLevel = (minLevel+maxLevel)/2; // otherwise there are issues + + DungeonFinderItemsMap const& itemBuffer = sObjectMgr.GetDungeonFinderItemsMap(); + for (DungeonFinderItemsMap::const_iterator it = itemBuffer.begin(); it != itemBuffer.end(); ++it) + { + DungeonFinderItems itemCache = it->second; + if (itemCache.dungeonType == type) + { + // should only be one of this inequality in the map + if ((avgLevel >= itemCache.minLevel) && (avgLevel <= itemCache.maxLevel)) + { + rewards.itemId = itemCache.itemReward; + rewards.itemAmount = itemCache.itemAmount; + return rewards; + } + } + } + } + return rewards; +} + +DungeonTypes LFGMgr::GetDungeonType(uint32 dungeonId) +{ + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(dungeonId); + if (dungeon) + { + switch (dungeon->expansionLevel) + { + case 0: + return DUNGEON_CLASSIC; + case 1: + { + if (dungeon->difficulty == DUNGEON_DIFFICULTY_NORMAL) + return DUNGEON_TBC; + else if (dungeon->difficulty == DUNGEON_DIFFICULTY_HEROIC) + return DUNGEON_TBC_HEROIC; + } + case 2: + { + if (dungeon->difficulty == DUNGEON_DIFFICULTY_NORMAL) + return DUNGEON_WOTLK; + else if (dungeon->difficulty == DUNGEON_DIFFICULTY_HEROIC) + return DUNGEON_WOTLK_HEROIC; + } + default: + return DUNGEON_UNKNOWN; + } + } + return DUNGEON_UNKNOWN; +} + +void LFGMgr::RegisterPlayerDaily(uint32 guidLow, DungeonTypes dungeon) +{ + switch (dungeon) + { + case DUNGEON_CLASSIC: + case DUNGEON_TBC: + m_dailyAny.insert(guidLow); + break; + case DUNGEON_TBC_HEROIC: + m_dailyTBCHeroic.insert(guidLow); + break; + case DUNGEON_WOTLK: + m_dailyLKNormal.insert(guidLow); + break; + case DUNGEON_WOTLK_HEROIC: + m_dailyLKHeroic.insert(guidLow); + break; + default: + break; + } +} + +bool LFGMgr::HasPlayerDoneDaily(uint32 guidLow, DungeonTypes dungeon) +{ + switch (dungeon) + { + case DUNGEON_CLASSIC: + case DUNGEON_TBC: + return (m_dailyAny.find(guidLow) != m_dailyAny.end()) ? true : false; + case DUNGEON_TBC_HEROIC: + return (m_dailyTBCHeroic.find(guidLow) != m_dailyTBCHeroic.end()) ? true : false; + case DUNGEON_WOTLK: + return (m_dailyLKNormal.find(guidLow) != m_dailyLKNormal.end()) ? true : false; + case DUNGEON_WOTLK_HEROIC: + return (m_dailyLKHeroic.find(guidLow) != m_dailyLKHeroic.end()) ? true : false; + default: + return false; + } + return false; +} + +void LFGMgr::ResetDailyRecords() +{ + m_dailyAny.clear(); + m_dailyTBCHeroic.clear(); + m_dailyLKNormal.clear(); + m_dailyLKHeroic.clear(); +} + +bool LFGMgr::IsSeasonActive(uint32 dungeonId) +{ + switch (dungeonId) + { + case 285: + return IsHolidayActive(HOLIDAY_HALLOWS_END); + case 286: + return IsHolidayActive(HOLIDAY_FIRE_FESTIVAL); + case 287: + return IsHolidayActive(HOLIDAY_BREWFEST); + case 288: + return IsHolidayActive(HOLIDAY_LOVE_IS_IN_THE_AIR); + default: + return false; + } + return false; +} + +dungeonEntries LFGMgr::FindRandomDungeonsForPlayer(uint32 level, uint8 expansion) +{ + dungeonEntries randomDungeons; + + // go through the dungeon dbc and select the applicable dungeons + for (uint32 id = 0; id < sLfgDungeonsStore.GetNumRows(); ++id) + { + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(id); + if (dungeon) + { + if ( (dungeon->typeID == LFG_TYPE_RANDOM_DUNGEON) + || (IsSeasonal(dungeon->flags) && IsSeasonActive(dungeon->ID)) ) + if ((uint8)dungeon->expansionLevel <= expansion && dungeon->minLevel <= level + && dungeon->maxLevel >= level) + randomDungeons[dungeon->ID] = dungeon->Entry(); + } + } + return randomDungeons; +} + +dungeonForbidden LFGMgr::FindRandomDungeonsNotForPlayer(Player* plr) +{ + uint32 level = plr->getLevel(); + uint8 expansion = plr->GetSession()->Expansion(); + + dungeonForbidden randomDungeons; + + for (uint32 id = 0; id < sLfgDungeonsStore.GetNumRows(); ++id) + { + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(id); + if (dungeon) + { + uint32 forbiddenReason = 0; + + if ((uint8)dungeon->expansionLevel > expansion) + forbiddenReason = (uint32)LFG_FORBIDDEN_EXPANSION; + else if (dungeon->typeID == LFG_TYPE_RAID) + forbiddenReason = (uint32)LFG_FORBIDDEN_RAID; + else if (dungeon->minLevel > level) + forbiddenReason = (uint32)LFG_FORBIDDEN_LOW_LEVEL; + else if (dungeon->maxLevel < level) + forbiddenReason = (uint32)LFG_FORBIDDEN_HIGH_LEVEL; + else if (IsSeasonal(dungeon->flags) && !IsSeasonActive(dungeon->ID)) // check pointers/function args + forbiddenReason = (uint32)LFG_FORBIDDEN_NOT_IN_SEASON; + else if (DungeonFinderRequirements const* req = sObjectMgr.GetDungeonFinderRequirements((uint32)dungeon->mapID, dungeon->difficulty)) + { + if (req->minItemLevel && (plr->GetEquipGearScore(false,false) < req->minItemLevel)) + forbiddenReason = (uint32)LFG_FORBIDDEN_LOW_GEAR_SCORE; + else if (req->achievement && !plr->GetAchievementMgr().HasAchievement(req->achievement)) + forbiddenReason = (uint32)LFG_FORBIDDEN_MISSING_ACHIEVEMENT; + else if (plr->GetTeam() == ALLIANCE && req->allianceQuestId && !plr->GetQuestRewardStatus(req->allianceQuestId)) + forbiddenReason = (uint32)LFG_FORBIDDEN_QUEST_INCOMPLETE; + else if (plr->GetTeam() == HORDE && req->hordeQuestId && !plr->GetQuestRewardStatus(req->hordeQuestId)) + forbiddenReason = (uint32)LFG_FORBIDDEN_QUEST_INCOMPLETE; + else + if (req->item) + { + if (!plr->HasItemCount(req->item, 1) && (!req->item2 || !plr->HasItemCount(req->item2, 1))) + forbiddenReason = LFG_FORBIDDEN_MISSING_ITEM; + } + else if (req->item2 && !plr->HasItemCount(req->item2, 1)) + forbiddenReason = LFG_FORBIDDEN_MISSING_ITEM; + } + + if (forbiddenReason) + randomDungeons[dungeon->Entry()] = forbiddenReason; + } + } + return randomDungeons; +} + +void LFGMgr::UpdateNeededRoles(ObjectGuid guid, LFGPlayers* information) +{ + uint8 tankCount = 0, dpsCount = 0, healCount = 0; + for (roleMap::iterator it = information->currentRoles.begin(); it != information->currentRoles.end(); ++it) + { + uint8 withoutLeader = it->second; + withoutLeader &= ~PLAYER_ROLE_LEADER; + + switch (withoutLeader) + { + case PLAYER_ROLE_TANK: + ++tankCount; + break; + case PLAYER_ROLE_HEALER: + ++healCount; + break; + case PLAYER_ROLE_DAMAGE: + ++dpsCount; + break; + } + } + + std::set::iterator itr = information->dungeonList.begin(); + + // check dungeon type for max of each role [normal heroic etc.] + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(*itr); + if (dungeon) + { + // atm we're just handling DUNGEON_DIFFICULTY_NORMAL + if (dungeon->difficulty == DUNGEON_DIFFICULTY_NORMAL) + { + information->neededTanks = NORMAL_TANK_OR_HEALER_COUNT - tankCount; + information->neededHealers = NORMAL_TANK_OR_HEALER_COUNT - healCount; + information->neededDps = NORMAL_DAMAGE_COUNT - dpsCount; + } + } + + m_playerData[guid] = *information; +} + +void LFGMgr::AddToQueue(ObjectGuid guid) +{ + LFGPlayers* information = GetPlayerOrPartyData(guid); + if (!information) + return; + + // This will be necessary for finding matches in the queue + UpdateNeededRoles(guid, information); + + // put info into wait time maps for starters + for (roleMap::iterator it = information->currentRoles.begin(); it != information->currentRoles.end(); ++it) + AddToWaitMap(it->second, information->dungeonList); + + // just in case someone's already been in the queue. + queueSet::iterator qItr = m_queueSet.find(guid); + if (qItr == m_queueSet.end()) + m_queueSet.insert(guid); +} + +void LFGMgr::RemoveFromQueue(ObjectGuid guid) +{ + m_queueSet.erase(guid); + + //todo - might need to implement a removefromwaitmap function +} + +void LFGMgr::AddToWaitMap(uint8 role, std::set dungeons) +{ + // use withoutLeader for switch operator + uint8 withoutLeader = role; + withoutLeader &= ~PLAYER_ROLE_LEADER; + + switch (withoutLeader) + { + case PLAYER_ROLE_TANK: + { + for (std::set::iterator itr = dungeons.begin(); itr != dungeons.end(); ++itr) + { + waitTimeMap::iterator it = m_tankWaitTime.find(*itr); + if (it != m_tankWaitTime.end()) + { + // Increment current player count by one + ++it->second.playerCount; + } + else + { + LFGWait waitInfo(QUEUE_DEFAULT_TIME, -1, 1, false); + m_tankWaitTime[*itr] = waitInfo; + } + } + } break; + case PLAYER_ROLE_HEALER: + { + for (std::set::iterator itr = dungeons.begin(); itr != dungeons.end(); ++itr) + { + waitTimeMap::iterator it = m_healerWaitTime.find(*itr); + if (it != m_healerWaitTime.end()) + { + // Increment current player count by one + ++it->second.playerCount; + } + else + { + LFGWait waitInfo(QUEUE_DEFAULT_TIME, -1, 1, false); + m_healerWaitTime[*itr] = waitInfo; + } + } + } break; + case PLAYER_ROLE_DAMAGE: + { + for (std::set::iterator itr = dungeons.begin(); itr != dungeons.end(); ++itr) + { + waitTimeMap::iterator it = m_dpsWaitTime.find(*itr); + if (it != m_dpsWaitTime.end()) + { + // Increment current player count by one + ++it->second.playerCount; + } + else + { + LFGWait waitInfo(QUEUE_DEFAULT_TIME, -1, 1, false); + m_dpsWaitTime[*itr] = waitInfo; + } + } + } break; + default: + break; + } + + // insert the average time regardless of role + for (std::set::iterator itr = dungeons.begin(); itr != dungeons.end(); ++itr) + { + waitTimeMap::iterator it = m_avgWaitTime.find(*itr); + if (it != m_avgWaitTime.end()) + ++it->second.playerCount; + else + { + LFGWait waitInfo(QUEUE_DEFAULT_TIME, -1, 1, false); + m_avgWaitTime[*itr] = waitInfo; + } + } +} + +void LFGMgr::FindQueueMatches() +{ + // Fetch information on all the queued players/groups + for (queueSet::iterator itr = m_queueSet.begin(); itr != m_queueSet.end(); ++itr) + FindSpecificQueueMatches(*itr); +} + +void LFGMgr::FindSpecificQueueMatches(ObjectGuid guid) +{ + uint64 rawGuid = guid.GetRawValue(); + LFGPlayers* queueInfo = GetPlayerOrPartyData(guid); + if (queueInfo) + { + // compare to everyone else in queue for compatibility + // after a match is found call UpdateNeededRoles + // Use the roleMap to store player guid/role information; merge into queueInfo struct & delete other struct/map entry + for (queueSet::iterator itr = m_queueSet.begin(); itr != m_queueSet.end(); ++itr) + { + if (*itr == guid) + continue; + + LFGPlayers* matchInfo = GetPlayerOrPartyData(*itr); + if (matchInfo) + { + // 1. iterate through queueInfo's dungeon set and search the matchInfo for a matching entry. + // 2. if an(y) entry is found, great and proceed! + // 2a. if an entry is found and the amounts of players-to-roles are compatible, make + // a new map of only the inter-compatible dungeons and use that if the other checks pass + // 3. Regardless of outcome, after the end of calculations send a LFGQueueStatus packet + bool fullyCompatible = false; + std::set compatibleDungeons; + + for (std::set::iterator dItr = matchInfo->dungeonList.begin(); dItr != matchInfo->dungeonList.end(); ++itr) + { + if (queueInfo->dungeonList.find(*dItr) != queueInfo->dungeonList.end()) + compatibleDungeons.insert(*dItr); + } + + if (!compatibleDungeons.empty()) + { + // check for player / role count and also team compatibility + // if function returns true, then merge groups into one + if (RoleMapsAreCompatible(queueInfo, matchInfo) && MatchesAreOfSameTeam(queueInfo, matchInfo)) + MergeGroups(guid, *itr, compatibleDungeons); + } + } + } + } +} + +bool LFGMgr::RoleMapsAreCompatible(LFGPlayers* groupOne, LFGPlayers* groupTwo) +{ + // When this is called we already know that the dungeons match, so just focus on roles + // compare: neededX(role) from each struct and the amount of people per role in the roleMap + if ((groupOne->currentRoles.size() + groupTwo->currentRoles.size()) > NORMAL_TOTAL_ROLE_COUNT) + return false; + else + { + // make sure we don't have too many players of a certain role here + if (((NORMAL_DAMAGE_COUNT - groupOne->neededDps) + (NORMAL_DAMAGE_COUNT - groupTwo->neededDps)) > NORMAL_DAMAGE_COUNT) + return false; + else if (((NORMAL_TANK_OR_HEALER_COUNT - groupOne->neededHealers) + (NORMAL_TANK_OR_HEALER_COUNT - groupTwo->neededHealers)) > NORMAL_TANK_OR_HEALER_COUNT) + return false; + else if (((NORMAL_TANK_OR_HEALER_COUNT - groupOne->neededTanks) + (NORMAL_TANK_OR_HEALER_COUNT - groupTwo->neededTanks)) > NORMAL_TANK_OR_HEALER_COUNT) + return false; + else + return true; // the player/role counts line up! + } + return false; +} + +bool LFGMgr::MatchesAreOfSameTeam(LFGPlayers* groupOne, LFGPlayers* groupTwo) +{ + // we should safely be able to compare any two players from each struct to + // determine compatibility + roleMap::iterator it1 = groupOne->currentRoles.begin(); + roleMap::iterator it2 = groupTwo->currentRoles.begin(); + + // now we find the players from the maps + Player* pPlayer1 = ObjectAccessor::FindPlayer(it1->first); + Player* pPlayer2 = ObjectAccessor::FindPlayer(it2->first); + + // todo: disable this if a config option is set + if (pPlayer1->GetTeamId() == pPlayer2->GetTeamId()) + return true; + + return false; +} + +void LFGMgr::MergeGroups(ObjectGuid guidOne, ObjectGuid guidTwo, std::set compatibleDungeons) +{ + // merge into the entry for rawGuidOne, then see if they are + // able to enter the dungeon at this point or not + LFGPlayers* mainGroup = GetPlayerOrPartyData(guidOne); + LFGPlayers* bufferGroup = GetPlayerOrPartyData(guidTwo); + + if (!mainGroup || !bufferGroup) + return; + + // update the dungeon selection with the compatible ones + mainGroup->dungeonList.clear(); + mainGroup->dungeonList = compatibleDungeons; + + // move players / roles into a single roleMap + for (roleMap::iterator it = bufferGroup->currentRoles.begin(); it != bufferGroup->currentRoles.end(); ++it) + mainGroup->currentRoles[it->first] = it->second; + + // update the role count / needed role info + UpdateNeededRoles(guidOne, mainGroup); + + // being safe + //mainGroup = GetPlayerOrPartyData(rawGuidOne); + + // Then do the following: + if ((mainGroup->neededTanks == 0) && (mainGroup->neededHealers == 0) && (mainGroup->neededDps == 0)) + SendDungeonProposal(mainGroup); + + m_playerData.erase(guidTwo); +} + +void LFGMgr::SendQueueStatus() +{ + // First we should get the current time + time_t timeNow = time(0); + + // Check who is listed as being in the queue + for (queueSet::iterator itr = m_queueSet.begin(); itr != m_queueSet.end(); ++itr) + { + // make sure it's not a false entry + LFGPlayers* queueInfo = GetPlayerOrPartyData(*itr); + if (queueInfo && queueInfo->currentState == LFG_STATE_QUEUED) + { + for (roleMap::iterator rItr = queueInfo->currentRoles.begin(); rItr != queueInfo->currentRoles.end(); ++rItr) + { + if (Player* pPlayer = ObjectAccessor::FindPlayer(rItr->first)) + { + uint32 dungeonId = *queueInfo->dungeonList.begin(); + + LFGQueueStatus status; + status.dungeonID = dungeonId; + status.neededTanks = queueInfo->neededTanks; + status.neededHeals = queueInfo->neededHealers; + status.neededDps = queueInfo->neededDps; + status.timeSpentInQueue = uint32(timeNow - queueInfo->joinedTime); + + int32 playerWaitTime; + + // strip leader flag from role + uint8 withoutLeader = rItr->second; + withoutLeader &= ~PLAYER_ROLE_LEADER; + + switch (withoutLeader) + { + case PLAYER_ROLE_TANK: + playerWaitTime = m_tankWaitTime[dungeonId].time; + break; + case PLAYER_ROLE_HEALER: + playerWaitTime = m_healerWaitTime[dungeonId].time; + break; + case PLAYER_ROLE_DAMAGE: + playerWaitTime = m_dpsWaitTime[dungeonId].time; + break; + } + + status.playerAvgWaitTime = playerWaitTime; + status.dpsAvgWaitTime = m_dpsWaitTime[dungeonId].time; + status.healerAvgWaitTime = m_healerWaitTime[dungeonId].time; + status.tankAvgWaitTime = m_tankWaitTime[dungeonId].time; + status.avgWaitTime = m_avgWaitTime[dungeonId].time; + + // Send packet to client + pPlayer->GetSession()->SendLfgQueueStatus(status); + } + } + } + } +} + +uint32 LFGMgr::GetDungeonEntry(uint32 ID) +{ + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(ID); + if (dungeon) + return dungeon->Entry(); + else + return 0; +} + +// called each time a player selects their role +void LFGMgr::PerformRoleCheck(Player* pPlayer, Group* pGroup, uint8 roles) +{ + ObjectGuid groupGuid = pGroup->GetObjectGuid(); + ObjectGuid plrGuid = pPlayer->GetObjectGuid(); + + roleCheckMap::iterator it = m_roleCheckMap.find(groupGuid); + if (it == m_roleCheckMap.end()) + return; // no role check map found + + LFGRoleCheck roleCheck = it->second; + bool roleChosen = roleCheck.state != LFG_ROLECHECK_DEFAULT && plrGuid; + + if (!plrGuid) + roleCheck.state = LFG_ROLECHECK_ABORTED; // aborted if anyone cancels during role check + else if (roles < PLAYER_ROLE_TANK) // kind of a sanity check- the client shouldn't allow this to happen + roleCheck.state = LFG_ROLECHECK_NO_ROLE; + else + { + roleCheck.currentRoles[plrGuid] = roles; + + roleMap::iterator rItr = roleCheck.currentRoles.begin(); + do + { + if (rItr->second != PLAYER_ROLE_NONE) + ++rItr; + } while (rItr != roleCheck.currentRoles.end()); + + if (rItr == roleCheck.currentRoles.end()) // meaning that everyone confirmed their roles + roleCheck.state = ValidateGroupRoles(roleCheck.currentRoles) ? LFG_ROLECHECK_FINISHED : LFG_ROLECHECK_MISSING_ROLE; + } + + std::set dungeonBuff; + if (roleCheck.randomDungeonID) + dungeonBuff.insert(roleCheck.randomDungeonID); + else + dungeonBuff = roleCheck.dungeonList; + + partyForbidden nullForbidden; + + for (roleMap::iterator itr = roleCheck.currentRoles.begin(); itr != roleCheck.currentRoles.end(); ++itr) + { + ObjectGuid guidBuff = itr->first; + if (roleChosen) + SendRoleChosen(guidBuff, plrGuid, roles); // send SMSG_LFG_ROLE_CHOSEN to each player + + // send SMSG_LFG_ROLE_CHECK_UPDATE + SendRoleCheckUpdate(guidBuff, roleCheck); + + switch (roleCheck.state) + { + case LFG_ROLECHECK_INITIALITING: + continue; + case LFG_ROLECHECK_FINISHED: + // set current plr's state to queued. then set their role in that struct + // then send lfgupdate packet with UPDATETYPE_ADDED_TO_QUEUE + SetPlayerState(guidBuff, LFG_STATE_QUEUED); + SetPlayerUpdateType(guidBuff, LFG_UPDATE_ADDED_TO_QUEUE); + SendLfgUpdate(guidBuff, GetPlayerStatus(guidBuff), true); + break; + default: + if (roleCheck.leaderGuidRaw == guidBuff.GetRawValue()) + SendLfgJoinResult(guidBuff, ERR_LFG_ROLE_CHECK_FAILED, LFG_STATE_ROLECHECK, nullForbidden); + SetPlayerUpdateType(guidBuff, LFG_UPDATE_ROLECHECK_FAILED); + SendLfgUpdate(guidBuff, GetPlayerStatus(guidBuff), true); + break; + } + } + + if (roleCheck.state == LFG_ROLECHECK_FINISHED) + { + LFGPlayers* queueInfo = GetPlayerOrPartyData(groupGuid); + queueInfo->currentState = LFG_STATE_QUEUED; + queueInfo->currentRoles = roleCheck.currentRoles; + queueInfo->joinedTime = time(NULL); + + m_playerData[groupGuid] = *queueInfo; + + AddToQueue(groupGuid); + } + else if (roleCheck.state != LFG_ROLECHECK_INITIALITING) + { + // todo: add players back to individual queues if applicable + roleCheck.state = LFG_ROLECHECK_NO_ROLE; + + for (roleMap::iterator roleMapItr = roleCheck.currentRoles.begin(); roleMapItr != roleCheck.currentRoles.end(); ++roleMapItr) + { + ObjectGuid plrGuid = roleMapItr->first; + + SetPlayerState(plrGuid, LFG_STATE_NONE); + + SendRoleCheckUpdate(plrGuid, roleCheck); // role check failed + SendLfgUpdate(plrGuid, GetPlayerStatus(plrGuid), true); // not in lfg system anymore + } + m_roleCheckMap.erase(groupGuid); + } +} + +bool LFGMgr::ValidateGroupRoles(roleMap groupMap) +{ + if (groupMap.empty()) // sanity check + return false; + + uint8 tankCount = 0, dpsCount = 0, healCount = 0; + + for (roleMap::iterator it = groupMap.begin(); it != groupMap.end(); ++it) + { + uint8 withoutLeader = it->second; + withoutLeader &= ~PLAYER_ROLE_LEADER; + + switch (withoutLeader) + { + case PLAYER_ROLE_TANK: + ++tankCount; + break; + case PLAYER_ROLE_HEALER: + ++healCount; + break; + case PLAYER_ROLE_DAMAGE: + ++dpsCount; + break; + } + } + + return (tankCount + dpsCount + healCount == groupMap.size()) ? true : false; +} + +//todo: remove from queue, update queue average settings +void LFGMgr::SendDungeonProposal(LFGPlayers* lfgGroup) +{ + ++m_proposalId; // increment number to make a new proposal id + + std::set::iterator dItr = lfgGroup->dungeonList.begin(); + + // note: group create function's parameters are leader guid & leader name + LFGProposal newProposal; + newProposal.id = m_proposalId; + newProposal.state = LFG_PROPOSAL_INITIATING; + newProposal.encounters = 0; // todo: check if group has already started a dungeon and are looking for another plr + newProposal.currentRoles = lfgGroup->currentRoles; + newProposal.dungeonID = *dItr; + newProposal.isNew = true; + newProposal.joinedQueue = lfgGroup->joinedTime; + + bool premadeGroup = IsProposalSameGroup(newProposal); + + // iterate through role map just so get everyone's guid + for (roleMap::iterator it = lfgGroup->currentRoles.begin(); it != lfgGroup->currentRoles.end(); ++it) + { + ObjectGuid plrGuid = it->first; + SetPlayerState(plrGuid, LFG_STATE_PROPOSAL); + + Player* pPlayer = ObjectAccessor::FindPlayer(plrGuid); + + if (Group* pGroup = pPlayer->GetGroup()) + { + ObjectGuid grpGuid = pGroup->GetObjectGuid(); + + SetPlayerUpdateType(plrGuid, LFG_UPDATE_PROPOSAL_BEGIN); + + if (premadeGroup && pGroup->IsLeader(plrGuid)) + newProposal.groupLeaderGuid = plrGuid.GetRawValue(); + + if (premadeGroup && !newProposal.groupRawGuid) + newProposal.groupRawGuid = grpGuid.GetRawValue(); + + newProposal.groups[plrGuid] = grpGuid; + + SendLfgUpdate(plrGuid, GetPlayerStatus(plrGuid), true); + } + else + { + newProposal.groups[plrGuid] = ObjectGuid(); + + //SetPlayerUpdateType(plrGuid, LFG_UPDATE_GROUP_FOUND); + //SendLfgUpdate(plrGuid, GetPlayerStatus(plrGuid), false); + + SetPlayerUpdateType(plrGuid, LFG_UPDATE_PROPOSAL_BEGIN); + SendLfgUpdate(plrGuid, GetPlayerStatus(plrGuid), false); + } + + newProposal.answers[plrGuid] = LFG_ANSWER_PENDING; + + // then send SMSG_LFG_PROPOSAL_UPDATE + pPlayer->GetSession()->SendLfgProposalUpdate(newProposal); + } + + // then if group guid is set, call Group::SetAsLfgGroup() + if (premadeGroup) + { + Player* pGroupLeader = ObjectAccessor::FindPlayer(ObjectGuid(newProposal.groupLeaderGuid)); + pGroupLeader->GetGroup()->SetAsLfgGroup(); + } + + // also save the proposal + m_proposalMap[newProposal.id] = newProposal; +} + +bool LFGMgr::IsProposalSameGroup(LFGProposal const& proposal) +{ + bool firstLoop = true; + bool isSameGroup = true; + + ObjectGuid priorGroupGuid; + + // when this is called we don't have the groups part filled, so iterate via role map + for (roleMap::const_iterator it = proposal.currentRoles.begin(); it != proposal.currentRoles.end(); ++it) + { + ObjectGuid plrGuid = it->first; + + Player* pPlayer = ObjectAccessor::FindPlayer(plrGuid); + if (Group* pGroup = pPlayer->GetGroup()) + { + ObjectGuid grpGuid = pGroup->GetObjectGuid(); + + if (firstLoop) + priorGroupGuid = grpGuid; + else + { + if (isSameGroup) + { + if (grpGuid != priorGroupGuid) + isSameGroup = false; + } + } + } + } + return isSameGroup; +} + +// From a CMSG_LFG_PROPOSAL_RESPONSE call +void LFGMgr::ProposalUpdate(uint32 proposalID, ObjectGuid plrGuid, bool accepted) +{ + //note: create a group here if it doesn't exist and everyone accepted proposal + LFGProposal* proposal = GetProposalData(proposalID); + + if (!proposal) + return; + + bool allOkay = true; // true if everyone answered LFG_ANSWER_AGREE + + // Update answer map to given value + LFGProposalAnswer plrAnswer = (LFGProposalAnswer)accepted; + proposal->answers[plrGuid] = plrAnswer; + + // If the player declined, the proposal is over + if (plrAnswer == LFG_ANSWER_DENY) + ProposalDeclined(plrGuid, proposal); + + for (proposalAnswerMap::iterator it = proposal->answers.begin(); it != proposal->answers.end(); ++it) + { + if (it->second != LFG_ANSWER_AGREE) + allOkay = false; + } + + // if !allOkay, send proposal updates to all + if (!allOkay) + { + for (proposalAnswerMap::iterator itr = proposal->answers.begin(); itr != proposal->answers.end(); ++itr) + { + ObjectGuid proposalPlrGuid = itr->first; + Player* pProposalPlayer = ObjectAccessor::FindPlayer(proposalPlrGuid); + pProposalPlayer->GetSession()->SendLfgProposalUpdate(*proposal); + } + + return; + } + + // at this point everyone's good to join the dungeon! + + time_t joinedTime = time(NULL); + bool sendProposalUpdate = proposal->state != LFG_PROPOSAL_SUCCESS; + + // now update the proposal's state to successful and inform the players + proposal->state = LFG_PROPOSAL_SUCCESS; + for (roleMap::iterator rItr = proposal->currentRoles.begin(); rItr != proposal->currentRoles.end(); ++rItr) + { + // get the player's role + uint8 proposalPlrRole = rItr->second; + proposalPlrRole &= ~PLAYER_ROLE_LEADER; + + ObjectGuid proposalPlrGuid = rItr->first; + Player* pProposalPlayer = ObjectAccessor::FindPlayer(proposalPlrGuid); + + if (sendProposalUpdate) + pProposalPlayer->GetSession()->SendLfgProposalUpdate(*proposal); + + // amount of time spent in queue + int32 timeWaited = (joinedTime - proposal->joinedQueue) / IN_MILLISECONDS; + + // tell the lfg system to update the average wait times on the next tick + UpdateWaitMap(LFGRoles(proposalPlrRole), proposal->dungeonID, timeWaited); + + // send some updates to the player, depending on group status + LFGPlayerStatus proposalPlrStatus = GetPlayerStatus(proposalPlrGuid); + proposalPlrStatus.updateType = LFG_UPDATE_GROUP_FOUND; + + if (pProposalPlayer->GetGroup()) + { + SendLfgUpdate(proposalPlrGuid, proposalPlrStatus, true); + RemoveFromQueue(pProposalPlayer->GetGroup()->GetObjectGuid()); // not the best way to handle this + } + else + { + SendLfgUpdate(proposalPlrGuid, proposalPlrStatus, false); + RemoveFromQueue(proposalPlrGuid); + } + + proposalPlrStatus.updateType = LFG_UPDATE_LEAVE; + SendLfgUpdate(proposalPlrGuid, proposalPlrStatus, false); + SendLfgUpdate(proposalPlrGuid, proposalPlrStatus, true); + + proposalPlrStatus.state = LFG_STATE_IN_DUNGEON; + } + + CreateDungeonGroup(proposal); + m_proposalMap.erase(proposal->id); +} + +bool LFGMgr::HasLeaderFlag(roleMap const& roles) +{ + for (roleMap::const_iterator it = roles.begin(); it != roles.end(); ++it) + { + if (it->second & PLAYER_ROLE_LEADER) + return true; + } + return false; +} + +void LFGMgr::CreateDungeonGroup(LFGProposal* proposal) +{ + if (!proposal) + return; + + Group* pGroup; + + if (!proposal->groupRawGuid) + { + bool leaderIsSet = false; + bool leaderRoleIsSet = HasLeaderFlag(proposal->currentRoles); + ObjectGuid leaderGuid; + + pGroup = new Group(); + + for (playerGroupMap::iterator it = proposal->groups.begin(); it != proposal->groups.end(); ++it) + { + // remove plr from group w/ guid it->second + // set leader on first loop, then set leaderisset to true + ObjectGuid pGroupPlrGuid = it->first; + Player* pGroupPlr = ObjectAccessor::FindPlayer(pGroupPlrGuid); + + if (it->second) + pGroupPlr->GetGroup()->RemoveMember(pGroupPlrGuid, 0); + + if (!leaderIsSet) + { + bool currentPlrIsLeader = false; + if (leaderRoleIsSet) + { + for (roleMap::iterator itr = proposal->currentRoles.begin(); itr != proposal->currentRoles.end(); ++itr) + { + if (itr->second & PLAYER_ROLE_LEADER) + { + leaderGuid = itr->first; + Player* leaderRef = ObjectAccessor::FindPlayer(leaderGuid); + + pGroup->Create(leaderRef->GetObjectGuid(), leaderRef->GetName()); + + if (pGroupPlrGuid == leaderGuid) + currentPlrIsLeader = true; + } + } + } + else + pGroup->Create(pGroupPlrGuid, pGroupPlr->GetName()); + + if (!currentPlrIsLeader) + pGroup->AddMember(pGroupPlrGuid, pGroupPlr->GetName()); + + leaderIsSet = true; + } + else if (leaderIsSet && pGroupPlrGuid != leaderGuid) + pGroup->AddMember(pGroupPlrGuid, pGroupPlr->GetName()); + } + pGroup->SetAsLfgGroup(); + } + else + { + Player* pGroupLeader = ObjectAccessor::FindPlayer(ObjectGuid(proposal->groupLeaderGuid)); + pGroup = pGroupLeader->GetGroup(); + } + + // set dungeon difficulty for group + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(proposal->dungeonID); + if (!dungeon) + return; + + pGroup->SetDungeonDifficulty(Difficulty(dungeon->difficulty)); //todo: check for raids and if so call setraiddifficulty + + // Add group to our group set and group map, then teleport to the dungeon + ObjectGuid groupGuid = pGroup->GetObjectGuid(); + LFGGroupStatus groupStatus(LFG_STATE_IN_DUNGEON, dungeon->ID, proposal->currentRoles, pGroup->GetLeaderGuid()); + + m_groupSet.insert(groupGuid); + m_groupStatusMap[groupGuid] = groupStatus; + TeleportToDungeon(dungeon->ID, pGroup); + + pGroup->SendUpdate(); +} + +void LFGMgr::TeleportToDungeon(uint32 dungeonID, Group* pGroup) +{ + // if the group's leader is already in the dungeon, teleport anyone not in dungeon to them + // if nobody is in the dungeon, teleport all to beginning of dungeon (sObjectMgr.GetMapEntranceTrigger(mapid [not dungeonid])) + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(dungeonID); + if (!dungeon || !pGroup) + return; + + uint32 mapID = (uint32)dungeon->mapID; + float x, y, z, o; + LFGTeleportError err = LFG_TELEPORTERROR_OK; + + Player* pGroupLeader = ObjectAccessor::FindPlayer(pGroup->GetLeaderGuid()); + + if (pGroupLeader && pGroupLeader->GetMapId() == mapID) // Already in the dungeon + { + // set teleport location to that of the group leader + x = pGroupLeader->GetPositionX(); + y = pGroupLeader->GetPositionY(); + z = pGroupLeader->GetPositionZ(); + o = pGroupLeader->GetOrientation(); + } + else + { + if (AreaTrigger const* at = sObjectMgr.GetMapEntranceTrigger(mapID)) + { + x = at->target_X; + y = at->target_Y; + z = at->target_Z; + o = at->target_Orientation; + } + else + err = LFG_TELEPORTERROR_INVALID_LOCATION; + } + + dungeonForbidden lockedDungeons; + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* pGroupPlr = itr->getSource()) + { + // further checks: player is dead, in vehicle, in battleground, on taxi, etc + LFGTeleportError plrErr = LFG_TELEPORTERROR_OK; + + if (pGroupPlr->IsDead()) + plrErr = LFG_TELEPORTERROR_PLAYER_DEAD; + if (pGroupPlr->IsFalling()) + plrErr = LFG_TELEPORTERROR_FALLING; + if (pGroupPlr->GetVehicleInfo()) + plrErr = LFG_TELEPORTERROR_IN_VEHICLE; + + lockedDungeons = FindRandomDungeonsNotForPlayer(pGroupPlr); + if (lockedDungeons.find(dungeon->Entry()) != lockedDungeons.end()) + plrErr = LFG_TELEPORTERROR_INVALID_LOCATION; + + if (err == LFG_TELEPORTERROR_OK && plrErr == LFG_TELEPORTERROR_OK && pGroupPlr->GetMapId() != mapID) + { + if (pGroupPlr->GetMap() && !pGroupPlr->GetMap()->IsDungeon() && !pGroupPlr->GetMap()->IsRaid() && !pGroupPlr->InBattleGround()) + pGroupPlr->SetBattleGroundEntryPoint(); // store current position and such + + if (!pGroupPlr->TeleportTo(mapID, x, y, z, o)) + plrErr = LFG_TELEPORTERROR_INVALID_LOCATION; + } + + if (err != LFG_TELEPORTERROR_OK) + pGroupPlr->GetSession()->SendLfgTeleportError(err); + else if (plrErr != LFG_TELEPORTERROR_OK) + pGroupPlr->GetSession()->SendLfgTeleportError(plrErr); + else + SetPlayerState(pGroupPlr->GetObjectGuid(), LFG_STATE_IN_DUNGEON); + } + } +} + +void LFGMgr::TeleportPlayer(Player* pPlayer, bool out) +{ + // Fetch necessary data first + Group* pGroup = pPlayer->GetGroup(); + LFGGroupStatus* status = GetGroupStatus(pGroup->GetObjectGuid()); + + if (!pGroup || !status) + { + pPlayer->GetSession()->SendLfgTeleportError((uint8)LFG_TELEPORTERROR_INVALID_LOCATION); + return; + } + + // Get dungeon info and then teleport the player out if applicable + if (out) + { + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(status->dungeonID); + if (dungeon && pPlayer->GetMapId() == dungeon->mapID) + pPlayer->TeleportToBGEntryPoint(); + } +} + +LFGGroupStatus* LFGMgr::GetGroupStatus(ObjectGuid guid) +{ + groupStatusMap::iterator it = m_groupStatusMap.find(guid); + if (it != m_groupStatusMap.end()) + return &(it->second); + else + return NULL; +} + +void LFGMgr::ProposalDeclined(ObjectGuid guid, LFGProposal* proposal) +{ + Player* pPlayer = ObjectAccessor::FindPlayer(guid); + + if (!pPlayer) + return; + + bool leaveGroupLFG = false; + + for (roleMap::iterator it = proposal->currentRoles.begin(); it != proposal->currentRoles.end(); ++it) + { + ObjectGuid groupPlrGuid = it->first; + + // update each player with a LFG_UPDATE_PROPOSAL_DECLINED + SetPlayerUpdateType(groupPlrGuid, LFG_UPDATE_PROPOSAL_DECLINED); + + Player* pGroupPlayer = ObjectAccessor::FindPlayer(groupPlrGuid); + Group* pGroup = pGroupPlayer->GetGroup(); + + // if player was in a premade group and declined, remove the group. + if (groupPlrGuid == guid) + { + //LeaveLFG(pGroupPlayer, true); + if (pGroup && (pGroup->GetObjectGuid().GetRawValue() == proposal->groupRawGuid)) + leaveGroupLFG = true; + + SendLfgUpdate(groupPlrGuid, GetPlayerStatus(groupPlrGuid), false); + } + else + { + if (proposal->groupRawGuid) + SendLfgUpdate(groupPlrGuid, GetPlayerStatus(groupPlrGuid), true); + else + SendLfgUpdate(groupPlrGuid, GetPlayerStatus(groupPlrGuid), false); + } + } + + if (!leaveGroupLFG) + { + proposal->currentRoles.erase(guid); + proposal->answers.erase(guid); + proposal->groups.erase(guid); + } + else + { + m_proposalMap.erase(proposal->id); + } + + LeaveLFG(pPlayer, leaveGroupLFG); +} + +void LFGMgr::UpdateWaitMap(LFGRoles role, uint32 dungeonID, time_t waitTime) +{ + if (!role || !dungeonID || !waitTime) + return; + + switch (role) + { + case PLAYER_ROLE_TANK: + { + waitTimeMap::iterator it = m_tankWaitTime.find(dungeonID); + if (it != m_tankWaitTime.end()) + { + LFGWait wait = it->second; + + wait.previousTime = wait.time; + wait.time = waitTime; + wait.doAverage = true; + + m_tankWaitTime[dungeonID] = wait; + } + } + break; + case PLAYER_ROLE_HEALER: + { + waitTimeMap::iterator hIt = m_healerWaitTime.find(dungeonID); + if (hIt != m_healerWaitTime.end()) + { + LFGWait wait = hIt->second; + + wait.previousTime = wait.time; + wait.time = waitTime; + wait.doAverage = true; + + m_healerWaitTime[dungeonID] = wait; + } + } + break; + case PLAYER_ROLE_DAMAGE: + { + waitTimeMap::iterator dIt = m_dpsWaitTime.find(dungeonID); + if (dIt != m_dpsWaitTime.end()) + { + LFGWait wait = dIt->second; + + wait.previousTime = wait.time; + wait.time = waitTime; + wait.doAverage = true; + + m_dpsWaitTime[dungeonID] = wait; + } + } + break; + default: + { + waitTimeMap::iterator aIt = m_avgWaitTime.find(dungeonID); + if (aIt != m_avgWaitTime.end()) + { + LFGWait wait = aIt->second; + + wait.previousTime = wait.time; + wait.time = waitTime; + wait.doAverage = true; + + m_avgWaitTime[dungeonID] = wait; + } + } + break; + } + +} + +void LFGMgr::HandleBossKilled(Player* pPlayer) +{ + Group* pGroup = pPlayer->GetGroup(); + + ObjectGuid groupGuid = pGroup->GetObjectGuid(); + LFGGroupStatus* status = GetGroupStatus(groupGuid); + + if (!pGroup || !status) + return; + + // set each player's lfgstate to LFG_STATE_FINISHED_DUNGEON + // fetch reward info, and if it's the first dungeon of the day (per player), + // give them 2x the xp (or 1x if it's not the first), and the reward item + // (special case for 2nd wotlk heroic and +). If no room in inventory, send + // via ingame mail. + status->state = LFG_STATE_FINISHED_DUNGEON; + + DungeonTypes type = GetDungeonType(status->dungeonID); + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) //todo: check if we will need to use mail or not + { + if (Player* pGroupPlr = itr->getSource()) + { + SetPlayerState(pGroupPlr->GetObjectGuid(), LFG_STATE_FINISHED_DUNGEON); + + // check if player did a random dungeon + uint32 randomDungeonId = 0; + LfgDungeonsEntry const* dungeon = sLfgDungeonsStore.LookupEntry(status->dungeonID); + if (dungeon->typeID == LFG_TYPE_RANDOM_DUNGEON || IsSeasonal(dungeon->flags)) + randomDungeonId = dungeon->ID; + + // get rewards + uint32 groupPlrLevel = pGroupPlr->getLevel(); + const DungeonFinderRewards* rewards = sObjectMgr.GetDungeonFinderRewards(groupPlrLevel); // Fetch base xp/money reward + ItemRewards itemRewards = GetDungeonItemRewards(status->dungeonID, type); // fetch item reward + + int32 multiplier; // base reward modifier + bool hasDoneDaily = HasPlayerDoneDaily(pGroupPlr->GetGUIDLow(), type); // first dungeon of the day? + (hasDoneDaily) ? multiplier = 1 : multiplier = 2; + + uint32 xpReward = multiplier*rewards->baseXPReward; // player's xp reward + uint32 moneyReward = uint32(multiplier*rewards->baseMonetaryReward); // player's money reward + + uint32 itemReward = 0; // reward item + uint32 itemAmount = 0; // amount of item + if (hasDoneDaily && (type == DUNGEON_WOTLK_HEROIC)) + { + itemReward = WOTLK_SPECIAL_HEROIC_ITEM; + itemAmount = WOTLK_SPECIAL_HEROIC_AMNT; + } + else if (!hasDoneDaily) + { + itemReward = itemRewards.itemId; + itemAmount = itemRewards.itemAmount; + } + + // and then fill a structure corresponding to SMSG_LFG_PLAYER_REWARD and + // send one of these to each player + LFGRewards reward(randomDungeonId, status->dungeonID, hasDoneDaily, moneyReward, xpReward, itemReward, itemAmount); + pGroupPlr->GetSession()->SendLfgRewards(reward); + } + } + + // now we can remove the group from our maps + m_groupStatusMap.erase(groupGuid); + m_groupSet.erase(groupGuid); +} + +void LFGMgr::AttemptToKickPlayer(Group* pGroup, ObjectGuid guid, ObjectGuid kicker, std::string reason) +{ + ObjectGuid groupGuid = pGroup->GetObjectGuid(); + LFGGroupStatus* status = GetGroupStatus(groupGuid); + + bootStatusMap::iterator bIt = m_bootStatusMap.find(groupGuid); + if (!status) + return; + + status->state = LFG_STATE_BOOT; + m_groupStatusMap[groupGuid] = *status; + + // This function is only called when a group is set/in a dungeon so we can go straight to the boot packets + time_t now = time(NULL); + proposalAnswerMap votes; + + // safe to say the person attempting to kick them will vote yes, the kick-ee will vote no + votes[guid] = LFG_ANSWER_DENY; + votes[kicker] = LFG_ANSWER_AGREE; + + // set group state to boot vote, same for player states until it's over + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) //todo: check if we will need to use mail or not + { + if (Player* pGroupPlr = itr->getSource()) + { + ObjectGuid pGroupPlrGuid = pGroupPlr->GetObjectGuid(); + + SetPlayerState(pGroupPlrGuid, LFG_STATE_BOOT); + + if ( (pGroupPlrGuid != guid) && (pGroupPlrGuid != kicker) ) + votes[pGroupPlrGuid] = LFG_ANSWER_PENDING; + } + } + + LFGBoot boot(true, guid, reason, votes, now); + m_bootStatusMap[groupGuid] = boot; + + for (GroupReference* it = pGroup->GetFirstMember(); it != NULL; it = it->next()) + { + if (Player* groupPlr = it->getSource()) + groupPlr->GetSession()->SendLfgBootUpdate(boot); + } +} + +void LFGMgr::CastVote(Player* pPlayer, bool vote) +{ + if (!pPlayer) + return; + + Group* pGroup = pPlayer->GetGroup(); + ObjectGuid groupGuid = pGroup->GetObjectGuid(); + + LFGGroupStatus* status = GetGroupStatus(groupGuid); + + if (!status || status->state != LFG_STATE_BOOT) + return; + + bootStatusMap::iterator it = m_bootStatusMap.find(groupGuid); + if (it == m_bootStatusMap.end()) + return; + + LFGBoot boot = it->second; + boot.answers[pPlayer->GetObjectGuid()] = LFGProposalAnswer(vote); + + int32 yay = 0, nay = 0; // keep a count of votes + for (proposalAnswerMap::iterator pIt = boot.answers.begin(); pIt != boot.answers.end(); ++pIt) + { + LFGProposalAnswer answer = pIt->second; + if (answer == LFG_ANSWER_AGREE) + ++yay; + else if (answer == LFG_ANSWER_DENY) + ++nay; + } + + if (yay < REQUIRED_VOTES_FOR_BOOT && nay < REQUIRED_VOTES_FOR_BOOT) + { + m_bootStatusMap[groupGuid] = boot; + return; + } + + // if we dont have enough votes to kick or keep plr, don't send packet update + // if else, set boot.inProgress to false, set plr + group states back to lfg-state-dungeon, + // send packet update to group, kick plr if we had the votes, and then erase entry from boot map + + boot.inProgress = false; + status->state = LFG_STATE_IN_DUNGEON; + + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* pGroupPlr = itr->getSource()) + { + ObjectGuid plrGuid = pGroupPlr->GetObjectGuid(); + + if (plrGuid != boot.playerVotedOn) + { + SetPlayerState(plrGuid, LFG_STATE_IN_DUNGEON); + pGroupPlr->GetSession()->SendLfgBootUpdate(boot); + } + } + } + + if (yay == REQUIRED_VOTES_FOR_BOOT) + { + // kick player from group + if (pGroup->RemoveMember(boot.playerVotedOn, 1) <= 1) + { + // group->Disband(); already disbanded in RemoveMember + sObjectMgr.RemoveGroup(pGroup); + delete pGroup; + // removemember sets the player's group pointer to NULL + } + } +} + +void LFGMgr::SendRoleChosen(ObjectGuid plrGuid, ObjectGuid confirmedGuid, uint8 roles) +{ + Player* pPlayer = ObjectAccessor::FindPlayer(plrGuid); + + if (pPlayer) + pPlayer->GetSession()->SendLfgRoleChosen(confirmedGuid.GetRawValue(), roles); +} + +void LFGMgr::SendRoleCheckUpdate(ObjectGuid plrGuid, LFGRoleCheck const& roleCheck) +{ + Player* pPlayer = ObjectAccessor::FindPlayer(plrGuid); + + if (pPlayer) + pPlayer->GetSession()->SendLfgRoleCheckUpdate(roleCheck); +} + +void LFGMgr::SendLfgUpdate(ObjectGuid plrGuid, LFGPlayerStatus status, bool isGroup) +{ + Player* pPlayer = ObjectAccessor::FindPlayer(plrGuid); + + if (pPlayer) + pPlayer->GetSession()->SendLfgUpdate(isGroup, status); +} + +void LFGMgr::SendLfgJoinResult(ObjectGuid plrGuid, LfgJoinResult result, LFGState state, partyForbidden const& lockedDungeons) +{ + Player* pPlayer = ObjectAccessor::FindPlayer(plrGuid); + + if (pPlayer) + pPlayer->GetSession()->SendLfgJoinResult(result, state, lockedDungeons); +} + +void LFGMgr::RemoveOldRoleChecks() +{ + for (roleCheckMap::iterator roleItr = m_roleCheckMap.begin(); roleItr != m_roleCheckMap.end(); ++roleItr) + { + ObjectGuid groupGuid = roleItr->first; + + LFGRoleCheck roleCheck = roleItr->second; + if ((roleCheck.waitForRoleTime - time(NULL)) <= 0) // no time left + { + roleCheck.state = LFG_ROLECHECK_NO_ROLE; + + for (roleMap::iterator roleMapItr = roleCheck.currentRoles.begin(); roleMapItr != roleCheck.currentRoles.end(); ++roleMapItr) + { + ObjectGuid plrGuid = roleMapItr->first; + + SetPlayerState(plrGuid, LFG_STATE_NONE); + + SendRoleCheckUpdate(plrGuid, roleCheck); // role check failed + SendLfgUpdate(plrGuid, GetPlayerStatus(plrGuid), true); // not in lfg system anymore + } + + m_roleCheckMap.erase(groupGuid); + } + } +} diff --git a/src/game/WorldHandlers/LFGMgr.h b/src/game/WorldHandlers/LFGMgr.h new file mode 100644 index 000000000..bbccae138 --- /dev/null +++ b/src/game/WorldHandlers/LFGMgr.h @@ -0,0 +1,700 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2016 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +#ifndef __MANGOS_LFGMGR_H +#define __MANGOS_LFGMGR_H + +#include "Common.h" +#include "Policies/Singleton.h" +#include "Group.h" +#include +#include + +class Object; +class ObjectGuid; +class Player; +class Group; + +struct LFGBoot; +struct LFGGroupStatus; +struct LFGPlayers; +struct LFGPlayerStatus; +struct LFGProposal; +struct LFGRoleCheck; +struct LFGWait; + +// Begin Section: Enumerations + +enum LFGFlags +{ + LFG_FLAG_UNK1 = 0x1, + LFG_FLAG_UNK2 = 0x2, + LFG_FLAG_SEASONAL = 0x4, + LFG_FLAG_UNK3 = 0x8 +}; + +/// Possible statuses to send after a request to join the dungeon finder +enum LfgJoinResult +{ + ERR_LFG_OK = 0x00, + ERR_LFG_ROLE_CHECK_FAILED = 0x01, + ERR_LFG_GROUP_FULL = 0x02, + ERR_LFG_NO_LFG_OBJECT = 0x04, + ERR_LFG_NO_SLOTS_PLAYER = 0x05, + ERR_LFG_NO_SLOTS_PARTY = 0x06, + ERR_LFG_MISMATCHED_SLOTS = 0x07, + ERR_LFG_PARTY_PLAYERS_FROM_DIFFERENT_REALMS = 0x08, + ERR_LFG_MEMBERS_NOT_PRESENT = 0x09, + ERR_LFG_GET_INFO_TIMEOUT = 0x0A, + ERR_LFG_INVALID_SLOT = 0x0B, + ERR_LFG_DESERTER_PLAYER = 0x0C, + ERR_LFG_DESERTER_PARTY = 0x0D, + ERR_LFG_RANDOM_COOLDOWN_PLAYER = 0x0E, + ERR_LFG_RANDOM_COOLDOWN_PARTY = 0x0F, + ERR_LFG_TOO_MANY_MEMBERS = 0x10, + ERR_LFG_CANT_USE_DUNGEONS = 0x11, + ERR_LFG_ROLE_CHECK_FAILED2 = 0x12, +}; + +enum LfgUpdateType +{ + LFG_UPDATE_DEFAULT = 0, + LFG_UPDATE_LEADER_LEAVE = 1, + LFG_UPDATE_ROLECHECK_ABORTED = 4, + LFG_UPDATE_JOIN = 5, + LFG_UPDATE_ROLECHECK_FAILED = 6, + LFG_UPDATE_LEAVE = 7, + LFG_UPDATE_PROPOSAL_FAILED = 8, + LFG_UPDATE_PROPOSAL_DECLINED = 9, + LFG_UPDATE_GROUP_FOUND = 10, + LFG_UPDATE_ADDED_TO_QUEUE = 12, + LFG_UPDATE_PROPOSAL_BEGIN = 13, + LFG_UPDATE_STATUS = 14, + LFG_UPDATE_GROUP_MEMBER_OFFLINE = 15, + LFG_UPDATE_GROUP_DISBAND = 16, +}; + +enum LfgType +{ + 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, + LFG_TYPE_RANDOM_DUNGEON = 6 +}; + +/// Reasons a player cannot enter a dungeon +enum LFGForbiddenTypes +{ + LFG_FORBIDDEN_EXPANSION = 1, + LFG_FORBIDDEN_LOW_LEVEL = 2, + LFG_FORBIDDEN_HIGH_LEVEL = 3, + LFG_FORBIDDEN_LOW_GEAR_SCORE = 4, + LFG_FORBIDDEN_HIGH_GEAR_SCORE = 5, + LFG_FORBIDDEN_RAID = 6, + LFG_FORBIDDEN_ATTUNEMENT_LOW_LEVEL = 1001, + LFG_FORBIDDEN_ATTUNEMENT_HIGH_LEVEL = 1002, + LFG_FORBIDDEN_QUEST_INCOMPLETE = 1022, + LFG_FORBIDDEN_MISSING_ITEM = 1025, + LFG_FORBIDDEN_NOT_IN_SEASON = 1031, + LFG_FORBIDDEN_MISSING_ACHIEVEMENT = 1034 +}; + +/// Spells that affect the mechanisms of the dungeon finder +enum LFGSpells +{ + LFG_DESERTER_SPELL = 71041, + LFG_COOLDOWN_SPELL = 71328, +}; + +enum LFGTimes +{ + LFG_TIME_ROLECHECK = 45*IN_MILLISECONDS, + LFG_TIME_BOOT = 120, + LFG_TIME_PROPOSAL = 45, +}; + +/// Proposal answers +enum LFGProposalAnswer +{ + LFG_ANSWER_PENDING = -1, + LFG_ANSWER_DENY = 0, + LFG_ANSWER_AGREE = 1 +}; + +/// Player states in the lfg system +enum LFGState +{ + LFG_STATE_NONE, + LFG_STATE_ROLECHECK, + LFG_STATE_QUEUED, + LFG_STATE_PROPOSAL, + LFG_STATE_BOOT, + LFG_STATE_IN_DUNGEON, + LFG_STATE_FINISHED_DUNGEON, + LFG_STATE_RAIDBROWSER +}; + +/// Proposal states +enum LFGProposalState +{ + LFG_PROPOSAL_INITIATING = 0, + LFG_PROPOSAL_FAILED = 1, + LFG_PROPOSAL_SUCCESS = 2 +}; + +/// Role check states +enum LFGRoleCheckState +{ + LFG_ROLECHECK_DEFAULT = 0, // Internal use = Not initialized. + LFG_ROLECHECK_FINISHED = 1, // Role check finished + LFG_ROLECHECK_INITIALITING = 2, // Role check begins + LFG_ROLECHECK_MISSING_ROLE = 3, // Someone hasn't selected a role after 2 mins + LFG_ROLECHECK_WRONG_ROLES = 4, // Can't form a group with the role selection + LFG_ROLECHECK_ABORTED = 5, // Someone left the group + LFG_ROLECHECK_NO_ROLE = 6 // Someone didn't select a role +}; + +/// Role types +enum LFGRoles +{ + PLAYER_ROLE_NONE = 0x00, + PLAYER_ROLE_LEADER = 0x01, + PLAYER_ROLE_TANK = 0x02, + PLAYER_ROLE_HEALER = 0x04, + PLAYER_ROLE_DAMAGE = 0x08 +}; + +/// Role amounts +enum LFGRoleCount +{ + NORMAL_TANK_OR_HEALER_COUNT = 1, // Tanks / Heals + NORMAL_DAMAGE_COUNT = 3, // DPS + NORMAL_TOTAL_ROLE_COUNT = 5 // Amount of players total per normal dungeon +}; + +/// Teleport errors +enum LFGTeleportError +{ + // 7 = "You can't do that right now" | 5 = No client reaction + LFG_TELEPORTERROR_OK = 0, + LFG_TELEPORTERROR_PLAYER_DEAD = 1, + LFG_TELEPORTERROR_FALLING = 2, + LFG_TELEPORTERROR_IN_VEHICLE = 3, + LFG_TELEPORTERROR_FATIGUE = 4, + LFG_TELEPORTERROR_INVALID_LOCATION = 6, + LFG_TELEPORTERROR_CHARMING = 8 +}; + +enum DungeonTypes +{ + DUNGEON_CLASSIC = 0, + DUNGEON_TBC = 1, + DUNGEON_TBC_HEROIC = 2, + DUNGEON_WOTLK = 3, + DUNGEON_WOTLK_HEROIC = 4, + DUNGEON_UNKNOWN +}; + +// End Section: Enumerations + +// Begin Section: Constants & Definitions + +/// Heroic dungeon rewards in WoTLK after already doing a dungeon +const uint32 WOTLK_SPECIAL_HEROIC_ITEM = 47241; +const uint32 WOTLK_SPECIAL_HEROIC_AMNT = 2; + +/// Default average queue time (in case we don't have data to base calculations on) +const int32 QUEUE_DEFAULT_TIME = 15*MINUTE; // 15 minutes [system is measured in seconds] + +/// Amount of votes needed to kick a player out of a group +const int32 REQUIRED_VOTES_FOR_BOOT = 3; + +typedef std::set dailyEntries; // for players who did one of X type instance per day +typedef std::set queueSet; // List of players / groups in the queue +typedef std::set groupSet; // List of groups doing a dungeon via the finder + +typedef UNORDERED_MAP dungeonEntries; // ID, Entry +typedef UNORDERED_MAP dungeonForbidden; // Entry, LFGForbiddenTypes +typedef UNORDERED_MAP proposalMap; // Proposal ID, info on a proposal +typedef UNORDERED_MAP waitTimeMap; // DungeonID, wait info +typedef UNORDERED_MAP partyForbidden; // ObjectGuid of player, map of locked dungeons +typedef UNORDERED_MAP roleMap; // ObjectGuid of player, role(s) selected +typedef UNORDERED_MAP roleCheckMap; // ObjectGuid of group, role information +typedef UNORDERED_MAP playerStatusMap; // ObjectGuid of player, info on specific players only +typedef UNORDERED_MAP playerData; // ObjectGuid of plr/group, info on specific player or group. TODO: rename to queueData +typedef UNORDERED_MAP proposalAnswerMap; // ObjectGuid of player, answer to proposal +typedef UNORDERED_MAP playerGroupMap; // ObjectGuid of player, ObjectGuid of group +typedef UNORDERED_MAP groupStatusMap; // ObjectGuid of group, group status structure +typedef UNORDERED_MAP bootStatusMap; // ObjectGuid of group, boot vote status + +// End Section: Constants & Definitions + +// Begin Section: Structures + +/// Item rewards taken from DungeonFinderItems in ObjectMgr, parsed by dbc values +struct ItemRewards +{ + uint32 itemId; + uint32 itemAmount; + + ItemRewards() : itemId(0), itemAmount(0) {} + ItemRewards(uint32 ItemId, uint32 ItemAmount) : itemId(ItemId), itemAmount(ItemAmount) {} +}; + +/// Information the dungeon finder needs about each player (or group) +struct LFGPlayers //TODO: rename to LFGQueueData +{ + LFGState currentState; // where the player is at with the dungeon finder + std::set dungeonList; // The dungeons this player or group are queued for (ID, not entry) + roleMap currentRoles; // tank, dps, healer, etc.. + std::string comments; + bool isGroup; + + time_t joinedTime; + uint8 neededTanks; + uint8 neededHealers; + uint8 neededDps; + + LFGPlayers() : currentState(LFG_STATE_NONE), currentRoles(0), isGroup(false) {} + LFGPlayers(LFGState state, std::set dungeonSelection, roleMap CurrentRoles, std::string comment, bool IsGroup, time_t JoinedTime, + uint8 NeededTanks, uint8 NeededHealers, uint8 NeededDps) : currentState(state), dungeonList(dungeonSelection), + currentRoles(CurrentRoles), comments(comment), isGroup(IsGroup), joinedTime(JoinedTime), neededTanks(NeededTanks), + neededHealers(NeededHealers), neededDps(NeededDps) {} +}; + +struct LFGRoleCheck +{ + LFGRoleCheckState state; // current status of the role check + roleMap currentRoles; // map of players to roles + std::set dungeonList; // The dungeons this player or group are queued for + uint32 randomDungeonID; // The random dungeon ID + uint64 leaderGuidRaw; // ObjectGuid(raw) of leader + time_t waitForRoleTime; // How long we'll wait for the players to confirm their roles +}; + +struct LFGWait +{ + int32 time; // current wait time for x (in seconds, so (time_t x / IN_MILLISECONDS) + int32 previousTime; // how long it took for the last person to go from queue to instance + uint32 playerCount; // amount of players in x queue for calculations [not sure if needed when finished implementing system] + bool doAverage; // tells the lfgmgr during a world update whether or not to recalculate waiting time + + LFGWait() : time(-1), previousTime(-1), playerCount(0), doAverage(false) {} + LFGWait(int32 currentTime, int32 lastTime, uint32 currentPlayerCount, bool shouldRecalculate) + : time(currentTime), previousTime(lastTime), playerCount(currentPlayerCount), doAverage(shouldRecalculate) {} +}; + +/// For SMSG_LFG_QUEUE_STATUS +struct LFGQueueStatus +{ + uint32 dungeonID; // queue info for x dungeon + int32 playerAvgWaitTime; // average wait time for the current player + int32 avgWaitTime; // average wait time for the dungeon + int32 tankAvgWaitTime; // average wait time for the tank(s) + int32 healerAvgWaitTime; // average wait time for the healer(s) + int32 dpsAvgWaitTime; // average wait time for the dps' + uint8 neededTanks; // amount of tanks needed + uint8 neededHeals; // amount of healers needed + uint8 neededDps; // amount of dps needed + uint32 timeSpentInQueue; // time already spent in the queue +}; + +/// For CMSG_LFG_GET_STATUS, SMSG_LFG_UPDATE_PARTY, and SMSG_LFG_UPDATE_PLAYER +struct LFGPlayerStatus +{ + LFGState state; + LfgUpdateType updateType; + std::set dungeonList; + std::string comment; + + LFGPlayerStatus() { } + LFGPlayerStatus(LFGState State, LfgUpdateType UpdateType, std::set DungeonList, std::string Comment) + : state(State), updateType(UpdateType), dungeonList(DungeonList), comment(Comment) { } +}; + +/// Information on a group currently in a dungeon +struct LFGGroupStatus //todo: check for this in joinlfg function, not lfgplayers struct +{ + LFGState state; // State of the group + uint32 dungeonID; // ID of the dungeon the group should be in + roleMap playerRoles; // Container holding each player's objectguid and their roles + ObjectGuid leaderGuid; // The group leader's object guid + + LFGGroupStatus() { } + LFGGroupStatus(LFGState State, uint32 DungeonID, roleMap PlayerRoles, ObjectGuid LeaderGuid) + : state(State), dungeonID(DungeonID), playerRoles(PlayerRoles), leaderGuid(LeaderGuid) { } +}; + +/// For SMSG_LFG_PROPOSAL_UPDATE +struct LFGProposal +{ + uint32 id; // proposal id + uint32 dungeonID; // dungeon id + LFGProposalState state; // proposal state + uint32 encounters; // encounters done + uint64 groupRawGuid; // group raw guid value + uint64 groupLeaderGuid; // group leader's guid + bool isNew; // is new or old group + roleMap currentRoles; // group player's roles + proposalAnswerMap answers; // answers to a proposal + playerGroupMap groups; // data on which groups players belong/belonged to + time_t joinedQueue; // time from when the players joined the queue +}; + +// For SMSG_LFG_PLAYER_REWARD +struct LFGRewards +{ + uint32 randomDungeonEntry; // Entry of the random dungeon done (0 if not random) + uint32 groupDungeonEntry; // Entry of the dungeon done by your group + bool hasDoneDaily; // First dungeon of the day? + uint32 moneyReward; // Amount of money rewarded + uint32 expReward; // Amount of experience rewarded + uint32 itemID; // ID of item reward + uint32 itemAmount; // How many of x item is rewarded + + LFGRewards() { } + LFGRewards(uint32 RandomDungeonEntry, uint32 GroupDungeonEntry, bool HasDoneDaily, + uint32 MoneyReward, uint32 ExpReward, uint32 ItemID, uint32 ItemAmount) : + randomDungeonEntry(RandomDungeonEntry), groupDungeonEntry(GroupDungeonEntry), + hasDoneDaily(HasDoneDaily), moneyReward(MoneyReward), expReward(ExpReward), + itemID(ItemID), itemAmount(ItemAmount) { } +}; + +// For SMSG_LFG_BOOT_PLAYER +struct LFGBoot +{ + bool inProgress; // Is the boot vote still occurring? + ObjectGuid playerVotedOn; // ObjectGuid of the player being voted on + std::string reason; // Reason stated for the vote + proposalAnswerMap answers; // Player's votes + time_t startTime; // When the vote started + + LFGBoot() { } + LFGBoot(bool InProgress, ObjectGuid PlayerVotedOn, std::string Reason, proposalAnswerMap Answers, time_t StartTime) + : inProgress(InProgress), playerVotedOn(PlayerVotedOn), reason(Reason), answers(Answers), startTime(StartTime) { } +}; + +// End Section: Structures + +class LFGMgr +{ +public: + LFGMgr(); + ~LFGMgr(); + + /// Update queue information and such + void Update(); + + /** + * @brief Attempt to join the dungeon finder queue, as long as the player(s) + * fit the criteria. + * + * @param roles Roles selected in lfg window + * @param dungeons List of dungeon(s) selected + * @param comments Comments made by the player + * @param plr Pointer to the player sending the packet + */ + void JoinLFG(uint32 roles, std::set dungeons, std::string comments, Player* plr); + + /** + * @brief Leave the lfg/dungeon finder system. + * + * @param plr The pointer to the player sending the request + * @param isGroup Whether or not they are the leader of a group / in a group + */ + void LeaveLFG(Player* plr, bool isGroup); + + /** + * @brief Go through a number of checks to see if the player/group can join + * the LFG queue + * + * @param plr The pointer to the player + */ + LfgJoinResult GetJoinResult(Player* plr); + + /** + * @brief Fetch the playerstatus struct of a player on request, if existant + * + * @param guid the player's objectguid + */ + LFGPlayerStatus GetPlayerStatus(ObjectGuid guid); + + /** + * @brief Set the player's comment string + * + * @param guid The player's objectguid + * @param comment Their comments + */ + void SetPlayerComment(ObjectGuid guid, std::string comment); + + /** + * @brief Set the player's LFG state + * + * @param guid The player's objectguid + * @param state the LFGState value + */ + void SetPlayerState(ObjectGuid guid, LFGState state); + + /** + * @brief Set the player's LFG update type + * + * @param guid The player's objectguid + * @param updateType The LfgUpdateType value + */ + void SetPlayerUpdateType(ObjectGuid guid, LfgUpdateType updateType); + + /** + * @brief Used to fetch the item rewards of a dungeon from the database + * + * @param dungeonId the dungeon ID used in the DBCs + * @param type the type of dungeon + */ + ItemRewards GetDungeonItemRewards(uint32 dungeonId, DungeonTypes type); + + /** + * @brief Used to determine the type of dungeon for ease of use. + * + * @param dungeonId the dungeon ID used in the DBCs + */ + DungeonTypes GetDungeonType(uint32 dungeonId); + + /** + * @brief Used to record the first time a player has entered x type of dungeon in the day. + * + * @param guidLow the player's guidLow + * @param dungeon the specific type/expansion of dungeon + */ + void RegisterPlayerDaily(uint32 guidLow, DungeonTypes dungeon); + + /** + * @brief Used to find whether or not the player has done x type of dungeon today. + * + * @param guidLow the player's guidLow + * @param dungeon the specific type/expansion of dungeon + */ + bool HasPlayerDoneDaily(uint32 guidLow, DungeonTypes dungeon); + + /// Reset accounts of players completing a/any dungeon for the day for new rewards + void ResetDailyRecords(); + + /** + * @brief Find out whether or not a special dungeon is available for that season + * + * @param dungeonId the ID of the dungeon in question + */ + bool IsSeasonActive(uint32 dungeonId); + + /** + * @brief Find the random dungeons applicable for a player + * + * @param level The level of said player + * @param expansion The player's expansion + */ + dungeonEntries FindRandomDungeonsForPlayer(uint32 level, uint8 expansion); + + /** + * @brief Find the random dungeons not applicable for a player + * + * @param level The level of said player + * @param expansion The player's expansion + */ + dungeonForbidden FindRandomDungeonsNotForPlayer(Player* plr); + + /// Given the ID of a dungeon, spit out its entry + uint32 GetDungeonEntry(uint32 ID); + + /// Teleports a player out of a dungeon (called by CMSG_LFG_TELEPORT) + void TeleportPlayer(Player* pPlayer, bool out); + + /// Queue Functions Below + + /** + * Find the player's or group's information and update the system with + * the amount of each role they need to find. + * + * @param guid The guid assigned to the structure + * @param information The LFGPlayers structure containing their information + */ + void UpdateNeededRoles(ObjectGuid guid, LFGPlayers* information); + + /** + * @brief Add the player or group to the Dungeon Finder queue + * + * @param guid the player/group's ObjectGuid + */ + void AddToQueue(ObjectGuid guid); + + /** + * @brief Remove the player or group from the Dungeon Finder queue + * + * @param guid the player/group's ObjectGuid + */ + void RemoveFromQueue(ObjectGuid guid); + + /// Search the queue for compatible matches + void FindQueueMatches(); + + /** + * @brief Search the queue for matches based off of one's guid + * + * @param guid The player or group's guid + */ + void FindSpecificQueueMatches(ObjectGuid guid); + + /// Send a periodic status update for queued players + void SendQueueStatus(); + + /// Role-Related Functions + + /** + * @brief Set and/or confirm roles for a group. + * + * @param pPlayer The pointer to the player issuing the request + * @param pGroup The pointer to that player's group + * @param roles The group leader's role(s) + */ + void PerformRoleCheck(Player* pPlayer, Group* pGroup, uint8 roles); + + /// Make sure role selections are okay + bool ValidateGroupRoles(roleMap groupMap); + + /// Proposal-Related Functions + + void ProposalUpdate(uint32 proposalID, ObjectGuid plrGuid, bool accepted); + + /// Handles reward hooks -- called by achievement manager + void HandleBossKilled(Player* pPlayer); + + /// Group kick hook + void AttemptToKickPlayer(Group* pGroup, ObjectGuid guid, ObjectGuid kicker, std::string reason); + + // Called when a player votes yes or no on a boot vote + void CastVote(Player* pPlayer, bool vote); + +protected: + bool IsSeasonal(uint32 dbcFlags) { return ((dbcFlags & LFG_FLAG_SEASONAL) != 0) ? true : false; } + + /// Check if player/party is already in the system, return that data + LFGPlayers* GetPlayerOrPartyData(ObjectGuid guid); + + /// Get a proposal structure given its id + LFGProposal* GetProposalData(uint32 proposalID); + + /// Get information on a group currently in a dungeon + LFGGroupStatus* GetGroupStatus(ObjectGuid guid); + + /// Add the player to their respective waiting map for their dungeon + void AddToWaitMap(uint8 role, std::set dungeons); + + /// Checks if any players have the leader flag for their roles + bool HasLeaderFlag(roleMap const& roles); + + /// Compares two groups/players to see if their role combinations are compatible + bool RoleMapsAreCompatible(LFGPlayers* groupOne, LFGPlayers* groupTwo); + + /// Checks whether or not two combinations of players/groups are on the same team (alliance/horde) + bool MatchesAreOfSameTeam(LFGPlayers* groupOne, LFGPlayers* groupTwo); + + /// Are the players in a proposal already grouped up? + bool IsProposalSameGroup(LFGProposal const& proposal); + + /// Update a proposal after a player refused to join + void ProposalDeclined(ObjectGuid guid, LFGProposal* proposal); + + /// Updates a wait map with the amount of time it took the last player to join + void UpdateWaitMap(LFGRoles role, uint32 dungeonID, time_t waitTime); + + /// Creates a group so they can enter a dungeon together + void CreateDungeonGroup(LFGProposal* proposal); + + /// Sends a group to the dungeon assigned to them + void TeleportToDungeon(uint32 dungeonID, Group* pGroup); + + /** + * @brief Merges two players/groups/etc into one for dungeon assignment. + * + * @param guidOne The guid assigned to the first group in m_playerData + * @param guidTwo The guid assigned to the second group in m_playerData + * @param compatibleDungeons The dungeons that both players or groups agreed to doing + */ + void MergeGroups(ObjectGuid guidOne, ObjectGuid guidTwo, std::set compatibleDungeons); + + /// Send a proposal to each member of a group + void SendDungeonProposal(LFGPlayers* lfgGroup); + + /// Tell a group member that someone else just confirmed their role + void SendRoleChosen(ObjectGuid plrGuid, ObjectGuid confirmedGuid, uint8 roles); + + /// Send SMSG_LFG_ROLE_CHECK_UPDATE to a specific player + void SendRoleCheckUpdate(ObjectGuid plrGuid, LFGRoleCheck const& roleCheck); + + /// Send SMSG_LFG_UPDATE_PARTY or SMSG_LFG_UPDATE_PLAYER + void SendLfgUpdate(ObjectGuid plrGuid, LFGPlayerStatus status, bool isGroup); + + /// Send SMSG_LFG_JOIN_RESULT + void SendLfgJoinResult(ObjectGuid plrGuid, LfgJoinResult result, LFGState state, partyForbidden const& lockedDungeons); + + /// Get rid of expired role checks + void RemoveOldRoleChecks(); + +private: + /// Daily occurences of a player doing X type dungeon + dailyEntries m_dailyAny; + dailyEntries m_dailyTBCHeroic; + dailyEntries m_dailyLKNormal; + dailyEntries m_dailyLKHeroic; + + /// General info related to joining / leaving the dungeon finder + playerData m_playerData; + queueSet m_queueSet; + + /// Dungeon Finder Status for players + playerStatusMap m_playerStatusMap; + + groupSet m_groupSet; + groupStatusMap m_groupStatusMap; + + /// Role check information + roleCheckMap m_roleCheckMap; + + /// Boot vote information + bootStatusMap m_bootStatusMap; + + /// Wait times for the queue + waitTimeMap m_tankWaitTime; + waitTimeMap m_healerWaitTime; + waitTimeMap m_dpsWaitTime; + waitTimeMap m_avgWaitTime; + + /// Proposal information + uint32 m_proposalId; + proposalMap m_proposalMap; +}; + +#define sLFGMgr MaNGOS::Singleton::Instance() + +#endif diff --git a/src/game/WorldHandlers/World.cpp b/src/game/WorldHandlers/World.cpp index 06dea53ca..7ecfdcec1 100644 --- a/src/game/WorldHandlers/World.cpp +++ b/src/game/WorldHandlers/World.cpp @@ -35,7 +35,6 @@ #include "Opcodes.h" #include "WorldSession.h" #include "WorldPacket.h" -#include "Weather.h" #include "Player.h" #include "SkillExtraItems.h" #include "SkillDiscovery.h" @@ -76,6 +75,8 @@ #include "CreatureLinkingMgr.h" #include "Calendar.h" #include "PhaseMgr.h" +#include "Weather.h" +#include "LFGMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" #endif /*ENABLE_ELUNA*/ @@ -2271,6 +2272,36 @@ void World::InitCurrencyResetTime() delete result; } +void World::InitRandomBGResetTime() +{ + QueryResult * result = CharacterDatabase.Query("SELECT NextRandomBGResetTime FROM saved_variables"); + if (!result) + m_NextRandomBGReset = time_t(time(NULL)); // game time not yet init + else + m_NextRandomBGReset = time_t((*result)[0].GetUInt64()); + + // generate time by config + time_t curTime = time(NULL); + tm localTm = *localtime(&curTime); + localTm.tm_hour = getConfig(CONFIG_UINT32_RANDOM_BG_RESET_HOUR); + localTm.tm_min = 0; + localTm.tm_sec = 0; + + // current day reset time + time_t nextDayResetTime = mktime(&localTm); + + // next reset time before current moment + if (curTime >= nextDayResetTime) + nextDayResetTime += DAY; + + // normalize reset time + m_NextRandomBGReset = m_NextRandomBGReset < curTime ? nextDayResetTime - DAY : nextDayResetTime; + if (!result) + CharacterDatabase.PExecute("INSERT INTO saved_variables (NextRandomBGResetTime) VALUES ('" UI64FMTD "')", uint64(m_NextRandomBGReset)); + else + delete result; +} + void World::ResetDailyQuests() { DETAIL_LOG("Daily quests reset for all characters."); diff --git a/src/game/WorldHandlers/World.h b/src/game/WorldHandlers/World.h index 5a7dcc653..2470468ca 100644 --- a/src/game/WorldHandlers/World.h +++ b/src/game/WorldHandlers/World.h @@ -216,6 +216,7 @@ enum eConfigUInt32Values CONFIG_UINT32_GUID_RESERVE_SIZE_CREATURE, CONFIG_UINT32_GUID_RESERVE_SIZE_GAMEOBJECT, CONFIG_UINT32_MIN_LEVEL_FOR_RAID, + CONFIG_UINT32_RANDOM_BG_RESET_HOUR, CONFIG_UINT32_VALUE_COUNT }; @@ -640,6 +641,7 @@ class World void InitCurrencyResetTime(); void InitDailyQuestResetTime(); + void InitRandomBGResetTime(); void InitWeeklyQuestResetTime(); void SetMonthlyQuestResetTime(bool initialize = true); void ResetCurrencyWeekCounts(); @@ -722,11 +724,9 @@ class World // scheduled reset times time_t m_NextCurrencyReset; time_t m_NextDailyQuestReset; - time_t m_NextWeeklyQuestReset; - time_t m_NextMonthlyQuestReset; - - // next daily quests reset time time_t m_NextRandomBGReset; + time_t m_NextWeeklyQuestReset; + time_t m_NextMonthlyQuestReset; // Player Queue Queue m_QueuedSessions;