From f11d3a5402904984339d4166c45fc4e2bca549dd Mon Sep 17 00:00:00 2001 From: Petric Date: Mon, 29 Mar 2021 17:24:52 +0100 Subject: [PATCH] feat(CORE/Instance): access_requirement db refactor and improved output (#3696) --- .../rev_1605128047204479800.sql | 204 +++++++++++ src/server/game/DungeonFinding/LFGMgr.cpp | 62 +++- src/server/game/Entities/Player/Player.cpp | 336 ++++++++++++++++-- src/server/game/Entities/Player/Player.h | 25 +- src/server/game/Globals/ObjectMgr.cpp | 227 ++++++++---- src/server/game/Globals/ObjectMgr.h | 19 +- src/server/game/Miscellaneous/Language.h | 14 +- src/server/game/World/IWorld.h | 4 + src/server/game/World/World.cpp | 4 + src/server/scripts/Commands/cs_reload.cpp | 11 +- src/server/worldserver/worldserver.conf.dist | 39 ++ 11 files changed, 807 insertions(+), 138 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1605128047204479800.sql diff --git a/data/sql/updates/pending_db_world/rev_1605128047204479800.sql b/data/sql/updates/pending_db_world/rev_1605128047204479800.sql new file mode 100644 index 000000000..09d20ce08 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1605128047204479800.sql @@ -0,0 +1,204 @@ +INSERT INTO `version_db_world` (`sql_rev`) VALUES ('1605128047204479800'); + +-- SUMMARY: We edit the old table, then we create the new table, then we transfer the rows from the old table to the new table, then we remove no longer used columns from the old table, then we add foreign keys, then we add the strings, then update the .reload command + +-- Add new column ID +ALTER TABLE `access_requirement` +ADD `id` tinyint unsigned NOT NULL COMMENT 'The dungeon template ID' AUTO_INCREMENT UNIQUE FIRST; + +-- Set new primary key +ALTER TABLE `access_requirement` +ADD PRIMARY KEY `id` (`id`), +DROP INDEX `PRIMARY`, +DROP INDEX `id`; + +-- New table +CREATE TABLE `dungeon_access_requirements` ( + `dungeon_access_id` tinyint unsigned NOT NULL COMMENT 'ID from dungeon_access_template', + `requirement_type` tinyint unsigned NOT NULL COMMENT '0 = achiev, 1 = quest, 2 = item', + `requirement_id` mediumint unsigned NOT NULL COMMENT 'Achiev/quest/item ID', + `requirement_note` varchar(255) COLLATE 'utf8_general_ci' NULL COMMENT 'Optional msg shown ingame to player if he cannot enter. You can add extra info', + `faction` tinyint unsigned NOT NULL DEFAULT 2 COMMENT '0 = Alliance, 1 = Horde, 2 = Both factions', + `priority` tinyint unsigned NULL COMMENT 'Priority order for the requirement, sorted by type. 0 is the highest priority', + `leader_only` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '0 = check the requirement for the player trying to enter, 1 = check the requirement for the party leader', + `comment` varchar(255) COLLATE 'utf8_general_ci' NULL, + PRIMARY KEY (`dungeon_access_id`, `requirement_type`, `requirement_id`) +) COMMENT='Add (multiple) requirements before being able to enter a dungeon/raid' ENGINE='MyISAM' COLLATE 'utf8_general_ci'; + +-- Transfer from old table to new table: +-- ------------------- ITEMS + +INSERT INTO `dungeon_access_requirements` (`dungeon_access_id`, `requirement_type`, `requirement_id`, `requirement_note`, `faction`) + SELECT `id`, + 2 AS requirement_type, + `item`, + NULL, + 0 + FROM `access_requirement` + WHERE `item` > 0 AND `item2` = 0 + UNION + -- flamewrought key alliance + SELECT `id`, + 2 AS requirement_type, + `item2`, + NULL, + 0 AS faction + FROM `access_requirement` + WHERE `item2` > 0 + UNION + -- flamewrought key horde + SELECT `id`, + 2 AS requirement_type, + `item`, + NULL, + 1 AS faction + FROM `access_requirement` + WHERE `item2` > 0 +; + +-- ------------------ ACHIEVS + +INSERT INTO `dungeon_access_requirements` (`dungeon_access_id`, `requirement_type`, `requirement_id`) + SELECT `id`, + 0 AS requirement_type, + `completed_achievement` + FROM `access_requirement` + WHERE `completed_achievement` > 0 +; + +-- ------------------ QUESTS + +INSERT INTO `dungeon_access_requirements` (`dungeon_access_id`, `requirement_type`, `requirement_id`, `faction`, `requirement_note`, `leader_only`) + -- Alliance quests only + SELECT `id`, + 1 AS requirement_type, + `quest_done_A`, + 0 AS faction, + quest_failed_text, + 1 as leader_only + FROM `access_requirement` + WHERE `quest_done_A` > 0 AND `quest_done_A` != `quest_done_H` + UNION + -- Horde quests only + SELECT `id`, + 1 AS requirement_type, + `quest_done_H`, + 1 AS faction, + quest_failed_text, + 1 as leader_only + FROM `access_requirement` + WHERE `quest_done_H` > 0 AND `quest_done_A` != `quest_done_H` + UNION + -- Both factions + SELECT `id`, + 1 AS requirement_type, + `quest_done_H`, + 2 AS faction, + quest_failed_text, + 1 as leader_only + FROM `access_requirement` + WHERE `quest_done_H` > 0 AND `quest_done_A` = `quest_done_H` +; + +-- REORDER PRIMARY KEY +CREATE TEMPORARY TABLE IF NOT EXISTS `temp` select * from `dungeon_access_requirements` ORDER BY dungeon_access_id, faction; +TRUNCATE `dungeon_access_requirements`; +ALTER TABLE `dungeon_access_requirements`; +INSERT INTO `dungeon_access_requirements` SELECT * FROM `temp`; + +-- Verify result with this +-- SELECT `id`, `quest_failed_text` FROM `access_requirement` WHERE `quest_failed_text` IS NOT NULL; + + +-- Remove columns + rename table +ALTER TABLE `access_requirement` +DROP `item`, +DROP `item2`, +DROP `quest_done_A`, +DROP `quest_done_H`, +DROP `completed_achievement`, +DROP `quest_failed_text`, +RENAME TO `dungeon_access_template`, +COMMENT='Dungeon/raid access template and single requirements'; + +-- Rename columns +ALTER TABLE `dungeon_access_template` +CHANGE `mapId` `map_id` mediumint unsigned NOT NULL COMMENT 'Map ID from instance_template' AFTER `id`, +CHANGE `difficulty` `difficulty` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '5 man: 0 = normal, 1 = heroic, 2 = epic (not implemented) | 10 man: 0 = normal, 2 = heroic | 25 man: 1 = normal, 3 = heroic' AFTER `map_id`, +CHANGE `level_min` `min_level` tinyint unsigned NULL AFTER `difficulty`, +CHANGE `level_max` `max_level` tinyint unsigned NULL AFTER `min_level`, +CHANGE `item_level` `min_avg_item_level` smallint unsigned NULL COMMENT 'Min average ilvl required to enter' AFTER `max_level`, +CHANGE `comment` `comment` varchar(255) COLLATE 'utf8_general_ci' NULL COMMENT 'Dungeon Name 5/10/25/40 man - Normal/Heroic' AFTER `min_avg_item_level`; + +-- Add KEY CONSTRAINTS +ALTER TABLE `dungeon_access_template` ADD CONSTRAINT `FK_dungeon_access_template__instance_template` FOREIGN KEY (`map_id`) REFERENCES `instance_template` (`map`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `dungeon_access_requirements` ADD CONSTRAINT `FK_dungeon_access_requirements__dungeon_access_template` FOREIGN KEY (`dungeon_access_id`) REFERENCES `dungeon_access_template` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + + +-- Add the acore_strings +DELETE FROM `acore_string` WHERE `entry` IN (882,883,884,885,886,887,888); +INSERT INTO `acore_string` (`entry`, `content_default`) VALUES +(882, 'To enter, you must complete the following quest(s):'), +(883, 'To enter, you must complete the following achievement(s):'), +(884, 'To enter, you must have the following item(s) in your inventory:'), +(885, '- Note:'), +(886, 'You cannot enter. Access requirements not met.'), +(887, 'To be able to enter, your equipment\'s average item level must be superior or equal to %u. Your current equipment\'s average ilevel is: %u.'), +(888, 'You must be below level %u to enter.'), +(889, 'To enter, the group leader (%s) must have completed the following quest(s):'), +(890, 'To enter, the group leader (%s) must have completed the following achievement(s):'), +(891, 'To enter, the group leader (%s) must have the following item(s) in his/her inventory:'); + +-- Update old command +UPDATE `command` SET `name` = 'reload dungeon_access_template', `help` = 'Syntax: .reload dungeon_access_template\r Reload dungeon_access_template table.' WHERE `name` = 'reload access_requirement'; +REPLACE INTO `command` (`name`, `security`, `help`) VALUES ('reload dungeon_access_requirements', 3, 'Syntax: .reload dungeon_access_requirements\r Reload dungeon_access_requirements table.'); + + +-- Improve a big part of the comments +-- TODO: Still need to differentiate normal from heroic in the comments and also add the correct raid number for anything above level 60, I didn't want to make any mistake and i was tired. +-- Also can add the whole zone for Hellfire Citadel, Coilfang Reservoir, etc... +UPDATE `dungeon_access_template` SET `comment` = 'Deadmines (DM)' WHERE `id` = 3; +UPDATE `dungeon_access_template` SET `comment` = 'Wailing Caverns (WC)' WHERE `id` = 4; +UPDATE `dungeon_access_template` SET `comment` = 'Blackfathom Deeps Entrance' WHERE `id` = 6; +UPDATE `dungeon_access_template` SET `comment` = 'Sunken Temple (of Atal''Hakkar) Entrance' WHERE `id` = 9; +UPDATE `dungeon_access_template` SET `comment` = 'Ahn''Qiraj Ruins (AQ20) - 20man' WHERE `id` = 27; +UPDATE `dungeon_access_template` SET `comment` = 'Blackwing Lair (BWL) - 40man' WHERE `id` = 26; +UPDATE `dungeon_access_template` SET `comment` = 'Blackrock Spire - Both Lower (LBRS) & Upper (UBRS) - 5/10man' WHERE `id` = 13; +UPDATE `dungeon_access_template` SET `comment` = 'Blackrock Depths (BRD)' WHERE `id` = 14; +UPDATE `dungeon_access_template` SET `comment` = 'Ahn''Qiraj Temple (AQ40) - 40man' WHERE `id` = 28; +UPDATE `dungeon_access_template` SET `comment` = 'Ulduar' WHERE `id` = 88; +UPDATE `dungeon_access_template` SET `comment` = 'Ulduar' WHERE `id` = 89; +UPDATE `dungeon_access_template` SET `comment` = 'Gundrak (North entrance)' WHERE `id` = 90; +UPDATE `dungeon_access_template` SET `comment` = 'Gundrak (North entrance)' WHERE `id` = 91; + +UPDATE dungeon_access_template SET `comment` = replace(comment, ' (Entrance)', ''); +UPDATE dungeon_access_template SET `comment` = replace(comment, ' (entrance)', ''); +UPDATE dungeon_access_template SET `comment` = replace(comment, ' Entrance', ''); + +UPDATE `dungeon_access_template` SET `comment` = 'Zul''Farrak (ZF)' WHERE `id` = 12; +UPDATE `dungeon_access_template` SET `comment` = 'Zul''Gurub (ZG) - 20man' WHERE `id` = 20; +UPDATE `dungeon_access_template` SET `comment` = 'Ragefire Chasm (RF)' WHERE `id` = 23; +UPDATE `dungeon_access_template` SET `comment` = 'Karazhan - 10man' WHERE `comment` LIKE '%karaz%' AND `id` = 29; +UPDATE `dungeon_access_template` SET `comment` = 'Onyxia''s Lair - 10man' WHERE `id` = 15; +UPDATE `dungeon_access_template` SET `comment` = 'Onyxia''s Lair - 25man' WHERE `id` = 16; +UPDATE `dungeon_access_template` SET `comment` = 'The Obsidian Sanctum - 10man' WHERE `id` = 94; +UPDATE `dungeon_access_template` SET `comment` = 'Naxxramas - 10man' WHERE `id` = 30; +UPDATE `dungeon_access_template` SET `comment` = 'The Eye of Eternity (Malygos) - 10man' WHERE `id` = 96; +UPDATE `dungeon_access_template` SET `comment` = 'Vault of Archavon - 10man' WHERE `id` = 100; +UPDATE `dungeon_access_template` SET `comment` = 'Ulduar - 10man' WHERE `id` = 88; +UPDATE `dungeon_access_template` SET `comment` = 'Trial of the Crusader - 10man Normal' WHERE `id` = 108; +UPDATE `dungeon_access_template` SET `comment` = 'Icecrown Citadel - 10man Normal' WHERE `id` = 102; +UPDATE `dungeon_access_template` SET `comment` = 'The Ruby Sanctum - 10man Normal' WHERE `id` = 118; +UPDATE `dungeon_access_template` SET `comment` = 'Scarlet Monastery (SM) - All wings' WHERE `id` = 11; +UPDATE `dungeon_access_template` SET `comment` = 'Maraudon - All wings' WHERE `id` = 22; +UPDATE `dungeon_access_template` SET `comment` = 'Dire Maul - All wings' WHERE `id` = 25; +UPDATE `dungeon_access_template` SET `comment` = 'Molten Core - 40man' WHERE `id` = 24; +UPDATE `dungeon_access_template` SET `comment` = 'Stratholme' WHERE `id` = 21; +UPDATE `dungeon_access_template` SET `comment` = 'Caverns Of Time: Old Hillsbrad Foothills/Escape from Durnholde - Normal' WHERE `id` = 62; +UPDATE `dungeon_access_template` SET `comment` = 'Caverns Of Time: Old Hillsbrad Foothills/Escape from Durnholde - Heroic' WHERE `id` = 63; +UPDATE `dungeon_access_template` SET `comment` = 'Caverns Of Time: Black Morass/Opening the Dark Portal - Normal' WHERE `id` = 17; +UPDATE `dungeon_access_template` SET `comment` = 'Caverns Of Time: Black Morass/Opening the Dark Portal - Heroic' WHERE `id` = 18; +UPDATE `dungeon_access_template` SET `comment` = 'Hellfire Citadel: Magtheridon''s Lair - 25man' WHERE `id` = 39; +UPDATE `dungeon_access_template` SET `comment` = 'Coilfang Reservoir: Serpentshrine Cavern - 25man' WHERE `id` = 46; +UPDATE `dungeon_access_template` SET `comment` = 'Magisters'' Terrace - Normal' WHERE `id` = 76; +UPDATE `dungeon_access_template` SET `comment` = 'Magisters'' Terrace - Heroic' WHERE `id` = 77; diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index 7b48d4863..fa0d47293 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -391,6 +391,7 @@ namespace lfg if (!dungeon) // should never happen - We provide a list from sLFGDungeonStore continue; MapEntry const* mapEntry = sMapStore.LookupEntry(dungeon->map); + DungeonProgressionRequirements const* ar = sObjectMgr->GetAccessRequirement(dungeon->map, Difficulty(dungeon->difficulty)); uint32 lockData = 0; if (dungeon->expansion > expansion) @@ -401,29 +402,58 @@ namespace lfg lockData = LFG_LOCKSTATUS_RAID_LOCKED; else if (dungeon->difficulty > DUNGEON_DIFFICULTY_NORMAL && (!mapEntry || !mapEntry->IsRaid()) && sInstanceSaveMgr->PlayerIsPermBoundToInstance(player->GetGUIDLow(), dungeon->map, Difficulty(dungeon->difficulty))) lockData = LFG_LOCKSTATUS_RAID_LOCKED; - else if (dungeon->minlevel > level) + else if ((dungeon->minlevel > level && !sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE)) || (sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE) && ar && ar->levelMin > 0 && ar->levelMin > level)) lockData = LFG_LOCKSTATUS_TOO_LOW_LEVEL; - else if (dungeon->maxlevel < level) + else if ((dungeon->maxlevel < level && !sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE)) || (sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE) && ar && ar->levelMax > 0 && ar->levelMax < level)) lockData = LFG_LOCKSTATUS_TOO_HIGH_LEVEL; else if (dungeon->seasonal && !IsSeasonActive(dungeon->id)) lockData = LFG_LOCKSTATUS_NOT_IN_SEASON; - else if (AccessRequirement const* ar = sObjectMgr->GetAccessRequirement(dungeon->map, Difficulty(dungeon->difficulty))) + else if (ar) { - if (ar->achievement && !player->HasAchieved(ar->achievement)) - lockData = LFG_LOCKSTATUS_MISSING_ACHIEVEMENT; - else if (ar->reqItemLevel && (float)ar->reqItemLevel > avgItemLevel) - lockData = LFG_LOCKSTATUS_TOO_LOW_GEAR_SCORE; - else if (player->GetTeamId() == TEAM_ALLIANCE && ar->quest_A && !player->GetQuestRewardStatus(ar->quest_A)) - lockData = LFG_LOCKSTATUS_QUEST_NOT_COMPLETED; - else if (player->GetTeamId() == TEAM_HORDE && ar->quest_H && !player->GetQuestRewardStatus(ar->quest_H)) - lockData = LFG_LOCKSTATUS_QUEST_NOT_COMPLETED; - else if (ar->item) + // Check required items + for (const ProgressionRequirement* itemRequirement : ar->items) { - if (!player->HasItemCount(ar->item) && (!ar->item2 || !player->HasItemCount(ar->item2))) - lockData = LFG_LOCKSTATUS_MISSING_ITEM; + if (itemRequirement->faction == TEAM_NEUTRAL || itemRequirement->faction == player->GetTeamId(true)) + { + if (!player->HasItemCount(itemRequirement->id, 1)) + { + lockData = LFG_LOCKSTATUS_MISSING_ITEM; + break; + } + } + } + + //Check for quests + for (const ProgressionRequirement* questRequirement : ar->quests) + { + if (questRequirement->faction == TEAM_NEUTRAL || questRequirement->faction == player->GetTeamId(true)) + { + if (!player->GetQuestRewardStatus(questRequirement->id)) + { + lockData = LFG_LOCKSTATUS_QUEST_NOT_COMPLETED; + break; + } + } + } + + //Check for ilvl + if (ar->reqItemLevel && (float)ar->reqItemLevel > avgItemLevel) + { + lockData = LFG_LOCKSTATUS_TOO_LOW_GEAR_SCORE; + } + + //Check if player has the required achievements + for (const ProgressionRequirement* achievementRequirement : ar->achievements) + { + if (achievementRequirement->faction == TEAM_NEUTRAL || achievementRequirement->faction == player->GetTeamId(true)) + { + if (!player->HasAchieved(achievementRequirement->id)) + { + lockData = LFG_LOCKSTATUS_MISSING_ACHIEVEMENT; + break; + } + } } - else if (ar->item2 && !player->HasItemCount(ar->item2)) - lockData = LFG_LOCKSTATUS_MISSING_ITEM; } sScriptMgr->OnInitializeLockedDungeons(player, level, lockData); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 5729798d4..1a38d0e7a 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -19639,7 +19639,115 @@ void Player::SendSavedInstances() } } -bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report) +void Player::PrettyPrintRequirementsQuestList(const std::vector& missingQuests) const +{ + LocaleConstant loc_idx = GetSession()->GetSessionDbLocaleIndex(); + for (const ProgressionRequirement* missingReq : missingQuests) + { + Quest const* questTemplate = sObjectMgr->GetQuestTemplate(missingReq->id); + if (!questTemplate) + { + continue; + } + + std::string questTitle = questTemplate->GetTitle(); + if (QuestLocale const* questLocale = sObjectMgr->GetQuestLocale(questTemplate->GetQuestId())) + { + ObjectMgr::GetLocaleString(questLocale->Title, loc_idx, questTitle); + } + + std::stringstream stream; + stream << "|cffff7c0a|Hquest:"; + stream << questTemplate->GetQuestId(); + stream << ":"; + stream << questTemplate->GetQuestLevel(); + stream << "|h["; + stream << questTitle; + stream << "]|h|r"; + + if (missingReq->note.empty()) + { + ChatHandler(GetSession()).PSendSysMessage(" - %s", stream.str().c_str()); + } + else + { + ChatHandler(GetSession()).PSendSysMessage(" - %s %s %s", stream.str().c_str(), sObjectMgr->GetAcoreString(LANG_ACCESS_REQUIREMENT_NOTE, loc_idx), missingReq->note.c_str()); + } + } +} + +void Player::PrettyPrintRequirementsAchievementsList(const std::vector& missingAchievements) const +{ + LocaleConstant loc_idx = GetSession()->GetSessionDbLocaleIndex(); + for (const ProgressionRequirement* missingReq : missingAchievements) + { + AchievementEntry const* achievementEntry = sAchievementStore.LookupEntry(missingReq->id); + if (!achievementEntry) + { + continue; + } + + std::string name = *achievementEntry->name; + + std::stringstream stream; + stream << "|cffff7c0a|Hachievement:"; + stream << missingReq->id; + stream << ":"; + stream << std::hex << GetGUID() << std::dec; + stream << ":0:0:0:0:0:0:0:0|h["; + stream << name; + stream << "]|h|r"; + + if (missingReq->note.empty()) + { + ChatHandler(GetSession()).PSendSysMessage(" - %s", stream.str().c_str()); + } + else + { + ChatHandler(GetSession()).PSendSysMessage(" - %s %s %s", stream.str().c_str(), sObjectMgr->GetAcoreString(LANG_ACCESS_REQUIREMENT_NOTE, loc_idx), missingReq->note.c_str()); + } + } +} + +void Player::PrettyPrintRequirementsItemsList(const std::vector& missingItems) const +{ + LocaleConstant loc_idx = GetSession()->GetSessionDbLocaleIndex(); + for (const ProgressionRequirement* missingReq : missingItems) + { + const ItemTemplate* itemTemplate = sObjectMgr->GetItemTemplate(missingReq->id); + if (!itemTemplate) + { + continue; + } + + //Get the localised name + std::string name = itemTemplate->Name1; + if (ItemLocale const* il = sObjectMgr->GetItemLocale(itemTemplate->ItemId)) + { + ObjectMgr::GetLocaleString(il->Name, loc_idx, name); + } + + std::stringstream stream; + stream << "|c"; + stream << std::hex << ItemQualityColors[itemTemplate->Quality] << std::dec; + stream << "|Hitem:"; + stream << itemTemplate->ItemId; + stream << ":0:0:0:0:0:0:0:0:0|h["; + stream << name; + stream << "]|h|r"; + + if (missingReq->note.empty()) + { + ChatHandler(GetSession()).PSendSysMessage(" - %s", stream.str().c_str()); + } + else + { + ChatHandler(GetSession()).PSendSysMessage(" - %s %s %s", stream.str().c_str(), sObjectMgr->GetAcoreString(LANG_ACCESS_REQUIREMENT_NOTE, loc_idx), missingReq->note.c_str()); + } + } +} + +bool Player::Satisfy(DungeonProgressionRequirements const* ar, uint32 target_map, bool report) { if (!IsGameMaster() && ar) { @@ -19658,52 +19766,214 @@ bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report LevelMax = ar->levelMax; } - uint32 missingItem = 0; - if (ar->item) - { - if (!HasItemCount(ar->item, 1) && - (!ar->item2 || !HasItemCount(ar->item2))) - missingItem = ar->item; - } - else if (ar->item2 && !HasItemCount(ar->item2)) - missingItem = ar->item2; - if (DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, target_map, this)) { GetSession()->SendAreaTriggerMessage("%s", GetSession()->GetAcoreString(LANG_INSTANCE_CLOSED)); return false; } - uint32 missingQuest = 0; - if (GetTeamId(true) == TEAM_ALLIANCE && ar->quest_A && !GetQuestRewardStatus(ar->quest_A)) - missingQuest = ar->quest_A; - else if (GetTeamId(true) == TEAM_HORDE && ar->quest_H && !GetQuestRewardStatus(ar->quest_H)) - missingQuest = ar->quest_H; + Player* partyLeader = this; + std::string leaderName = m_session->GetAcoreString(LANG_YOU); + { + const uint64 leaderGuid = GetGroup() ? GetGroup()->GetLeaderGUID() : GetGUID(); + Player* tempLeader = HashMapHolder::Find(leaderGuid); - uint32 missingAchievement = 0; - Player* leader = this; - uint64 leaderGuid = GetGroup() ? GetGroup()->GetLeaderGUID() : GetGUID(); - if (leaderGuid != GetGUID()) - leader = HashMapHolder::Find(leaderGuid); + if (leaderGuid != GetGUID()) + { + if (tempLeader != nullptr) + { + partyLeader = tempLeader; + } + leaderName = GetGroup()->GetLeaderName(); + } + } + + //Check all items + std::vector missingPlayerItems; + std::vector missingLeaderItems; + for (const ProgressionRequirement* itemRequirement : ar->items) + { + Player* checkPlayer = this; + std::vector* missingItems = &missingPlayerItems; + if (itemRequirement->checkLeaderOnly) + { + checkPlayer = partyLeader; + missingItems = &missingLeaderItems; + } + + if (itemRequirement->faction == TEAM_NEUTRAL || itemRequirement->faction == checkPlayer->GetTeamId(true)) + { + if (!checkPlayer->HasItemCount(itemRequirement->id, 1)) + { + missingItems->push_back(itemRequirement); + } + } + } + + //Check all achievements + std::vector missingPlayerAchievements; + std::vector missingLeaderAchievements; + for (const ProgressionRequirement* achievementRequirement : ar->achievements) + { + Player* checkPlayer = this; + std::vector* missingAchievements = &missingPlayerAchievements; + if(achievementRequirement->checkLeaderOnly) + { + checkPlayer = partyLeader; + missingAchievements = &missingLeaderAchievements; + } + + if (achievementRequirement->faction == TEAM_NEUTRAL || achievementRequirement->faction == GetTeamId(true)) + { + if (!checkPlayer || !checkPlayer->HasAchieved(achievementRequirement->id)) + { + missingAchievements->push_back(achievementRequirement); + } + } + } + + //Check all quests + std::vector missingPlayerQuests; + std::vector missingLeaderQuests; + for (const ProgressionRequirement* questRequirement : ar->quests) + { + Player* checkPlayer = this; + std::vector* missingQuests = &missingPlayerQuests; + if (questRequirement->checkLeaderOnly) + { + checkPlayer = partyLeader; + missingQuests = &missingLeaderQuests; + } + + if (questRequirement->faction == TEAM_NEUTRAL || questRequirement->faction == checkPlayer->GetTeamId(true)) + { + if (!checkPlayer->GetQuestRewardStatus(questRequirement->id)) + { + missingQuests->push_back(questRequirement); + } + } + } + + //Check if avg ILVL requirement is allowed + bool ilvlRequirementNotMet = false; + if (sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_PORTAL_CHECK_ILVL)) + { + const int32 currentIlvl = (int32)GetAverageItemLevelForDF(); + if (ar->reqItemLevel > currentIlvl) + { + ilvlRequirementNotMet = true; + } + } - if (ar->achievement) - if (!leader || !leader->HasAchieved(ar->achievement)) - missingAchievement = ar->achievement; Difficulty target_difficulty = GetDifficulty(mapEntry->IsRaid()); MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(target_map, target_difficulty); - if (LevelMin || LevelMax || missingItem || missingQuest || missingAchievement) + if (LevelMin || LevelMax || ilvlRequirementNotMet + || missingPlayerItems.size() || missingPlayerQuests.size() || missingPlayerAchievements.size() + || missingLeaderItems.size() || missingLeaderQuests.size() || missingLeaderAchievements.size()) { if (report) { - if (missingQuest && !ar->questFailedText.empty()) - ChatHandler(GetSession()).PSendSysMessage("%s", ar->questFailedText.c_str()); - else if (mapDiff->hasErrorMessage) // if (missingAchievement) covered by this case - SendTransferAborted(target_map, TRANSFER_ABORT_DIFFICULTY, target_difficulty); - else if (missingItem) - GetSession()->SendAreaTriggerMessage(GetSession()->GetAcoreString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, sObjectMgr->GetItemTemplate(missingItem)->Name1.c_str()); - else if (LevelMin) - GetSession()->SendAreaTriggerMessage(GetSession()->GetAcoreString(LANG_LEVEL_MINREQUIRED), LevelMin); + uint8 requirementPrintMode = sWorld->getIntConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_PRINT_MODE); + + if (requirementPrintMode == 0) + { + //Just print out the requirements are not met + ChatHandler(GetSession()).SendSysMessage(LANG_ACCESS_REQUIREMENT_NOT_MET); + } + else if(requirementPrintMode == 1) + { + //Blizzlike method of printing out the requirements + if (missingLeaderQuests.size() && !missingLeaderQuests[0]->note.empty()) + { + ChatHandler(GetSession()).PSendSysMessage("%s", missingLeaderQuests[0]->note.c_str()); + } + else if (mapDiff->hasErrorMessage) + { // if (missingAchievement) covered by this case + SendTransferAborted(target_map, TRANSFER_ABORT_DIFFICULTY, target_difficulty); + } + else if (missingPlayerItems.size()) + { + GetSession()->SendAreaTriggerMessage(GetSession()->GetAcoreString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, sObjectMgr->GetItemTemplate(missingPlayerItems[0]->id)->Name1.c_str()); + } + else if (LevelMin) + { + GetSession()->SendAreaTriggerMessage(GetSession()->GetAcoreString(LANG_LEVEL_MINREQUIRED), LevelMin); + } + else if (ilvlRequirementNotMet) + { + ChatHandler(GetSession()).PSendSysMessage(LANG_ACCESS_REQUIREMENT_AVERAGE_ILVL_NOT_MET, ar->reqItemLevel, (uint16)GetAverageItemLevelForDF()); + } + } + else + { + bool errorAlreadyPrinted = false; + //Pretty way of printing out requirements + if (missingPlayerQuests.size()) + { + ChatHandler(GetSession()).SendSysMessage(LANG_ACCESS_REQUIREMENT_COMPLETE_QUESTS); + PrettyPrintRequirementsQuestList(missingPlayerQuests); + errorAlreadyPrinted = true; + } + if (missingLeaderQuests.size()) + { + ChatHandler(GetSession()).PSendSysMessage(LANG_ACCESS_REQUIREMENT_LEADER_COMPLETE_QUESTS, leaderName.c_str()); + PrettyPrintRequirementsQuestList(missingLeaderQuests); + errorAlreadyPrinted = true; + } + + if (missingPlayerAchievements.size()) + { + ChatHandler(GetSession()).SendSysMessage(LANG_ACCESS_REQUIREMENT_COMPLETE_ACHIEVEMENTS); + PrettyPrintRequirementsAchievementsList(missingPlayerAchievements); + errorAlreadyPrinted = true; + } + if (missingLeaderAchievements.size()) + { + ChatHandler(GetSession()).PSendSysMessage(LANG_ACCESS_REQUIREMENT_LEADER_COMPLETE_ACHIEVEMENTS, leaderName.c_str()); + PrettyPrintRequirementsAchievementsList(missingLeaderAchievements); + errorAlreadyPrinted = true; + } + + if (missingPlayerItems.size()) + { + ChatHandler(GetSession()).SendSysMessage(LANG_ACCESS_REQUIREMENT_OBTAIN_ITEMS); + PrettyPrintRequirementsItemsList(missingPlayerItems); + errorAlreadyPrinted = true; + } + + if (missingLeaderItems.size()) + { + ChatHandler(GetSession()).PSendSysMessage(LANG_ACCESS_REQUIREMENT_LEADER_OBTAIN_ITEMS, leaderName.c_str()); + PrettyPrintRequirementsItemsList(missingLeaderItems); + errorAlreadyPrinted = true; + } + + if (ilvlRequirementNotMet) + { + ChatHandler(GetSession()).PSendSysMessage(LANG_ACCESS_REQUIREMENT_AVERAGE_ILVL_NOT_MET, ar->reqItemLevel, (uint16)GetAverageItemLevelForDF()); + } + + if (LevelMin) + { + GetSession()->SendAreaTriggerMessage(GetSession()->GetAcoreString(LANG_LEVEL_MINREQUIRED), LevelMin); + } + else if (LevelMax) + { + GetSession()->SendAreaTriggerMessage(GetSession()->GetAcoreString(LANG_ACCESS_REQUIREMENT_MAX_LEVEL), LevelMax); + } + else if (mapDiff->hasErrorMessage && !errorAlreadyPrinted) + { + SendTransferAborted(target_map, TRANSFER_ABORT_DIFFICULTY, target_difficulty); + } + } + + //Print the extra string + uint32 optionalStringID = sWorld->getIntConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_OPTIONAL_STRING_ID); + if (optionalStringID > 0) + { + ChatHandler(GetSession()).SendSysMessage(optionalStringID); + } } return false; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index c9230ae82..3c93caa1c 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -837,17 +837,23 @@ enum PlayerCharmedAISpells #define MAX_PLAYER_SUMMON_DELAY (2*MINUTE) #define MAX_MONEY_AMOUNT (0x7FFFFFFF-1) -struct AccessRequirement +struct ProgressionRequirement +{ + uint32 id; + TeamId faction; + std::string note; + uint32 priority; + bool checkLeaderOnly; +}; + +struct DungeonProgressionRequirements { uint8 levelMin; uint8 levelMax; - uint32 item; - uint32 item2; - uint32 quest_A; - uint32 quest_H; - uint32 achievement; - std::string questFailedText; uint16 reqItemLevel; + std::vector quests; + std::vector items; + std::vector achievements; }; enum CharDeleteMethod @@ -2449,7 +2455,10 @@ public: [[nodiscard]] uint32 GetPendingBind() const { return _pendingBindId; } void SendRaidInfo(); void SendSavedInstances(); - bool Satisfy(AccessRequirement const* ar, uint32 target_map, bool report = false); + void PrettyPrintRequirementsQuestList(const std::vector& missingQuests) const; + void PrettyPrintRequirementsAchievementsList(const std::vector& missingAchievements) const; + void PrettyPrintRequirementsItemsList(const std::vector& missingItems) const; + bool Satisfy(DungeonProgressionRequirements const* ar, uint32 target_map, bool report = false); bool CheckInstanceLoginValid(); [[nodiscard]] bool CheckInstanceCount(uint32 instanceId) const; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 290bba7b1..6d7d3e016 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -338,8 +338,29 @@ ObjectMgr::~ObjectMgr() for (DungeonEncounterList::iterator encounterItr = itr->second.begin(); encounterItr != itr->second.end(); ++encounterItr) delete *encounterItr; - for (AccessRequirementContainer::iterator itr = _accessRequirementStore.begin(); itr != _accessRequirementStore.end(); ++itr) - delete itr->second; + for (DungeonProgressionRequirementsContainer::iterator itr = _accessRequirementStore.begin(); itr != _accessRequirementStore.end(); ++itr) + { + std::unordered_map difficulties = itr->second; + for (auto difficultiesItr = difficulties.begin(); difficultiesItr != difficulties.end(); ++difficultiesItr) + { + for (auto questItr = difficultiesItr->second->quests.begin(); questItr != difficultiesItr->second->quests.end(); ++questItr) + { + delete* questItr; + } + + for (auto achievementItr = difficultiesItr->second->achievements.begin(); achievementItr != difficultiesItr->second->achievements.end(); ++achievementItr) + { + delete* achievementItr; + } + + for (auto itemsItr = difficultiesItr->second->items.begin(); itemsItr != difficultiesItr->second->items.end(); ++itemsItr) + { + delete* itemsItr; + } + + delete difficultiesItr->second; + } + } } ObjectMgr* ObjectMgr::instance() @@ -6171,96 +6192,164 @@ void ObjectMgr::LoadAccessRequirements() if (!_accessRequirementStore.empty()) { - for (AccessRequirementContainer::iterator itr = _accessRequirementStore.begin(); itr != _accessRequirementStore.end(); ++itr) - delete itr->second; + for (DungeonProgressionRequirementsContainer::iterator itr = _accessRequirementStore.begin(); itr != _accessRequirementStore.end(); ++itr) + { + std::unordered_map difficulties = itr->second; + for (auto difficultiesItr = difficulties.begin(); difficultiesItr != difficulties.end(); ++difficultiesItr) + { + for (auto questItr = difficultiesItr->second->quests.begin(); questItr != difficultiesItr->second->quests.end(); ++questItr) + { + delete* questItr; + } + + for (auto achievementItr = difficultiesItr->second->achievements.begin(); achievementItr != difficultiesItr->second->achievements.end(); ++achievementItr) + { + delete* achievementItr; + } + + for (auto itemsItr = difficultiesItr->second->items.begin(); itemsItr != difficultiesItr->second->items.end(); ++itemsItr) + { + delete* itemsItr; + } + + delete difficultiesItr->second; + } + } _accessRequirementStore.clear(); // need for reload case } - - // 0 1 2 3 4 5 6 7 8 9 10 - QueryResult result = WorldDatabase.Query("SELECT mapid, difficulty, level_min, level_max, item, item2, quest_done_A, quest_done_H, completed_achievement, quest_failed_text, item_level FROM access_requirement"); - if (!result) + // 0 1 2 3 4 5 + QueryResult access_template_result = WorldDatabase.Query("SELECT id, map_id, difficulty, min_level, max_level, min_avg_item_level FROM dungeon_access_template"); + if (!access_template_result) { - sLog->outString(">> Loaded 0 access requirement definitions. DB table `access_requirement` is empty."); + sLog->outString(">> Loaded 0 access requirement definitions. DB table `dungeon_access_template` is empty."); sLog->outString(); return; } uint32 count = 0; + uint32 countProgressionRequirements = 0; do { - Field* fields = result->Fetch(); + Field* fields = access_template_result->Fetch(); - ++count; + //Get the common variables for the access requirements + uint8 dungeon_access_id = fields[0].GetUInt8(); + uint32 mapid = fields[1].GetUInt32(); + uint8 difficulty = fields[2].GetUInt8(); - uint32 mapid = fields[0].GetUInt32(); - uint8 difficulty = fields[1].GetUInt8(); - uint32 requirement_ID = MAKE_PAIR32(mapid, difficulty); + //Set up the access requirements + DungeonProgressionRequirements* ar = new DungeonProgressionRequirements(); + ar->levelMin = fields[3].GetUInt8(); + ar->levelMax = fields[4].GetUInt8(); + ar->reqItemLevel = fields[5].GetUInt16(); - AccessRequirement* ar = new AccessRequirement(); - - ar->levelMin = fields[2].GetUInt8(); - ar->levelMax = fields[3].GetUInt8(); - ar->item = fields[4].GetUInt32(); - ar->item2 = fields[5].GetUInt32(); - ar->quest_A = fields[6].GetUInt32(); - ar->quest_H = fields[7].GetUInt32(); - ar->achievement = fields[8].GetUInt32(); - ar->questFailedText = fields[9].GetString(); - ar->reqItemLevel = fields[10].GetUInt16(); - - if (ar->item) + // 0 1 2 3 4 6 + QueryResult progression_requirements_results = WorldDatabase.PQuery("SELECT requirement_type, requirement_id, requirement_note, faction, priority, leader_only FROM dungeon_access_requirements where dungeon_access_id = %u", dungeon_access_id); + if (progression_requirements_results) { - ItemTemplate const* pProto = GetItemTemplate(ar->item); - if (!pProto) + do { - sLog->outError("Key item %u does not exist for map %u difficulty %u, removing key requirement.", ar->item, mapid, difficulty); - ar->item = 0; - } + Field* progression_requirement_row = progression_requirements_results->Fetch(); + + const uint8 requirement_type = progression_requirement_row[0].GetUInt8(); + const uint32 requirement_id = progression_requirement_row[1].GetUInt32(); + const std::string requirement_note = progression_requirement_row[2].GetString(); + const uint8 requirement_faction = progression_requirement_row[3].GetUInt8(); + const uint8 requirement_priority = progression_requirement_row[4].IsNull() ? UINT8_MAX : progression_requirement_row[4].GetUInt8(); + const bool requirement_checkLeaderOnly = progression_requirement_row[5].GetBool(); + + ProgressionRequirement* progression_requirement = new ProgressionRequirement(); + progression_requirement->id = requirement_id; + progression_requirement->note = requirement_note; + progression_requirement->faction = (TeamId)requirement_faction; + progression_requirement->priority = requirement_priority; + progression_requirement->checkLeaderOnly = requirement_checkLeaderOnly; + + std::vector* currentRequirementsList = nullptr; + + switch (requirement_type) + { + case 0: + { + //Achievement + if (!sAchievementStore.LookupEntry(progression_requirement->id)) + { + sLog->outErrorDb("Required achievement %u for faction %u does not exist for map %u difficulty %u, remove or fix this achievement requirement.", progression_requirement->id, requirement_faction, mapid, difficulty); + break; + } + + currentRequirementsList = &ar->achievements; + break; + } + case 1: + { + //Quest + if (!GetQuestTemplate(progression_requirement->id)) + { + sLog->outErrorDb("Required quest %u for faction %u does not exist for map %u difficulty %u, remove or fix this quest requirement.", progression_requirement->id, requirement_faction, mapid, difficulty); + break; + } + + currentRequirementsList = &ar->quests; + break; + } + case 2: + { + //Item + ItemTemplate const* pProto = GetItemTemplate(progression_requirement->id); + if (!pProto) + { + sLog->outError("Required item %u for faction %u does not exist for map %u difficulty %u, remove or fix this item requirement.", progression_requirement->id, requirement_faction, mapid, difficulty); + break; + } + + currentRequirementsList = &ar->items; + break; + } + default: + sLog->outError("requirement_type of %u is not valid for map %u difficulty %u. Please use 0 for achievements, 1 for quest, 2 for items or remove this entry from the db.", requirement_type, mapid, difficulty); + break; + } + + //Check if array is valid and delete the progression requirement + if (!currentRequirementsList) + { + delete progression_requirement; + continue; + } + + //Insert into the array + if (currentRequirementsList->size() > requirement_priority) + { + currentRequirementsList->insert(currentRequirementsList->begin() + requirement_priority, progression_requirement); + } + else + { + currentRequirementsList->push_back(progression_requirement); + } + + + } while (progression_requirements_results->NextRow()); } - if (ar->item2) - { - ItemTemplate const* pProto = GetItemTemplate(ar->item2); - if (!pProto) - { - sLog->outError("Second item %u does not exist for map %u difficulty %u, removing key requirement.", ar->item2, mapid, difficulty); - ar->item2 = 0; - } - } + //Sort all arrays for priority + auto sortFunction = [](const ProgressionRequirement* const a, const ProgressionRequirement* const b) {return a->priority > b->priority; }; + std::sort(ar->achievements.begin(), ar->achievements.end(), sortFunction); + std::sort(ar->quests.begin(), ar->quests.end(), sortFunction); + std::sort(ar->items.begin(), ar->items.end(), sortFunction); - if (ar->quest_A) - { - if (!GetQuestTemplate(ar->quest_A)) - { - sLog->outErrorDb("Required Alliance Quest %u not exist for map %u difficulty %u, remove quest done requirement.", ar->quest_A, mapid, difficulty); - ar->quest_A = 0; - } - } + countProgressionRequirements += ar->achievements.size(); + countProgressionRequirements += ar->quests.size(); + countProgressionRequirements += ar->items.size(); + count++; - if (ar->quest_H) - { - if (!GetQuestTemplate(ar->quest_H)) - { - sLog->outErrorDb("Required Horde Quest %u not exist for map %u difficulty %u, remove quest done requirement.", ar->quest_H, mapid, difficulty); - ar->quest_H = 0; - } - } + _accessRequirementStore[mapid][difficulty] = ar; + } while (access_template_result->NextRow()); - if (ar->achievement) - { - if (!sAchievementStore.LookupEntry(ar->achievement)) - { - sLog->outErrorDb("Required Achievement %u not exist for map %u difficulty %u, remove quest done requirement.", ar->achievement, mapid, difficulty); - ar->achievement = 0; - } - } - _accessRequirementStore[requirement_ID] = ar; - } while (result->NextRow()); - - sLog->outString(">> Loaded %u access requirement definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + sLog->outString(">> Loaded %u rows from dungeon_access_template and %u rows from dungeon_access_requirements in %u ms", count, countProgressionRequirements, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index dfcdeee4b..878e1bc2b 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -31,7 +31,7 @@ #include class Item; -struct AccessRequirement; +struct DungeonProgressionRequirements; struct PlayerClassInfo; struct PlayerClassLevelInfo; struct PlayerInfo; @@ -704,7 +704,7 @@ public: typedef std::unordered_map AreaTriggerScriptContainer; - typedef std::unordered_map AccessRequirementContainer; + typedef std::unordered_map> DungeonProgressionRequirementsContainer; typedef std::unordered_map RepRewardRateContainer; typedef std::unordered_map RepOnKillContainer; @@ -832,11 +832,18 @@ public: return nullptr; } - [[nodiscard]] AccessRequirement const* GetAccessRequirement(uint32 mapid, Difficulty difficulty) const + [[nodiscard]] DungeonProgressionRequirements const* GetAccessRequirement(uint32 mapid, Difficulty difficulty) const { - AccessRequirementContainer::const_iterator itr = _accessRequirementStore.find(MAKE_PAIR32(mapid, difficulty)); + DungeonProgressionRequirementsContainer::const_iterator itr = _accessRequirementStore.find(mapid); if (itr != _accessRequirementStore.end()) - return itr->second; + { + std::unordered_map difficultiesProgressionRequirements = itr->second; + auto difficultiesItr = difficultiesProgressionRequirements.find(difficulty); + if (difficultiesItr != difficultiesProgressionRequirements.end()) + { + return difficultiesItr->second; + } + } return nullptr; } @@ -1386,7 +1393,7 @@ private: AreaTriggerContainer _areaTriggerStore; AreaTriggerTeleportContainer _areaTriggerTeleportStore; AreaTriggerScriptContainer _areaTriggerScriptStore; - AccessRequirementContainer _accessRequirementStore; + DungeonProgressionRequirementsContainer _accessRequirementStore; DungeonEncounterContainer _dungeonEncounterStore; RepRewardRateContainer _repRewardRateStore; diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 913f060a9..dd7e6d203 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -844,6 +844,18 @@ enum AcoreStrings LANG_RBAC_EMAIL_REQUIRED = 881, // Room for in-game strings 882-999 not used + //Access Requirements + LANG_ACCESS_REQUIREMENT_COMPLETE_QUESTS = 882, + LANG_ACCESS_REQUIREMENT_COMPLETE_ACHIEVEMENTS = 883, + LANG_ACCESS_REQUIREMENT_OBTAIN_ITEMS = 884, + LANG_ACCESS_REQUIREMENT_NOTE = 885, + LANG_ACCESS_REQUIREMENT_NOT_MET = 886, + LANG_ACCESS_REQUIREMENT_AVERAGE_ILVL_NOT_MET = 887, + LANG_ACCESS_REQUIREMENT_MAX_LEVEL = 888, + LANG_ACCESS_REQUIREMENT_LEADER_COMPLETE_QUESTS = 889, + LANG_ACCESS_REQUIREMENT_LEADER_COMPLETE_ACHIEVEMENTS = 890, + LANG_ACCESS_REQUIREMENT_LEADER_OBTAIN_ITEMS = 891, + // Level 4 (CLI only commands) LANG_COMMAND_EXIT = 1000, LANG_ACCOUNT_DELETED = 1001, @@ -1320,6 +1332,6 @@ enum AcoreStrings LANG_BG_READY_CHECK_ERROR = 30084, LANG_DEBUG_BG_CONF = 30085, - LANG_DEBUG_ARENA_CONF = 30086 + LANG_DEBUG_ARENA_CONF = 30086, }; #endif diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index b077dbbec..8aae18843 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -163,6 +163,8 @@ enum WorldBoolConfigs CONFIG_SET_ALL_CREATURES_WITH_WAYPOINT_MOVEMENT_ACTIVE, CONFIG_DEBUG_BATTLEGROUND, CONFIG_DEBUG_ARENA, + CONFIG_DUNGEON_ACCESS_REQUIREMENTS_PORTAL_CHECK_ILVL, + CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE, CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID, CONFIG_SET_SHAPASSHASH, CONFIG_SET_BOP_ITEM_TRADEABLE, @@ -364,6 +366,8 @@ enum WorldIntConfigs CONFIG_CHARTER_COST_ARENA_5v5, CONFIG_MAX_WHO_LIST_RETURN, CONFIG_WAYPOINT_MOVEMENT_STOP_TIME_FOR_PLAYER, + CONFIG_DUNGEON_ACCESS_REQUIREMENTS_PRINT_MODE, + CONFIG_DUNGEON_ACCESS_REQUIREMENTS_OPTIONAL_STRING_ID, CONFIG_GUILD_BANK_INITIAL_TABS, CONFIG_GUILD_BANK_TAB_COST_0, CONFIG_GUILD_BANK_TAB_COST_1, diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 7360e7a04..735f0ade5 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1403,6 +1403,10 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_WAYPOINT_MOVEMENT_STOP_TIME_FOR_PLAYER] = sConfigMgr->GetOption("WaypointMovementStopTimeForPlayer", 120); + m_int_configs[CONFIG_DUNGEON_ACCESS_REQUIREMENTS_PRINT_MODE] = sConfigMgr->GetOption("DungeonAccessRequirements.PrintMode", 1); + m_bool_configs[CONFIG_DUNGEON_ACCESS_REQUIREMENTS_PORTAL_CHECK_ILVL] = sConfigMgr->GetOption("DungeonAccessRequirements.PortalAvgIlevelCheck", false); + m_bool_configs[CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE] = sConfigMgr->GetOption("DungeonAccessRequirements.LFGLevelDBCOverride", false); + m_int_configs[CONFIG_DUNGEON_ACCESS_REQUIREMENTS_OPTIONAL_STRING_ID] = sConfigMgr->GetOption("DungeonAccessRequirements.OptionalStringID", 0); m_int_configs[CONFIG_NPC_EVADE_IF_NOT_REACHABLE] = sConfigMgr->GetOption("NpcEvadeIfTargetIsUnreachable", 5); m_int_configs[CONFIG_NPC_REGEN_TIME_IF_NOT_REACHABLE_IN_RAID] = sConfigMgr->GetOption("NpcRegenHPTimeIfTargetIsUnreachable", 10); m_bool_configs[CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID] = sConfigMgr->GetOption("NpcRegenHPIfTargetIsUnreachable", true); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index e9442122d..725acfe8d 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -55,7 +55,8 @@ public: static std::vector reloadCommandTable = { { "auctions", SEC_ADMINISTRATOR, true, &HandleReloadAuctionsCommand, "" }, - { "access_requirement", SEC_ADMINISTRATOR, true, &HandleReloadAccessRequirementCommand, "" }, + { "dungeon_access_template", SEC_ADMINISTRATOR, true, &HandleReloadDungeonAccessCommand, "" }, + { "dungeon_access_requirements", SEC_ADMINISTRATOR, true, &HandleReloadDungeonAccessCommand, "" }, { "achievement_criteria_data", SEC_ADMINISTRATOR, true, &HandleReloadAchievementCriteriaDataCommand, "" }, { "achievement_reward", SEC_ADMINISTRATOR, true, &HandleReloadAchievementRewardCommand, "" }, { "all", SEC_ADMINISTRATOR, true, nullptr, "", reloadAllCommandTable }, @@ -175,7 +176,7 @@ public: HandleReloadAllGossipsCommand(handler, ""); HandleReloadAllLocalesCommand(handler, ""); - HandleReloadAccessRequirementCommand(handler, ""); + HandleReloadDungeonAccessCommand(handler, ""); HandleReloadMailLevelRewardCommand(handler, ""); HandleReloadCommandCommand(handler, ""); HandleReloadReservedNameCommand(handler, ""); @@ -324,11 +325,11 @@ public: return true; } - static bool HandleReloadAccessRequirementCommand(ChatHandler* handler, const char* /*args*/) + static bool HandleReloadDungeonAccessCommand(ChatHandler* handler, const char* /*args*/) { - sLog->outString("Re-Loading Access Requirement definitions..."); + sLog->outString("Re-Loading Dungeon Access Requirement definitions..."); sObjectMgr->LoadAccessRequirements(); - handler->SendGlobalGMSysMessage("DB table `access_requirement` reloaded."); + handler->SendGlobalGMSysMessage("DB tables `dungeon_access_template` AND `dungeon_access_requirements` reloaded."); return true; } diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 11e48c42c..e9dcd62db 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -3609,6 +3609,45 @@ Calculate.Gameoject.Zone.Area.Data = 0 LFG.Location.All = 0 +# +# DungeonAccessRequirements.PrintMode +# +# Description: Select the preferred format to display information to the player who cannot enter a portal dungeon because when has not met the access requirements: +# Default: 1 - (Display only one requirement at a time (BlizzLike, like in the LFG interface)) +# 0 - (Display no extra information, only "Requirements not met") +# 2 - (Display detailed requirements, all at once, with clickable links) +# + +DungeonAccessRequirements.PrintMode = 1 + +# +# DungeonAccessRequirements.PortalAvgIlevelCheck +# +# Description: Enable average item level requirement when entering a dungeon/raid's portal (= deny the entry if player has too low average ilevel, like in LFG). +# Default: 0 - (Disabled -> Blizzlike) +# 1 - (Enabled) + +DungeonAccessRequirements.PortalAvgIlevelCheck = 0 + +# +# DungeonAccessRequirements.LFGLevelDBCOverride +# +# Description: If enabled, use `min_level` and `max_level` values from table `dungeon_access_requirements` to list or to hide a dungeon from the LFG window. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +DungeonAccessRequirements.LFGLevelDBCOverride = 0 + +# +# DungeonAccessRequirements.OptionalStringID +# +# Description: Display an extra message from acore_strings in the chat after printing the dungeon access requirements. +# To enable it set the ID of your desired string from the table acore_strings +# Default: 0 - (Disabled) +# 1+ - (Enabled) + +DungeonAccessRequirements.OptionalStringID = 0 + # # ICC Buff # Description: Specify ICC buff