diff --git a/sql/mangos.sql b/sql/mangos.sql index 1b34bbb35..97fe190fb 100644 --- a/sql/mangos.sql +++ b/sql/mangos.sql @@ -24,7 +24,7 @@ CREATE TABLE `db_version` ( `version` varchar(120) default NULL, `creature_ai_version` varchar(120) default NULL, `cache_id` int(10) default '0', - `required_12186_01_mangos_item_template` bit(1) default NULL + `required_12195_02_mangos_mangos_string"` bit(1) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Used DB version notes'; -- @@ -141,7 +141,6 @@ CREATE TABLE `areatrigger_teleport` ( `heroic_key2` mediumint(8) unsigned NOT NULL default '0', `required_quest_done` int(11) unsigned NOT NULL default '0', `required_quest_done_heroic` int(11) unsigned NOT NULL default '0', - `required_failed_text` text, `target_map` smallint(5) unsigned NOT NULL default '0', `target_position_x` float NOT NULL default '0', `target_position_y` float NOT NULL default '0', @@ -4039,6 +4038,7 @@ INSERT INTO `mangos_string` VALUES (815,'Initiate',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (816,'Your body is too exhausted to travel to the Spectral Realm.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (817,'Warning: You\'ve entered a no-fly zone and are about to be dismounted!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(818,'You can\'t enter Black Morass until you rescue Thrall from Durnholde Keep.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (1000,'Exiting daemon...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (1001,'Account deleted: %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (1002,'Account %s NOT deleted (probably sql file format was updated)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), diff --git a/sql/updates/12195_01_mangos_areatrigger_teleport.sql b/sql/updates/12195_01_mangos_areatrigger_teleport.sql new file mode 100644 index 000000000..135881a04 --- /dev/null +++ b/sql/updates/12195_01_mangos_areatrigger_teleport.sql @@ -0,0 +1,3 @@ +ALTER TABLE db_version CHANGE COLUMN required_12186_01_mangos_item_template required_12195_01_mangos_areatrigger_teleport bit; + +ALTER TABLE areatrigger_teleport DROP COLUMN required_failed_text; diff --git a/sql/updates/12195_02_mangos_mangos_string.sql b/sql/updates/12195_02_mangos_mangos_string.sql new file mode 100644 index 000000000..3b2e2dfbe --- /dev/null +++ b/sql/updates/12195_02_mangos_mangos_string.sql @@ -0,0 +1,5 @@ +ALTER TABLE db_version CHANGE COLUMN required_12195_01_mangos_areatrigger_teleport required_12195_02_mangos_mangos_string bit; + +DELETE FROM mangos_string WHERE entry=818; +INSERT INTO mangos_string VALUES +(818,'You can\'t enter Black Morass until you rescue Thrall from Durnholde Keep.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); diff --git a/src/game/DBCStores.cpp b/src/game/DBCStores.cpp index 45dcf7c84..15ba104da 100644 --- a/src/game/DBCStores.cpp +++ b/src/game/DBCStores.cpp @@ -140,7 +140,6 @@ DBCStorage sLockStore(LockEntryfmt); DBCStorage sMailTemplateStore(MailTemplateEntryfmt); DBCStorage sMapStore(MapEntryfmt); -// DBC used only for initialization sMapDifficultyMap at startup. DBCStorage sMapDifficultyStore(MapDifficultyEntryfmt); // only for loading MapDifficultyMap sMapDifficultyMap; @@ -540,8 +539,7 @@ void LoadDBCStores(const std::string& dataPath) // fill data for(uint32 i = 1; i < sMapDifficultyStore.GetNumRows(); ++i) if(MapDifficultyEntry const* entry = sMapDifficultyStore.LookupEntry(i)) - sMapDifficultyMap[MAKE_PAIR32(entry->MapId,entry->Difficulty)] = MapDifficulty(entry->resetTime,entry->maxPlayers); - sMapDifficultyStore.Clear(); + sMapDifficultyMap[MAKE_PAIR32(entry->MapId, entry->Difficulty)] = entry; LoadDBC(availableDbcLocales,bar,bad_dbc_files,sMovieStore, dbcPath,"Movie.dbc"); LoadDBC(availableDbcLocales,bar,bad_dbc_files,sNumTalentsAtLevelStore, dbcPath,"NumTalentsAtLevel.dbc"); @@ -1024,10 +1022,10 @@ bool Map2ZoneCoordinates(float& x,float& y,uint32 zone) return true; } -MapDifficulty const* GetMapDifficultyData(uint32 mapId, Difficulty difficulty) +MapDifficultyEntry const* GetMapDifficultyData(uint32 mapId, Difficulty difficulty) { MapDifficultyMap::const_iterator itr = sMapDifficultyMap.find(MAKE_PAIR32(mapId,difficulty)); - return itr != sMapDifficultyMap.end() ? &itr->second : NULL; + return itr != sMapDifficultyMap.end() ? itr->second : NULL; } PvPDifficultyEntry const* GetBattlegroundBracketByLevel( uint32 mapid, uint32 level ) diff --git a/src/game/DBCStores.h b/src/game/DBCStores.h index fdc83baaf..9e47c9fa4 100644 --- a/src/game/DBCStores.h +++ b/src/game/DBCStores.h @@ -63,8 +63,8 @@ bool IsTotemCategoryCompatiableWith(uint32 itemTotemCategoryId, uint32 requiredT bool Zone2MapCoordinates(float& x,float& y,uint32 zone); bool Map2ZoneCoordinates(float& x,float& y,uint32 zone); -typedef std::map MapDifficultyMap; -MapDifficulty const* GetMapDifficultyData(uint32 mapId, Difficulty difficulty); +typedef std::map MapDifficultyMap; +MapDifficultyEntry const* GetMapDifficultyData(uint32 mapId, Difficulty difficulty); // natural order for difficulties up-down iteration // difficulties for dungeons/battleground ordered in normal way diff --git a/src/game/DBCStructure.h b/src/game/DBCStructure.h index 699ee9e05..556cd9278 100644 --- a/src/game/DBCStructure.h +++ b/src/game/DBCStructure.h @@ -2411,15 +2411,6 @@ typedef std::set PetFamilySpellsSet; typedef std::map PetFamilySpellsStore; // Structures not used for casting to loaded DBC data and not required then packing -struct MapDifficulty -{ - MapDifficulty() : resetTime(0), maxPlayers(0) {} - MapDifficulty(uint32 _resetTime, uint32 _maxPlayers) : resetTime(_resetTime), maxPlayers(_maxPlayers) {} - - uint32 resetTime; // in secs, 0 if no fixed reset time - uint32 maxPlayers; // some heroic dungeons have 0 when expect same value as in normal dificulty case -}; - struct TalentSpellPos { TalentSpellPos() : talent_id(0), rank(0) {} diff --git a/src/game/Group.cpp b/src/game/Group.cpp index 9feb4ea44..7303c90b3 100644 --- a/src/game/Group.cpp +++ b/src/game/Group.cpp @@ -1780,7 +1780,7 @@ InstanceGroupBind* Group::GetBoundInstance(uint32 mapid, Player* player) Difficulty difficulty = player->GetDifficulty(mapEntry->IsRaid()); // some instances only have one difficulty - MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapid, difficulty); if (!mapDiff) difficulty = DUNGEON_DIFFICULTY_NORMAL; @@ -1794,7 +1794,7 @@ InstanceGroupBind* Group::GetBoundInstance(uint32 mapid, Player* player) InstanceGroupBind* Group::GetBoundInstance(Map* aMap, Difficulty difficulty) { // some instances only have one difficulty - MapDifficulty const* mapDiff = GetMapDifficultyData(aMap->GetId(), difficulty); + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(aMap->GetId(), difficulty); if (!mapDiff) return NULL; diff --git a/src/game/Language.h b/src/game/Language.h index f9fe32e30..e324c9b51 100644 --- a/src/game/Language.h +++ b/src/game/Language.h @@ -809,7 +809,8 @@ enum MangosStrings LANG_GUILD_INITIATE = 815, LANG_FAIL_ENTER_SPECTRAL_REALM = 816, LANG_NO_FLY_ZONE = 817, - // Room for in-game strings 818-999 not used + LANG_TELEREQ_QUEST_BLACK_MORASS = 818, + // Room for in-game strings 819-999 not used // Level 4 (CLI only commands) LANG_COMMAND_EXIT = 1000, diff --git a/src/game/Map.cpp b/src/game/Map.cpp index a6e333f64..2967815f4 100644 --- a/src/game/Map.cpp +++ b/src/game/Map.cpp @@ -824,21 +824,21 @@ void Map::UnloadAll(bool pForce) } } -MapDifficulty const* Map::GetMapDifficulty() const +MapDifficultyEntry const* Map::GetMapDifficulty() const { return GetMapDifficultyData(GetId(), GetDifficulty()); } uint32 Map::GetMaxPlayers() const { - if (MapDifficulty const* mapDiff = GetMapDifficulty()) + if (MapDifficultyEntry const* mapDiff = GetMapDifficulty()) { if (mapDiff->maxPlayers || IsRegularDifficulty()) // Normal case (expect that regular difficulty always have correct maxplayers) return mapDiff->maxPlayers; else // DBC have 0 maxplayers for heroic instances with expansion < 2 { // The heroic entry exists, so we don't have to check anything, simply return normal max players - MapDifficulty const* normalDiff = GetMapDifficultyData(i_id, REGULAR_DIFFICULTY); + MapDifficultyEntry const* normalDiff = GetMapDifficultyData(i_id, REGULAR_DIFFICULTY); return normalDiff ? normalDiff->maxPlayers : 0; } } diff --git a/src/game/Map.h b/src/game/Map.h index 133a3e46b..4e908a427 100644 --- a/src/game/Map.h +++ b/src/game/Map.h @@ -183,7 +183,7 @@ class MANGOS_DLL_SPEC Map : public GridRefManager bool IsRegularDifficulty() const { return GetDifficulty() == REGULAR_DIFFICULTY; } uint32 GetMaxPlayers() const; // dependent from map difficulty uint32 GetMaxResetDelay() const; // dependent from map difficulty - MapDifficulty const* GetMapDifficulty() const; // dependent from map difficulty + MapDifficultyEntry const* GetMapDifficulty() const; // dependent from map difficulty bool Instanceable() const { return i_mapEntry && i_mapEntry->Instanceable(); } // NOTE: this duplicate of Instanceable(), but Instanceable() can be changed when BG also will be instanceable diff --git a/src/game/MapManager.cpp b/src/game/MapManager.cpp index 306e0107b..81e681404 100644 --- a/src/game/MapManager.cpp +++ b/src/game/MapManager.cpp @@ -182,7 +182,7 @@ bool MapManager::CanPlayerEnter(uint32 mapid, Player* player) } // The player has a heroic mode and tries to enter into instance which has no a heroic mode - MapDifficulty const* mapDiff = GetMapDifficultyData(entry->MapID, player->GetDifficulty(entry->map_type == MAP_RAID)); + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(entry->MapID, player->GetDifficulty(entry->map_type == MAP_RAID)); if (!mapDiff) { bool isRegularTargetMap = player->GetDifficulty(entry->IsRaid()) == REGULAR_DIFFICULTY; diff --git a/src/game/MapPersistentStateMgr.cpp b/src/game/MapPersistentStateMgr.cpp index 979e506b0..b01a7acc3 100644 --- a/src/game/MapPersistentStateMgr.cpp +++ b/src/game/MapPersistentStateMgr.cpp @@ -319,7 +319,7 @@ bool BattleGroundPersistentState::CanBeUnload() const //== DungeonResetScheduler functions ====================== -uint32 DungeonResetScheduler::GetMaxResetTimeFor(MapDifficulty const* mapDiff) +uint32 DungeonResetScheduler::GetMaxResetTimeFor(MapDifficultyEntry const* mapDiff) { if (!mapDiff || !mapDiff->resetTime) return 0; @@ -332,7 +332,7 @@ uint32 DungeonResetScheduler::GetMaxResetTimeFor(MapDifficulty const* mapDiff) return delay; } -time_t DungeonResetScheduler::CalculateNextResetTime(MapDifficulty const* mapDiff, time_t prevResetTime) +time_t DungeonResetScheduler::CalculateNextResetTime(MapDifficultyEntry const* mapDiff, time_t prevResetTime) { uint32 diff = sWorld.getConfig(CONFIG_UINT32_INSTANCE_RESET_TIME_HOUR) * HOUR; uint32 period = GetMaxResetTimeFor(mapDiff); @@ -450,7 +450,7 @@ void DungeonResetScheduler::LoadResetTimes() uint32 map_diff_pair = itr->first; uint32 mapid = PAIR32_LOPART(map_diff_pair); Difficulty difficulty = Difficulty(PAIR32_HIPART(map_diff_pair)); - MapDifficulty const* mapDiff = &itr->second; + MapDifficultyEntry const* mapDiff = itr->second; // skip mapDiff without global reset time if (!mapDiff->resetTime) @@ -552,7 +552,7 @@ void DungeonResetScheduler::Update() { // re-schedule the next/new global reset/warning // calculate the next reset time - MapDifficulty const* mapDiff = GetMapDifficultyData(event.mapid, event.difficulty); + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(event.mapid, event.difficulty); MANGOS_ASSERT(mapDiff); time_t next_reset = DungeonResetScheduler::CalculateNextResetTime(mapDiff, resetTime); @@ -857,7 +857,7 @@ void MapPersistentStateManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficu if (!warn) { - MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapid, difficulty); if (!mapDiff || !mapDiff->resetTime) { sLog.outError("MapPersistentStateManager::ResetOrWarnAll: not valid difficulty or no reset delay for map %d", mapid); diff --git a/src/game/MapPersistentStateMgr.h b/src/game/MapPersistentStateMgr.h index 5a4f8cad5..571d1c4f2 100644 --- a/src/game/MapPersistentStateMgr.h +++ b/src/game/MapPersistentStateMgr.h @@ -34,7 +34,7 @@ struct InstanceTemplate; struct MapEntry; -struct MapDifficulty; +struct MapDifficultyEntry; struct GameObjectData; struct CreatureData; @@ -304,8 +304,8 @@ class DungeonResetScheduler return itr != m_resetTimeByMapDifficulty.end() ? itr->second : 0; } - static uint32 GetMaxResetTimeFor(MapDifficulty const* mapDiff); - static time_t CalculateNextResetTime(MapDifficulty const* mapDiff, time_t prevResetTime); + static uint32 GetMaxResetTimeFor(MapDifficultyEntry const* mapDiff); + static time_t CalculateNextResetTime(MapDifficultyEntry const* mapDiff, time_t prevResetTime); public: // modifiers void SetResetTimeFor(uint32 mapid, Difficulty d, time_t t) { diff --git a/src/game/MiscHandler.cpp b/src/game/MiscHandler.cpp index 3806ef8d0..b8e67f11a 100644 --- a/src/game/MiscHandler.cpp +++ b/src/game/MiscHandler.cpp @@ -688,42 +688,42 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recv_data) recv_data >> Trigger_ID; DEBUG_LOG("Trigger ID: %u", Trigger_ID); + Player* player = GetPlayer(); - if (GetPlayer()->IsTaxiFlying()) + if (player->IsTaxiFlying()) { - DEBUG_LOG("Player '%s' (GUID: %u) in flight, ignore Area Trigger ID: %u", GetPlayer()->GetName(), GetPlayer()->GetGUIDLow(), Trigger_ID); + DEBUG_LOG("Player '%s' (GUID: %u) in flight, ignore Area Trigger ID: %u", player->GetName(), player->GetGUIDLow(), Trigger_ID); return; } AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID); if (!atEntry) { - DEBUG_LOG("Player '%s' (GUID: %u) send unknown (by DBC) Area Trigger ID: %u", GetPlayer()->GetName(), GetPlayer()->GetGUIDLow(), Trigger_ID); + DEBUG_LOG("Player '%s' (GUID: %u) send unknown (by DBC) Area Trigger ID: %u", player->GetName(), player->GetGUIDLow(), Trigger_ID); return; } // delta is safe radius const float delta = 5.0f; - // check if player in the range of areatrigger - Player* pl = GetPlayer(); - if (!IsPointInAreaTriggerZone(atEntry, pl->GetMapId(), pl->GetPositionX(), pl->GetPositionY(), pl->GetPositionZ(), delta)) + // check if player in the range of areatrigger + if (!IsPointInAreaTriggerZone(atEntry, player->GetMapId(), player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), delta)) { - DEBUG_LOG("Player '%s' (GUID: %u) too far, ignore Area Trigger ID: %u", pl->GetName(), pl->GetGUIDLow(), Trigger_ID); + DEBUG_LOG("Player '%s' (GUID: %u) too far, ignore Area Trigger ID: %u", player->GetName(), player->GetGUIDLow(), Trigger_ID); return; } - if (sScriptMgr.OnAreaTrigger(pl, atEntry)) + if (sScriptMgr.OnAreaTrigger(player, atEntry)) return; uint32 quest_id = sObjectMgr.GetQuestForAreaTrigger(Trigger_ID); - if (quest_id && pl->isAlive() && pl->IsActiveQuest(quest_id)) + if (quest_id && player->isAlive() && player->IsActiveQuest(quest_id)) { Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); if (pQuest) { - if (pl->GetQuestStatus(quest_id) == QUEST_STATUS_INCOMPLETE) - pl->AreaExploredOrEventHappens(quest_id); + if (player->GetQuestStatus(quest_id) == QUEST_STATUS_INCOMPLETE) + player->AreaExploredOrEventHappens(quest_id); } } @@ -731,19 +731,19 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recv_data) if (sObjectMgr.IsTavernAreaTrigger(Trigger_ID)) { // set resting flag we are in the inn - if (pl->GetRestType() != REST_TYPE_IN_CITY) - pl->SetRestType(REST_TYPE_IN_TAVERN, Trigger_ID); + if (player->GetRestType() != REST_TYPE_IN_CITY) + player->SetRestType(REST_TYPE_IN_TAVERN, Trigger_ID); return; } - if (BattleGround* bg = pl->GetBattleGround()) + if (BattleGround* bg = player->GetBattleGround()) { - bg->HandleAreaTrigger(pl, Trigger_ID); + bg->HandleAreaTrigger(player, Trigger_ID); return; } - else if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(pl->GetCachedZoneId())) + else if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(player->GetCachedZoneId())) { - if (outdoorPvP->HandleAreaTrigger(pl, Trigger_ID)) + if (outdoorPvP->HandleAreaTrigger(player, Trigger_ID)) return; } @@ -756,111 +756,103 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recv_data) if (!targetMapEntry) return; - if (!pl->isGameMaster()) + // ghost resurrected at enter attempt to dungeon with corpse (including fail enter cases) + if (!player->isAlive() && targetMapEntry->IsDungeon()) { - // ghost resurrected at enter attempt to dungeon with corpse (including fail enter cases) - if (!pl->isAlive() && targetMapEntry->IsDungeon()) + int32 corpseMapId = 0; + if (Corpse* corpse = player->GetCorpse()) + corpseMapId = corpse->GetMapId(); + + // check back way from corpse to entrance + uint32 instance_map = corpseMapId; + do { - int32 corpseMapId = 0; - if (Corpse* corpse = pl->GetCorpse()) - corpseMapId = corpse->GetMapId(); + // most often fast case + if (instance_map == targetMapEntry->MapID) + break; - // check back way from corpse to entrance - uint32 instance_map = corpseMapId; - do - { - // most often fast case - if (instance_map == targetMapEntry->MapID) - break; - - InstanceTemplate const* instance = ObjectMgr::GetInstanceTemplate(instance_map); - instance_map = instance ? instance->parent : 0; - } - while (instance_map); - - // corpse not in dungeon or some linked deep dungeons - if (!instance_map) - { - WorldPacket data(SMSG_AREA_TRIGGER_NO_CORPSE); - pl->GetSession()->SendPacket(&data); - return; - } - - // need find areatrigger to inner dungeon for landing point - if (at->target_mapId != corpseMapId) - { - if (AreaTrigger const* corpseAt = sObjectMgr.GetMapEntranceTrigger(corpseMapId)) - { - at = corpseAt; - targetMapEntry = sMapStore.LookupEntry(at->target_mapId); - } - } - - // now we can resurrect player, and then check teleport requirements - pl->ResurrectPlayer(0.5f); - pl->SpawnCorpseBones(); + InstanceTemplate const* instance = ObjectMgr::GetInstanceTemplate(instance_map); + instance_map = instance ? instance->parent : 0; } + while (instance_map); - // check trigger requirements - bool missingItem = false; - bool missingLevel = false; - bool missingQuest = false; - - if (pl->getLevel() < at->requiredLevel && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL)) - missingLevel = true; - - // must have one or the other, report the first one that's missing - if (at->requiredItem) + // corpse not in dungeon or some linked deep dungeons + if (!instance_map) { - if (!pl->HasItemCount(at->requiredItem, 1) && - (!at->requiredItem2 || !GetPlayer()->HasItemCount(at->requiredItem2, 1))) - missingItem = true; - } - else if (at->requiredItem2 && !pl->HasItemCount(at->requiredItem2, 1)) - missingItem = true; - - bool isRegularTargetMap = !targetMapEntry->IsDungeon() || pl->GetDifficulty(targetMapEntry->IsRaid()) == REGULAR_DIFFICULTY; - - if (!isRegularTargetMap) - { - if (at->heroicKey) - { - if (!pl->HasItemCount(at->heroicKey, 1) && - (!at->heroicKey2 || !pl->HasItemCount(at->heroicKey2, 1))) - missingItem = true; - } - else if (at->heroicKey2 && !pl->HasItemCount(at->heroicKey2, 1)) - missingItem = true; - } - - if (!isRegularTargetMap) - { - if (at->requiredQuestHeroic && !pl->GetQuestRewardStatus(at->requiredQuestHeroic)) - missingQuest = true; - } - else - { - if (at->requiredQuest && !pl->GetQuestRewardStatus(at->requiredQuest)) - missingQuest = true; - } - - if (missingItem || missingLevel || missingQuest) - { - // hack for "Opening of the Dark Portal" - if (missingQuest && at->target_mapId == 269) - SendAreaTriggerMessage("%s", at->requiredFailedText.c_str()); - else if (missingQuest && targetMapEntry->IsContinent())// do not report anything for quest areatriggers - return; - // hack for TBC heroics - else if (missingLevel && !targetMapEntry->IsRaid() && GetPlayer()->GetDifficulty(false) == DUNGEON_DIFFICULTY_HEROIC && targetMapEntry->addon == 1) - SendAreaTriggerMessage(GetMangosString(LANG_LEVEL_MINREQUIRED), at->requiredLevel); - else - pl->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_DIFFICULTY, pl->GetDifficulty(targetMapEntry->IsRaid())); + WorldPacket data(SMSG_AREA_TRIGGER_NO_CORPSE); + player->GetSession()->SendPacket(&data); return; } + + // need find areatrigger to inner dungeon for landing point + if (at->target_mapId != corpseMapId) + { + if (AreaTrigger const* corpseAt = sObjectMgr.GetMapEntranceTrigger(corpseMapId)) + { + at = corpseAt; + targetMapEntry = sMapStore.LookupEntry(at->target_mapId); + } + } + + // now we can resurrect player, and then check teleport requirements + player->ResurrectPlayer(0.5f); + player->SpawnCorpseBones(); } - GetPlayer()->TeleportTo(at->target_mapId, at->target_X, at->target_Y, at->target_Z, at->target_Orientation, TELE_TO_NOT_LEAVE_TRANSPORT); + // check trigger requirements + uint32 miscRequirement = 0; + AreaLockStatus lockStatus = player->GetAreaTriggerLockStatus(at, player->GetDifficulty(targetMapEntry->IsRaid()), miscRequirement); + switch (lockStatus) + { + case AREA_LOCKSTATUS_OK: + player->TeleportTo(at->target_mapId, at->target_X, at->target_Y, at->target_Z, at->target_Orientation, TELE_TO_NOT_LEAVE_TRANSPORT); + break; + case AREA_LOCKSTATUS_TOO_LOW_LEVEL: + SendAreaTriggerMessage(GetMangosString(LANG_LEVEL_MINREQUIRED), miscRequirement); + break; + case AREA_LOCKSTATUS_ZONE_IN_COMBAT: + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_ZONE_IN_COMBAT); + break; + case AREA_LOCKSTATUS_INSTANCE_IS_FULL: + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_MAX_PLAYERS); + break; + case AREA_LOCKSTATUS_QUEST_NOT_COMPLETED: + if (at->target_mapId == 269) // Exception for Black Morass + { + SendAreaTriggerMessage(GetMangosString(LANG_TELEREQ_QUEST_BLACK_MORASS)); + break; + } + else if (targetMapEntry->IsContinent()) // do not report anything for quest areatrigge + { + DEBUG_LOG("HandleAreaTriggerOpcode: LockAreaStatus %u, do not teleport, no message sent (trigger %u)", lockStatus, Trigger_ID); + break; + } + // No break here! + case AREA_LOCKSTATUS_MISSING_ITEM: + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_DIFFICULTY, player->GetDifficulty(targetMapEntry->IsRaid())); + break; + case AREA_LOCKSTATUS_MISSING_DIFFICULTY: + { + Difficulty difficulty = player->GetDifficulty(targetMapEntry->IsRaid()); + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_DIFFICULTY, difficulty > RAID_DIFFICULTY_10MAN_HEROIC ? RAID_DIFFICULTY_10MAN_HEROIC : difficulty); + break; + } + case AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION: + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_INSUF_EXPAN_LVL, miscRequirement); + break; + case AREA_LOCKSTATUS_NOT_ALLOWED: + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_MAP_NOT_ALLOWED); + break; + case AREA_LOCKSTATUS_RAID_LOCKED: + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_NEED_GROUP); + break; + case AREA_LOCKSTATUS_UNKNOWN_ERROR: + player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_ERROR); + break; + default: + sLog.outError("HandleAreaTriggerOpcode: unhandled LockAreaStatus %u, when %s attempts to use area-trigger %u", lockStatus, player->GetGuidStr().c_str(), Trigger_ID); + break; + } } void WorldSession::HandleUpdateAccountData(WorldPacket& recv_data) @@ -1416,7 +1408,8 @@ void WorldSession::HandleSetDungeonDifficultyOpcode(WorldPacket& recv_data) return; } - if (_player->getLevel() < LEVELREQUIREMENT_HEROIC) + // Exception to set mode to normal for low-level players + if (_player->getLevel() < LEVELREQUIREMENT_HEROIC && mode > REGULAR_DIFFICULTY) return; if (Group* pGroup = _player->GetGroup()) @@ -1460,7 +1453,8 @@ void WorldSession::HandleSetRaidDifficultyOpcode(WorldPacket& recv_data) return; } - if (_player->getLevel() < LEVELREQUIREMENT_HEROIC) + // Exception to set mode to normal for low-level players + if (_player->getLevel() < LEVELREQUIREMENT_HEROIC && mode > REGULAR_DIFFICULTY) return; if (Group* pGroup = _player->GetGroup()) diff --git a/src/game/MovementHandler.cpp b/src/game/MovementHandler.cpp index 886e65c02..8d6a2494a 100644 --- a/src/game/MovementHandler.cpp +++ b/src/game/MovementHandler.cpp @@ -172,7 +172,7 @@ void WorldSession::HandleMoveWorldportAckOpcode() if (mInstance) { Difficulty diff = GetPlayer()->GetDifficulty(mEntry->IsRaid()); - if (MapDifficulty const* mapDiff = GetMapDifficultyData(mEntry->MapID, diff)) + if (MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mEntry->MapID, diff)) { if (mapDiff->resetTime) { diff --git a/src/game/ObjectMgr.cpp b/src/game/ObjectMgr.cpp index f43d4cca4..0f71f82a4 100755 --- a/src/game/ObjectMgr.cpp +++ b/src/game/ObjectMgr.cpp @@ -5573,8 +5573,8 @@ void ObjectMgr::LoadAreaTriggerTeleports() uint32 count = 0; - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 - QueryResult* result = WorldDatabase.Query("SELECT id, required_level, required_item, required_item2, heroic_key, heroic_key2, required_quest_done, required_quest_done_heroic, required_failed_text, target_map, target_position_x, target_position_y, target_position_z, target_orientation FROM areatrigger_teleport"); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + QueryResult* result = WorldDatabase.Query("SELECT id, required_level, required_item, required_item2, heroic_key, heroic_key2, required_quest_done, required_quest_done_heroic, target_map, target_position_x, target_position_y, target_position_z, target_orientation FROM areatrigger_teleport"); if (!result) { @@ -5608,12 +5608,11 @@ void ObjectMgr::LoadAreaTriggerTeleports() at.heroicKey2 = fields[5].GetUInt32(); at.requiredQuest = fields[6].GetUInt32(); at.requiredQuestHeroic = fields[7].GetUInt32(); - at.requiredFailedText = fields[8].GetCppString(); - at.target_mapId = fields[9].GetUInt32(); - at.target_X = fields[10].GetFloat(); - at.target_Y = fields[11].GetFloat(); - at.target_Z = fields[12].GetFloat(); - at.target_Orientation = fields[13].GetFloat(); + at.target_mapId = fields[8].GetUInt32(); + at.target_X = fields[9].GetFloat(); + at.target_Y = fields[10].GetFloat(); + at.target_Z = fields[11].GetFloat(); + at.target_Orientation = fields[12].GetFloat(); AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID); if (!atEntry) diff --git a/src/game/ObjectMgr.h b/src/game/ObjectMgr.h index 51b67607a..31dd68e31 100755 --- a/src/game/ObjectMgr.h +++ b/src/game/ObjectMgr.h @@ -83,7 +83,6 @@ struct AreaTrigger uint32 heroicKey2; uint32 requiredQuest; uint32 requiredQuestHeroic; - std::string requiredFailedText; uint32 target_mapId; float target_X; float target_Y; diff --git a/src/game/Player.cpp b/src/game/Player.cpp index c3861b859..b9f75c896 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -16774,7 +16774,7 @@ void Player::_LoadBoundInstances(QueryResult* result) continue; } - MapDifficulty const* mapDiff = GetMapDifficultyData(mapId, Difficulty(difficulty)); + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapId, Difficulty(difficulty)); if (!mapDiff) { sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent difficulty %d instance for map %u", GetName(), GetGUIDLow(), difficulty, mapId); @@ -16803,7 +16803,7 @@ void Player::_LoadBoundInstances(QueryResult* result) InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty) { // some instances only have one difficulty - MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapid, difficulty); if (!mapDiff) return NULL; @@ -23611,3 +23611,123 @@ void Player::SendRatedBGStats() SendDirectMessage(&data); } + +AreaLockStatus Player::GetAreaTriggerLockStatus(AreaTrigger const* at, Difficulty difficulty, uint32& miscRequirement) +{ + miscRequirement = 0; + + if (!at) + return AREA_LOCKSTATUS_UNKNOWN_ERROR; + + MapEntry const* mapEntry = sMapStore.LookupEntry(at->target_mapId); + if (!mapEntry) + return AREA_LOCKSTATUS_UNKNOWN_ERROR; + + bool isRegularTargetMap = !mapEntry->IsDungeon() || GetDifficulty(mapEntry->IsRaid()) == REGULAR_DIFFICULTY; + + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(at->target_mapId, difficulty); + if (mapEntry->IsDungeon() && !mapDiff) + return AREA_LOCKSTATUS_MISSING_DIFFICULTY; + + // Expansion requirement + if (GetSession()->Expansion() < mapEntry->Expansion()) + { + miscRequirement = mapEntry->Expansion(); + return AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION; + } + + // Gamemaster can always enter + if (isGameMaster()) + return AREA_LOCKSTATUS_OK; + + // Level Requirements + if (getLevel() < at->requiredLevel && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL)) + { + miscRequirement = at->requiredLevel; + return AREA_LOCKSTATUS_TOO_LOW_LEVEL; + } + if (!isRegularTargetMap && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL) && getLevel() < uint32(maxLevelForExpansion[mapEntry->Expansion()])) + { + miscRequirement = maxLevelForExpansion[mapEntry->Expansion()]; + return AREA_LOCKSTATUS_TOO_LOW_LEVEL; + } + + // Raid Requirements + if (mapEntry->IsRaid() && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_RAID)) + if (!GetGroup() || !GetGroup()->isRaidGroup()) + return AREA_LOCKSTATUS_RAID_LOCKED; + + // Item Requirements: must have requiredItem OR requiredItem2, report the first one that's missing + if (at->requiredItem) + { + if (!HasItemCount(at->requiredItem, 1) && + (!at->requiredItem2 || !HasItemCount(at->requiredItem2, 1))) + { + miscRequirement = at->requiredItem; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + } + else if (at->requiredItem2 && !HasItemCount(at->requiredItem2, 1)) + { + miscRequirement = at->requiredItem2; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + // Heroic item requirements + if (!isRegularTargetMap && at->heroicKey) + { + if (!HasItemCount(at->heroicKey, 1) && (!at->heroicKey2 || !HasItemCount(at->heroicKey2, 1))) + { + miscRequirement = at->heroicKey; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + } + else if (!isRegularTargetMap && at->heroicKey2 && !HasItemCount(at->heroicKey2, 1)) + { + miscRequirement = at->heroicKey2; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + + // Quest Requirements + if (isRegularTargetMap && at->requiredQuest && !GetQuestRewardStatus(at->requiredQuest)) + { + miscRequirement = at->requiredQuest; + return AREA_LOCKSTATUS_QUEST_NOT_COMPLETED; + } + if (!isRegularTargetMap && at->requiredQuestHeroic && !GetQuestRewardStatus(at->requiredQuestHeroic)) + { + miscRequirement = at->requiredQuestHeroic; + return AREA_LOCKSTATUS_QUEST_NOT_COMPLETED; + } + + // If the map is not created, assume it is possible to enter it. + DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(at->target_mapId); + Map* map = sMapMgr.FindMap(at->target_mapId, state ? state->GetInstanceId() : 0); + + // ToDo add achievement check + + // Map's state check + if (map && map->IsDungeon()) + { + // cannot enter if the instance is full (player cap), GMs don't count + if (((DungeonMap*)map)->GetPlayersCountExceptGMs() >= ((DungeonMap*)map)->GetMaxPlayers()) + return AREA_LOCKSTATUS_INSTANCE_IS_FULL; + + // In Combat check + if (map && map->GetInstanceData() && map->GetInstanceData()->IsEncounterInProgress()) + return AREA_LOCKSTATUS_ZONE_IN_COMBAT; + + // Bind Checks + InstancePlayerBind* pBind = GetBoundInstance(at->target_mapId, GetDifficulty(mapEntry->IsRaid())); + if (pBind && pBind->perm && pBind->state != state) + return AREA_LOCKSTATUS_HAS_BIND; + if (pBind && pBind->perm && pBind->state != map->GetPersistentState()) + return AREA_LOCKSTATUS_HAS_BIND; + } + + return AREA_LOCKSTATUS_OK; +}; + +AreaLockStatus Player::GetAreaLockStatus(uint32 mapId, Difficulty difficulty, uint32& miscRequirement) +{ + return GetAreaTriggerLockStatus(sObjectMgr.GetMapEntranceTrigger(mapId), difficulty, miscRequirement); +}; diff --git a/src/game/Player.h b/src/game/Player.h index cd5078005..5f4811128 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -54,6 +54,8 @@ class DungeonPersistentState; class Spell; class Item; +struct AreaTrigger; + typedef std::deque PlayerMails; #define PLAYER_MAX_SKILLS 128 @@ -2326,6 +2328,9 @@ class MANGOS_DLL_SPEC Player : public Unit static void ConvertInstancesToGroup(Player* player, Group* group = NULL, ObjectGuid player_guid = ObjectGuid()); DungeonPersistentState* GetBoundInstanceSaveForSelfOrGroup(uint32 mapid); + AreaLockStatus GetAreaLockStatus(uint32 mapId, Difficulty difficulty, uint32& miscRequirement); + AreaLockStatus GetAreaTriggerLockStatus(AreaTrigger const* at, Difficulty difficulty, uint32& miscRequirement); + /*********************************************************/ /*** GROUP SYSTEM ***/ /*********************************************************/ diff --git a/src/game/SharedDefines.h b/src/game/SharedDefines.h index 8b000aa74..8daa484e4 100644 --- a/src/game/SharedDefines.h +++ b/src/game/SharedDefines.h @@ -3206,6 +3206,23 @@ enum ActivateTaxiReply ERR_TAXINOTSTANDING = 12 }; +enum AreaLockStatus +{ + AREA_LOCKSTATUS_OK = 0, + AREA_LOCKSTATUS_UNKNOWN_ERROR = 1, + AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION = 2, + AREA_LOCKSTATUS_TOO_LOW_LEVEL = 3, + AREA_LOCKSTATUS_TOO_HIGH_LEVEL = 4, + AREA_LOCKSTATUS_RAID_LOCKED = 5, + AREA_LOCKSTATUS_QUEST_NOT_COMPLETED = 6, + AREA_LOCKSTATUS_MISSING_ITEM = 7, + AREA_LOCKSTATUS_MISSING_DIFFICULTY = 8, + AREA_LOCKSTATUS_ZONE_IN_COMBAT = 9, + AREA_LOCKSTATUS_INSTANCE_IS_FULL = 10, + AREA_LOCKSTATUS_NOT_ALLOWED = 11, + AREA_LOCKSTATUS_HAS_BIND = 12, +}; + // we need to stick to 1 version or half of the stuff will work for someone // others will not and opposite // will only support WoW, WoW:TBC, WoW:WotLK and WoW:Cataclysm 4.3.4 client build 15595... @@ -3219,4 +3236,14 @@ enum ActivateTaxiReply // that it not have expansion installed and reject #define MAX_EXPANSION 3 +// Maxlevel for expansion +enum MaxLevel +{ + MAX_LEVEL_CLASSIC = 60, + MAX_LEVEL_TBC = 70, + MAX_LEVEL_WOTLK = 80, +}; + +static const MaxLevel maxLevelForExpansion[MAX_EXPANSION + 1] = { MAX_LEVEL_CLASSIC, MAX_LEVEL_TBC, MAX_LEVEL_WOTLK }; + #endif diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index 4d8b3ab6c..5438cda32 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "12194" + #define REVISION_NR "12195" #endif // __REVISION_NR_H__ diff --git a/src/shared/revision_sql.h b/src/shared/revision_sql.h index 5e20a9362..c788b8df8 100644 --- a/src/shared/revision_sql.h +++ b/src/shared/revision_sql.h @@ -1,6 +1,6 @@ #ifndef __REVISION_SQL_H__ #define __REVISION_SQL_H__ #define REVISION_DB_CHARACTERS "required_12161_01_characters_characters" - #define REVISION_DB_MANGOS "required_12186_01_mangos_item_template" + #define REVISION_DB_MANGOS "required_12195_02_mangos_mangos_string" #define REVISION_DB_REALMD "required_12112_01_realmd_account_access" #endif // __REVISION_SQL_H__