diff --git a/sql/updates/world/custom/custom_2019_03_30_00_world.sql b/sql/updates/world/custom/custom_2019_03_30_00_world.sql new file mode 100644 index 00000000000..82a658cdead --- /dev/null +++ b/sql/updates/world/custom/custom_2019_03_30_00_world.sql @@ -0,0 +1,10 @@ +UPDATE `access_requirement` SET `quest_failed_text`= '0'; +ALTER TABLE `access_requirement` CHANGE `quest_failed_text` `heroic_exclusive` TINYINT(1) DEFAULT 0 NULL; + +DELETE FROM `access_requirement` WHERE `mapId` IN (859, 568, 938, 939, 940) AND `difficulty`= 0; +INSERT INTO `access_requirement` (`mapId`, `difficulty`, `heroic_exclusive`, `comment`) VALUES +(859, 0, 1, 'Zul''Gurub - Heroic Exclusive'), +(568, 0, 1, 'Zul''Aman - Heroic Exclusive'), +(938, 0, 1, 'End Time - Heroic Exclusive'), +(939, 0, 1, 'Well of Eternity - Heroic Exclusive'), +(940, 0, 1, 'Hour of Twilight - Heroic Exclusive'); diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 37297b67a4c..95ae5fb88bf 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -561,10 +561,12 @@ void LoadDBCStores(const std::string& dataPath) } // fill data - sMapDifficultyMap[MAKE_PAIR32(0, 0)] = MapDifficulty(0, 0, false);//map 0 is missing from MapDifficulty.dbc use this till its ported to sql + sMapDifficultyMap[MAKE_PAIR32(0, 0)] = MapDifficulty(0, 0, false); // map 0 is missing from MapDifficulty.dbc use this till its ported to sql for (MapDifficultyEntry const* entry : sMapDifficultyStore) sMapDifficultyMap[MAKE_PAIR32(entry->MapId, entry->Difficulty)] = MapDifficulty(entry->resetTime, entry->maxPlayers, entry->areaTriggerText[0] > 0); + sMapDifficultyMap.erase(MAKE_PAIR32(189, 1)); // Scarlet Monastry has a incorrect heroic entry so we drop it + for (NameGenEntry const* entry : sNameGenStore) sGenNameVectoArraysMap[entry->race].stringVectorArray[entry->gender].push_back(std::string(entry->name)); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 27fe89b5529..b2c7232862e 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -17811,7 +17811,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder* holder) SendTransferAborted(map->GetId(), TRANSFER_ABORT_DIFFICULTY, map->GetDifficulty()); break; case Map::CANNOT_ENTER_INSTANCE_BIND_MISMATCH: - ChatHandler(GetSession()).PSendSysMessage(GetSession()->GetTrinityString(LANG_INSTANCE_BIND_MISMATCH), map->GetMapName()); + SendTransferAborted(map->GetId(), TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE); break; case Map::CANNOT_ENTER_TOO_MANY_INSTANCES: SendTransferAborted(map->GetId(), TRANSFER_ABORT_TOO_MANY_INSTANCES); @@ -19637,6 +19637,12 @@ bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report if (!mapEntry) return false; + if (DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, target_map, this)) + { + GetSession()->SendAreaTriggerMessage("%s", GetSession()->GetTrinityString(LANG_INSTANCE_CLOSED)); + return false; + } + if (!sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_LEVEL)) { if (ar->levelMin && getLevel() < ar->levelMin) @@ -19655,12 +19661,6 @@ bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report else if (ar->item2 && !HasItemCount(ar->item2)) missingItem = ar->item2; - if (DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, target_map, this)) - { - GetSession()->SendAreaTriggerMessage("%s", GetSession()->GetTrinityString(LANG_INSTANCE_CLOSED)); - return false; - } - uint32 missingQuest = 0; if (GetTeam() == ALLIANCE && ar->quest_A && !GetQuestRewardStatus(ar->quest_A)) missingQuest = ar->quest_A; @@ -19677,21 +19677,17 @@ bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report if (!leader || !leader->HasAchieved(ar->achievement)) missingAchievement = ar->achievement; + bool heroicExclusive = ar->heroicExclusive; + Difficulty target_difficulty = GetDifficulty(mapEntry->IsRaid()); MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(target_map, target_difficulty); - if (LevelMin || LevelMax || missingItem || missingQuest || missingAchievement) + if (!mapDiff) + return false; + + if (LevelMin || LevelMax || missingItem || missingQuest || missingAchievement || heroicExclusive) { - 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()->GetTrinityString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(missingItem))->Name1.c_str()); - else if (LevelMin) - GetSession()->SendAreaTriggerMessage(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED), LevelMin); - } + if (report && mapDiff->hasErrorMessage) + SendTransferAborted(target_map, TRANSFER_ABORT_DIFFICULTY, target_difficulty); return false; } } diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 4c24e33c5f2..e86a1d1ae6d 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -7058,8 +7058,8 @@ void ObjectMgr::LoadAccessRequirements() _accessRequirementStore.clear(); // need for reload case } - // 0 1 2 3 4 5 6 7 8 9 - QueryResult result = WorldDatabase.Query("SELECT mapid, difficulty, level_min, level_max, item, item2, quest_done_A, quest_done_H, completed_achievement, quest_failed_text FROM access_requirement"); + // 0 1 2 3 4 5 6 7 8 9 + QueryResult result = WorldDatabase.Query("SELECT mapid, difficulty, level_min, level_max, item, item2, quest_done_A, quest_done_H, completed_achievement, heroic_exclusive FROM access_requirement"); if (!result) { @@ -7087,7 +7087,7 @@ void ObjectMgr::LoadAccessRequirements() ar->quest_A = fields[6].GetUInt32(); ar->quest_H = fields[7].GetUInt32(); ar->achievement = fields[8].GetUInt32(); - ar->questFailedText = fields[9].GetString(); + ar->heroicExclusive = fields[9].GetUInt8(); if (ar->item) { diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index e81448d344c..a0060f238c7 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -474,7 +474,7 @@ struct AccessRequirement uint32 quest_A; uint32 quest_H; uint32 achievement; - std::string questFailedText; + uint8 heroicExclusive; }; struct BroadcastText diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index a7d012904fb..c49115ead52 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -764,9 +764,7 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recvData) { char const* mapName = entry->Name; TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map '%s' because their permanent bind is incompatible with their group's", player->GetName().c_str(), mapName); - // is there a special opcode for this? - // @todo figure out how to get player localized difficulty string (e.g. "10 player", "Heroic" etc) - ChatHandler(player->GetSession()).PSendSysMessage(player->GetSession()->GetTrinityString(LANG_INSTANCE_BIND_MISMATCH), mapName); + player->SendTransferAborted(entry->ID, TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE, player->GetDifficulty(entry->IsRaid())); } reviveAtTrigger = true; break; diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 91822e8ea4c..58651c0b2ff 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -3841,7 +3841,7 @@ Map::EnterState InstanceMap::CannotEnter(Player* player) } // cannot enter while an encounter is in progress (unless this is a relog, in which case it is permitted) - if (!player->IsLoading() && IsRaid() && GetInstanceScript() && GetInstanceScript()->IsEncounterInProgress()) + if (!player->IsLoading() && (IsDungeon() || IsRaid()) && GetInstanceScript() && GetInstanceScript()->IsEncounterInProgress()) return CANNOT_ENTER_ZONE_IN_COMBAT; // cannot enter if player is permanent saved to a different instance id diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 061705496b6..48be4fa1579 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -447,18 +447,19 @@ class TC_GAME_API Map : public GridRefManager enum EnterState { CAN_ENTER = 0, - CANNOT_ENTER_ALREADY_IN_MAP = 1, // Player is already in the map - CANNOT_ENTER_NO_ENTRY, // No map entry was found for the target map ID - CANNOT_ENTER_UNINSTANCED_DUNGEON, // No instance template was found for dungeon map - CANNOT_ENTER_DIFFICULTY_UNAVAILABLE, // Requested instance difficulty is not available for target map - CANNOT_ENTER_NOT_IN_RAID, // Target instance is a raid instance and the player is not in a raid group - CANNOT_ENTER_CORPSE_IN_DIFFERENT_INSTANCE, // Player is dead and their corpse is not in target instance - CANNOT_ENTER_INSTANCE_BIND_MISMATCH, // Player's permanent instance save is not compatible with their group's current instance bind - CANNOT_ENTER_TOO_MANY_INSTANCES, // Player has entered too many instances recently - CANNOT_ENTER_MAX_PLAYERS, // Target map already has the maximum number of players allowed - CANNOT_ENTER_ZONE_IN_COMBAT, // A boss encounter is currently in progress on the target map + CANNOT_ENTER_ALREADY_IN_MAP = 1, // Player is already in the map + CANNOT_ENTER_NO_ENTRY, // No map entry was found for the target map ID + CANNOT_ENTER_UNINSTANCED_DUNGEON, // No instance template was found for dungeon map + CANNOT_ENTER_DIFFICULTY_UNAVAILABLE, // Requested instance difficulty is not available for target map + CANNOT_ENTER_NOT_IN_RAID, // Target instance is a raid instance and the player is not in a raid group + CANNOT_ENTER_CORPSE_IN_DIFFERENT_INSTANCE, // Player is dead and their corpse is not in target instance + CANNOT_ENTER_INSTANCE_BIND_MISMATCH, // Player's permanent instance save is not compatible with their group's current instance bind + CANNOT_ENTER_TOO_MANY_INSTANCES, // Player has entered too many instances recently + CANNOT_ENTER_MAX_PLAYERS, // Target map already has the maximum number of players allowed + CANNOT_ENTER_ZONE_IN_COMBAT, // A boss encounter is currently in progress on the target map CANNOT_ENTER_UNSPECIFIED_REASON }; + virtual EnterState CannotEnter(Player* /*player*/) { return CAN_ENTER; } char const* GetMapName() const; diff --git a/src/server/game/Maps/MapManager.cpp b/src/server/game/Maps/MapManager.cpp index 28878f381cb..dcc3424a890 100644 --- a/src/server/game/Maps/MapManager.cpp +++ b/src/server/game/Maps/MapManager.cpp @@ -150,34 +150,40 @@ Map* MapManager::FindMap(uint32 mapid, uint32 instanceId) const Map::EnterState MapManager::PlayerCannotEnter(uint32 mapid, Player* player, bool loginCheck) { MapEntry const* entry = sMapStore.LookupEntry(mapid); + + // No entry found in Map.dbc if (!entry) return Map::CANNOT_ENTER_NO_ENTRY; + // Non-instanced maps can always be entered if (!entry->IsDungeon()) return Map::CAN_ENTER; InstanceTemplate const* instance = sObjectMgr->GetInstanceTemplate(mapid); + + // Cannot enter instance maps when no instance_template entry is present if (!instance) return Map::CANNOT_ENTER_UNINSTANCED_DUNGEON; - Difficulty targetDifficulty, requestedDifficulty; - targetDifficulty = requestedDifficulty = player->GetDifficulty(entry->IsRaid()); - // Get the highest available difficulty if current setting is higher than the instance allows + // Cannot enter instance if MapDifficulty data for highest available difficulty does not exist + Difficulty targetDifficulty = player->GetDifficulty(entry->IsRaid()); MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(entry->ID, targetDifficulty); if (!mapDiff) return Map::CANNOT_ENTER_DIFFICULTY_UNAVAILABLE; - //Bypass checks for GMs + // Game Masters can always enter if (player->IsGameMaster()) return Map::CAN_ENTER; char const* mapName = entry->Name; - Group* group = player->GetGroup(); - if (entry->IsRaid()) // can only enter in a raid group + + // Cannot enter raid maps when not in a raid-group. Ignore rule when CONFIG_INSTANCE_IGNORE_RAID = true + if (entry->IsRaid()) if ((!group || !group->isRaidGroup()) && !sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_RAID)) return Map::CANNOT_ENTER_NOT_IN_RAID; + // Cannot enter instance if player's corpse is in a different instance if (!player->IsAlive()) { if (player->HasCorpse()) @@ -202,7 +208,7 @@ Map::EnterState MapManager::PlayerCannotEnter(uint32 mapid, Player* player, bool TC_LOG_DEBUG("maps", "Map::CanPlayerEnter - player '%s' is dead but does not have a corpse!", player->GetName().c_str()); } - //Get instance where player's group is bound & its map + // Get instance where player's group is bound & its map if (!loginCheck && group) { InstanceGroupBind* boundInstance = group->GetBoundInstance(entry); @@ -212,7 +218,7 @@ Map::EnterState MapManager::PlayerCannotEnter(uint32 mapid, Player* player, bool return denyReason; } - // players are only allowed to enter 5 instances per hour + // Players are only allowed to enter 5 instances per hour if (entry->IsDungeon() && (!player->GetGroup() || (player->GetGroup() && !player->GetGroup()->isLFGGroup()))) { uint32 instanceIdToCheck = 0; @@ -224,7 +230,7 @@ Map::EnterState MapManager::PlayerCannotEnter(uint32 mapid, Player* player, bool return Map::CANNOT_ENTER_TOO_MANY_INSTANCES; } - //Other requirements + // Other requirements if (player->Satisfy(sObjectMgr->GetAccessRequirement(mapid, targetDifficulty), mapid, true)) return Map::CAN_ENTER; else