diff options
Diffstat (limited to 'src/server/game/DungeonFinding/LFGQueue.cpp')
-rw-r--r-- | src/server/game/DungeonFinding/LFGQueue.cpp | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/server/game/DungeonFinding/LFGQueue.cpp b/src/server/game/DungeonFinding/LFGQueue.cpp new file mode 100644 index 0000000000..c83f41426f --- /dev/null +++ b/src/server/game/DungeonFinding/LFGQueue.cpp @@ -0,0 +1,591 @@ +/* + * Copyright (C) + * + * This program is free software you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ObjectDefines.h" +#include "Containers.h" +#include "DBCStructure.h" +#include "DBCStores.h" +#include "Group.h" +#include "LFGQueue.h" +#include "LFGMgr.h" +#include "Log.h" +#include "ObjectMgr.h" +#include "World.h" +#include "GroupMgr.h" + +namespace lfg +{ + +void LFGQueue::AddToQueue(uint64 guid, bool failedProposal) +{ + //sLog->outString("ADD AddToQueue: %u, failed proposal: %u", GUID_LOPART(guid), failedProposal ? 1 : 0); + LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(guid); + if (itQueue == QueueDataStore.end()) + { + sLog->outError("LFGQueue::AddToQueue: Queue data not found for [" UI64FMTD "]", guid); + return; + } + //sLog->outString("AddToQueue success: %u", GUID_LOPART(guid)); + AddToNewQueue(guid, failedProposal); +} + +void LFGQueue::RemoveFromQueue(uint64 guid, bool partial) +{ + //sLog->outString("REMOVE RemoveFromQueue: %u, partial: %u", GUID_LOPART(guid), partial ? 1 : 0); + RemoveFromNewQueue(guid); + RemoveFromCompatibles(guid); + + LfgQueueDataContainer::iterator itDelete = QueueDataStore.end(); + for (LfgQueueDataContainer::iterator itr = QueueDataStore.begin(); itr != QueueDataStore.end(); ++itr) + { + if (itr->first != guid) + { + if (itr->second.bestCompatible.hasGuid(guid)) + { + //sLog->outString("CLEAR bestCompatible: %s, because of guid: %u", itr->second.bestCompatible.toString().c_str(), GUID_LOPART(guid)); + itr->second.bestCompatible.clear(); + } + } + else + { + //sLog->outString("CLEAR bestCompatible SELF: %s, because of guid: %u", itr->second.bestCompatible.toString().c_str(), GUID_LOPART(guid)); + //itr->second.bestCompatible.clear(); // don't clear here, because UpdateQueueTimers will try to find with every diff update + itDelete = itr; + } + } + + // xinef: partial + if (!partial && itDelete != QueueDataStore.end()) + { + //sLog->outString("ERASE QueueDataStore for: %u", GUID_LOPART(guid)); + //sLog->outString("ERASE QueueDataStore for: %u, itDelete: %u,%u,%u", GUID_LOPART(guid), itDelete->second.dps, itDelete->second.healers, itDelete->second.tanks); + QueueDataStore.erase(itDelete); + //sLog->outString("ERASE QueueDataStore for: %u SUCCESS", GUID_LOPART(guid)); + } +} + +void LFGQueue::AddToNewQueue(uint64 guid, bool front) +{ + if (front) + { + //sLog->outString("ADD AddToNewQueue at FRONT: %u", GUID_LOPART(guid)); + restoredAfterProposal.push_back(guid); + newToQueueStore.push_front(guid); + } + else + { + //sLog->outString("ADD AddToNewQueue at the END: %u", GUID_LOPART(guid)); + newToQueueStore.push_back(guid); + } +} + +void LFGQueue::RemoveFromNewQueue(uint64 guid) +{ + //sLog->outString("REMOVE RemoveFromNewQueue: %u", GUID_LOPART(guid)); + newToQueueStore.remove(guid); + restoredAfterProposal.remove(guid); +} + +void LFGQueue::AddQueueData(uint64 guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap) +{ + //sLog->outString("JOINED AddQueueData: %u", GUID_LOPART(guid)); + QueueDataStore[guid] = LfgQueueData(joinTime, dungeons, rolesMap); + AddToQueue(guid); +} + +void LFGQueue::RemoveQueueData(uint64 guid) +{ + //sLog->outString("LEFT RemoveQueueData: %u", GUID_LOPART(guid)); + LfgQueueDataContainer::iterator it = QueueDataStore.find(guid); + if (it != QueueDataStore.end()) + QueueDataStore.erase(it); +} + +void LFGQueue::UpdateWaitTimeAvg(int32 waitTime, uint32 dungeonId) +{ + LfgWaitTime &wt = waitTimesAvgStore[dungeonId]; + uint32 old_number = wt.number++; + wt.time = int32((wt.time * old_number + waitTime) / wt.number); +} + +void LFGQueue::UpdateWaitTimeTank(int32 waitTime, uint32 dungeonId) +{ + LfgWaitTime &wt = waitTimesTankStore[dungeonId]; + uint32 old_number = wt.number++; + wt.time = int32((wt.time * old_number + waitTime) / wt.number); +} + +void LFGQueue::UpdateWaitTimeHealer(int32 waitTime, uint32 dungeonId) +{ + LfgWaitTime &wt = waitTimesHealerStore[dungeonId]; + uint32 old_number = wt.number++; + wt.time = int32((wt.time * old_number + waitTime) / wt.number); +} + +void LFGQueue::UpdateWaitTimeDps(int32 waitTime, uint32 dungeonId) +{ + LfgWaitTime &wt = waitTimesDpsStore[dungeonId]; + uint32 old_number = wt.number++; + wt.time = int32((wt.time * old_number + waitTime) / wt.number); +} + +void LFGQueue::RemoveFromCompatibles(uint64 guid) +{ + //sLog->outString("COMPATIBLES REMOVE for: %u", GUID_LOPART(guid)); + for (LfgCompatibleContainer::iterator it = CompatibleList.begin(); it != CompatibleList.end(); ++it) + if (it->hasGuid(guid)) + { + //sLog->outString("Removed Compatible: %s, because of guid: %u", it->toString().c_str(), GUID_LOPART(guid)); + it->clear(); // set to 0, this will be removed while iterating in FindNewGroups + } + for (LfgCompatibleContainer::iterator itr = CompatibleTempList.begin(); itr != CompatibleTempList.end(); ) + { + LfgCompatibleContainer::iterator it = itr++; + if (it->hasGuid(guid)) + { + //sLog->outString("Erased Temp Compatible: %s, because of guid: %u", it->toString().c_str(), GUID_LOPART(guid)); + CompatibleTempList.erase(it); + } + } +} + +void LFGQueue::AddToCompatibles(Lfg5Guids const& key) +{ + //sLog->outString("COMPATIBLES ADD: %s", key.toString().c_str()); + CompatibleTempList.push_back(key); +} + +uint8 LFGQueue::FindGroups() +{ + //sLog->outString("FIND GROUPS!"); + uint8 newGroupsProcessed = 0; + while (!newToQueueStore.empty()) + { + ++newGroupsProcessed; + uint64 newGuid = newToQueueStore.front(); + bool pushCompatiblesToFront = (std::find(restoredAfterProposal.begin(), restoredAfterProposal.end(), newGuid) != restoredAfterProposal.end()); + //sLog->outString("newToQueueStore guid: %u, front: %u", GUID_LOPART(newGuid), pushCompatiblesToFront ? 1 : 0); + RemoveFromNewQueue(newGuid); + + FindNewGroups(newGuid); + + CompatibleList.splice((pushCompatiblesToFront ? CompatibleList.begin() : CompatibleList.end()), CompatibleTempList); + CompatibleTempList.clear(); + + return newGroupsProcessed; // pussywizard: only one per update, shouldn't be a problem + } + return newGroupsProcessed; +} + +LfgCompatibility LFGQueue::FindNewGroups(const uint64& newGuid) +{ + // each combination of dps+heal+tank (tank*8 + heal+4 + dps) has a value assigned 0..15 + // first 16 bits of the mask are for marking if such combination was found once, second 16 bits for marking second occurence of that combination, etc + uint64 foundMask = 0; + uint32 foundCount = 0; + + //sLog->outString("FIND NEW GROUPS for: %u", GUID_LOPART(newGuid)); + + // we have to take into account that FindNewGroups is called every X minutes if number of compatibles is low! + // build set of already present compatibles for this guid + std::set<Lfg5Guids> currentCompatibles; + for (Lfg5GuidsList::iterator it = CompatibleList.begin(); it != CompatibleList.end(); ++it) + if (it->hasGuid(newGuid)) + { + // unset roles here so they are not copied, restore after insertion + LfgRolesMap* r = it->roles; + it->roles = NULL; + currentCompatibles.insert(*it); + it->roles = r; + } + + LfgCompatibility selfCompatibility = LFG_COMPATIBILITY_PENDING; + if (currentCompatibles.empty()) + { + selfCompatibility = CheckCompatibility(Lfg5Guids(), newGuid, foundMask, foundCount, currentCompatibles); + if (selfCompatibility != LFG_COMPATIBLES_WITH_LESS_PLAYERS) // group is already compatible (a party of 5 players) + return selfCompatibility; + } + + for (Lfg5GuidsList::iterator it = CompatibleList.begin(); it != CompatibleList.end(); ) + { + Lfg5GuidsList::iterator itr = it++; + if (itr->empty()) + { + //sLog->outString("ERASE from CompatibleList"); + CompatibleList.erase(itr); + continue; + } + LfgCompatibility compatibility = CheckCompatibility(*itr, newGuid, foundMask, foundCount, currentCompatibles); + if (compatibility == LFG_COMPATIBLES_MATCH) + return LFG_COMPATIBLES_MATCH; + if ((foundMask & 0x3FFF3FFF3FFF3FFF) == 0x3FFF3FFF3FFF3FFF) // each combination of dps+heal+tank already found 4 times + break; + } + + return selfCompatibility; +} + +LfgCompatibility LFGQueue::CheckCompatibility(Lfg5Guids const& checkWith, const uint64& newGuid, uint64& foundMask, uint32& foundCount, const std::set<Lfg5Guids>& currentCompatibles) +{ + //sLog->outString("CHECK CheckCompatibility: %s, new guid: %u", checkWith.toString().c_str(), GUID_LOPART(newGuid)); + Lfg5Guids check(checkWith, false); // here newGuid is at front + Lfg5Guids strGuids(checkWith, false); // here guids are sorted + check.force_insert_front(newGuid); + strGuids.insert(newGuid); + + if (!currentCompatibles.empty() && currentCompatibles.find(strGuids) != currentCompatibles.end()) + return LFG_INCOMPATIBLES_TOO_MUCH_PLAYERS; + + LfgProposal proposal; + LfgDungeonSet proposalDungeons; + LfgGroupsMap proposalGroups; + LfgRolesMap proposalRoles; + + // Check if more than one LFG group and number of players joining + uint8 numPlayers = 0; + uint8 numLfgGroups = 0; + uint64 guid; + uint64 addToFoundMask = 0; + + for (uint8 i=0; i<5 && (guid=check.guid[i]) != 0 && numLfgGroups < 2 && numPlayers <= MAXGROUPSIZE; ++i) + { + LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(guid); + if (itQueue == QueueDataStore.end()) + { + sLog->outError("LFGQueue::CheckCompatibility: [" UI64FMTD "] is not queued but listed as queued!", guid); + RemoveFromQueue(guid); + return LFG_COMPATIBILITY_PENDING; + } + + // Store group so we don't need to call Mgr to get it later (if it's player group will be 0 otherwise would have joined as group) + for (LfgRolesMap::const_iterator it2 = itQueue->second.roles.begin(); it2 != itQueue->second.roles.end(); ++it2) + proposalGroups[it2->first] = IS_GROUP_GUID(itQueue->first) ? itQueue->first : 0; + + numPlayers += itQueue->second.roles.size(); + + if (sLFGMgr->IsLfgGroup(guid)) + { + if (!numLfgGroups) + proposal.group = guid; + ++numLfgGroups; + } + } + + if (numLfgGroups > 1) + return LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS; + + // Group with less that MAXGROUPSIZE members always compatible + if (check.size() == 1 && numPlayers < MAXGROUPSIZE) + { + LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(check.front()); + LfgRolesMap roles = itQueue->second.roles; + uint8 roleCheckResult = LFGMgr::CheckGroupRoles(roles); + strGuids.addRoles(roles); + itQueue->second.bestCompatible.clear(); // this may be left after a failed proposal (not cleared, because UpdateQueueTimers would try to generate it with every update) + //UpdateBestCompatibleInQueue(itQueue, strGuids); + AddToCompatibles(strGuids); + if (roleCheckResult && roleCheckResult <= 15) + foundMask |= ( (((uint64)1)<<(roleCheckResult-1)) | (((uint64)1)<<(16+roleCheckResult-1)) | (((uint64)1)<<(32+roleCheckResult-1)) | (((uint64)1)<<(48+roleCheckResult-1)) ); + return LFG_COMPATIBLES_WITH_LESS_PLAYERS; + } + + if (numPlayers > MAXGROUPSIZE) + return LFG_INCOMPATIBLES_TOO_MUCH_PLAYERS; + + // If it's single group no need to check for duplicate players, ignores, bad roles or bad dungeons as it's been checked before joining + if (check.size() > 1) + { + for (uint8 i=0; i<5 && check.guid[i]; ++i) + { + const LfgRolesMap &roles = QueueDataStore[check.guid[i]].roles; + for (LfgRolesMap::const_iterator itRoles = roles.begin(); itRoles != roles.end(); ++itRoles) + { + LfgRolesMap::const_iterator itPlayer; + for (itPlayer = proposalRoles.begin(); itPlayer != proposalRoles.end(); ++itPlayer) + { + if (itRoles->first == itPlayer->first) + { + // pussywizard: LFG ZOMG! this means that this player was in two different LfgQueueData (in QueueDataStore), and at least one of them is a group guid, because we do checks so there aren't 2 same guids in current CHECK + //sLog->outError("LFGQueue::CheckCompatibility: ERROR! Player multiple times in queue! [" UI64FMTD "]", itRoles->first); + break; + } + else if (sLFGMgr->HasIgnore(itRoles->first, itPlayer->first)) + break; + } + if (itPlayer == proposalRoles.end()) + proposalRoles[itRoles->first] = itRoles->second; + else + break; + } + } + + if (numPlayers != proposalRoles.size()) + return LFG_INCOMPATIBLES_HAS_IGNORES; + + uint8 roleCheckResult = LFGMgr::CheckGroupRoles(proposalRoles); + if (!roleCheckResult || roleCheckResult > 0xF) + return LFG_INCOMPATIBLES_NO_ROLES; + + // now, every combination can occur only 4 times (explained in FindNewGroups) + if (foundMask & (((uint64)1)<<(roleCheckResult-1))) + { + if (foundMask & (((uint64)1)<<(16+roleCheckResult-1))) + { + if (foundMask & (((uint64)1)<<(32+roleCheckResult-1))) + { + if (foundMask & (((uint64)1)<<(48+roleCheckResult-1))) + { + if (foundCount >= 10) // but only after finding at least 10 compatibles (this helps when there are few groups) + return LFG_INCOMPATIBLES_NO_ROLES; + } + else + addToFoundMask |= (((uint64)1)<<(48+roleCheckResult-1)); + } + else + addToFoundMask |= (((uint64)1)<<(32+roleCheckResult-1)); + } + else + addToFoundMask |= (((uint64)1)<<(16+roleCheckResult-1)); + } + else + addToFoundMask |= (((uint64)1)<<(roleCheckResult-1)); + + proposalDungeons = QueueDataStore[check.front()].dungeons; + for (uint8 i=1; i<5 && check.guid[i]; ++i) + { + LfgDungeonSet temporal; + LfgDungeonSet &dungeons = QueueDataStore[check.guid[i]].dungeons; + std::set_intersection(proposalDungeons.begin(), proposalDungeons.end(), dungeons.begin(), dungeons.end(), std::inserter(temporal, temporal.begin())); + proposalDungeons = temporal; + } + + if (proposalDungeons.empty()) + return LFG_INCOMPATIBLES_NO_DUNGEONS; + } + else + { + uint64 gguid = check.front(); + const LfgQueueData &queue = QueueDataStore[gguid]; + proposalDungeons = queue.dungeons; + proposalRoles = queue.roles; + LFGMgr::CheckGroupRoles(proposalRoles); // assing new roles + } + + // Enough players? + if (numPlayers != MAXGROUPSIZE) + { + strGuids.addRoles(proposalRoles); + for (uint8 i=0; i<5 && check.guid[i]; ++i) + { + LfgQueueDataContainer::iterator itr = QueueDataStore.find(check.guid[i]); + if (!itr->second.bestCompatible.empty()) // update if groups don't have it empty (for empty it will be generated in UpdateQueueTimers) + UpdateBestCompatibleInQueue(itr, strGuids); + } + AddToCompatibles(strGuids); + foundMask |= addToFoundMask; + ++foundCount; + return LFG_COMPATIBLES_WITH_LESS_PLAYERS; + } + + uint64 gguid = check.front(); + proposal.queues = strGuids; + proposal.isNew = numLfgGroups != 1 || sLFGMgr->GetOldState(gguid) != LFG_STATE_DUNGEON; + + if (!sLFGMgr->AllQueued(check)) // can't create proposal + return LFG_COMPATIBILITY_PENDING; + + // Create a new proposal + proposal.cancelTime = time(NULL) + LFG_TIME_PROPOSAL; + proposal.state = LFG_PROPOSAL_INITIATING; + proposal.leader = 0; + proposal.dungeonId = Trinity::Containers::SelectRandomContainerElement(proposalDungeons); + + bool leader = false; + for (LfgRolesMap::const_iterator itRoles = proposalRoles.begin(); itRoles != proposalRoles.end(); ++itRoles) + { + // Assing new leader + if (itRoles->second & PLAYER_ROLE_LEADER) + { + if (!leader || !proposal.leader || urand(0, 1)) + proposal.leader = itRoles->first; + leader = true; + } + else if (!leader && (!proposal.leader || urand(0, 1))) + proposal.leader = itRoles->first; + + // Assing player data and roles + LfgProposalPlayer &data = proposal.players[itRoles->first]; + data.role = itRoles->second; + data.group = proposalGroups.find(itRoles->first)->second; + if (!proposal.isNew && data.group && data.group == proposal.group) // Player from existing group, autoaccept + data.accept = LFG_ANSWER_AGREE; + } + + for (uint8 i=0; i<5 && proposal.queues.guid[i]; ++i) + RemoveFromQueue(proposal.queues.guid[i], true); + + sLFGMgr->AddProposal(proposal); + + return LFG_COMPATIBLES_MATCH; +} + +void LFGQueue::UpdateQueueTimers(uint32 diff) +{ + time_t currTime = time(NULL); + bool sendQueueStatus = false; + + if (m_QueueStatusTimer > LFG_QUEUEUPDATE_INTERVAL) + { + m_QueueStatusTimer = 0; + sendQueueStatus = true; + } + else + m_QueueStatusTimer += diff; + + //sLog->outString("UPDATE UpdateQueueTimers"); + for (Lfg5GuidsList::iterator it = CompatibleList.begin(); it != CompatibleList.end(); ) + { + Lfg5GuidsList::iterator itr = it++; + if (itr->empty()) + { + //sLog->outString("UpdateQueueTimers ERASE compatible"); + CompatibleList.erase(itr); + } + } + + if (!sendQueueStatus) + { + for (LfgQueueDataContainer::iterator itQueue = QueueDataStore.begin(); itQueue != QueueDataStore.end(); ) + { + if (currTime - itQueue->second.joinTime > 2*HOUR) + { + uint64 guid = itQueue->first; + QueueDataStore.erase(itQueue++); + sLFGMgr->LeaveAllLfgQueues(guid, true); + continue; + } + if (itQueue->second.bestCompatible.empty()) + { + uint32 numOfCompatibles = FindBestCompatibleInQueue(itQueue); + if (numOfCompatibles /*must be positive, because proposals don't delete QueueQueueData*/ && currTime-itQueue->second.lastRefreshTime >= 60 && numOfCompatibles < (5-itQueue->second.bestCompatible.roles->size())*25) + { + itQueue->second.lastRefreshTime = currTime; + AddToQueue(itQueue->first, false); + } + } + ++itQueue; + } + return; + } + + //sLog->outTrace(LOG_FILTER_LFG, "Updating queue timers..."); + for (LfgQueueDataContainer::iterator itQueue = QueueDataStore.begin(); itQueue != QueueDataStore.end(); ++itQueue) + { + LfgQueueData& queueinfo = itQueue->second; + uint32 dungeonId = (*queueinfo.dungeons.begin()); + uint32 queuedTime = uint32(currTime - queueinfo.joinTime); + uint8 role = PLAYER_ROLE_NONE; + int32 waitTime = -1; + int32 wtTank = waitTimesTankStore[dungeonId].time; + int32 wtHealer = waitTimesHealerStore[dungeonId].time; + int32 wtDps = waitTimesDpsStore[dungeonId].time; + int32 wtAvg = waitTimesAvgStore[dungeonId].time; + + for (LfgRolesMap::const_iterator itPlayer = queueinfo.roles.begin(); itPlayer != queueinfo.roles.end(); ++itPlayer) + role |= itPlayer->second; + role &= ~PLAYER_ROLE_LEADER; + + switch (role) + { + case PLAYER_ROLE_NONE: // Should not happen - just in case + waitTime = -1; + break; + case PLAYER_ROLE_TANK: + waitTime = wtTank; + break; + case PLAYER_ROLE_HEALER: + waitTime = wtHealer; + break; + case PLAYER_ROLE_DAMAGE: + waitTime = wtDps; + break; + default: + waitTime = wtAvg; + break; + } + + if (queueinfo.bestCompatible.empty()) + { + //sLog->outString("found empty bestCompatible"); + FindBestCompatibleInQueue(itQueue); + } + + LfgQueueStatusData queueData(dungeonId, waitTime, wtAvg, wtTank, wtHealer, wtDps, queuedTime, queueinfo.tanks, queueinfo.healers, queueinfo.dps); + for (LfgRolesMap::const_iterator itPlayer = queueinfo.roles.begin(); itPlayer != queueinfo.roles.end(); ++itPlayer) + { + uint64 pguid = itPlayer->first; + LFGMgr::SendLfgQueueStatus(pguid, queueData); + } + } +} + +time_t LFGQueue::GetJoinTime(uint64 guid) +{ + return QueueDataStore[guid].joinTime; +} + +uint32 LFGQueue::FindBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue) +{ + uint32 numOfCompatibles = 0; + for (LfgCompatibleContainer::const_iterator itr = CompatibleList.begin(); itr != CompatibleList.end(); ++itr) + if (itr->hasGuid(itrQueue->first)) + { + ++numOfCompatibles; + UpdateBestCompatibleInQueue(itrQueue, *itr); + } + return numOfCompatibles; +} + +void LFGQueue::UpdateBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue, Lfg5Guids const& key) +{ + //sLog->outString("UpdateBestCompatibleInQueue: %s", key.toString().c_str()); + LfgQueueData& queueData = itrQueue->second; + + uint8 storedSize = queueData.bestCompatible.size(); + uint8 size = key.size(); + + if (size <= storedSize) + return; + + queueData.bestCompatible = key; + queueData.tanks = LFG_TANKS_NEEDED; + queueData.healers = LFG_HEALERS_NEEDED; + queueData.dps = LFG_DPS_NEEDED; + for (LfgRolesMap::const_iterator it = key.roles->begin(); it != key.roles->end(); ++it) + { + uint8 role = it->second; + if (role & PLAYER_ROLE_TANK) + --queueData.tanks; + else if (role & PLAYER_ROLE_HEALER) + --queueData.healers; + else + --queueData.dps; + } +} + +} // namespace lfg |