From 7525fb165e0cbaca2ac41d284dbdccef35e8bd71 Mon Sep 17 00:00:00 2001 From: Ovalord <1Don7H4v3@m41L.com> Date: Sat, 2 Dec 2017 22:02:45 +0100 Subject: [PATCH] Core/LFG: * added missing dungeon ID to high priestess Azil's encounter credit * fixed the last encounter lfg reward. Cataclysm dungeons use a single entry for both difficulties so the instance needs to find the proper dungeon for the difficulty. Fixed. * increased amount of required votes to kick a player from a party * use the remaining teleport error messages for their according purpose * use a bgdata location as teleport location when teleporting out of a dungeon * fixed lfg state after group has been formed * the amount of required roles is now determined by dbc values (thanks to Onkelz28) * added some safety checks for reward data packets if no quest is found * fixed lfr queue and reward display * add the possibility to make lfg reward quests repeatable. This is getting used for dungeon quests with valor point rewards. Setting the alternative quest once the limit has been reached is handled corewise now. * implement valor points display in the lfg window. Note: to see it for the first time you need at least 1 valor point * implement Call to Arms system. Thanks to Tisk for the base code * added loot for the Call to Arms Satches of Exotic mysteries --- .../world/4.3.4/2017_12_04_xx_world.sql | 72 ++++++++ src/server/game/DataStores/DBCStructure.h | 3 + src/server/game/DataStores/DBCfmt.h | 2 +- src/server/game/DungeonFinding/LFG.h | 7 - src/server/game/DungeonFinding/LFGMgr.cpp | 157 ++++++++++++++---- src/server/game/DungeonFinding/LFGMgr.h | 42 +++-- src/server/game/DungeonFinding/LFGQueue.cpp | 130 +++++++++------ src/server/game/DungeonFinding/LFGQueue.h | 20 ++- src/server/game/Entities/Player/Player.cpp | 35 +++- src/server/game/Entities/Player/Player.h | 12 ++ src/server/game/Groups/Group.cpp | 34 ++++ src/server/game/Groups/Group.h | 3 + src/server/game/Handlers/GroupHandler.cpp | 2 +- src/server/game/Handlers/LFGHandler.cpp | 109 +++++++++--- src/server/game/Instances/InstanceScript.cpp | 6 +- src/server/game/World/World.cpp | 1 + src/server/game/World/World.h | 1 + src/server/worldserver/worldserver.conf.dist | 8 + 18 files changed, 512 insertions(+), 132 deletions(-) create mode 100644 sql/updates/world/4.3.4/2017_12_04_xx_world.sql diff --git a/sql/updates/world/4.3.4/2017_12_04_xx_world.sql b/sql/updates/world/4.3.4/2017_12_04_xx_world.sql new file mode 100644 index 00000000000..2952e78a3a0 --- /dev/null +++ b/sql/updates/world/4.3.4/2017_12_04_xx_world.sql @@ -0,0 +1,72 @@ +-- Make Cataclysm lfg quests repeatable. Their currency weekly cap is handled by the core +UPDATE `quest_template_addon` SET `SpecialFlags`= 8 | 1 WHERE `ID` IN (29185, 28905, 28907); + +-- Add loot to Satches of Exotic Mysteries +DELETE FROM `item_loot_template` WHERE `entry`= 69903; +INSERT INTO `item_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `MinCount`, `MaxCount`) VALUES +(69903, 0, 69903, 100, 2, 2); +DELETE FROM `reference_loot_template` WHERE `entry`= 69903; +INSERT INTO `reference_loot_template` (`Entry`, `Item`, `Chance`, `Lootmode`, `GroupId`, `MinCount`, `MaxCount`) VALUES +(69903, 58085, 10, 1, 1, 1, 1), +(69903, 58090, 7, 1, 1, 1, 3), +(69903, 57191, 7, 1, 1, 1, 3), +(69903, 58087, 6, 1, 1, 1, 1), +(69903, 52195, 5, 1, 1, 1, 1), +(69903, 52194, 5, 1, 1, 1, 1), +(69903, 52192, 5, 1, 1, 1, 1), +(69903, 52193, 5, 1, 1, 1, 1), +(69903, 58086, 5, 1, 1, 1, 1), +(69903, 52190, 5, 1, 1, 1, 1), +(69903, 58145, 5, 1, 1, 1, 3), +(69903, 52191, 4, 1, 1, 1, 1), +(69903, 58088, 3, 1, 1, 1, 1), +(69903, 57192, 3, 1, 1, 1, 3), +(69903, 62251, 3, 1, 1, 1, 3), +(69903, 57193, 1.7, 1, 1, 1, 3), +(69903, 58091, 1.6, 1, 1, 1, 3), +(69903, 58146, 1.5, 1, 1, 1, 3), +(69903, 57194, 1.5, 1, 1, 1, 3), +(69903, 67438, 1.4, 1, 1, 1, 1), +(69903, 54436, 0.6, 1, 1, 1, 1), +(69903, 8490, 0.6, 1, 1, 1, 1), +(69903, 48120, 0.6, 1, 1, 1, 1), +(69903, 8495, 0.6, 1, 1, 1, 1), +(69903, 46398, 0.5, 1, 1, 1, 1), +(69903, 8496, 0.5, 1, 1, 1, 1), +(69903, 69991, 0.5, 1, 1, 1, 1), +(69903, 43698, 0.4, 1, 1, 1, 1), +(69903, 44980, 0.4, 1, 1, 1, 1), +(69903, 44721, 0.4, 1, 1, 1, 1), +(69903, 69992, 0.4, 1, 1, 1, 1), +(69903, 44965, 0.4, 1, 1, 1, 1), +(69903, 44971, 0.4, 1, 1, 1, 1), +(69903, 44984, 0.3, 1, 1, 1, 1), +(69903, 29901, 0.3, 1, 1, 1, 1), +(69903, 8491, 0.3, 1, 1, 1, 1), +(69903, 8485, 0.3, 1, 1, 1, 1), +(69903, 8487, 0.3, 1, 1, 1, 1), +(69903, 10392, 0.3, 1, 1, 1, 1), +(69903, 44970, 0.3, 1, 1, 1, 1), +(69903, 44973, 0.3, 1, 1, 1, 1), +(69903, 44974, 0.3, 1, 1, 1, 1), +(69903, 44982, 0.3, 1, 1, 1, 1), +(69903, 29953, 0.3, 1, 1, 1, 1), +(69903, 8500, 0.3, 1, 1, 1, 1), +(69903, 45002, 0.3, 1, 1, 1, 1), +(69903, 8492, 0.3, 1, 1, 1, 1), +(69903, 10394, 0.3, 1, 1, 1, 1), +(69903, 8497, 0.3, 1, 1, 1, 1), +(69903, 44707, 0.3, 1, 1, 1, 1), +(69903, 45606, 0.3, 1, 1, 1, 1), +(69903, 10393, 0.3, 1, 1, 1, 1), +(69903, 29904, 0.3, 1, 1, 1, 1), +(69903, 35504, 0.2, 1, 1, 1, 1), +(69903, 43953, 0.2, 1, 1, 1, 1), +(69903, 43962, 0.2, 1, 1, 1, 1), +(69903, 44151, 0.19, 1, 1, 1, 1), +(69903, 13335, 0.07, 1, 1, 1, 1), +(69903, 32768, 0.07, 1, 1, 1, 1), +(69903, 8494, 0.06, 1, 1, 1, 1); + +-- Add missing dungeon ID to High Priestess Azil +UPDATE `instance_encounters` SET `lastEncounterDungeon`= 307 WHERE `entry`= 1057; diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h index 5da71459789..bce7dbfc12e 100644 --- a/src/server/game/DataStores/DBCStructure.h +++ b/src/server/game/DataStores/DBCStructure.h @@ -1540,6 +1540,9 @@ struct LFGDungeonEntry uint32 grouptype; // 15 //char* desc; // 16 Description uint32 randomCategoryId; // 17 RandomDungeonID assigned for this dungeon + uint32 requiredTanks; // 18 + uint32 requiredHealers; // 19 + uint32 requiredDamageDealers; // 20 // Helpers uint32 Entry() const { return ID + (type << 24); } }; diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h index cc394e308f1..d3af8f72dd7 100644 --- a/src/server/game/DataStores/DBCfmt.h +++ b/src/server/game/DataStores/DBCfmt.h @@ -101,7 +101,7 @@ char const ItemLimitCategoryEntryfmt[] = "nxii"; char const ItemRandomPropertiesfmt[] = "nxiiixxs"; char const ItemRandomSuffixfmt[] = "nsxiiiiiiiiii"; char const ItemSetEntryfmt[] = "dsiiiiiiiiiixxxxxxxiiiiiiiiiiiiiiiiii"; -char const LFGDungeonEntryfmt[] = "nsiiiiiiiiixxixixixxx"; +char const LFGDungeonEntryfmt[] = "nsiiiiiiiiixxixixiiii"; char const LightEntryfmt[] = "nifffxxxxxxxxxx"; char const LiquidTypefmt[] = "nxxixixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; char const LockEntryfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiixxxxxxxx"; diff --git a/src/server/game/DungeonFinding/LFG.h b/src/server/game/DungeonFinding/LFG.h index 638f625813e..fd9e3a2bf19 100644 --- a/src/server/game/DungeonFinding/LFG.h +++ b/src/server/game/DungeonFinding/LFG.h @@ -24,13 +24,6 @@ namespace lfg { -enum LFGEnum -{ - LFG_TANKS_NEEDED = 1, - LFG_HEALERS_NEEDED = 1, - LFG_DPS_NEEDED = 3 -}; - enum LfgRoles { PLAYER_ROLE_NONE = 0x00, diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index 5554b42fc7f..eef9891d6fe 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -28,6 +28,7 @@ #include "LFGQueue.h" #include "Group.h" #include "Player.h" +#include "Map.h" #include "RBAC.h" #include "GroupMgr.h" #include "GameEventMgr.h" @@ -38,7 +39,9 @@ namespace lfg { LFGMgr::LFGMgr(): m_QueueTimer(0), m_lfgProposalId(1), - m_options(sWorld->getIntConfig(CONFIG_LFG_OPTIONSMASK)) + m_options(sWorld->getIntConfig(CONFIG_LFG_OPTIONSMASK)), + _isCallToArmEligible(sWorld->getBoolConfig(CONFIG_LFG_CALL_TO_ARMS_ENABLED)), + _callToArmsRoles(PLAYER_ROLE_TANK | PLAYER_ROLE_HEALER) { } @@ -167,6 +170,14 @@ LFGDungeonData const* LFGMgr::GetLFGDungeon(uint32 id) return NULL; } +bool LFGMgr::IsCallToArmEligible(uint32 level, uint32 dungeonId) +{ + if (LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(dungeonId)) + return (level == DEFAULT_MAX_LEVEL && dungeon->type == LFG_TYPE_RANDOM && dungeon->difficulty == DUNGEON_DIFFICULTY_HEROIC); + + return false; +} + void LFGMgr::LoadLFGDungeons(bool reload /* = false */) { uint32 oldMSTime = getMSTime(); @@ -186,7 +197,7 @@ void LFGMgr::LoadLFGDungeons(bool reload /* = false */) case LFG_TYPE_HEROIC: case LFG_TYPE_RAID: case LFG_TYPE_RANDOM: - LfgDungeonStore[dungeon->ID] = LFGDungeonData(dungeon); + LfgDungeonStore.insert(LFGDungeonContainer::value_type(dungeon->ID, LFGDungeonData(dungeon))); break; } } @@ -356,8 +367,22 @@ void LFGMgr::Update(uint32 diff) if (m_QueueTimer > LFG_QUEUEUPDATE_INTERVAL) { m_QueueTimer = 0; + uint32 tankCount = 0; + uint32 healerCount = 0; + uint32 dpsCount = 0; + for (LfgQueueContainer::iterator it = QueuesStore.begin(); it != QueuesStore.end(); ++it) - it->second.UpdateQueueTimers(it->first, currTime); + it->second.UpdateQueueTimers(it->first, currTime, tankCount, healerCount, dpsCount); + + LFGMgr::RemoveCallToArmsRole(PLAYER_ROLE_HEALER); + LFGMgr::RemoveCallToArmsRole(PLAYER_ROLE_TANK); + LFGMgr::RemoveCallToArmsRole(PLAYER_ROLE_DAMAGE); + if (!tankCount || tankCount < dpsCount) + LFGMgr::AddCallToArmsRole(PLAYER_ROLE_TANK); + if (!healerCount || healerCount < dpsCount) + LFGMgr::AddCallToArmsRole(PLAYER_ROLE_HEALER); + if (dpsCount && (dpsCount < tankCount || dpsCount < healerCount)) + AddCallToArmsRole(PLAYER_ROLE_DAMAGE); } else m_QueueTimer += diff; @@ -671,6 +696,16 @@ void LFGMgr::UpdateRoleCheck(ObjectGuid gguid, ObjectGuid guid /* = ObjectGuid:: LfgRoleCheck& roleCheck = itRoleCheck->second; bool sendRoleChosen = roleCheck.state != LFG_ROLECHECK_DEFAULT && guid; + LfgDungeonSet dungeons; + if (roleCheck.rDungeonId) + dungeons.insert(roleCheck.rDungeonId); + else + dungeons = roleCheck.dungeons; + + LFGDungeonData const* dungeon = nullptr; + if (!dungeons.empty()) + dungeon = sLFGMgr->GetLFGDungeon(*dungeons.begin()); + if (!guid) roleCheck.state = LFG_ROLECHECK_ABORTED; else if (roles < PLAYER_ROLE_TANK) // Player selected no role. @@ -688,16 +723,10 @@ void LFGMgr::UpdateRoleCheck(ObjectGuid gguid, ObjectGuid guid /* = ObjectGuid:: { // use temporal var to check roles, CheckGroupRoles modifies the roles check_roles = roleCheck.roles; - roleCheck.state = CheckGroupRoles(check_roles) ? LFG_ROLECHECK_FINISHED : LFG_ROLECHECK_WRONG_ROLES; + roleCheck.state = (dungeon && CheckGroupRoles(check_roles, dungeon)) ? LFG_ROLECHECK_FINISHED : LFG_ROLECHECK_WRONG_ROLES; } } - LfgDungeonSet dungeons; - if (roleCheck.rDungeonId) - dungeons.insert(roleCheck.rDungeonId); - else - dungeons = roleCheck.dungeons; - LfgJoinResult joinResult = LFG_JOIN_FAILED; if (roleCheck.state == LFG_ROLECHECK_MISSING_ROLE || roleCheck.state == LFG_ROLECHECK_WRONG_ROLES) joinResult = LFG_JOIN_ROLE_CHECK_FAILED; @@ -806,8 +835,9 @@ void LFGMgr::GetCompatibleDungeons(LfgDungeonSet& dungeons, GuidSet const& playe @param[in] groles Map of roles to check @return True if roles are compatible */ -bool LFGMgr::CheckGroupRoles(LfgRolesMap& groles) +bool LFGMgr::CheckGroupRoles(LfgRolesMap& groles, LFGDungeonData const* dungeon) { + ASSERT(dungeon); if (groles.empty()) return false; @@ -826,11 +856,11 @@ bool LFGMgr::CheckGroupRoles(LfgRolesMap& groles) if (role != PLAYER_ROLE_DAMAGE) { it->second -= PLAYER_ROLE_DAMAGE; - if (CheckGroupRoles(groles)) + if (CheckGroupRoles(groles, dungeon)) return true; it->second += PLAYER_ROLE_DAMAGE; } - else if (damage == LFG_DPS_NEEDED) + else if (damage == dungeon->requiredDamageDealers) return false; else damage++; @@ -841,11 +871,11 @@ bool LFGMgr::CheckGroupRoles(LfgRolesMap& groles) if (role != PLAYER_ROLE_HEALER) { it->second -= PLAYER_ROLE_HEALER; - if (CheckGroupRoles(groles)) + if (CheckGroupRoles(groles, dungeon)) return true; it->second += PLAYER_ROLE_HEALER; } - else if (healer == LFG_HEALERS_NEEDED) + else if (healer == dungeon->requiredHealers) return false; else healer++; @@ -856,11 +886,11 @@ bool LFGMgr::CheckGroupRoles(LfgRolesMap& groles) if (role != PLAYER_ROLE_TANK) { it->second -= PLAYER_ROLE_TANK; - if (CheckGroupRoles(groles)) + if (CheckGroupRoles(groles, dungeon)) return true; it->second += PLAYER_ROLE_TANK; } - else if (tank == LFG_TANKS_NEEDED) + else if (tank == dungeon->requiredTanks) return false; else tank++; @@ -909,7 +939,10 @@ void LFGMgr::MakeNewGroup(LfgProposal const& proposal) if (!grp) { grp = new Group(); - grp->ConvertToLFG(); + if (dungeon->IsRaid()) + grp->ConvertToLFR(); + else + grp->ConvertToLFG(); grp->Create(player); ObjectGuid gguid = grp->GetGUID(); SetState(gguid, LFG_STATE_PROPOSAL); @@ -920,26 +953,35 @@ void LFGMgr::MakeNewGroup(LfgProposal const& proposal) grp->SetLfgRoles(pguid, proposal.players.find(pguid)->second.role); + for (GuidList::const_iterator itr = players.begin(); itr != players.end(); ++itr) + { + ObjectGuid temppguid = (*itr); + player->GetSession()->SendNameQueryOpcode(temppguid); + } + // Add the cooldown spell if queued for a random dungeon if (dungeon->type == LFG_TYPE_RANDOM) player->CastSpell(player, LFG_SPELL_DUNGEON_COOLDOWN, false); } ASSERT(grp); - grp->SetDungeonDifficulty(Difficulty(dungeon->difficulty)); + if (dungeon->IsRaid()) + grp->SetRaidDifficulty(Difficulty(dungeon->difficulty)); + else + grp->SetDungeonDifficulty(Difficulty(dungeon->difficulty)); ObjectGuid gguid = grp->GetGUID(); SetDungeon(gguid, dungeon->Entry()); SetState(gguid, LFG_STATE_DUNGEON); _SaveToDB(gguid, grp->GetDbStoreId()); + // Update group info + grp->SendUpdate(); + // Teleport Player for (GuidList::const_iterator it = playersToTeleport.begin(); it != playersToTeleport.end(); ++it) if (Player* player = ObjectAccessor::FindPlayer(*it)) TeleportPlayer(player, false); - - // Update group info - grp->SendUpdate(); } uint32 LFGMgr::AddProposal(LfgProposal& proposal) @@ -1221,8 +1263,14 @@ void LFGMgr::UpdateBoot(ObjectGuid guid, bool accept) } } + bool raidFinderGroup = false; + if (Group* group = sGroupMgr->GetGroupByGUID(gguid.GetCounter())) + if (group->isLFRGroup()) + raidFinderGroup = true; + // if we don't have enough votes (agree or deny) do nothing - if (agreeNum < LFG_GROUP_KICK_VOTES_NEEDED && (votesNum - agreeNum) < LFG_GROUP_KICK_VOTES_NEEDED) + if (agreeNum < (raidFinderGroup ? LFR_GROUP_KICK_VOTES_NEEDED : LFG_GROUP_KICK_VOTES_NEEDED) + && (votesNum - agreeNum) < (raidFinderGroup ? LFR_GROUP_KICK_VOTES_NEEDED : LFG_GROUP_KICK_VOTES_NEEDED)) return; // Send update info to all players @@ -1235,7 +1283,7 @@ void LFGMgr::UpdateBoot(ObjectGuid guid, bool accept) } SetVoteKick(gguid, false); - if (agreeNum == LFG_GROUP_KICK_VOTES_NEEDED) // Vote passed - Kick player + if (agreeNum == (raidFinderGroup ? LFR_GROUP_KICK_VOTES_NEEDED : LFG_GROUP_KICK_VOTES_NEEDED)) // Vote passed - Kick player { if (Group* group = sGroupMgr->GetGroupByGUID(gguid.GetCounter())) Player::RemoveFromGroup(group, boot.victim, GROUP_REMOVEMETHOD_KICK_LFG); @@ -1251,7 +1299,7 @@ void LFGMgr::UpdateBoot(ObjectGuid guid, bool accept) @param[in] out Teleport out (true) or in (false) @param[in] fromOpcode Function called from opcode handlers? (Default false) */ -void LFGMgr::TeleportPlayer(Player* player, bool out, bool fromOpcode /*= false*/) +void LFGMgr::TeleportPlayer(Player* player, bool out, bool fromOpcode /*= false*/, bool saveEntryPoint /*= true*/) { LFGDungeonData const* dungeon = NULL; Group* group = player->GetGroup(); @@ -1299,7 +1347,7 @@ void LFGMgr::TeleportPlayer(Player* player, bool out, bool fromOpcode /*= false* float z = dungeon->z; float orientation = dungeon->o; - if (!fromOpcode) + if (!fromOpcode && saveEntryPoint) { // Select a player inside to be teleported to for (GroupReference* itr = group->GetFirstMember(); itr != NULL && !mapid; itr = itr->next()) @@ -1316,8 +1364,17 @@ void LFGMgr::TeleportPlayer(Player* player, bool out, bool fromOpcode /*= false* } } } + else if (player->HasValidLFGLeavePoint(mapid) && saveEntryPoint) + { + Position pos; + player->GetLFGLeavePoint(&pos); + x = pos.m_positionX; + y = pos.m_positionY; + z = pos.m_positionZ; + orientation = pos.GetOrientation(); + } - if (!player->GetMap()->IsDungeon()) + if (!player->GetMap()->IsDungeon() && saveEntryPoint) player->SetBattlegroundEntryPoint(); if (player->IsInFlight()) @@ -1330,7 +1387,15 @@ void LFGMgr::TeleportPlayer(Player* player, bool out, bool fromOpcode /*= false* error = LFG_TELEPORTERROR_INVALID_LOCATION; } else - error = LFG_TELEPORTERROR_INVALID_LOCATION; + { + if (player->IsInCombat()) + error = LFG_TELEPORTERROR_IN_COMBAT; + else if (player->GetMapId() == uint32(dungeon->map)) + { + player->SetLFGLeavePoint(); + player->TeleportToBGEntryPoint(); + } + } if (error != LFG_TELEPORTERROR_OK) player->GetSession()->SendLfgTeleportError(uint8(error)); @@ -1430,6 +1495,17 @@ void LFGMgr::FinishDungeon(ObjectGuid gguid, const uint32 dungeonId, Map const* // we give reward without informing client (retail does this) player->RewardQuest(quest, 0, NULL, false); } + uint8 tmpRole = 0; + if (Group *group = player->GetGroup()) + tmpRole = group->GetLfgRoles(player->GetGUID()); + + if (IsCallToArmEligible(player->getLevel(), rDungeonId & 0x00FFFFFF)) + if (player->GetCallToArmsTempRoles() & tmpRole) + { + const Quest *q = sObjectMgr->GetQuestTemplate(LFG_CALL_TO_ARMS_QUEST); + player->RewardQuest(q, 0, NULL, false); + } + player->SetTempCallToArmsRoles(0); // Give rewards TC_LOG_DEBUG("lfg.dungeon.finish", "Group: %s, Player: %s done dungeon %u, %s previously done.", gguid.ToString().c_str(), guid.ToString().c_str(), GetDungeon(gguid), done ? " " : " not"); @@ -1556,6 +1632,20 @@ uint32 LFGMgr::GetDungeonMapId(ObjectGuid guid) return mapId; } +uint32 LFGMgr::GetDungeonIdForDifficulty(uint32 dungeonId, Difficulty difficulty) +{ + if (LFGDungeonData const* baseDungeon = GetLFGDungeon(dungeonId)) + { + uint16 mapId = baseDungeon->map; + LfgDungeonSet const& dungeons = GetDungeonsByRandom(0); + for (LfgDungeonSet::const_iterator it = dungeons.begin(); it != dungeons.end(); ++it) + if (LFGDungeonData const* dungeon = GetLFGDungeon(*it)) + if (dungeon->map == mapId && Difficulty(dungeon->difficulty) == difficulty) + return dungeon->id; + } + return 0; +} + uint8 LFGMgr::GetRoles(ObjectGuid guid) { uint8 roles = PlayersStore[guid].GetRoles(); @@ -1608,6 +1698,8 @@ LfgLockMap const LFGMgr::GetLockedDungeons(ObjectGuid guid) lockStatus = LFG_LOCKSTATUS_RAID_LOCKED; else if (dungeon->difficulty > DUNGEON_DIFFICULTY_NORMAL && player->GetBoundInstance(dungeon->map, Difficulty(dungeon->difficulty))) lockStatus = LFG_LOCKSTATUS_RAID_LOCKED; + else if (dungeon->difficulty > DUNGEON_DIFFICULTY_NORMAL && player->GetBoundInstance(dungeon->map, Difficulty(dungeon->difficulty), dungeon->type == LFG_TYPE_RAID)) + lockStatus = LFG_LOCKSTATUS_RAID_LOCKED; else if (dungeon->minlevel > level) lockStatus = LFG_LOCKSTATUS_TOO_LOW_LEVEL; else if (dungeon->maxlevel < level) @@ -2039,11 +2131,18 @@ LfgDungeonSet LFGMgr::GetRandomAndSeasonalDungeons(uint8 level, uint8 expansion) for (lfg::LFGDungeonContainer::const_iterator itr = LfgDungeonStore.begin(); itr != LfgDungeonStore.end(); ++itr) { lfg::LFGDungeonData const& dungeon = itr->second; - if ((dungeon.type == lfg::LFG_TYPE_RANDOM || (dungeon.seasonal && sLFGMgr->IsSeasonActive(dungeon.id))) + if ((dungeon.type == lfg::LFG_TYPE_RANDOM || dungeon.IsRaid()|| (dungeon.seasonal && sLFGMgr->IsSeasonActive(dungeon.id))) && dungeon.expansion <= expansion && dungeon.minlevel <= level && level <= dungeon.maxlevel) randomDungeons.insert(dungeon.Entry()); } return randomDungeons; } +bool LFGDungeonData::IsRaid() const +{ + if (MapEntry const* mapInfo = sMapStore.LookupEntry(map)) + return mapInfo->IsRaid(); + return false; +} + } // namespace lfg diff --git a/src/server/game/DungeonFinding/LFGMgr.h b/src/server/game/DungeonFinding/LFGMgr.h index 95522d47ae1..23fd6ddbd9d 100644 --- a/src/server/game/DungeonFinding/LFGMgr.h +++ b/src/server/game/DungeonFinding/LFGMgr.h @@ -48,7 +48,8 @@ enum LFGMgrEnum LFG_SPELL_DUNGEON_COOLDOWN = 71328, LFG_SPELL_DUNGEON_DESERTER = 71041, LFG_SPELL_LUCK_OF_THE_DRAW = 72221, - LFG_GROUP_KICK_VOTES_NEEDED = 3 + LFG_GROUP_KICK_VOTES_NEEDED = 4, + LFR_GROUP_KICK_VOTES_NEEDED = 15 }; enum LfgFlags @@ -56,7 +57,7 @@ enum LfgFlags LFG_FLAG_UNK1 = 0x1, LFG_FLAG_UNK2 = 0x2, LFG_FLAG_SEASONAL = 0x4, - LFG_FLAG_UNK3 = 0x8 + LFG_FLAG_UNK3 = 0x8, }; /// Determines the type of instance @@ -87,7 +88,8 @@ enum LfgTeleportError LFG_TELEPORTERROR_IN_VEHICLE = 3, LFG_TELEPORTERROR_FATIGUE = 4, LFG_TELEPORTERROR_INVALID_LOCATION = 6, - LFG_TELEPORTERROR_CHARMING = 8 // FIXME - It can be 7 or 8 (Need proper data) + LFG_TELEPORTERROR_CHARMING = 7, + LFG_TELEPORTERROR_IN_COMBAT = 8 }; /// Queue join results @@ -126,6 +128,8 @@ enum LfgRoleCheckState LFG_ROLECHECK_NO_ROLE = 6 // Someone selected no role }; +#define LFG_CALL_TO_ARMS_QUEST 29339 + // Forward declaration (just to have all typedef together) struct LFGDungeonData; struct LfgReward; @@ -268,15 +272,14 @@ struct LfgPlayerBoot struct LFGDungeonData { - LFGDungeonData(): id(0), name(""), map(0), type(0), expansion(0), group(0), minlevel(0), - maxlevel(0), difficulty(REGULAR_DIFFICULTY), seasonal(false), x(0.0f), y(0.0f), z(0.0f), o(0.0f), - requiredItemLevel(0) - { } + LFGDungeonData() = delete; + LFGDungeonData(LFGDungeonEntry const* dbc): id(dbc->ID), name(dbc->name), map(dbc->map), type(dbc->type), expansion(dbc->expansion), group(dbc->grouptype), minlevel(dbc->minlevel), maxlevel(dbc->maxlevel), difficulty(Difficulty(dbc->difficulty)), seasonal((dbc->flags & LFG_FLAG_SEASONAL) != 0), x(0.0f), y(0.0f), z(0.0f), o(0.0f), - requiredItemLevel(0) + requiredItemLevel(0), requiredTanks(dbc->requiredTanks), requiredHealers(dbc->requiredHealers), + requiredDamageDealers(dbc->requiredDamageDealers) { } uint32 id; @@ -291,9 +294,14 @@ struct LFGDungeonData bool seasonal; float x, y, z, o; uint16 requiredItemLevel; + uint32 requiredTanks; + uint32 requiredHealers; + uint32 requiredDamageDealers; // Helpers uint32 Entry() const { return id + (type << 24); } + uint32 GetMaxGroupSize() const { return requiredTanks + requiredHealers + requiredDamageDealers; } + bool IsRaid() const; }; class TC_GAME_API LFGMgr @@ -331,6 +339,8 @@ class TC_GAME_API LFGMgr uint32 GetDungeon(ObjectGuid guid, bool asId = true); /// Get the map id of the current dungeon uint32 GetDungeonMapId(ObjectGuid guid); + //// Get the heroic version of the current dungeon Id + uint32 GetDungeonIdForDifficulty(uint32 dungeonId, Difficulty difficulty); /// Get kicks left in current group uint8 GetKicksLeft(ObjectGuid gguid); /// Load Lfg group info from DB @@ -339,6 +349,12 @@ class TC_GAME_API LFGMgr void SetupGroupMember(ObjectGuid guid, ObjectGuid gguid); /// Return Lfg dungeon entry for given dungeon id uint32 GetLFGDungeonEntry(uint32 id); + /// Handles Call to Arms bonuses + bool IsCallToArmEligible(uint32 level, uint32 dungeonId); + uint8 GetRolesForCallToArms() { return _callToArmsRoles; } + void AddCallToArmsRole(uint8 role) { _callToArmsRoles |= role; } + void RemoveCallToArmsRole(uint8 role) { _callToArmsRoles = _callToArmsRoles & ~role; } + void SetCallToArmEligible(bool val) { _isCallToArmEligible = val; } // cs_lfg /// Get current player roles @@ -386,7 +402,7 @@ class TC_GAME_API LFGMgr /// Returns all random and seasonal dungeons for given level and expansion LfgDungeonSet GetRandomAndSeasonalDungeons(uint8 level, uint8 expansion); /// Teleport a player to/from selected dungeon - void TeleportPlayer(Player* player, bool out, bool fromOpcode = false); + void TeleportPlayer(Player* player, bool out, bool fromOpcode = false, bool saveEntryPoint = true); /// Inits new proposal to boot a player void InitBoot(ObjectGuid gguid, ObjectGuid kguid, ObjectGuid vguid, std::string const& reason); /// Updates player boot proposal with new player answer @@ -420,11 +436,13 @@ class TC_GAME_API LFGMgr /// Gets queue join time time_t GetQueueJoinTime(ObjectGuid guid); /// Checks if given roles match, modifies given roles map with new roles - static bool CheckGroupRoles(LfgRolesMap &groles); + static bool CheckGroupRoles(LfgRolesMap& groles, LFGDungeonData const* dungeon); /// Checks if given players are ignoring each other static bool HasIgnore(ObjectGuid guid1, ObjectGuid guid2); /// Sends queue status to player static void SendLfgQueueStatus(ObjectGuid guid, LfgQueueStatusData const& data); + /// Returns dungeon datas based on the dungeon ID + LFGDungeonData const* GetLFGDungeon(uint32 id); private: uint8 GetTeam(ObjectGuid guid); @@ -438,7 +456,6 @@ class TC_GAME_API LFGMgr void RemovePlayerData(ObjectGuid guid); void GetCompatibleDungeons(LfgDungeonSet& dungeons, GuidSet const& players, LfgLockPartyMap& lockMap, bool isContinue); void _SaveToDB(ObjectGuid guid, uint32 db_guid); - LFGDungeonData const* GetLFGDungeon(uint32 id); // Proposals void RemoveProposal(LfgProposalContainer::iterator itProposal, LfgUpdateType type); @@ -475,6 +492,9 @@ class TC_GAME_API LFGMgr LfgPlayerBootContainer BootsStore; ///< Current player kicks LfgPlayerDataContainer PlayersStore; ///< Player data LfgGroupDataContainer GroupsStore; ///< Group data + // Call to Arms System + bool _isCallToArmEligible; + uint8 _callToArmsRoles; }; } // namespace lfg diff --git a/src/server/game/DungeonFinding/LFGQueue.cpp b/src/server/game/DungeonFinding/LFGQueue.cpp index 459dd4a1fb6..8ece5ba91f9 100644 --- a/src/server/game/DungeonFinding/LFGQueue.cpp +++ b/src/server/game/DungeonFinding/LFGQueue.cpp @@ -80,6 +80,23 @@ char const* GetCompatibleString(LfgCompatibility compatibles) } } +void LfgQueueData::InitializeGroupSetup() +{ + tanks = -1; + healers = -1; + dps = -1; + + if (!dungeons.empty()) + { + if (LFGDungeonData const* dungeon = sLFGMgr->GetLFGDungeon(*dungeons.begin())) + { + tanks = dungeon->requiredTanks; + healers = dungeon->requiredHealers; + dps = dungeon->requiredDamageDealers; + } + } +} + std::string LFGQueue::GetDetailedMatchRoles(GuidList const& check) const { if (check.empty()) @@ -365,7 +382,7 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) LfgRolesMap proposalRoles; // Check for correct size - if (check.size() > MAXGROUPSIZE || check.empty()) + if (check.empty()) { TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s): Size wrong - Not compatibles", GetDetailedMatchRoles(check).c_str()); return LFG_INCOMPATIBLES_WRONG_GROUP_SIZE; @@ -388,10 +405,54 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) check.push_front(frontGuid); } + std::ostringstream o; + + // Find compatible dungeons + if (check.size() > 1) + { + GuidList::iterator itguid = check.begin(); + proposalDungeons = QueueDataStore[*itguid].dungeons; + + for (++itguid; itguid != check.end(); ++itguid) + { + LfgDungeonSet temporal; + LfgDungeonSet& dungeons = QueueDataStore[*itguid].dungeons; + + std::set_intersection(proposalDungeons.begin(), proposalDungeons.end(), dungeons.begin(), dungeons.end(), std::inserter(temporal, temporal.begin())); + proposalDungeons = temporal; + } + } + else + { + proposalDungeons = QueueDataStore[check.front()].dungeons; + } + + LFGDungeonData const* dungeon = nullptr; + do + { + if (proposalDungeons.empty()) + { + TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "LFGQueue::CheckCompatibility: (%s) No compatible dungeons%s", strGuids.c_str(), o.str().c_str()); + SetCompatibles(strGuids, LFG_INCOMPATIBLES_NO_DUNGEONS); + return LFG_INCOMPATIBLES_NO_DUNGEONS; + } + + proposal.dungeonId = Trinity::Containers::SelectRandomContainerElement(proposalDungeons); + proposalDungeons.erase(proposal.dungeonId); + dungeon = sLFGMgr->GetLFGDungeon(proposal.dungeonId); + } while (!dungeon); + + // Check for correct size + if (check.size() > dungeon->GetMaxGroupSize()) + { + TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "LFGQueue::CheckCompatibility: (%s): Size wrong - Not compatibles", strGuids.c_str()); + return LFG_INCOMPATIBLES_WRONG_GROUP_SIZE; + } + // Check if more than one LFG group and number of players joining uint8 numPlayers = 0; uint8 numLfgGroups = 0; - for (GuidList::const_iterator it = check.begin(); it != check.end() && numLfgGroups < 2 && numPlayers <= MAXGROUPSIZE; ++it) + for (GuidList::const_iterator it = check.begin(); it != check.end() && numLfgGroups < 1 && numPlayers <= dungeon->GetMaxGroupSize(); ++it) { ObjectGuid guid = *it; LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(guid); @@ -416,21 +477,6 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) } } - // Group with less that MAXGROUPSIZE members always compatible - if (check.size() == 1 && numPlayers != MAXGROUPSIZE) - { - TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) single group. Compatibles", GetDetailedMatchRoles(check).c_str()); - LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(check.front()); - - LfgCompatibilityData data(LFG_COMPATIBLES_WITH_LESS_PLAYERS); - data.roles = itQueue->second.roles; - LFGMgr::CheckGroupRoles(data.roles); - - UpdateBestCompatibleInQueue(itQueue, strGuids, data.roles); - SetCompatibilityData(strGuids, data); - return LFG_COMPATIBLES_WITH_LESS_PLAYERS; - } - if (numLfgGroups > 1) { TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) More than one Lfggroup (%u)", GetDetailedMatchRoles(check).c_str(), numLfgGroups); @@ -438,7 +484,7 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) return LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS; } - if (numPlayers > MAXGROUPSIZE) + if (numPlayers > dungeon->GetMaxGroupSize()) { TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Too many players (%u)", GetDetailedMatchRoles(check).c_str(), numPlayers); SetCompatibles(strGuids, LFG_INCOMPATIBLES_TOO_MUCH_PLAYERS); @@ -474,7 +520,7 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) } LfgRolesMap debugRoles = proposalRoles; - if (!LFGMgr::CheckGroupRoles(proposalRoles)) + if (!LFGMgr::CheckGroupRoles(proposalRoles, dungeon)) { std::ostringstream o; for (LfgRolesMap::const_iterator it = debugRoles.begin(); it != debugRoles.end(); ++it) @@ -484,38 +530,15 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) SetCompatibles(strGuids, LFG_INCOMPATIBLES_NO_ROLES); return LFG_INCOMPATIBLES_NO_ROLES; } - - GuidList::iterator itguid = check.begin(); - proposalDungeons = QueueDataStore[*itguid].dungeons; - std::ostringstream o; - o << ", " << itguid->GetRawValue() << ": (" << ConcatenateDungeons(proposalDungeons) << ")"; - for (++itguid; itguid != check.end(); ++itguid) - { - LfgDungeonSet temporal; - LfgDungeonSet& dungeons = QueueDataStore[*itguid].dungeons; - o << ", " << itguid->GetRawValue() << ": (" << ConcatenateDungeons(dungeons) << ")"; - std::set_intersection(proposalDungeons.begin(), proposalDungeons.end(), dungeons.begin(), dungeons.end(), std::inserter(temporal, temporal.begin())); - proposalDungeons = temporal; - } - - if (proposalDungeons.empty()) - { - TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) No compatible dungeons%s", GetDetailedMatchRoles(check).c_str(), o.str().c_str()); - SetCompatibles(strGuids, LFG_INCOMPATIBLES_NO_DUNGEONS); - return LFG_INCOMPATIBLES_NO_DUNGEONS; - } } else { - ObjectGuid gguid = *check.begin(); - const LfgQueueData &queue = QueueDataStore[gguid]; - proposalDungeons = queue.dungeons; - proposalRoles = queue.roles; - LFGMgr::CheckGroupRoles(proposalRoles); // assing new roles + proposalRoles = QueueDataStore[check.front()].roles; + LFGMgr::CheckGroupRoles(proposalRoles, dungeon); // assign new roles } // Enough players? - if (numPlayers != MAXGROUPSIZE) + if (numPlayers != dungeon->GetMaxGroupSize()) { TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Compatibles but not enough players(%u)", GetDetailedMatchRoles(check).c_str(), numPlayers); LfgCompatibilityData data(LFG_COMPATIBLES_WITH_LESS_PLAYERS); @@ -528,7 +551,7 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) return LFG_COMPATIBLES_WITH_LESS_PLAYERS; } - ObjectGuid gguid = *check.begin(); + ObjectGuid gguid = check.front(); proposal.queues = check; proposal.isNew = numLfgGroups != 1 || sLFGMgr->GetOldState(gguid) != LFG_STATE_DUNGEON; @@ -543,7 +566,6 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) proposal.cancelTime = time(NULL) + LFG_TIME_PROPOSAL; proposal.state = LFG_PROPOSAL_INITIATING; proposal.leader.Clear(); - proposal.dungeonId = Trinity::Containers::SelectRandomContainerElement(proposalDungeons); bool leader = false; for (LfgRolesMap::const_iterator itRoles = proposalRoles.begin(); itRoles != proposalRoles.end(); ++itRoles) @@ -581,7 +603,7 @@ LfgCompatibility LFGQueue::CheckCompatibility(GuidList check) return LFG_COMPATIBLES_MATCH; } -void LFGQueue::UpdateQueueTimers(uint8 queueId, time_t currTime) +void LFGQueue::UpdateQueueTimers(uint8 queueId, time_t currTime, uint32 &tankCount, uint32 &healerCount, uint32 &dpsCount) { TC_LOG_TRACE("lfg.queue.timers.update", "Updating queue timers..."); for (LfgQueueDataContainer::iterator itQueue = QueueDataStore.begin(); itQueue != QueueDataStore.end(); ++itQueue) @@ -600,6 +622,13 @@ void LFGQueue::UpdateQueueTimers(uint8 queueId, time_t currTime) role |= itPlayer->second; role &= ~PLAYER_ROLE_LEADER; + if (role & PLAYER_ROLE_TANK) + tankCount++; + if (role & PLAYER_ROLE_HEALER) + healerCount++; + if (role & PLAYER_ROLE_DAMAGE) + dpsCount++; + switch (role) { case PLAYER_ROLE_NONE: // Should not happen - just in case @@ -724,9 +753,8 @@ void LFGQueue::UpdateBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQu queueData.bestCompatible.c_str(), key.c_str(), itrQueue->first.ToString().c_str()); queueData.bestCompatible = key; - queueData.tanks = LFG_TANKS_NEEDED; - queueData.healers = LFG_HEALERS_NEEDED; - queueData.dps = LFG_DPS_NEEDED; + queueData.InitializeGroupSetup(); + for (LfgRolesMap::const_iterator it = roles.begin(); it != roles.end(); ++it) { uint8 role = it->second; diff --git a/src/server/game/DungeonFinding/LFGQueue.h b/src/server/game/DungeonFinding/LFGQueue.h index e24cbb80c6f..e4aa866bd0f 100644 --- a/src/server/game/DungeonFinding/LFGQueue.h +++ b/src/server/game/DungeonFinding/LFGQueue.h @@ -51,14 +51,16 @@ struct LfgCompatibilityData /// Stores player or group queue info struct LfgQueueData { - LfgQueueData(): joinTime(time_t(time(NULL))), tanks(LFG_TANKS_NEEDED), - healers(LFG_HEALERS_NEEDED), dps(LFG_DPS_NEEDED) - { } + LfgQueueData(): joinTime(time_t(time(NULL))) + { + InitializeGroupSetup(); + } - LfgQueueData(time_t _joinTime, LfgDungeonSet const& _dungeons, LfgRolesMap const& _roles): - joinTime(_joinTime), tanks(LFG_TANKS_NEEDED), healers(LFG_HEALERS_NEEDED), - dps(LFG_DPS_NEEDED), dungeons(_dungeons), roles(_roles) - { } + LfgQueueData(time_t _joinTime, LfgDungeonSet const& _dungeons, LfgRolesMap const& _roles) : + joinTime(_joinTime), dungeons(_dungeons), roles(_roles) + { + InitializeGroupSetup(); + } time_t joinTime; ///< Player queue join time (to calculate wait times) uint8 tanks; ///< Tanks needed @@ -67,6 +69,8 @@ struct LfgQueueData LfgDungeonSet dungeons; ///< Selected Player/Group Dungeon/s LfgRolesMap roles; ///< Selected Player Role/s std::string bestCompatible; ///< Best compatible combination of people queued + + void InitializeGroupSetup(); }; struct LfgWaitTime @@ -101,7 +105,7 @@ class TC_GAME_API LFGQueue void UpdateWaitTimeDps(int32 waitTime, uint32 dungeonId); // Update Queue timers - void UpdateQueueTimers(uint8 queueId, time_t currTime); + void UpdateQueueTimers(uint8 queueId, time_t currTime, uint32 &tankCount, uint32 &healerCount, uint32 &dpsCount); time_t GetJoinTime(ObjectGuid guid) const; // Find new group diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index f487d8da1f1..51e3655b576 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -540,6 +540,8 @@ Player::Player(WorldSession* session): Unit(true), _archaeology(this) m_achievementMgr = new AchievementMgr(this); m_reputationMgr = new ReputationMgr(this); + + _hasValidLFGLeavePoint = false; } Player::~Player() @@ -14865,6 +14867,13 @@ bool Player::CanRewardQuest(Quest const* quest, bool msg) if (quest->GetRewOrReqMoney() < 0 && !HasEnoughMoney(-int64(quest->GetRewOrReqMoney()))) return false; + // dungeon finder quests cannot be rewarded when hit weekly currency limit + if (quest->IsDFQuest()) + for (uint8 i = 0; i < QUEST_REWARD_CURRENCY_COUNT; i++) + if (CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(quest->RewardCurrencyId[i])) + if (GetCurrencyOnWeek(quest->RewardCurrencyId[i], false) == GetCurrencyWeekCap(currency)) + return false; + return true; } @@ -15224,7 +15233,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, CharacterDatabase.CommitTransaction(trans); } - if (quest->IsDaily() || quest->IsDFQuest()) + if ((quest->IsDaily() || quest->IsDFQuest()) && !quest->IsRepeatable()) { SetDailyQuestStatus(quest_id); if (quest->IsDaily()) @@ -15779,7 +15788,7 @@ bool Player::SatisfyQuestDay(Quest const* qInfo, bool msg) const if (!qInfo->IsDaily() && !qInfo->IsDFQuest()) return true; - if (qInfo->IsDFQuest()) + if (qInfo->IsDFQuest() && !qInfo->IsRepeatable()) { if (m_DFQuests.find(qInfo->GetQuestId()) != m_DFQuests.end()) return false; @@ -22575,6 +22584,28 @@ void Player::SetBattlegroundEntryPoint() m_bgData.joinPos = WorldLocation(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, 0.0f); } +void Player::GetLFGLeavePoint(Position* pos) +{ + if (pos) + { + pos->Relocate(m_bgData.leavePos.m_positionX, m_bgData.leavePos.m_positionY, + m_bgData.leavePos.m_positionZ, m_bgData.leavePos.GetOrientation()); + _hasValidLFGLeavePoint = false; + } +} + +bool Player::HasValidLFGLeavePoint(uint32 mapid) +{ + return _hasValidLFGLeavePoint && MapManager::IsValidMapCoord(mapid, m_bgData.leavePos.m_positionX, m_bgData.leavePos.m_positionY, + m_bgData.leavePos.m_positionZ, m_bgData.leavePos.GetOrientation()); +} + +void Player::SetLFGLeavePoint() +{ + m_bgData.leavePos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + _hasValidLFGLeavePoint = true; +} + void Player::SetBGTeam(uint32 team) { m_bgData.bgTeam = team; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 8be5ca484c8..2762f6ef0b8 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1115,6 +1115,7 @@ struct BGData uint32 taxiPath[2]; WorldLocation joinPos; ///< From where player entered BG + WorldLocation leavePos; void ClearTaxiPath() { taxiPath[0] = taxiPath[1] = 0; } bool HasTaxiPath() const { return taxiPath[0] && taxiPath[1]; } @@ -2376,6 +2377,10 @@ class TC_GAME_API Player : public Unit, public GridObject bool isUsingLfg() const; bool inRandomLfgDungeon() const; + void GetLFGLeavePoint(Position* pos); + bool HasValidLFGLeavePoint(uint32 mapid); + void SetLFGLeavePoint(); + typedef std::set DFQuestsDoneList; DFQuestsDoneList m_DFQuests; @@ -2540,6 +2545,9 @@ class TC_GAME_API Player : public Unit, public GridObject VoidStorageItem* GetVoidStorageItem(uint8 slot) const; VoidStorageItem* GetVoidStorageItem(uint64 id, uint8& slot) const; + void SetTempCallToArmsRoles(uint8 roles) { _tmpLfgRolesCheck = roles; } + uint8 GetCallToArmsTempRoles() { return _tmpLfgRolesCheck; } + protected: // Gamemaster whisper whitelist GuidList WhisperList; @@ -2890,6 +2898,8 @@ class TC_GAME_API Player : public Unit, public GridObject uint32 _activeCheats; uint32 _maxPersonalArenaRate; + bool _hasValidLFGLeavePoint; + // variables to save health and mana before duel and restore them after duel uint32 healthBeforeDuel; uint32 manaBeforeDuel; @@ -2897,6 +2907,8 @@ class TC_GAME_API Player : public Unit, public GridObject WorldLocation _corpseLocation; Archaeology _archaeology; + + uint8 _tmpLfgRolesCheck; }; TC_GAME_API void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index b1cde60c4c4..38102060d80 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -241,6 +241,26 @@ void Group::ConvertToLFG() SendUpdate(); } +void Group::ConvertToLFR() +{ + m_groupType = GroupType(m_groupType | GROUPTYPE_LFG | GROUPTYPE_LFG_RESTRICTED | GROUPTYPE_RAID); + m_lootMethod = NEED_BEFORE_GREED; + + _initRaidSubGroupsCounter(); + + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE); + + stmt->setUInt8(0, uint8(m_groupType)); + stmt->setUInt32(1, m_dbStoreId); + + CharacterDatabase.Execute(stmt); + } + + SendUpdate(); +} + void Group::ConvertToRaid() { m_groupType = GroupType(m_groupType | GROUPTYPE_RAID); @@ -2374,6 +2394,15 @@ void Group::SetLfgRoles(ObjectGuid guid, uint8 roles) SendUpdate(); } +uint8 Group::GetLfgRoles(ObjectGuid guid) +{ + member_witerator slot = _getMemberWSlot(guid); + if (slot == m_memberSlots.end()) + return 0; + + return slot->roles; +} + bool Group::IsFull() const { return isRaidGroup() ? (m_memberSlots.size() >= MAXRAIDSIZE) : (m_memberSlots.size() >= MAXGROUPSIZE); @@ -2384,6 +2413,11 @@ bool Group::isLFGGroup() const return (m_groupType & GROUPTYPE_LFG) != 0; } +bool Group::isLFRGroup() const +{ + return (m_groupType & GROUPTYPE_LFG) && (m_groupType & GROUPTYPE_RAID); +} + bool Group::isRaidGroup() const { return (m_groupType & GROUPTYPE_RAID) != 0; diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index 56aaf3ba5cc..4f676dc6909 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -206,6 +206,7 @@ class TC_GAME_API Group void SetLootThreshold(ItemQualities threshold); void Disband(bool hideDestroy = false); void SetLfgRoles(ObjectGuid guid, uint8 roles); + uint8 GetLfgRoles(ObjectGuid guid); void SetGroupMarkerMask(uint32 mask) { m_markerMask = mask; } void AddGroupMarkerMask(uint32 mask) { m_markerMask |= mask; } @@ -222,6 +223,7 @@ class TC_GAME_API Group // properties accessories bool IsFull() const; bool isLFGGroup() const; + bool isLFRGroup() const; bool isRaidGroup() const; bool isBGGroup() const; bool isBFGroup() const; @@ -265,6 +267,7 @@ class TC_GAME_API Group uint8 GetMemberGroup(ObjectGuid guid) const; void ConvertToLFG(); + void ConvertToLFR(); void ConvertToRaid(); void ConvertToGroup(); diff --git a/src/server/game/Handlers/GroupHandler.cpp b/src/server/game/Handlers/GroupHandler.cpp index 1dc2ef55a82..563de9b0497 100644 --- a/src/server/game/Handlers/GroupHandler.cpp +++ b/src/server/game/Handlers/GroupHandler.cpp @@ -794,7 +794,7 @@ void WorldSession::HandleGroupRaidConvertOpcode(WorldPacket& recvData) if (!group) return; - if (_player->InBattleground()) + if (_player->InBattleground() || group->isLFGGroup()) return; // error handling diff --git a/src/server/game/Handlers/LFGHandler.cpp b/src/server/game/Handlers/LFGHandler.cpp index d5bf09bdbeb..8687b77a4d9 100644 --- a/src/server/game/Handlers/LFGHandler.cpp +++ b/src/server/game/Handlers/LFGHandler.cpp @@ -132,6 +132,8 @@ void WorldSession::HandleLfgJoinOpcode(WorldPacket& recvData) TC_LOG_DEBUG("lfg", "CMSG_LFG_JOIN %s roles: %u, Dungeons: %u, Comment: %s", GetPlayerInfo().c_str(), roles, uint8(newDungeons.size()), comment.c_str()); + GetPlayer()->SetTempCallToArmsRoles(sLFGMgr->GetRolesForCallToArms()); + sLFGMgr->JoinLfg(GetPlayer(), uint8(roles), newDungeons, comment); } @@ -170,7 +172,10 @@ void WorldSession::HandleLfgLeaveOpcode(WorldPacket& recvData) // Check cheating - only leader can leave the queue if (!group || group->GetLeaderGUID() == guid) + { + GetPlayer()->SetTempCallToArmsRoles(0); sLFGMgr->LeaveLfg(gguid); + } } void WorldSession::HandleLfgProposalResultOpcode(WorldPacket& recvData) @@ -309,8 +314,9 @@ void WorldSession::SendLfgPlayerLockInfo() data << uint8(randomDungeons.size()); // Random Dungeon count for (lfg::LfgDungeonSet::const_iterator it = randomDungeons.begin(); it != randomDungeons.end(); ++it) { - data << uint32(*it); // Dungeon Entry (id + type) - lfg::LfgReward const* reward = sLFGMgr->GetRandomDungeonReward(*it, level); + uint32 dungeonId = *it; + data << uint32(dungeonId); // Dungeon Entry (id + type) + lfg::LfgReward const* reward = sLFGMgr->GetRandomDungeonReward(dungeonId, level); Quest const* quest = NULL; bool done = false; if (reward) @@ -325,26 +331,87 @@ void WorldSession::SendLfgPlayerLockInfo() } data << uint8(done); - data << uint32(0); // currencyQuantity - data << uint32(0); // some sort of overall cap/weekly cap - data << uint32(0); // currencyID - data << uint32(0); // tier1Quantity - data << uint32(0); // tier1Limit - data << uint32(0); // overallQuantity - data << uint32(0); // overallLimit - data << uint32(0); // periodPurseQuantity - data << uint32(0); // periodPurseLimit - data << uint32(0); // purseQuantity - data << uint32(0); // purseLimit - data << uint32(0); // some sort of reward for completion - data << uint32(0); // completedEncounters - data << uint8(0); // Call to Arms eligible - for (uint32 i = 0; i < 3; ++i) + CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(CURRENCY_TYPE_VALOR_POINTS); + int8 valorPointsField = -1; + + for (uint8 i = 0; i < QUEST_REWARD_CURRENCY_COUNT; i++) + if (quest && quest->RewardCurrencyId[i] == CURRENCY_TYPE_VALOR_POINTS) + valorPointsField = i; + + if (currency && quest) { - data << uint32(0); // Call to Arms Role - //if (role) - // BuildQuestReward(data, ctaRoleQuest, GetPlayer()); + data << uint32(valorPointsField >= 0 ? quest->RewardCurrencyCount[valorPointsField] : 0); // currencyQuantity + data << uint32(GetPlayer()->GetCurrencyWeekCap(currency)); // some sort of overall cap/weekly cap + data << uint32(quest->RewardCurrencyId[0]); // currencyID + data << uint32(GetPlayer()->GetCurrencyOnWeek(CURRENCY_TYPE_VALOR_POINTS, false)); // tier1Quantity + data << uint32(GetPlayer()->GetCurrencyWeekCap(currency)); // tier1Limit + data << uint32(0); // overallQuantity + data << uint32(GetPlayer()->GetCurrencyWeekCap(currency)); // overallLimit + data << uint32(GetPlayer()->GetCurrencyOnWeek(CURRENCY_TYPE_VALOR_POINTS, false)); // periodPurseQuantity + data << uint32(GetPlayer()->GetCurrencyWeekCap(currency)); // periodPurseLimit + data << uint32(GetPlayer()->GetCurrencyTotalCap(currency)); // purseQuantity + data << uint32(0); // purseLimit + data << uint32(valorPointsField >= 0 ? quest->RewardCurrencyCount[valorPointsField] : 0); // some sort of reward for completion + } + else + { + data << uint32(0); // currencyQuantity + data << uint32(0); // some sort of overall cap/weekly cap + data << uint32(0); // currencyID + data << uint32(0); // tier1Quantity + data << uint32(0); // tier1Limit + data << uint32(0); // overallQuantity + data << uint32(0); // overallLimit + data << uint32(0); // periodPurseQuantity + data << uint32(0); // periodPurseLimit + data << uint32(0); // purseQuantity + data << uint32(0); // purseLimit + data << uint32(0); // some sort of reward for completion + } + data << uint32(0); // completedEncounters + + bool isCallToArmEligible = sLFGMgr->IsCallToArmEligible(level, dungeonId & 0x00FFFFFF); + + data << uint8(isCallToArmEligible); // Call to Arms eligible + + if (isCallToArmEligible) + { + uint8 roleTank = sLFGMgr->GetRolesForCallToArms() & lfg::PLAYER_ROLE_TANK ? lfg::PLAYER_ROLE_TANK : 0; + uint8 roleHeal = sLFGMgr->GetRolesForCallToArms() & lfg::PLAYER_ROLE_HEALER ? lfg::PLAYER_ROLE_HEALER : 0; + uint8 roleDPS = sLFGMgr->GetRolesForCallToArms() & lfg::PLAYER_ROLE_DAMAGE ? lfg::PLAYER_ROLE_DAMAGE : 0; + + Quest const* ctaQuest = sObjectMgr->GetQuestTemplate(LFG_CALL_TO_ARMS_QUEST); + if (roleTank) + { + data << uint32(roleTank); + BuildQuestReward(data, ctaQuest, GetPlayer()); + } + else + data << uint32(0); + if (roleHeal) + { + data << uint32(roleHeal); + BuildQuestReward(data, ctaQuest, GetPlayer()); + } + else + data << uint32(0); + if (roleDPS) + { + data << uint32(roleDPS); + BuildQuestReward(data, ctaQuest, GetPlayer()); + } + else + data << uint32(0); + } + else + { + for (uint32 i = 0; i < 3; ++i) + { + data << uint32(0); // Call to Arms Role + //if (role) + // BuildQuestReward(data, ctaRoleQuest, GetPlayer()); + } } if (quest) @@ -473,7 +540,7 @@ void WorldSession::SendLfgUpdateStatus(lfg::LfgUpdateData const& updateData, boo data.WriteBit(party); data.WriteBits(size, 24); data.WriteBit(guid[6]); - data.WriteBit(size > 0); // Extra info + data.WriteBit(queued); // Extra info data.WriteBits(updateData.comment.length(), 9); data.WriteBit(guid[4]); data.WriteBit(guid[7]); diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index bce1c51b176..15dee928089 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -663,7 +663,11 @@ void InstanceScript::UpdateEncounterState(EncounterCreditType type, uint32 credi completedEncounters |= 1 << encounter->dbcEntry->encounterIndex; if (encounter->lastEncounterDungeon) { - dungeonId = encounter->lastEncounterDungeon; + if (instance->GetDifficulty() != sLFGDungeonStore.LookupEntry(encounter->lastEncounterDungeon)->difficulty) + dungeonId = sLFGMgr->GetDungeonIdForDifficulty(encounter->lastEncounterDungeon, instance->GetDifficulty()); + else + dungeonId = encounter->lastEncounterDungeon; + TC_LOG_DEBUG("lfg", "UpdateEncounterState: Instance %s (instanceId %u) completed encounter %s. Credit Dungeon: %u", instance->GetMapName(), instance->GetInstanceId(), encounter->dbcEntry->encounterName, dungeonId); break; } diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 330df3cadd7..fdffaf3dd33 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1327,6 +1327,7 @@ void World::LoadConfigSettings(bool reload) // Dungeon finder m_int_configs[CONFIG_LFG_OPTIONSMASK] = sConfigMgr->GetIntDefault("DungeonFinder.OptionsMask", 1); + m_bool_configs[CONFIG_LFG_CALL_TO_ARMS_ENABLED] = sConfigMgr->GetBoolDefault("DungeonFinder.CallToArmsEnable", false); // DBC_ItemAttributes m_bool_configs[CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES] = sConfigMgr->GetBoolDefault("DBC.EnforceItemAttributes", true); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index d8a79514fa6..7749ff8a7c8 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -188,6 +188,7 @@ enum WorldBoolConfigs CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED, CONFIG_CHECK_GOBJECT_LOS, CONFIG_RESPAWN_DYNAMIC_ESCORTNPC, + CONFIG_LFG_CALL_TO_ARMS_ENABLED, BOOL_CONFIG_VALUE_COUNT }; diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index c050a992393..691632aff92 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -1213,6 +1213,14 @@ DeletedCharacterTicketTrace = 0 DungeonFinder.OptionsMask = 1 +# +# DungeonFinder.CallToArmsEnable +# Description: Enable Call to Arms bonus mechanism for random Cataclysm dungeons +# Default: 0 - (Disabled) +# 1 - (Enabled) + +DungeonFinder.CallToArmsEnable = 0 + # # DBC.EnforceItemAttributes # Description: Disallow overriding item attributes stored in DBC files with values from the