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