Core/Maps: Changed functions checking if map can be entered to use TransferAbortReason directly instead of intermediate Map::EnterState enum

This commit is contained in:
Shauren
2022-10-08 19:11:34 +02:00
parent d1473786ee
commit c3e1ff90a5
6 changed files with 229 additions and 273 deletions

View File

@@ -1434,8 +1434,11 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati
// Check enter rights before map getting to avoid creating instance copy for player
// this check not dependent from map instance copy and same for all instance copies of selected map
if (Map::PlayerCannotEnter(mapid, this, false))
if (TransferAbortParams abortParams = Map::PlayerCannotEnter(mapid, this))
{
SendTransferAborted(mapid, abortParams.Reason, abortParams.Arg, abortParams.MapDifficultyXConditionId);
return false;
}
// Seamless teleport can happen only if cosmetic maps match
if (!oldmap ||
@@ -1443,113 +1446,104 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati
!((oldmap->GetEntry()->CosmeticParentMapID != -1) ^ (oldmap->GetEntry()->CosmeticParentMapID != mEntry->CosmeticParentMapID))))
options &= ~TELE_TO_SEAMLESS;
//I think this always returns true. Correct me if I am wrong.
// If the map is not created, assume it is possible to enter it.
// It will be created in the WorldPortAck.
//Map* map = sMapMgr->FindBaseNonInstanceMap(mapid);
//if (!map || map->CanEnter(this))
//lets reset near teleport flag if it wasn't reset during chained teleports
SetSemaphoreTeleportNear(false);
//setup delayed teleport flag
SetDelayedTeleportFlag(IsCanDelayTeleport());
//if teleport spell is cast in Unit::Update() func
//then we need to delay it until update process will be finished
if (IsHasDelayedTeleport())
{
//lets reset near teleport flag if it wasn't reset during chained teleports
SetSemaphoreTeleportNear(false);
//setup delayed teleport flag
SetDelayedTeleportFlag(IsCanDelayTeleport());
//if teleport spell is cast in Unit::Update() func
//then we need to delay it until update process will be finished
if (IsHasDelayedTeleport())
{
SetSemaphoreTeleportFar(true);
//lets save teleport destination for player
m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
m_teleport_instanceId = instanceId;
m_teleport_options = options;
return true;
}
SetSelection(ObjectGuid::Empty);
CombatStop();
ResetContestedPvP();
// remove player from battleground on far teleport (when changing maps)
if (Battleground const* bg = GetBattleground())
{
// Note: at battleground join battleground id set before teleport
// and we already will found "current" battleground
// just need check that this is targeted map or leave
if (bg->GetMapId() != mapid)
LeaveBattleground(false); // don't teleport to entry point
}
// remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned
if (mEntry->IsBattleArena() && !IsGameMaster())
{
RemoveArenaSpellCooldowns(true);
RemoveArenaAuras();
if (pet)
pet->RemoveArenaAuras();
}
// remove pet on map change
if (pet)
UnsummonPetTemporaryIfAny();
// remove all dyn objects
RemoveAllDynObjects();
// remove all areatriggers entities
RemoveAllAreaTriggers();
// stop spellcasting
// not attempt interrupt teleportation spell at caster teleport
if (!(options & TELE_TO_SPELL))
if (IsNonMeleeSpellCast(true))
InterruptNonMeleeSpells(true);
//remove auras before removing from map...
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving | SpellAuraInterruptFlags::Turning);
if (!GetSession()->PlayerLogout() && !(options & TELE_TO_SEAMLESS))
{
// send transfer packets
WorldPackets::Movement::TransferPending transferPending;
transferPending.MapID = mapid;
transferPending.OldMapPosition = GetPosition();
if (Transport* transport = dynamic_cast<Transport*>(GetTransport()))
{
transferPending.Ship.emplace();
transferPending.Ship->ID = transport->GetEntry();
transferPending.Ship->OriginMapID = GetMapId();
}
SendDirectMessage(transferPending.Write());
}
// remove from old map now
if (oldmap)
oldmap->RemovePlayerFromMap(this, false);
SetSemaphoreTeleportFar(true);
//lets save teleport destination for player
m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
m_teleport_instanceId = instanceId;
m_teleport_options = options;
SetFallInformation(0, GetPositionZ());
// if the player is saved before worldportack (at logout for example)
// this will be used instead of the current location in SaveToDB
return true;
}
if (!GetSession()->PlayerLogout())
SetSelection(ObjectGuid::Empty);
CombatStop();
ResetContestedPvP();
// remove player from battleground on far teleport (when changing maps)
if (Battleground const* bg = GetBattleground())
{
// Note: at battleground join battleground id set before teleport
// and we already will found "current" battleground
// just need check that this is targeted map or leave
if (bg->GetMapId() != mapid)
LeaveBattleground(false); // don't teleport to entry point
}
// remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned
if (mEntry->IsBattleArena() && !IsGameMaster())
{
RemoveArenaSpellCooldowns(true);
RemoveArenaAuras();
if (pet)
pet->RemoveArenaAuras();
}
// remove pet on map change
if (pet)
UnsummonPetTemporaryIfAny();
// remove all dyn objects
RemoveAllDynObjects();
// remove all areatriggers entities
RemoveAllAreaTriggers();
// stop spellcasting
// not attempt interrupt teleportation spell at caster teleport
if (!(options & TELE_TO_SPELL))
if (IsNonMeleeSpellCast(true))
InterruptNonMeleeSpells(true);
//remove auras before removing from map...
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving | SpellAuraInterruptFlags::Turning);
if (!GetSession()->PlayerLogout() && !(options & TELE_TO_SEAMLESS))
{
// send transfer packets
WorldPackets::Movement::TransferPending transferPending;
transferPending.MapID = mapid;
transferPending.OldMapPosition = GetPosition();
if (Transport* transport = dynamic_cast<Transport*>(GetTransport()))
{
WorldPackets::Movement::SuspendToken suspendToken;
suspendToken.SequenceIndex = m_movementCounter; // not incrementing
suspendToken.Reason = options & TELE_TO_SEAMLESS ? 2 : 1;
SendDirectMessage(suspendToken.Write());
transferPending.Ship.emplace();
transferPending.Ship->ID = transport->GetEntry();
transferPending.Ship->OriginMapID = GetMapId();
}
// move packet sent by client always after far teleport
// code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
SetSemaphoreTeleportFar(true);
SendDirectMessage(transferPending.Write());
}
//else
// return false;
// remove from old map now
if (oldmap)
oldmap->RemovePlayerFromMap(this, false);
m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
m_teleport_instanceId = instanceId;
m_teleport_options = options;
SetFallInformation(0, GetPositionZ());
// if the player is saved before worldportack (at logout for example)
// this will be used instead of the current location in SaveToDB
if (!GetSession()->PlayerLogout())
{
WorldPackets::Movement::SuspendToken suspendToken;
suspendToken.SequenceIndex = m_movementCounter; // not incrementing
suspendToken.Reason = options & TELE_TO_SEAMLESS ? 2 : 1;
SendDirectMessage(suspendToken.Write());
}
// move packet sent by client always after far teleport
// code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
SetSemaphoreTeleportFar(true);
}
return true;
}
@@ -17438,31 +17432,9 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
}
else if (map->IsDungeon()) // if map is dungeon...
{
if (Map::EnterState denyReason = ((InstanceMap*)map)->CannotEnter(this)) // ... and can't enter map, then look for entry point.
if (TransferAbortParams denyReason = map->CannotEnter(this)) // ... and can't enter map, then look for entry point.
{
switch (denyReason)
{
case Map::CANNOT_ENTER_DIFFICULTY_UNAVAILABLE:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_DIFFICULTY, map->GetDifficultyID());
break;
case Map::CANNOT_ENTER_INSTANCE_BIND_MISMATCH:
ChatHandler(GetSession()).PSendSysMessage(GetSession()->GetTrinityString(LANG_INSTANCE_BIND_MISMATCH), map->GetMapName());
break;
case Map::CANNOT_ENTER_TOO_MANY_INSTANCES:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_TOO_MANY_INSTANCES);
break;
case Map::CANNOT_ENTER_MAX_PLAYERS:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_MAX_PLAYERS);
break;
case Map::CANNOT_ENTER_ZONE_IN_COMBAT:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_ZONE_IN_COMBAT);
break;
case Map::CANNOT_ENTER_INSTANCE_SHUTTING_DOWN:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_NOT_FOUND);
break;
default:
break;
}
SendTransferAborted(map->GetId(), denyReason.Reason, denyReason.Arg, denyReason.MapDifficultyXConditionId);
areaTrigger = sObjectMgr->GetGoBackTrigger(mapId);
check = true;
}
@@ -19014,7 +18986,7 @@ void Player::SendRaidInfo()
SendDirectMessage(instanceInfo.Write());
}
bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report)
bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, TransferAbortParams* params, bool report)
{
if (!IsGameMaster())
{
@@ -19065,12 +19037,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()->SendNotification("%s", GetSession()->GetTrinityString(LANG_INSTANCE_CLOSED));
return false;
}
if (GetTeam() == ALLIANCE && ar->quest_A && !GetQuestRewardStatus(ar->quest_A))
missingQuest = ar->quest_A;
else if (GetTeam() == HORDE && ar->quest_H && !GetQuestRewardStatus(ar->quest_H))
@@ -19088,12 +19054,22 @@ bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report
if (LevelMin || LevelMax || failedMapDifficultyXCondition || missingItem || missingQuest || missingAchievement)
{
if (params)
params->Reason = TRANSFER_ABORT_ERROR;
if (report)
{
if (missingQuest && !ar->questFailedText.empty())
ChatHandler(GetSession()).PSendSysMessage("%s", ar->questFailedText.c_str());
else if (mapDiff->Message[sWorld->GetDefaultDbcLocale()][0] != '\0' || failedMapDifficultyXCondition) // if (missingAchievement) covered by this case
SendTransferAborted(target_map, TRANSFER_ABORT_DIFFICULTY, target_difficulty, failedMapDifficultyXCondition);
{
if (params)
{
params->Reason = TRANSFER_ABORT_DIFFICULTY;
params->Arg = target_difficulty;
params->MapDifficultyXConditionId = failedMapDifficultyXCondition;
}
}
else if (missingItem)
GetSession()->SendNotification(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(missingItem))->GetName(GetSession()->GetSessionDbcLocale()));
else if (LevelMin)

View File

@@ -63,6 +63,7 @@ struct RewardPackEntry;
struct SkillRaceClassInfoEntry;
struct TalentEntry;
struct TrainerSpell;
struct TransferAbortParams;
struct VendorItem;
class AELootResult;
@@ -99,8 +100,9 @@ enum ItemClass : uint8;
enum LootError : uint8;
enum LootType : uint8;
enum PlayerRestState : uint8;
enum RestTypes : uint8;
enum class PlayerCreateMode : int8;
enum RestTypes : uint8;
enum TransferAbortReason : uint32;
namespace BattlePets
{
@@ -743,31 +745,6 @@ enum class ItemSearchCallbackResult
Continue
};
enum TransferAbortReason : uint32
{
TRANSFER_ABORT_NONE = 0,
TRANSFER_ABORT_ERROR = 1,
TRANSFER_ABORT_MAX_PLAYERS = 2, // Transfer Aborted: instance is full
TRANSFER_ABORT_NOT_FOUND = 3, // Transfer Aborted: instance not found
TRANSFER_ABORT_TOO_MANY_INSTANCES = 4, // You have entered too many instances recently.
TRANSFER_ABORT_ZONE_IN_COMBAT = 6, // Unable to zone in while an encounter is in progress.
TRANSFER_ABORT_INSUF_EXPAN_LVL = 7, // You must have <TBC, WotLK> expansion installed to access this area.
TRANSFER_ABORT_DIFFICULTY = 8, // <Normal, Heroic, Epic> difficulty mode is not available for %s.
TRANSFER_ABORT_UNIQUE_MESSAGE = 9, // Until you've escaped TLK's grasp, you cannot leave this place!
TRANSFER_ABORT_TOO_MANY_REALM_INSTANCES = 10, // Additional instances cannot be launched, please try again later.
TRANSFER_ABORT_NEED_GROUP = 11, // Transfer Aborted: you must be in a raid group to enter this instance
TRANSFER_ABORT_NOT_FOUND_2 = 12, // Transfer Aborted: instance not found
TRANSFER_ABORT_NOT_FOUND_3 = 13, // Transfer Aborted: instance not found
TRANSFER_ABORT_NOT_FOUND_4 = 14, // Transfer Aborted: instance not found
TRANSFER_ABORT_REALM_ONLY = 15, // All players in the party must be from the same realm to enter %s.
TRANSFER_ABORT_MAP_NOT_ALLOWED = 16, // Map cannot be entered at this time.
TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE = 18, // You are already locked to %s
TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER = 19, // You are ineligible to participate in at least one encounter in this instance because you are already locked to an instance in which it has been defeated.
TRANSFER_ABORT_DIFFICULTY_NOT_FOUND = 22, // client writes to console "Unable to resolve requested difficultyID %u to actual difficulty for map %d"
TRANSFER_ABORT_XREALM_ZONE_DOWN = 24, // Transfer Aborted: cross-realm zone is down
TRANSFER_ABORT_SOLO_PLAYER_SWITCH_DIFFICULTY = 26, // This instance is already in progress. You may only switch difficulties from inside the instance.
};
enum NewWorldReason
{
NEW_WORLD_NORMAL = 16, // Normal map change
@@ -2529,7 +2506,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetPendingBind(uint32 instanceId, uint32 bindTimer);
bool HasPendingBind() const { return _pendingBindId > 0; }
void SendRaidInfo();
bool Satisfy(AccessRequirement const* ar, uint32 target_map, bool report = false);
bool Satisfy(AccessRequirement const* ar, uint32 target_map, TransferAbortParams* params = nullptr, bool report = false);
bool CheckInstanceValidity(bool /*isLogin*/);
bool CheckInstanceCount(uint32 instanceId) const;
void AddInstanceEnterTime(uint32 instanceId, time_t enterTime);

View File

@@ -573,70 +573,78 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPackets::AreaTrigger::AreaTrigge
bool teleported = false;
if (player->GetMapId() != at->target_mapId)
{
if (Map::EnterState denyReason = Map::PlayerCannotEnter(at->target_mapId, player, false))
if (!player->IsAlive())
{
bool reviveAtTrigger = false; // should we revive the player if he is trying to enter the correct instance?
switch (denyReason)
if (player->HasCorpse())
{
case Map::CANNOT_ENTER_NO_ENTRY:
// let enter in ghost mode in instance that connected to inner instance with corpse
uint32 corpseMap = player->GetCorpseLocation().GetMapId();
do
{
if (corpseMap == at->target_mapId)
break;
InstanceTemplate const* corpseInstance = sObjectMgr->GetInstanceTemplate(corpseMap);
corpseMap = corpseInstance ? corpseInstance->Parent : 0;
} while (corpseMap);
if (!corpseMap)
{
SendPacket(WorldPackets::AreaTrigger::AreaTriggerNoCorpse().Write());
return;
}
TC_LOG_DEBUG("maps", "MAP: Player '%s' has corpse in instance %u and can enter.", player->GetName().c_str(), at->target_mapId);
}
else
TC_LOG_DEBUG("maps", "Map::CanPlayerEnter - player '%s' is dead but does not have a corpse!", player->GetName().c_str());
}
if (TransferAbortParams denyReason = Map::PlayerCannotEnter(at->target_mapId, player))
{
switch (denyReason.Reason)
{
case TRANSFER_ABORT_MAP_NOT_ALLOWED:
TC_LOG_DEBUG("maps", "MAP: Player '%s' attempted to enter map with id %d which has no entry", player->GetName().c_str(), at->target_mapId);
break;
case Map::CANNOT_ENTER_UNINSTANCED_DUNGEON:
TC_LOG_DEBUG("maps", "MAP: Player '%s' attempted to enter dungeon map %d but no instance template was found", player->GetName().c_str(), at->target_mapId);
break;
case Map::CANNOT_ENTER_DIFFICULTY_UNAVAILABLE:
case TRANSFER_ABORT_DIFFICULTY:
TC_LOG_DEBUG("maps", "MAP: Player '%s' attempted to enter instance map %d but the requested difficulty was not found", player->GetName().c_str(), at->target_mapId);
if (MapEntry const* entry = sMapStore.LookupEntry(at->target_mapId))
player->SendTransferAborted(entry->ID, TRANSFER_ABORT_DIFFICULTY, player->GetDifficultyID(entry));
break;
case Map::CANNOT_ENTER_NOT_IN_RAID:
case TRANSFER_ABORT_NEED_GROUP:
TC_LOG_DEBUG("maps", "MAP: Player '%s' must be in a raid group to enter map %d", player->GetName().c_str(), at->target_mapId);
player->SendRaidGroupOnlyMessage(RAID_GROUP_ERR_ONLY, 0);
reviveAtTrigger = true;
break;
case Map::CANNOT_ENTER_CORPSE_IN_DIFFERENT_INSTANCE:
SendPacket(WorldPackets::AreaTrigger::AreaTriggerNoCorpse().Write());
TC_LOG_DEBUG("maps", "MAP: Player '%s' does not have a corpse in instance map %d and cannot enter", player->GetName().c_str(), at->target_mapId);
break;
case Map::CANNOT_ENTER_INSTANCE_BIND_MISMATCH:
player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE);
case TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE:
TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map %d because their permanent bind is incompatible with their group's", player->GetName().c_str(), at->target_mapId);
reviveAtTrigger = true;
break;
case Map::CANNOT_ENTER_ALREADY_COMPLETED_ENCOUNTER:
player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER);
case TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER:
TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map %d because their permanent bind is incompatible with their group's", player->GetName().c_str(), at->target_mapId);
reviveAtTrigger = true;
break;
case Map::CANNOT_ENTER_TOO_MANY_INSTANCES:
player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_TOO_MANY_INSTANCES);
case TRANSFER_ABORT_TOO_MANY_INSTANCES:
TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map %d because he has exceeded the maximum number of instances per hour.", player->GetName().c_str(), at->target_mapId);
reviveAtTrigger = true;
break;
case Map::CANNOT_ENTER_MAX_PLAYERS:
player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_MAX_PLAYERS);
reviveAtTrigger = true;
case TRANSFER_ABORT_MAX_PLAYERS:
break;
case Map::CANNOT_ENTER_ZONE_IN_COMBAT:
player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_ZONE_IN_COMBAT);
reviveAtTrigger = true;
case TRANSFER_ABORT_ZONE_IN_COMBAT:
break;
case Map::CANNOT_ENTER_INSTANCE_SHUTTING_DOWN:
player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_NOT_FOUND);
case TRANSFER_ABORT_NOT_FOUND:
TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map %d because instance is resetting.", player->GetName().c_str(), at->target_mapId);
reviveAtTrigger = true;
break;
default:
break;
}
if (reviveAtTrigger) // check if the player is touching the areatrigger leading to the map his corpse is on
if (!player->IsAlive() && player->HasCorpse())
if (player->GetCorpseLocation().GetMapId() == at->target_mapId)
{
player->ResurrectPlayer(0.5f);
player->SpawnCorpseBones();
}
if (denyReason.Reason != TRANSFER_ABORT_NEED_GROUP)
player->SendTransferAborted(at->target_mapId, denyReason.Reason, denyReason.Arg, denyReason.MapDifficultyXConditionId);
if (!player->IsAlive() && player->HasCorpse())
{
if (player->GetCorpseLocation().GetMapId() == at->target_mapId)
{
player->ResurrectPlayer(0.5f);
player->SpawnCorpseBones();
}
}
return;
}

View File

@@ -21,8 +21,8 @@
#include "Errors.h"
#include "GameTime.h"
#include "Log.h"
#include "Map.h" // for TransferAbortReason
#include "MapManager.h"
#include "Player.h" // for TransferAbortReason
#include "World.h"
InstanceLockData::InstanceLockData() = default;

View File

@@ -1754,75 +1754,51 @@ bool Map::getObjectHitPos(PhaseShift const& phaseShift, float x1, float y1, floa
return result;
}
Map::EnterState Map::PlayerCannotEnter(uint32 mapid, Player* player, bool /*loginCheck*/)
TransferAbortParams Map::PlayerCannotEnter(uint32 mapid, Player* player)
{
MapEntry const* entry = sMapStore.LookupEntry(mapid);
if (!entry)
return CANNOT_ENTER_NO_ENTRY;
return TRANSFER_ABORT_MAP_NOT_ALLOWED;
if (!entry->IsDungeon())
return CAN_ENTER;
return TRANSFER_ABORT_NONE;
Difficulty targetDifficulty, requestedDifficulty;
targetDifficulty = requestedDifficulty = player->GetDifficultyID(entry);
Difficulty targetDifficulty = player->GetDifficultyID(entry);
// Get the highest available difficulty if current setting is higher than the instance allows
MapDifficultyEntry const* mapDiff = sDB2Manager.GetDownscaledMapDifficultyData(mapid, targetDifficulty);
if (!mapDiff)
return CANNOT_ENTER_DIFFICULTY_UNAVAILABLE;
return TRANSFER_ABORT_DIFFICULTY;
//Bypass checks for GMs
if (player->IsGameMaster())
return CAN_ENTER;
return TRANSFER_ABORT_NONE;
//Other requirements
if (!player->Satisfy(sObjectMgr->GetAccessRequirement(mapid, targetDifficulty), mapid, true))
return CANNOT_ENTER_UNSPECIFIED_REASON;
char const* mapName = entry->MapName[sWorld->GetDefaultDbcLocale()];
{
TransferAbortParams params(TRANSFER_ABORT_NONE);
if (!player->Satisfy(sObjectMgr->GetAccessRequirement(mapid, targetDifficulty), mapid, &params, true))
return params;
}
Group* group = player->GetGroup();
if (entry->IsRaid() && entry->Expansion() >= sWorld->getIntConfig(CONFIG_EXPANSION)) // can only enter in a raid group but raids from old expansion don't need a group
if ((!group || !group->isRaidGroup()) && !sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_RAID))
return CANNOT_ENTER_NOT_IN_RAID;
if (!player->IsAlive())
{
if (player->HasCorpse())
{
// let enter in ghost mode in instance that connected to inner instance with corpse
uint32 corpseMap = player->GetCorpseLocation().GetMapId();
do
{
if (corpseMap == mapid)
break;
InstanceTemplate const* corpseInstance = sObjectMgr->GetInstanceTemplate(corpseMap);
corpseMap = corpseInstance ? corpseInstance->Parent : 0;
} while (corpseMap);
if (!corpseMap)
return CANNOT_ENTER_CORPSE_IN_DIFFERENT_INSTANCE;
TC_LOG_DEBUG("maps", "MAP: Player '%s' has corpse in instance '%s' and can enter.", player->GetName().c_str(), mapName);
}
else
TC_LOG_DEBUG("maps", "Map::CanPlayerEnter - player '%s' is dead but does not have a corpse!", player->GetName().c_str());
}
return TRANSFER_ABORT_NEED_GROUP;
if (entry->Instanceable())
{
//Get instance where player's group is bound & its map
uint32 instanceIdToCheck = sMapMgr->FindInstanceIdForPlayer(mapid, player);
if (Map* boundMap = sMapMgr->FindMap(mapid, instanceIdToCheck))
if (EnterState denyReason = boundMap->CannotEnter(player))
if (TransferAbortParams denyReason = boundMap->CannotEnter(player))
return denyReason;
// players are only allowed to enter 10 instances per hour
if (!entry->GetFlags2().HasFlag(MapFlags2::IgnoreInstanceFarmLimit) && entry->IsDungeon() && !player->CheckInstanceCount(instanceIdToCheck) && !player->isDead())
return CANNOT_ENTER_TOO_MANY_INSTANCES;
return TRANSFER_ABORT_TOO_MANY_INSTANCES;
}
return CAN_ENTER;
return TRANSFER_ABORT_NONE;
}
char const* Map::GetMapName() const
@@ -2807,13 +2783,13 @@ void InstanceMap::InitVisibilityDistance()
/*
Do map specific checks to see if the player can enter
*/
Map::EnterState InstanceMap::CannotEnter(Player* player)
TransferAbortParams InstanceMap::CannotEnter(Player* player)
{
if (player->GetMapRef().getTarget() == this)
{
TC_LOG_ERROR("maps", "InstanceMap::CannotEnter - player %s %s already in map %d, %d, %d!", player->GetName().c_str(), player->GetGUID().ToString().c_str(), GetId(), GetInstanceId(), GetDifficultyID());
ABORT();
return CANNOT_ENTER_ALREADY_IN_MAP;
return TRANSFER_ABORT_ERROR;
}
// allow GM's to enter
@@ -2825,22 +2801,19 @@ Map::EnterState InstanceMap::CannotEnter(Player* player)
if (GetPlayersCountExceptGMs() >= maxPlayers)
{
TC_LOG_WARN("maps", "MAP: Instance '%u' of map '%s' cannot have more than '%u' players. Player '%s' rejected", GetInstanceId(), GetMapName(), maxPlayers, player->GetName().c_str());
return CANNOT_ENTER_MAX_PLAYERS;
return TRANSFER_ABORT_MAX_PLAYERS;
}
// 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())
return CANNOT_ENTER_ZONE_IN_COMBAT;
return TRANSFER_ABORT_ZONE_IN_COMBAT;
if (i_instanceLock)
{
// cannot enter if player is permanent saved to a different instance id
TransferAbortReason lockError = sInstanceLockMgr.CanJoinInstanceLock(player->GetGUID(), { GetEntry(), GetMapDifficulty() }, i_instanceLock);
if (lockError == TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE)
return CANNOT_ENTER_INSTANCE_BIND_MISMATCH;
if (lockError == TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER)
return CANNOT_ENTER_ALREADY_COMPLETED_ENCOUNTER;
if (lockError != TRANSFER_ABORT_NONE)
return lockError;
}
return Map::CannotEnter(player);
@@ -3290,17 +3263,17 @@ void BattlegroundMap::InitVisibilityDistance()
m_VisibilityNotifyPeriod = IsBattleArena() ? World::GetVisibilityNotifyPeriodInArenas() : World::GetVisibilityNotifyPeriodInBG();
}
Map::EnterState BattlegroundMap::CannotEnter(Player* player)
TransferAbortParams BattlegroundMap::CannotEnter(Player* player)
{
if (player->GetMapRef().getTarget() == this)
{
TC_LOG_ERROR("maps", "BGMap::CannotEnter - player %s is already in map!", player->GetGUID().ToString().c_str());
ABORT();
return CANNOT_ENTER_ALREADY_IN_MAP;
return TRANSFER_ABORT_ERROR;
}
if (player->GetBattlegroundId() != GetInstanceId())
return CANNOT_ENTER_INSTANCE_BIND_MISMATCH;
return TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE;
// player number limit is checked in bgmgr, no need to do it here

View File

@@ -41,7 +41,6 @@
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <unordered_set>
@@ -82,6 +81,46 @@ enum class ItemContext : uint8;
namespace Trinity { struct ObjectUpdater; }
namespace VMAP { enum class ModelIgnoreFlags : uint32; }
enum TransferAbortReason : uint32
{
TRANSFER_ABORT_NONE = 0,
TRANSFER_ABORT_ERROR = 1,
TRANSFER_ABORT_MAX_PLAYERS = 2, // Transfer Aborted: instance is full
TRANSFER_ABORT_NOT_FOUND = 3, // Transfer Aborted: instance not found
TRANSFER_ABORT_TOO_MANY_INSTANCES = 4, // You have entered too many instances recently.
TRANSFER_ABORT_ZONE_IN_COMBAT = 6, // Unable to zone in while an encounter is in progress.
TRANSFER_ABORT_INSUF_EXPAN_LVL = 7, // You must have <TBC, WotLK> expansion installed to access this area.
TRANSFER_ABORT_DIFFICULTY = 8, // <Normal, Heroic, Epic> difficulty mode is not available for %s.
TRANSFER_ABORT_UNIQUE_MESSAGE = 9, // Until you've escaped TLK's grasp, you cannot leave this place!
TRANSFER_ABORT_TOO_MANY_REALM_INSTANCES = 10, // Additional instances cannot be launched, please try again later.
TRANSFER_ABORT_NEED_GROUP = 11, // Transfer Aborted: you must be in a raid group to enter this instance
TRANSFER_ABORT_NOT_FOUND_2 = 12, // Transfer Aborted: instance not found
TRANSFER_ABORT_NOT_FOUND_3 = 13, // Transfer Aborted: instance not found
TRANSFER_ABORT_NOT_FOUND_4 = 14, // Transfer Aborted: instance not found
TRANSFER_ABORT_REALM_ONLY = 15, // All players in the party must be from the same realm to enter %s.
TRANSFER_ABORT_MAP_NOT_ALLOWED = 16, // Map cannot be entered at this time.
TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE = 18, // You are already locked to %s
TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER = 19, // You are ineligible to participate in at least one encounter in this instance because you are already locked to an instance in which it has been defeated.
TRANSFER_ABORT_DIFFICULTY_NOT_FOUND = 22, // client writes to console "Unable to resolve requested difficultyID %u to actual difficulty for map %d"
TRANSFER_ABORT_XREALM_ZONE_DOWN = 24, // Transfer Aborted: cross-realm zone is down
TRANSFER_ABORT_SOLO_PLAYER_SWITCH_DIFFICULTY = 26, // This instance is already in progress. You may only switch difficulties from inside the instance.
TRANSFER_ABORT_NOT_CROSS_FACTION_COMPATIBLE = 33, // This instance isn't available for cross-faction groups
};
struct TransferAbortParams
{
TransferAbortParams(TransferAbortReason reason, uint8 arg = 0, int32 mapDifficultyXConditionId = 0)
: Reason(reason), Arg(arg), MapDifficultyXConditionId(mapDifficultyXConditionId)
{
}
TransferAbortReason Reason;
uint8 Arg;
int32 MapDifficultyXConditionId;
operator bool() const { return Reason != TRANSFER_ABORT_NONE; }
};
struct ScriptAction
{
ObjectGuid sourceGUID;
@@ -262,25 +301,8 @@ class TC_GAME_API Map : public GridRefManager<NGridType>
uint32 GetInstanceId() const { return i_InstanceId; }
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_ALREADY_COMPLETED_ENCOUNTER, // Player is locked to encounter that wasn't defeated in the instance yet
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_INSTANCE_SHUTTING_DOWN,
CANNOT_ENTER_UNSPECIFIED_REASON
};
static EnterState PlayerCannotEnter(uint32 mapid, Player* player, bool loginCheck = false);
virtual EnterState CannotEnter(Player* /*player*/) { return CAN_ENTER; }
static TransferAbortParams PlayerCannotEnter(uint32 mapid, Player* player);
virtual TransferAbortParams CannotEnter(Player* /*player*/) { return { TRANSFER_ABORT_NONE }; }
char const* GetMapName() const;
// have meaning only for instanced map (that have set real difficulty)
@@ -823,7 +845,7 @@ class TC_GAME_API InstanceMap : public Map
void UpdateInstanceLock(UpdateBossStateSaveDataEvent const& updateSaveDataEvent);
void UpdateInstanceLock(UpdateAdditionalSaveDataEvent const& updateSaveDataEvent);
void CreateInstanceLockForPlayer(Player* player);
EnterState CannotEnter(Player* player) override;
TransferAbortParams CannotEnter(Player* player) override;
uint32 GetMaxPlayers() const;
TeamId GetTeamIdInInstance() const;
@@ -852,7 +874,7 @@ class TC_GAME_API BattlegroundMap : public Map
bool AddPlayerToMap(Player* player, bool initPlayer = true) override;
void RemovePlayerFromMap(Player*, bool) override;
EnterState CannotEnter(Player* player) override;
TransferAbortParams CannotEnter(Player* player) override;
void SetUnload();
//void UnloadAll(bool pForce);
void RemoveAllPlayers() override;