/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see .
 */
#include "Map.h"
#include "BattlefieldMgr.h"
#include "Battleground.h"
#include "CellImpl.h"
#include "CharacterPackets.h"
#include "Containers.h"
#include "Conversation.h"
#include "DatabaseEnv.h"
#include "DB2Stores.h"
#include "DynamicTree.h"
#include "GameObjectModel.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "GridStates.h"
#include "Group.h"
#include "InstanceLockMgr.h"
#include "InstancePackets.h"
#include "InstanceScenario.h"
#include "InstanceScript.h"
#include "Log.h"
#include "MapManager.h"
#include "Metric.h"
#include "MiscPackets.h"
#include "MotionMaster.h"
#include "ObjectAccessor.h"
#include "ObjectGridLoader.h"
#include "ObjectMgr.h"
#include "OutdoorPvPMgr.h"
#include "Pet.h"
#include "PhasingHandler.h"
#include "PoolMgr.h"
#include "ScriptMgr.h"
#include "SpellAuras.h"
#include "TerrainMgr.h"
#include "Transport.h"
#include "Vehicle.h"
#include "VMapFactory.h"
#include "VMapManager2.h"
#include "Vignette.h"
#include "VignettePackets.h"
#include "Weather.h"
#include "WeatherMgr.h"
#include "World.h"
#include "WorldSession.h"
#include "WorldStateMgr.h"
#include "WorldStatePackets.h"
#include 
#include 
#define DEFAULT_GRID_EXPIRY     300
#define MAX_GRID_LOAD_TIME      50
#define MAX_CREATURE_ATTACK_RADIUS  (45.0f * sWorld->getRate(RATE_CREATURE_AGGRO))
GridState* si_GridStates[MAX_GRID_STATE];
ZoneDynamicInfo::ZoneDynamicInfo() : MusicId(0), DefaultWeather(nullptr), WeatherId(WEATHER_STATE_FINE),
    Intensity(0.0f) { }
RespawnInfo::~RespawnInfo() = default;
struct RespawnInfoWithHandle;
struct RespawnListContainer : boost::heap::fibonacci_heap>
{
};
struct RespawnInfoWithHandle : RespawnInfo
{
    explicit RespawnInfoWithHandle(RespawnInfo const& other) : RespawnInfo(other) { }
    RespawnListContainer::handle_type handle;
};
Map::~Map()
{
    // UnloadAll must be called before deleting the map
    sScriptMgr->OnDestroyMap(this);
    // Delete all waiting spawns, else there will be a memory leak
    // This doesn't delete from database.
    UnloadAllRespawnInfos();
    while (!i_worldObjects.empty())
    {
        WorldObject* obj = *i_worldObjects.begin();
        ASSERT(obj->IsWorldObject());
        //ASSERT(obj->GetTypeId() == TYPEID_CORPSE);
        obj->RemoveFromWorld();
        obj->ResetMap();
    }
    if (!m_scriptSchedule.empty())
        sMapMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size());
    sOutdoorPvPMgr->DestroyOutdoorPvPForMap(this);
    sBattlefieldMgr->DestroyBattlefieldsForMap(this);
    m_terrain->UnloadMMapInstance(GetId(), GetInstanceId());
}
void Map::LoadAllCells()
{
    for (uint32 cellX = 0; cellX < TOTAL_NUMBER_OF_CELLS_PER_MAP; cellX++)
        for (uint32 cellY = 0; cellY < TOTAL_NUMBER_OF_CELLS_PER_MAP; cellY++)
            LoadGrid((cellX + 0.5f - CENTER_GRID_CELL_ID) * SIZE_OF_GRID_CELL, (cellY + 0.5f - CENTER_GRID_CELL_ID) * SIZE_OF_GRID_CELL);
}
void Map::InitStateMachine()
{
    si_GridStates[GRID_STATE_INVALID] = new InvalidState();
    si_GridStates[GRID_STATE_ACTIVE] = new ActiveState();
    si_GridStates[GRID_STATE_IDLE] = new IdleState();
    si_GridStates[GRID_STATE_REMOVAL] = new RemovalState();
}
void Map::DeleteStateMachine()
{
    delete si_GridStates[GRID_STATE_INVALID];
    delete si_GridStates[GRID_STATE_ACTIVE];
    delete si_GridStates[GRID_STATE_IDLE];
    delete si_GridStates[GRID_STATE_REMOVAL];
}
Map::Map(uint32 id, time_t expiry, uint32 InstanceId, Difficulty SpawnMode) :
_creatureToMoveLock(false), _gameObjectsToMoveLock(false), _dynamicObjectsToMoveLock(false), _areaTriggersToMoveLock(false),
i_mapEntry(sMapStore.LookupEntry(id)), i_spawnMode(SpawnMode), i_InstanceId(InstanceId),
m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE),
m_VisibilityNotifyPeriod(DEFAULT_VISIBILITY_NOTIFY_PERIOD),
m_activeNonPlayersIter(m_activeNonPlayers.end()), _transportsUpdateIter(_transports.end()),
i_gridExpiry(expiry), m_terrain(sTerrainMgr.LoadTerrain(id)), m_forceEnabledNavMeshFilterFlags(0), m_forceDisabledNavMeshFilterFlags(0),
i_scriptLock(false), _respawnTimes(std::make_unique()), _respawnCheckTimer(0), _vignetteUpdateTimer(5200, 5200)
{
    for (uint32 x = 0; x < MAX_NUMBER_OF_GRIDS; ++x)
    {
        for (uint32 y = 0; y < MAX_NUMBER_OF_GRIDS; ++y)
        {
            //z code
            setNGrid(nullptr, x, y);
        }
    }
    _zonePlayerCountMap.clear();
    //lets initialize visibility distance for map
    Map::InitVisibilityDistance();
    _weatherUpdateTimer.SetInterval(time_t(1 * IN_MILLISECONDS));
    GetGuidSequenceGenerator(HighGuid::Transport).Set(sObjectMgr->GetGenerator().GetNextAfterMaxUsed());
    _poolData = sPoolMgr->InitPoolsForMap(this);
    sTransportMgr->CreateTransportsForMap(this);
    m_terrain->LoadMMapInstance(GetId(), GetInstanceId());
    _worldStateValues = sWorldStateMgr->GetInitialWorldStatesForMap(this);
    sOutdoorPvPMgr->CreateOutdoorPvPForMap(this);
    sBattlefieldMgr->CreateBattlefieldsForMap(this);
    sScriptMgr->OnCreateMap(this);
}
void Map::InitVisibilityDistance()
{
    //init visibility for continents
    m_VisibleDistance = World::GetMaxVisibleDistanceOnContinents();
    m_VisibilityNotifyPeriod = World::GetVisibilityNotifyPeriodOnContinents();
}
// Template specialization of utility methods
template
void Map::AddToGrid(T* obj, Cell const& cell)
{
    NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
    if (obj->IsWorldObject())
        grid->GetGridType(cell.CellX(), cell.CellY()).template AddWorldObject(obj);
    else
        grid->GetGridType(cell.CellX(), cell.CellY()).template AddGridObject(obj);
}
template<>
void Map::AddToGrid(Creature* obj, Cell const& cell)
{
    NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
    if (obj->IsWorldObject())
        grid->GetGridType(cell.CellX(), cell.CellY()).AddWorldObject(obj);
    else
        grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
    obj->SetCurrentCell(cell);
}
template<>
void Map::AddToGrid(GameObject* obj, Cell const& cell)
{
    NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
    grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
    obj->SetCurrentCell(cell);
}
template<>
void Map::AddToGrid(DynamicObject* obj, Cell const& cell)
{
    NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
    if (obj->IsWorldObject())
        grid->GetGridType(cell.CellX(), cell.CellY()).AddWorldObject(obj);
    else
        grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
    obj->SetCurrentCell(cell);
}
template<>
void Map::AddToGrid(AreaTrigger* obj, Cell const& cell)
{
    NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
    grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
    obj->SetCurrentCell(cell);
}
template<>
void Map::AddToGrid(Corpse* obj, Cell const& cell)
{
    NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
    // Corpses are a special object type - they can be added to grid via a call to AddToMap
    // or loaded through ObjectGridLoader.
    // Both corpses loaded from database and these freshly generated by Player::CreateCoprse are added to _corpsesByCell
    // ObjectGridLoader loads all corpses from _corpsesByCell even if they were already added to grid before it was loaded
    // so we need to explicitly check it here (Map::AddToGrid is only called from Player::BuildPlayerRepop, not from ObjectGridLoader)
    // to avoid failing an assertion in GridObject::AddToGrid
    if (grid->isGridObjectDataLoaded())
    {
        if (obj->IsWorldObject())
            grid->GetGridType(cell.CellX(), cell.CellY()).AddWorldObject(obj);
        else
            grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
    }
}
template
void Map::SwitchGridContainers(T* /*obj*/, bool /*on*/) { }
template<>
void Map::SwitchGridContainers(Creature* obj, bool on)
{
    ASSERT(!obj->IsPermanentWorldObject());
    CellCoord p = Trinity::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
    if (!p.IsCoordValid())
    {
        TC_LOG_ERROR("maps", "Map::SwitchGridContainers: Object {} has invalid coordinates X:{} Y:{} grid cell [{}:{}]", obj->GetGUID().ToString(), obj->GetPositionX(), obj->GetPositionY(), p.x_coord, p.y_coord);
        return;
    }
    Cell cell(p);
    if (!IsGridLoaded(GridCoord(cell.data.Part.grid_x, cell.data.Part.grid_y)))
        return;
    if (sLog->ShouldLog("maps", LOG_LEVEL_DEBUG))
    {
        // Extract bitfield values
        uint32 const grid_x = cell.data.Part.grid_x;
        uint32 const grid_y = cell.data.Part.grid_y;
        TC_LOG_DEBUG("maps", "Switch object {} from grid[{}, {}] {}", obj->GetGUID().ToString(), grid_x, grid_y, on);
    }
    NGridType *ngrid = getNGrid(cell.GridX(), cell.GridY());
    ASSERT(ngrid != nullptr);
    GridType &grid = ngrid->GetGridType(cell.CellX(), cell.CellY());
    obj->RemoveFromGrid(); //This step is not really necessary but we want to do ASSERT in remove/add
    if (on)
    {
        grid.AddWorldObject(obj);
        AddWorldObject(obj);
    }
    else
    {
        grid.AddGridObject(obj);
        RemoveWorldObject(obj);
    }
    obj->m_isTempWorldObject = on;
}
template
void Map::DeleteFromWorld(T* obj)
{
    // Note: In case resurrectable corpse and pet its removed from global lists in own destructor
    delete obj;
}
template<>
void Map::DeleteFromWorld(Player* player)
{
    ObjectAccessor::RemoveObject(player);
    RemoveUpdateObject(player); /// @todo I do not know why we need this, it should be removed in ~Object anyway
    delete player;
}
//Create NGrid so the object can be added to it
//But object data is not loaded here
void Map::EnsureGridCreated(GridCoord const& p)
{
    if (!getNGrid(p.x_coord, p.y_coord))
    {
        TC_LOG_DEBUG("maps", "Creating grid[{}, {}] for map {} instance {}", p.x_coord, p.y_coord, GetId(), i_InstanceId);
        NGridType* ngrid = new NGridType(p.x_coord * MAX_NUMBER_OF_GRIDS + p.y_coord, p.x_coord, p.y_coord, i_gridExpiry, sWorld->getBoolConfig(CONFIG_GRID_UNLOAD));
        setNGrid(ngrid, p.x_coord, p.y_coord);
        // build a linkage between this map and NGridType
        buildNGridLinkage(ngrid);
        ngrid->SetGridState(GRID_STATE_IDLE);
        //z coord
        int gx = (MAX_NUMBER_OF_GRIDS - 1) - p.x_coord;
        int gy = (MAX_NUMBER_OF_GRIDS - 1) - p.y_coord;
        m_terrain->LoadMapAndVMap(gx, gy);
    }
}
//Load NGrid and make it active
void Map::EnsureGridLoadedForActiveObject(Cell const& cell, WorldObject const* object)
{
    EnsureGridLoaded(cell);
    NGridType *grid = getNGrid(cell.GridX(), cell.GridY());
    ASSERT(grid != nullptr);
    if (object->IsPlayer())
        GetMultiPersonalPhaseTracker().LoadGrid(object->GetPhaseShift(), *grid, this, cell);
    // refresh grid state & timer
    if (grid->GetGridState() != GRID_STATE_ACTIVE)
    {
        TC_LOG_DEBUG("maps", "Active object {} triggers loading of grid [{}, {}] on map {}", object->GetGUID().ToString(), cell.GridX(), cell.GridY(), GetId());
        ResetGridExpiry(*grid, 0.1f);
        grid->SetGridState(GRID_STATE_ACTIVE);
    }
}
//Create NGrid and load the object data in it
bool Map::EnsureGridLoaded(Cell const& cell)
{
    EnsureGridCreated(GridCoord(cell.GridX(), cell.GridY()));
    NGridType *grid = getNGrid(cell.GridX(), cell.GridY());
    ASSERT(grid != nullptr);
    if (!isGridObjectDataLoaded(cell.GridX(), cell.GridY()))
    {
        TC_LOG_DEBUG("maps", "Loading grid[{}, {}] for map {} instance {}", cell.GridX(), cell.GridY(), GetId(), i_InstanceId);
        setGridObjectDataLoaded(true, cell.GridX(), cell.GridY());
        LoadGridObjects(grid, cell);
        Balance();
        return true;
    }
    return false;
}
void Map::LoadGridObjects(NGridType* grid, Cell const& cell)
{
    ObjectGridLoader loader(*grid, this, cell);
    loader.LoadN();
}
void Map::GridMarkNoUnload(uint32 x, uint32 y)
{
    // First make sure this grid is loaded
    float gX = ((float(x) - 0.5f - CENTER_GRID_ID) * SIZE_OF_GRIDS) + (CENTER_GRID_OFFSET * 2);
    float gY = ((float(y) - 0.5f - CENTER_GRID_ID) * SIZE_OF_GRIDS) + (CENTER_GRID_OFFSET * 2);
    Cell cell = Cell(gX, gY);
    EnsureGridLoaded(cell);
    // Mark as don't unload
    NGridType* grid = getNGrid(x, y);
    grid->setUnloadExplicitLock(true);
}
void Map::GridUnmarkNoUnload(uint32 x, uint32 y)
{
    // If grid is loaded, clear unload lock
    if (IsGridLoaded(GridCoord(x, y)))
    {
        NGridType* grid = getNGrid(x, y);
        grid->setUnloadExplicitLock(false);
    }
}
void Map::LoadGrid(float x, float y)
{
    EnsureGridLoaded(Cell(x, y));
}
void Map::LoadGridForActiveObject(float x, float y, WorldObject const* object)
{
    EnsureGridLoadedForActiveObject(Cell(x, y), object);
}
bool Map::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
{
    CellCoord cellCoord = Trinity::ComputeCellCoord(player->GetPositionX(), player->GetPositionY());
    if (!cellCoord.IsCoordValid())
    {
        TC_LOG_ERROR("maps", "Map::Add: Player {} has invalid coordinates X:{} Y:{} grid cell [{}:{}]", player->GetGUID().ToString(), player->GetPositionX(), player->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
        return false;
    }
    Cell cell(cellCoord);
    EnsureGridLoadedForActiveObject(cell, player);
    AddToGrid(player, cell);
    // Check if we are adding to correct map
    ASSERT (player->GetMap() == this);
    player->SetMap(this);
    player->AddToWorld();
    if (initPlayer)
        SendInitSelf(player);
    SendInitTransports(player);
    if (initPlayer)
        player->m_clientGUIDs.clear();
    player->UpdateObjectVisibility(false);
    PhasingHandler::SendToPlayer(player);
    if (Instanceable())
        player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::EnteringInstance);
    if (player->IsAlive())
        ConvertCorpseToBones(player->GetGUID());
    sScriptMgr->OnPlayerEnterMap(this, player);
    return true;
}
void Map::UpdatePersonalPhasesForPlayer(Player const* player)
{
    Cell cell(player->GetPositionX(), player->GetPositionY());
    GetMultiPersonalPhaseTracker().OnOwnerPhaseChanged(player, getNGrid(cell.GridX(), cell.GridY()), this, cell);
}
int32 Map::GetWorldStateValue(int32 worldStateId) const
{
    if (int32 const* value = Trinity::Containers::MapGetValuePtr(_worldStateValues, worldStateId))
        return *value;
    return 0;
}
void Map::SetWorldStateValue(int32 worldStateId, int32 value, bool hidden)
{
    auto [itr, inserted] = _worldStateValues.try_emplace(worldStateId, 0);
    int32 oldValue = itr->second;
    if (oldValue == value && !inserted)
        return;
    itr->second = value;
    WorldStateTemplate const* worldStateTemplate = sWorldStateMgr->GetWorldStateTemplate(worldStateId);
    if (worldStateTemplate)
        sScriptMgr->OnWorldStateValueChange(worldStateTemplate, oldValue, value, this);
    // Broadcast update to all players on the map
    WorldPackets::WorldState::UpdateWorldState updateWorldState;
    updateWorldState.VariableID = worldStateId;
    updateWorldState.Value = value;
    updateWorldState.Hidden = hidden;
    updateWorldState.Write();
    for (MapReference const& mapReference : m_mapRefManager)
    {
        if (worldStateTemplate && !worldStateTemplate->AreaIds.empty())
        {
            bool isInAllowedArea = std::any_of(worldStateTemplate->AreaIds.begin(), worldStateTemplate->AreaIds.end(),
                [playerAreaId = mapReference.GetSource()->GetAreaId()](uint32 requiredAreaId) { return DB2Manager::IsInArea(playerAreaId, requiredAreaId); });
            if (!isInAllowedArea)
                continue;
        }
        mapReference.GetSource()->SendDirectMessage(updateWorldState.GetRawPacket());
    }
}
void Map::AddInfiniteAOIVignette(Vignettes::VignetteData* vignette)
{
    _infiniteAOIVignettes.push_back(vignette);
    WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
    vignette->FillPacket(vignetteUpdate.Added);
    vignetteUpdate.Write();
    for (MapReference const& ref : m_mapRefManager)
        if (Vignettes::CanSee(ref.GetSource(), *vignette))
            ref.GetSource()->SendDirectMessage(vignetteUpdate.GetRawPacket());
}
void Map::RemoveInfiniteAOIVignette(Vignettes::VignetteData* vignette)
{
    if (!std::erase(_infiniteAOIVignettes, vignette))
        return;
    WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
    vignetteUpdate.Removed.push_back(vignette->Guid);
    vignetteUpdate.Write();
    if (vignette->Data->GetFlags().HasFlag(VignetteFlags::ZoneInfiniteAOI))
    {
        for (MapReference const& ref : m_mapRefManager)
            if (ref.GetSource()->GetZoneId() == vignette->ZoneID)
                ref.GetSource()->SendDirectMessage(vignetteUpdate.GetRawPacket());
    }
    else
        SendToPlayers(vignetteUpdate.GetRawPacket());
}
template
void Map::InitializeObject(T* /*obj*/) { }
template<>
void Map::InitializeObject(Creature* obj)
{
    obj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
}
template<>
void Map::InitializeObject(GameObject* obj)
{
    obj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
}
template
bool Map::AddToMap(T* obj)
{
    /// @todo Needs clean up. An object should not be added to map twice.
    if (obj->IsInWorld())
    {
        ASSERT(obj->IsInGrid());
        obj->UpdateObjectVisibility(true);
        return true;
    }
    CellCoord cellCoord = Trinity::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
    //It will create many problems (including crashes) if an object is not added to grid after creation
    //The correct way to fix it is to make AddToMap return false and delete the object if it is not added to grid
    //But now AddToMap is used in too many places, I will just see how many ASSERT failures it will cause
    ASSERT(cellCoord.IsCoordValid());
    if (!cellCoord.IsCoordValid())
    {
        TC_LOG_ERROR("maps", "Map::Add: Object {} has invalid coordinates X:{} Y:{} grid cell [{}:{}]", obj->GetGUID().ToString(), obj->GetPositionX(), obj->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
        return false; //Should delete object
    }
    if (IsAlwaysActive())
        obj->setActive(true);
    Cell cell(cellCoord);
    if (obj->isActiveObject())
        EnsureGridLoadedForActiveObject(cell, obj);
    else
        EnsureGridCreated(GridCoord(cell.GridX(), cell.GridY()));
    AddToGrid(obj, cell);
    TC_LOG_DEBUG("maps", "Object {} enters grid[{}, {}]", obj->GetGUID().ToString(), cell.GridX(), cell.GridY());
    //Must already be set before AddToMap. Usually during obj->Create.
    //obj->SetMap(this);
    obj->AddToWorld();
    InitializeObject(obj);
    if (obj->isActiveObject())
        AddToActive(obj);
    //something, such as vehicle, needs to be update immediately
    //also, trigger needs to cast spell, if not update, cannot see visual
    obj->SetIsNewObject(true);
    obj->UpdateObjectVisibilityOnCreate();
    obj->SetIsNewObject(false);
    return true;
}
template<>
bool Map::AddToMap(Transport* obj)
{
    //TODO: Needs clean up. An object should not be added to map twice.
    if (obj->IsInWorld())
        return true;
    CellCoord cellCoord = Trinity::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
    if (!cellCoord.IsCoordValid())
    {
        TC_LOG_ERROR("maps", "Map::Add: Object {} has invalid coordinates X:{} Y:{} grid cell [{}:{}]", obj->GetGUID().ToString(), obj->GetPositionX(), obj->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
        return false; //Should delete object
    }
    _transports.insert(obj);
    if (obj->GetExpectedMapId() == GetId())
    {
        obj->AddToWorld();
        // Broadcast creation to players
        for (Map::PlayerList::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
        {
            if (itr->GetSource()->GetTransport() != obj && itr->GetSource()->InSamePhase(obj))
            {
                UpdateData data(GetId());
                obj->BuildCreateUpdateBlockForPlayer(&data, itr->GetSource());
                itr->GetSource()->m_visibleTransports.insert(obj->GetGUID());
                WorldPacket packet;
                data.BuildPacket(&packet);
                itr->GetSource()->SendDirectMessage(&packet);
            }
        }
    }
    return true;
}
bool Map::IsGridLoaded(GridCoord const& p) const
{
    return (getNGrid(p.x_coord, p.y_coord) && isGridObjectDataLoaded(p.x_coord, p.y_coord));
}
void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor &gridVisitor, TypeContainerVisitor &worldVisitor)
{
    // Check for valid position
    if (!obj->IsPositionValid())
        return;
    // Update mobs/objects in ALL visible cells around object!
    CellArea area = Cell::CalculateCellArea(obj->GetPositionX(), obj->GetPositionY(), obj->GetGridActivationRange());
    for (uint32 x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x)
    {
        for (uint32 y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y)
        {
            // marked cells are those that have been visited
            // don't visit the same cell twice
            uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
            if (isCellMarked(cell_id))
                continue;
            markCell(cell_id);
            CellCoord pair(x, y);
            Cell cell(pair);
            cell.SetNoCreate();
            Visit(cell, gridVisitor);
            Visit(cell, worldVisitor);
        }
    }
}
void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone)
{
    // Nothing to do if no change
    if (oldZone == newZone)
        return;
    if (oldZone != MAP_INVALID_ZONE)
    {
        uint32& oldZoneCount = _zonePlayerCountMap[oldZone];
        ASSERT(oldZoneCount, "A player left zone %u (went to %u) - but there were no players in the zone!", oldZone, newZone);
        --oldZoneCount;
    }
    ++_zonePlayerCountMap[newZone];
}
void Map::Update(uint32 t_diff)
{
    _dynamicTree.update(t_diff);
    /// update worldsessions for existing players
    for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter)
    {
        Player* player = m_mapRefIter->GetSource();
        if (player && player->IsInWorld())
        {
            //player->Update(t_diff);
            WorldSession* session = player->GetSession();
            MapSessionFilter updater(session);
            session->Update(t_diff, updater);
        }
    }
    /// process any due respawns
    if (_respawnCheckTimer <= t_diff)
    {
        ProcessRespawns();
        UpdateSpawnGroupConditions();
        _respawnCheckTimer = sWorld->getIntConfig(CONFIG_RESPAWN_MINCHECKINTERVALMS);
    }
    else
        _respawnCheckTimer -= t_diff;
    /// update active cells around players and active objects
    resetMarkedCells();
    Trinity::ObjectUpdater updater(t_diff);
    // for creature
    TypeContainerVisitor grid_object_update(updater);
    // for pets
    TypeContainerVisitor world_object_update(updater);
    // the player iterator is stored in the map object
    // to make sure calls to Map::Remove don't invalidate it
    for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter)
    {
        Player* player = m_mapRefIter->GetSource();
        if (!player || !player->IsInWorld())
            continue;
        // update players at tick
        player->Update(t_diff);
        VisitNearbyCellsOf(player, grid_object_update, world_object_update);
        // If player is using far sight or mind vision, visit that object too
        if (WorldObject* viewPoint = player->GetViewpoint())
            VisitNearbyCellsOf(viewPoint, grid_object_update, world_object_update);
        // Handle updates for creatures in combat with player and are more than 60 yards away
        if (player->IsInCombat())
        {
            std::vector toVisit;
            for (auto const& pair : player->GetCombatManager().GetPvECombatRefs())
                if (Creature* unit = pair.second->GetOther(player)->ToCreature())
                    if (unit->GetMapId() == player->GetMapId() && !unit->IsWithinDistInMap(player, GetVisibilityRange(), false))
                        toVisit.push_back(unit);
            for (Unit* unit : toVisit)
                VisitNearbyCellsOf(unit, grid_object_update, world_object_update);
        }
        { // Update any creatures that own auras the player has applications of
            std::unordered_set toVisit;
            for (std::pair pair : player->GetAppliedAuras())
            {
                if (Unit* caster = pair.second->GetBase()->GetCaster())
                    if (caster->GetTypeId() != TYPEID_PLAYER && !caster->IsWithinDistInMap(player, GetVisibilityRange(), false))
                        toVisit.insert(caster);
            }
            for (Unit* unit : toVisit)
                VisitNearbyCellsOf(unit, grid_object_update, world_object_update);
        }
        { // Update player's summons
            std::vector toVisit;
            // Totems
            for (ObjectGuid const& summonGuid : player->m_SummonSlot)
                if (!summonGuid.IsEmpty())
                    if (Creature* unit = GetCreature(summonGuid))
                        if (unit->GetMapId() == player->GetMapId() && !unit->IsWithinDistInMap(player, GetVisibilityRange(), false))
                            toVisit.push_back(unit);
            for (Unit* unit : toVisit)
                VisitNearbyCellsOf(unit, grid_object_update, world_object_update);
        }
    }
    // non-player active objects, increasing iterator in the loop in case of object removal
    for (m_activeNonPlayersIter = m_activeNonPlayers.begin(); m_activeNonPlayersIter != m_activeNonPlayers.end();)
    {
        WorldObject* obj = *m_activeNonPlayersIter;
        ++m_activeNonPlayersIter;
        if (!obj || !obj->IsInWorld())
            continue;
        VisitNearbyCellsOf(obj, grid_object_update, world_object_update);
    }
    for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)
    {
        WorldObject* obj = *_transportsUpdateIter;
        ++_transportsUpdateIter;
        obj->Update(t_diff);
    }
    if (_vignetteUpdateTimer.Update(t_diff))
    {
        for (Vignettes::VignetteData* vignette : _infiniteAOIVignettes)
        {
            if (vignette->NeedUpdate)
            {
                WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
                vignette->FillPacket(vignetteUpdate.Updated);
                vignetteUpdate.Write();
                for (MapReference const& ref : m_mapRefManager)
                    if (Vignettes::CanSee(ref.GetSource(), *vignette))
                        ref.GetSource()->SendDirectMessage(vignetteUpdate.GetRawPacket());
                vignette->NeedUpdate = false;
            }
        }
    }
    SendObjectUpdates();
    ///- Process necessary scripts
    if (!m_scriptSchedule.empty())
    {
        i_scriptLock = true;
        ScriptsProcess();
        i_scriptLock = false;
    }
    _weatherUpdateTimer.Update(t_diff);
    if (_weatherUpdateTimer.Passed())
    {
        for (auto&& zoneInfo : _zoneDynamicInfo)
            if (zoneInfo.second.DefaultWeather && !zoneInfo.second.DefaultWeather->Update(_weatherUpdateTimer.GetInterval()))
                zoneInfo.second.DefaultWeather.reset();
        _weatherUpdateTimer.Reset();
    }
    // update phase shift objects
    GetMultiPersonalPhaseTracker().Update(this, t_diff);
    MoveAllCreaturesInMoveList();
    MoveAllGameObjectsInMoveList();
    MoveAllAreaTriggersInMoveList();
    if (!m_mapRefManager.isEmpty() || !m_activeNonPlayers.empty())
        ProcessRelocationNotifies(t_diff);
    sScriptMgr->OnMapUpdate(this, t_diff);
    TC_METRIC_VALUE("map_creatures", uint64(GetObjectsStore().Size()),
        TC_METRIC_TAG("map_id", std::to_string(GetId())),
        TC_METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
    TC_METRIC_VALUE("map_gameobjects", uint64(GetObjectsStore().Size()),
        TC_METRIC_TAG("map_id", std::to_string(GetId())),
        TC_METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
}
struct ResetNotifier
{
    templateinline void resetNotify(GridRefManager &m)
    {
        for (typename GridRefManager::iterator iter=m.begin(); iter != m.end(); ++iter)
            iter->GetSource()->ResetAllNotifies();
    }
    template void Visit(GridRefManager &) { }
    void Visit(CreatureMapType &m) { resetNotify(m);}
    void Visit(PlayerMapType &m) { resetNotify(m);}
};
void Map::ProcessRelocationNotifies(const uint32 diff)
{
    for (GridRefManager::iterator i = GridRefManager::begin(); i != GridRefManager::end(); ++i)
    {
        NGridType *grid = i->GetSource();
        if (grid->GetGridState() != GRID_STATE_ACTIVE)
            continue;
        grid->getGridInfoRef()->getRelocationTimer().TUpdate(diff);
        if (!grid->getGridInfoRef()->getRelocationTimer().TPassed())
            continue;
        uint32 gx = grid->getX(), gy = grid->getY();
        CellCoord cell_min(gx*MAX_NUMBER_OF_CELLS, gy*MAX_NUMBER_OF_CELLS);
        CellCoord cell_max(cell_min.x_coord + MAX_NUMBER_OF_CELLS, cell_min.y_coord+MAX_NUMBER_OF_CELLS);
        for (uint32 x = cell_min.x_coord; x < cell_max.x_coord; ++x)
        {
            for (uint32 y = cell_min.y_coord; y < cell_max.y_coord; ++y)
            {
                uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
                if (!isCellMarked(cell_id))
                    continue;
                CellCoord pair(x, y);
                Cell cell(pair);
                cell.SetNoCreate();
                Trinity::DelayedUnitRelocation cell_relocation(cell, pair, *this, MAX_VISIBILITY_DISTANCE);
                TypeContainerVisitor grid_object_relocation(cell_relocation);
                TypeContainerVisitor world_object_relocation(cell_relocation);
                Visit(cell, grid_object_relocation);
                Visit(cell, world_object_relocation);
            }
        }
    }
    ResetNotifier reset;
    TypeContainerVisitor  grid_notifier(reset);
    TypeContainerVisitor world_notifier(reset);
    for (GridRefManager::iterator i = GridRefManager::begin(); i != GridRefManager::end(); ++i)
    {
        NGridType *grid = i->GetSource();
        if (grid->GetGridState() != GRID_STATE_ACTIVE)
            continue;
        if (!grid->getGridInfoRef()->getRelocationTimer().TPassed())
            continue;
        grid->getGridInfoRef()->getRelocationTimer().TReset(diff, m_VisibilityNotifyPeriod);
        uint32 gx = grid->getX(), gy = grid->getY();
        CellCoord cell_min(gx*MAX_NUMBER_OF_CELLS, gy*MAX_NUMBER_OF_CELLS);
        CellCoord cell_max(cell_min.x_coord + MAX_NUMBER_OF_CELLS, cell_min.y_coord+MAX_NUMBER_OF_CELLS);
        for (uint32 x = cell_min.x_coord; x < cell_max.x_coord; ++x)
        {
            for (uint32 y = cell_min.y_coord; y < cell_max.y_coord; ++y)
            {
                uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
                if (!isCellMarked(cell_id))
                    continue;
                CellCoord pair(x, y);
                Cell cell(pair);
                cell.SetNoCreate();
                Visit(cell, grid_notifier);
                Visit(cell, world_notifier);
            }
        }
    }
}
void Map::RemovePlayerFromMap(Player* player, bool remove)
{
    // Before leaving map, update zone/area for stats
    player->UpdateZone(MAP_INVALID_ZONE, 0);
    sScriptMgr->OnPlayerLeaveMap(this, player);
    GetMultiPersonalPhaseTracker().MarkAllPhasesForDeletion(player->GetGUID());
    player->CombatStop();
    bool const inWorld = player->IsInWorld();
    player->RemoveFromWorld();
    SendRemoveTransports(player);
    if (!inWorld) // if was in world, RemoveFromWorld() called DestroyForNearbyPlayers()
        player->UpdateObjectVisibilityOnDestroy();
    if (player->IsInGrid())
        player->RemoveFromGrid();
    else
        ASSERT(remove); //maybe deleted in logoutplayer when player is not in a map
    if (remove)
        DeleteFromWorld(player);
}
template
void Map::RemoveFromMap(T *obj, bool remove)
{
    bool const inWorld = obj->IsInWorld() && obj->GetTypeId() >= TYPEID_UNIT && obj->GetTypeId() <= TYPEID_GAMEOBJECT;
    obj->RemoveFromWorld();
    if (obj->isActiveObject())
        RemoveFromActive(obj);
    GetMultiPersonalPhaseTracker().UnregisterTrackedObject(obj);
    if (!inWorld) // if was in world, RemoveFromWorld() called DestroyForNearbyPlayers()
        obj->UpdateObjectVisibilityOnDestroy();
    obj->RemoveFromGrid();
    obj->ResetMap();
    if (remove)
        DeleteFromWorld(obj);
}
template<>
void Map::RemoveFromMap(Transport* obj, bool remove)
{
    if (obj->IsInWorld())
    {
        obj->RemoveFromWorld();
        UpdateData data(GetId());
        if (obj->IsDestroyedObject())
            obj->BuildDestroyUpdateBlock(&data);
        else
            obj->BuildOutOfRangeUpdateBlock(&data);
        WorldPacket packet;
        data.BuildPacket(&packet);
        for (Map::PlayerList::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
        {
            if (itr->GetSource()->GetTransport() != obj && itr->GetSource()->m_visibleTransports.count(obj->GetGUID()))
            {
                itr->GetSource()->SendDirectMessage(&packet);
                itr->GetSource()->m_visibleTransports.erase(obj->GetGUID());
            }
        }
    }
    if (_transportsUpdateIter != _transports.end())
    {
        TransportsContainer::iterator itr = _transports.find(obj);
        if (itr == _transports.end())
            return;
        if (itr == _transportsUpdateIter)
            ++_transportsUpdateIter;
        _transports.erase(itr);
    }
    else
        _transports.erase(obj);
    obj->ResetMap();
    if (remove)
        DeleteFromWorld(obj);
}
template 
/*static*/ bool Map::CheckGridIntegrity(T* object, bool moved, char const* objType)
{
    Cell const& cur_cell = object->GetCurrentCell();
    Cell xy_cell(object->GetPositionX(), object->GetPositionY());
    if (xy_cell != cur_cell)
    {
        TC_LOG_DEBUG("maps", "{} {} X: {} Y: {} ({}) is in grid[{}, {}]cell[{}, {}] instead of grid[{}, {}]cell[{}, {}]",
            objType, object->GetGUID().ToString(),
            object->GetPositionX(), object->GetPositionY(), (moved ? "final" : "original"),
            cur_cell.GridX(), cur_cell.GridY(), cur_cell.CellX(), cur_cell.CellY(),
            xy_cell.GridX(), xy_cell.GridY(), xy_cell.CellX(), xy_cell.CellY());
        return true;                                        // not crash at error, just output error in debug mode
    }
    return true;
}
void Map::PlayerRelocation(Player* player, float x, float y, float z, float orientation)
{
    ASSERT(player);
    Cell old_cell(player->GetPositionX(), player->GetPositionY());
    Cell new_cell(x, y);
    player->Relocate(x, y, z, orientation);
    if (player->IsVehicle())
        player->GetVehicleKit()->RelocatePassengers();
    if (old_cell.DiffGrid(new_cell) || old_cell.DiffCell(new_cell))
    {
        TC_LOG_DEBUG("maps", "Player {} relocation grid[{}, {}]cell[{}, {}]->grid[{}, {}]cell[{}, {}]", player->GetName(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
        player->RemoveFromGrid();
        if (old_cell.DiffGrid(new_cell))
            EnsureGridLoadedForActiveObject(new_cell, player);
        AddToGrid(player, new_cell);
    }
    player->UpdatePositionData();
    player->UpdateObjectVisibility(false);
}
void Map::CreatureRelocation(Creature* creature, float x, float y, float z, float ang, bool respawnRelocationOnFail)
{
    ASSERT(CheckGridIntegrity(creature, false, "Creature"));
    Cell new_cell(x, y);
    if (!respawnRelocationOnFail && !getNGrid(new_cell.GridX(), new_cell.GridY()))
        return;
    Cell old_cell = creature->GetCurrentCell();
    // delay creature move for grid/cell to grid/cell moves
    if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
    {
#ifdef TRINITY_DEBUG
        TC_LOG_DEBUG("maps", "Creature {} added to moving list from grid[{}, {}]cell[{}, {}] to grid[{}, {}]cell[{}, {}].", creature->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
#endif
        AddCreatureToMoveList(creature, x, y, z, ang);
        // in diffcell/diffgrid case notifiers called at finishing move creature in Map::MoveAllCreaturesInMoveList
    }
    else
    {
        creature->Relocate(x, y, z, ang);
        if (creature->IsVehicle())
            creature->GetVehicleKit()->RelocatePassengers();
        creature->UpdateObjectVisibility(false);
        creature->UpdatePositionData();
        RemoveCreatureFromMoveList(creature);
    }
    ASSERT(CheckGridIntegrity(creature, true, "Creature"));
}
void Map::GameObjectRelocation(GameObject* go, float x, float y, float z, float orientation, bool respawnRelocationOnFail)
{
    ASSERT(CheckGridIntegrity(go, false, "GameObject"));
    Cell new_cell(x, y);
    if (!respawnRelocationOnFail && !getNGrid(new_cell.GridX(), new_cell.GridY()))
        return;
    Cell old_cell = go->GetCurrentCell();
    // delay creature move for grid/cell to grid/cell moves
    if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
    {
#ifdef TRINITY_DEBUG
        TC_LOG_DEBUG("maps", "GameObject {} added to moving list from grid[{}, {}]cell[{}, {}] to grid[{}, {}]cell[{}, {}].", go->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
#endif
        AddGameObjectToMoveList(go, x, y, z, orientation);
        // in diffcell/diffgrid case notifiers called at finishing move go in Map::MoveAllGameObjectsInMoveList
    }
    else
    {
        go->Relocate(x, y, z, orientation);
        go->AfterRelocation();
        RemoveGameObjectFromMoveList(go);
    }
    ASSERT(CheckGridIntegrity(go, true, "GameObject"));
}
void Map::DynamicObjectRelocation(DynamicObject* dynObj, float x, float y, float z, float orientation)
{
    ASSERT(CheckGridIntegrity(dynObj, false, "DynamicObject"));
    Cell new_cell(x, y);
    if (!getNGrid(new_cell.GridX(), new_cell.GridY()))
        return;
    Cell old_cell = dynObj->GetCurrentCell();
    // delay creature move for grid/cell to grid/cell moves
    if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
    {
#ifdef TRINITY_DEBUG
        TC_LOG_DEBUG("maps", "GameObject {} added to moving list from grid[{}, {}]cell[{}, {}] to grid[{}, {}]cell[{}, {}].", dynObj->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
#endif
        AddDynamicObjectToMoveList(dynObj, x, y, z, orientation);
        // in diffcell/diffgrid case notifiers called at finishing move dynObj in Map::MoveAllGameObjectsInMoveList
    }
    else
    {
        dynObj->Relocate(x, y, z, orientation);
        dynObj->UpdatePositionData();
        dynObj->UpdateObjectVisibility(false);
        RemoveDynamicObjectFromMoveList(dynObj);
    }
    ASSERT(CheckGridIntegrity(dynObj, true, "DynamicObject"));
}
void Map::AreaTriggerRelocation(AreaTrigger* at, float x, float y, float z, float orientation)
{
    ASSERT(CheckGridIntegrity(at, false, "AreaTrigger"));
    Cell new_cell(x, y);
    if (!getNGrid(new_cell.GridX(), new_cell.GridY()))
        return;
    Cell old_cell = at->GetCurrentCell();
    // delay areatrigger move for grid/cell to grid/cell moves
    if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
    {
#ifdef TRINITY_DEBUG
        TC_LOG_DEBUG("maps", "AreaTrigger ({}) added to moving list from grid[{}, {}]cell[{}, {}] to grid[{}, {}]cell[{}, {}].", at->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
#endif
        AddAreaTriggerToMoveList(at, x, y, z, orientation);
        // in diffcell/diffgrid case notifiers called at finishing move at in Map::MoveAllAreaTriggersInMoveList
    }
    else
    {
        at->Relocate(x, y, z, orientation);
        at->UpdateShape();
        at->UpdateObjectVisibility(false);
        RemoveAreaTriggerFromMoveList(at);
    }
    ASSERT(CheckGridIntegrity(at, true, "AreaTrigger"));
}
void Map::AddCreatureToMoveList(Creature* c, float x, float y, float z, float ang)
{
    if (_creatureToMoveLock) //can this happen?
        return;
    if (c->_moveState == MAP_OBJECT_CELL_MOVE_NONE)
        _creaturesToMove.push_back(c);
    c->SetNewCellPosition(x, y, z, ang);
}
void Map::RemoveCreatureFromMoveList(Creature* c)
{
    if (_creatureToMoveLock) //can this happen?
        return;
    if (c->_moveState == MAP_OBJECT_CELL_MOVE_ACTIVE)
        c->_moveState = MAP_OBJECT_CELL_MOVE_INACTIVE;
}
void Map::AddGameObjectToMoveList(GameObject* go, float x, float y, float z, float ang)
{
    if (_gameObjectsToMoveLock) //can this happen?
        return;
    if (go->_moveState == MAP_OBJECT_CELL_MOVE_NONE)
        _gameObjectsToMove.push_back(go);
    go->SetNewCellPosition(x, y, z, ang);
}
void Map::RemoveGameObjectFromMoveList(GameObject* go)
{
    if (_gameObjectsToMoveLock) //can this happen?
        return;
    if (go->_moveState == MAP_OBJECT_CELL_MOVE_ACTIVE)
        go->_moveState = MAP_OBJECT_CELL_MOVE_INACTIVE;
}
void Map::AddDynamicObjectToMoveList(DynamicObject* dynObj, float x, float y, float z, float ang)
{
    if (_dynamicObjectsToMoveLock) //can this happen?
        return;
    if (dynObj->_moveState == MAP_OBJECT_CELL_MOVE_NONE)
        _dynamicObjectsToMove.push_back(dynObj);
    dynObj->SetNewCellPosition(x, y, z, ang);
}
void Map::RemoveDynamicObjectFromMoveList(DynamicObject* dynObj)
{
    if (_dynamicObjectsToMoveLock) //can this happen?
        return;
    if (dynObj->_moveState == MAP_OBJECT_CELL_MOVE_ACTIVE)
        dynObj->_moveState = MAP_OBJECT_CELL_MOVE_INACTIVE;
}
void Map::AddAreaTriggerToMoveList(AreaTrigger* at, float x, float y, float z, float ang)
{
    if (_areaTriggersToMoveLock) //can this happen?
        return;
    if (at->_moveState == MAP_OBJECT_CELL_MOVE_NONE)
        _areaTriggersToMove.push_back(at);
    at->SetNewCellPosition(x, y, z, ang);
}
void Map::RemoveAreaTriggerFromMoveList(AreaTrigger* at)
{
    if (_areaTriggersToMoveLock) //can this happen?
        return;
    if (at->_moveState == MAP_OBJECT_CELL_MOVE_ACTIVE)
        at->_moveState = MAP_OBJECT_CELL_MOVE_INACTIVE;
}
void Map::MoveAllCreaturesInMoveList()
{
    _creatureToMoveLock = true;
    for (std::vector::iterator itr = _creaturesToMove.begin(); itr != _creaturesToMove.end(); ++itr)
    {
        Creature* c = *itr;
        if (c->FindMap() != this) //pet is teleported to another map
            continue;
        if (c->_moveState != MAP_OBJECT_CELL_MOVE_ACTIVE)
        {
            c->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
            continue;
        }
        c->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
        if (!c->IsInWorld())
            continue;
        // do move or do move to respawn or remove creature if previous all fail
        if (CreatureCellRelocation(c, Cell(c->_newPosition.m_positionX, c->_newPosition.m_positionY)))
        {
            // update pos
            c->Relocate(c->_newPosition);
            if (c->IsVehicle())
                c->GetVehicleKit()->RelocatePassengers();
            //CreatureRelocationNotify(c, new_cell, new_cell.cellCoord());
            c->UpdatePositionData();
            c->UpdateObjectVisibility(false);
        }
        else
        {
            // if creature can't be move in new cell/grid (not loaded) move it to repawn cell/grid
            // creature coordinates will be updated and notifiers send
            if (!CreatureRespawnRelocation(c, false))
            {
                // ... or unload (if respawn grid also not loaded)
#ifdef TRINITY_DEBUG
                TC_LOG_DEBUG("maps", "Creature {} cannot be move to unloaded respawn grid.", c->GetGUID().ToString());
#endif
                //AddObjectToRemoveList(Pet*) should only be called in Pet::Remove
                //This may happen when a player just logs in and a pet moves to a nearby unloaded cell
                //To avoid this, we can load nearby cells when player log in
                //But this check is always needed to ensure safety
                /// @todo pets will disappear if this is outside CreatureRespawnRelocation
                //need to check why pet is frequently relocated to an unloaded cell
                if (c->IsPet())
                    ((Pet*)c)->Remove(PET_SAVE_NOT_IN_SLOT, true);
                else
                    AddObjectToRemoveList(c);
            }
        }
    }
    _creaturesToMove.clear();
    _creatureToMoveLock = false;
}
void Map::MoveAllGameObjectsInMoveList()
{
    _gameObjectsToMoveLock = true;
    for (std::vector::iterator itr = _gameObjectsToMove.begin(); itr != _gameObjectsToMove.end(); ++itr)
    {
        GameObject* go = *itr;
        if (go->FindMap() != this) //transport is teleported to another map
            continue;
        if (go->_moveState != MAP_OBJECT_CELL_MOVE_ACTIVE)
        {
            go->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
            continue;
        }
        go->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
        if (!go->IsInWorld())
            continue;
        // do move or do move to respawn or remove creature if previous all fail
        if (GameObjectCellRelocation(go, Cell(go->_newPosition.m_positionX, go->_newPosition.m_positionY)))
        {
            // update pos
            go->Relocate(go->_newPosition);
            go->AfterRelocation();
        }
        else
        {
            // if GameObject can't be move in new cell/grid (not loaded) move it to repawn cell/grid
            // GameObject coordinates will be updated and notifiers send
            if (!GameObjectRespawnRelocation(go, false))
            {
                // ... or unload (if respawn grid also not loaded)
#ifdef TRINITY_DEBUG
                TC_LOG_DEBUG("maps", "GameObject {} cannot be move to unloaded respawn grid.", go->GetGUID().ToString());
#endif
                AddObjectToRemoveList(go);
            }
        }
    }
    _gameObjectsToMove.clear();
    _gameObjectsToMoveLock = false;
}
void Map::MoveAllDynamicObjectsInMoveList()
{
    _dynamicObjectsToMoveLock = true;
    for (std::vector::iterator itr = _dynamicObjectsToMove.begin(); itr != _dynamicObjectsToMove.end(); ++itr)
    {
        DynamicObject* dynObj = *itr;
        if (dynObj->FindMap() != this) //transport is teleported to another map
            continue;
        if (dynObj->_moveState != MAP_OBJECT_CELL_MOVE_ACTIVE)
        {
            dynObj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
            continue;
        }
        dynObj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
        if (!dynObj->IsInWorld())
            continue;
        // do move or do move to respawn or remove creature if previous all fail
        if (DynamicObjectCellRelocation(dynObj, Cell(dynObj->_newPosition.m_positionX, dynObj->_newPosition.m_positionY)))
        {
            // update pos
            dynObj->Relocate(dynObj->_newPosition);
            dynObj->UpdatePositionData();
            dynObj->UpdateObjectVisibility(false);
        }
        else
        {
#ifdef TRINITY_DEBUG
            TC_LOG_DEBUG("maps", "DynamicObject {} cannot be moved to unloaded grid.", dynObj->GetGUID().ToString());
#endif
        }
    }
    _dynamicObjectsToMove.clear();
    _dynamicObjectsToMoveLock = false;
}
void Map::MoveAllAreaTriggersInMoveList()
{
    _areaTriggersToMoveLock = true;
    for (std::vector::iterator itr = _areaTriggersToMove.begin(); itr != _areaTriggersToMove.end(); ++itr)
    {
        AreaTrigger* at = *itr;
        if (at->FindMap() != this) //transport is teleported to another map
            continue;
        if (at->_moveState != MAP_OBJECT_CELL_MOVE_ACTIVE)
        {
            at->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
            continue;
        }
        at->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
        if (!at->IsInWorld())
            continue;
        // do move or do move to respawn or remove creature if previous all fail
        if (AreaTriggerCellRelocation(at, Cell(at->_newPosition.m_positionX, at->_newPosition.m_positionY)))
        {
            // update pos
            at->Relocate(at->_newPosition);
            at->UpdateShape();
            at->UpdateObjectVisibility(false);
        }
        else
        {
#ifdef TRINITY_DEBUG
            TC_LOG_DEBUG("maps", "AreaTrigger {} cannot be moved to unloaded grid.", at->GetGUID().ToString());
#endif
        }
    }
    _areaTriggersToMove.clear();
    _areaTriggersToMoveLock = false;
}
template 
bool Map::MapObjectCellRelocation(T* object, Cell new_cell, [[maybe_unused]] char const* objType)
{
    Cell const& old_cell = object->GetCurrentCell();
    if (!old_cell.DiffGrid(new_cell))                       // in same grid
    {
        // if in same cell then none do
        if (old_cell.DiffCell(new_cell))
        {
#ifdef TRINITY_DEBUG
            TC_LOG_DEBUG("maps", "{} {} moved in grid[{}, {}] from cell[{}, {}] to cell[{}, {}].", objType, object->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.CellX(), new_cell.CellY());
#endif
            object->RemoveFromGrid();
            AddToGrid(object, new_cell);
        }
        else
        {
#ifdef TRINITY_DEBUG
            TC_LOG_DEBUG("maps", "{} {} moved in same grid[{}, {}]cell[{}, {}].", objType, object->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY());
#endif
        }
        return true;
    }
    // in diff. grids but active creature
    if (object->isActiveObject())
    {
        EnsureGridLoadedForActiveObject(new_cell, object);
#ifdef TRINITY_DEBUG
        TC_LOG_DEBUG("maps", "Active {} {} moved from grid[{}, {}]cell[{}, {}] to grid[{}, {}]cell[{}, {}].", objType, object->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
#endif
        object->RemoveFromGrid();
        AddToGrid(object, new_cell);
        return true;
    }
    if (Creature* c = object->ToCreature())
        if (c->GetCharmerOrOwnerGUID().IsPlayer())
            EnsureGridLoaded(new_cell);
    // in diff. loaded grid normal object
    if (IsGridLoaded(GridCoord(new_cell.GridX(), new_cell.GridY())))
    {
#ifdef TRINITY_DEBUG
        TC_LOG_DEBUG("maps", "{} {} moved from grid[{}, {}]cell[{}, {}] to grid[{}, {}]cell[{}, {}].", objType, object->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
#endif
        object->RemoveFromGrid();
        EnsureGridCreated(GridCoord(new_cell.GridX(), new_cell.GridY()));
        AddToGrid(object, new_cell);
        return true;
    }
    // fail to move: normal object attempt move to unloaded grid
#ifdef TRINITY_DEBUG
    TC_LOG_DEBUG("maps", "{} {} attempted to move from grid[{}, {}]cell[{}, {}] to unloaded grid[{}, {}]cell[{}, {}].", objType, object->GetGUID().ToString(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY());
#endif
    return false;
}
bool Map::CreatureCellRelocation(Creature* c, Cell new_cell)
{
    return MapObjectCellRelocation(c, new_cell, "Creature");
}
bool Map::GameObjectCellRelocation(GameObject* go, Cell new_cell)
{
    return MapObjectCellRelocation(go, new_cell, "GameObject");
}
bool Map::DynamicObjectCellRelocation(DynamicObject* go, Cell new_cell)
{
    return MapObjectCellRelocation(go, new_cell, "DynamicObject");
}
bool Map::AreaTriggerCellRelocation(AreaTrigger* at, Cell new_cell)
{
    return MapObjectCellRelocation(at, new_cell, "AreaTrigger");
}
bool Map::CreatureRespawnRelocation(Creature* c, bool diffGridOnly)
{
    float resp_x, resp_y, resp_z, resp_o;
    c->GetRespawnPosition(resp_x, resp_y, resp_z, &resp_o);
    Cell resp_cell(resp_x, resp_y);
    //creature will be unloaded with grid
    if (diffGridOnly && !c->GetCurrentCell().DiffGrid(resp_cell))
        return true;
    c->CombatStop();
    c->GetMotionMaster()->Clear();
#ifdef TRINITY_DEBUG
    TC_LOG_DEBUG("maps", "Creature {} moved from grid[{}, {}]cell[{}, {}] to respawn grid[{}, {}]cell[{}, {}].", c->GetGUID().ToString(), c->GetCurrentCell().GridX(), c->GetCurrentCell().GridY(), c->GetCurrentCell().CellX(), c->GetCurrentCell().CellY(), resp_cell.GridX(), resp_cell.GridY(), resp_cell.CellX(), resp_cell.CellY());
#endif
    // teleport it to respawn point (like normal respawn if player see)
    if (CreatureCellRelocation(c, resp_cell))
    {
        c->Relocate(resp_x, resp_y, resp_z, resp_o);
        c->GetMotionMaster()->Initialize(); // prevent possible problems with default move generators
        //CreatureRelocationNotify(c, resp_cell, resp_cell.GetCellCoord());
        c->UpdatePositionData();
        c->UpdateObjectVisibility(false);
        return true;
    }
    return false;
}
bool Map::GameObjectRespawnRelocation(GameObject* go, bool diffGridOnly)
{
    float resp_x, resp_y, resp_z, resp_o;
    go->GetRespawnPosition(resp_x, resp_y, resp_z, &resp_o);
    Cell resp_cell(resp_x, resp_y);
    //GameObject will be unloaded with grid
    if (diffGridOnly && !go->GetCurrentCell().DiffGrid(resp_cell))
        return true;
#ifdef TRINITY_DEBUG
    TC_LOG_DEBUG("maps", "GameObject {} moved from grid[{}, {}]cell[{}, {}] to respawn grid[{}, {}]cell[{}, {}].", go->GetGUID().ToString(), go->GetCurrentCell().GridX(), go->GetCurrentCell().GridY(), go->GetCurrentCell().CellX(), go->GetCurrentCell().CellY(), resp_cell.GridX(), resp_cell.GridY(), resp_cell.CellX(), resp_cell.CellY());
#endif
    // teleport it to respawn point (like normal respawn if player see)
    if (GameObjectCellRelocation(go, resp_cell))
    {
        go->Relocate(resp_x, resp_y, resp_z, resp_o);
        go->UpdatePositionData();
        go->UpdateObjectVisibility(false);
        return true;
    }
    return false;
}
bool Map::UnloadGrid(NGridType& ngrid, bool unloadAll)
{
    const uint32 x = ngrid.getX();
    const uint32 y = ngrid.getY();
    {
        if (!unloadAll)
        {
            //pets, possessed creatures (must be active), transport passengers
            if (ngrid.GetWorldObjectCountInNGrid())
                return false;
            if (ActiveObjectsNearGrid(ngrid))
                return false;
        }
        TC_LOG_DEBUG("maps", "Unloading grid[{}, {}] for map {}", x, y, GetId());
        if (!unloadAll)
        {
            // Finish creature moves, remove and delete all creatures with delayed remove before moving to respawn grids
            // Must know real mob position before move
            MoveAllCreaturesInMoveList();
            MoveAllGameObjectsInMoveList();
            MoveAllAreaTriggersInMoveList();
            // move creatures to respawn grids if this is diff.grid or to remove list
            ObjectGridEvacuator worker;
            TypeContainerVisitor visitor(worker);
            ngrid.VisitAllGrids(visitor);
            // Finish creature moves, remove and delete all creatures with delayed remove before unload
            MoveAllCreaturesInMoveList();
            MoveAllGameObjectsInMoveList();
            MoveAllAreaTriggersInMoveList();
        }
        {
            ObjectGridCleaner worker;
            TypeContainerVisitor visitor(worker);
            ngrid.VisitAllGrids(visitor);
        }
        RemoveAllObjectsInRemoveList();
        // After removing all objects from the map, purge empty tracked phases
        GetMultiPersonalPhaseTracker().UnloadGrid(ngrid);
        {
            ObjectGridUnloader worker;
            TypeContainerVisitor visitor(worker);
            ngrid.VisitAllGrids(visitor);
        }
        ASSERT(i_objectsToRemove.empty());
        delete &ngrid;
        setNGrid(nullptr, x, y);
    }
    int gx = (MAX_NUMBER_OF_GRIDS - 1) - x;
    int gy = (MAX_NUMBER_OF_GRIDS - 1) - y;
    m_terrain->UnloadMap(gx, gy);
    TC_LOG_DEBUG("maps", "Unloading grid[{}, {}] for map {} finished", x, y, GetId());
    return true;
}
void Map::RemoveAllPlayers()
{
    if (HavePlayers())
    {
        for (MapRefManager::iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr)
        {
            Player* player = itr->GetSource();
            if (!player->IsBeingTeleportedFar())
            {
                // this is happening for bg
                TC_LOG_ERROR("maps", "Map::UnloadAll: player {} is still in map {} during unload, this should not happen!", player->GetName(), GetId());
                player->TeleportTo(player->m_homebind);
            }
        }
    }
}
void Map::UnloadAll()
{
    // clear all delayed moves, useless anyway do this moves before map unload.
    _creaturesToMove.clear();
    _gameObjectsToMove.clear();
    for (GridRefManager::iterator i = GridRefManager::begin(); i != GridRefManager::end();)
    {
        NGridType &grid(*i->GetSource());
        ++i;
        UnloadGrid(grid, true);       // deletes the grid and removes it from the GridRefManager
    }
    for (TransportsContainer::iterator itr = _transports.begin(); itr != _transports.end();)
    {
        Transport* transport = *itr;
        ++itr;
        RemoveFromMap(transport, true);
    }
    for (auto& cellCorpsePair : _corpsesByCell)
    {
        for (Corpse* corpse : cellCorpsePair.second)
        {
            corpse->RemoveFromWorld();
            corpse->ResetMap();
            delete corpse;
        }
    }
    _corpsesByCell.clear();
    _corpsesByPlayer.clear();
    _corpseBones.clear();
}
void Map::GetFullTerrainStatusForPosition(PhaseShift const& phaseShift, float x, float y, float z, PositionFullTerrainStatus& data,
    Optional reqLiquidType, float collisionHeight)
{
    m_terrain->GetFullTerrainStatusForPosition(phaseShift, GetId(), x, y, z, data, reqLiquidType, collisionHeight, &_dynamicTree);
}
ZLiquidStatus Map::GetLiquidStatus(PhaseShift const& phaseShift, float x, float y, float z, Optional ReqLiquidType, LiquidData* data,
    float collisionHeight)
{
    return m_terrain->GetLiquidStatus(phaseShift, GetId(), x, y, z, ReqLiquidType, data, collisionHeight);
}
uint32 Map::GetAreaId(PhaseShift const& phaseShift, float x, float y, float z)
{
    return m_terrain->GetAreaId(phaseShift, GetId(), x, y, z, &_dynamicTree);
}
uint32 Map::GetZoneId(PhaseShift const& phaseShift, float x, float y, float z)
{
    return m_terrain->GetZoneId(phaseShift, GetId(), x, y, z, &_dynamicTree);
}
void Map::GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, float x, float y, float z)
{
    return m_terrain->GetZoneAndAreaId(phaseShift, GetId(), zoneid, areaid, x, y, z, &_dynamicTree);
}
float Map::GetMinHeight(PhaseShift const& phaseShift, float x, float y)
{
    return m_terrain->GetMinHeight(phaseShift, GetId(), x, y);
}
float Map::GetGridHeight(PhaseShift const& phaseShift, float x, float y)
{
    return m_terrain->GetGridHeight(phaseShift, GetId(), x, y);
}
float Map::GetStaticHeight(PhaseShift const& phaseShift, float x, float y, float z, bool checkVMap, float maxSearchDist)
{
    return m_terrain->GetStaticHeight(phaseShift, GetId(), x, y, z, checkVMap, maxSearchDist);
}
float Map::GetWaterLevel(PhaseShift const& phaseShift, float x, float y)
{
    return m_terrain->GetWaterLevel(phaseShift, GetId(), x, y);
}
bool Map::IsInWater(PhaseShift const& phaseShift, float x, float y, float z, LiquidData* data)
{
    return m_terrain->IsInWater(phaseShift, GetId(), x, y, z, data);
}
bool Map::IsUnderWater(PhaseShift const& phaseShift, float x, float y, float z)
{
    return m_terrain->IsUnderWater(phaseShift, GetId(), x, y, z);
}
float Map::GetWaterOrGroundLevel(PhaseShift const& phaseShift, float x, float y, float z, float* ground, bool swim, float collisionHeight)
{
    return m_terrain->GetWaterOrGroundLevel(phaseShift, GetId(), x, y, z, ground, swim, collisionHeight, &_dynamicTree);
}
bool Map::isInLineOfSight(PhaseShift const& phaseShift, float x1, float y1, float z1, float x2, float y2, float z2, LineOfSightChecks checks, VMAP::ModelIgnoreFlags ignoreFlags) const
{
    if ((checks & LINEOFSIGHT_CHECK_VMAP)
      && !VMAP::VMapFactory::createOrGetVMapManager()->isInLineOfSight(PhasingHandler::GetTerrainMapId(phaseShift, GetId(), m_terrain.get(), x1, y1), x1, y1, z1, x2, y2, z2, ignoreFlags))
        return false;
    if (sWorld->getBoolConfig(CONFIG_CHECK_GOBJECT_LOS) && (checks & LINEOFSIGHT_CHECK_GOBJECT)
      && !_dynamicTree.isInLineOfSight({ x1, y1, z1 }, { x2, y2, z2 }, phaseShift))
        return false;
    return true;
}
bool Map::getObjectHitPos(PhaseShift const& phaseShift, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist)
{
    G3D::Vector3 startPos(x1, y1, z1);
    G3D::Vector3 dstPos(x2, y2, z2);
    G3D::Vector3 resultPos;
    bool result = _dynamicTree.getObjectHitPos(startPos, dstPos, resultPos, modifyDist, phaseShift);
    rx = resultPos.x;
    ry = resultPos.y;
    rz = resultPos.z;
    return result;
}
TransferAbortParams Map::PlayerCannotEnter(uint32 mapid, Player* player)
{
    MapEntry const* entry = sMapStore.LookupEntry(mapid);
    if (!entry)
        return TRANSFER_ABORT_MAP_NOT_ALLOWED;
    if (!entry->IsDungeon())
        return TRANSFER_ABORT_NONE;
    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 TRANSFER_ABORT_DIFFICULTY;
    //Bypass checks for GMs
    if (player->IsGameMaster())
        return TRANSFER_ABORT_NONE;
    //Other requirements
    {
        TransferAbortParams params(TRANSFER_ABORT_NONE);
        if (!player->Satisfy(sObjectMgr->GetAccessRequirement(mapid, targetDifficulty), mapid, ¶ms, 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 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 (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 TRANSFER_ABORT_TOO_MANY_INSTANCES;
    }
    return TRANSFER_ABORT_NONE;
}
char const* Map::GetMapName() const
{
    return i_mapEntry->MapName[sWorld->GetDefaultDbcLocale()];
}
void Map::SendInitSelf(Player* player)
{
    TC_LOG_DEBUG("maps", "Creating player data for himself {}", player->GetGUID().ToString());
    UpdateData data(player->GetMapId());
    // attach to player data current transport data
    if (Transport* transport = dynamic_cast(player->GetTransport()))
    {
        transport->BuildCreateUpdateBlockForPlayer(&data, player);
        player->m_visibleTransports.insert(transport->GetGUID());
    }
    // build data for self presence in world at own client (one time for map)
    player->BuildCreateUpdateBlockForPlayer(&data, player);
    // build other passengers at transport also (they always visible and marked as visible and will not send at visibility update at add to map
    if (Transport* transport = dynamic_cast(player->GetTransport()))
        for (WorldObject* passenger : transport->GetPassengers())
            if (player != passenger && player->HaveAtClient(passenger))
                passenger->BuildCreateUpdateBlockForPlayer(&data, player);
    WorldPacket packet;
    data.BuildPacket(&packet);
    player->SendDirectMessage(&packet);
}
void Map::SendInitTransports(Player* player)
{
    // Hack to send out transports
    UpdateData transData(GetId());
    for (Transport* transport : _transports)
    {
        if (transport->IsInWorld() && transport != player->GetTransport() && player->InSamePhase(transport))
        {
            transport->BuildCreateUpdateBlockForPlayer(&transData, player);
            player->m_visibleTransports.insert(transport->GetGUID());
        }
    }
    WorldPacket packet;
    transData.BuildPacket(&packet);
    player->SendDirectMessage(&packet);
}
void Map::SendRemoveTransports(Player* player)
{
    // Hack to send out transports
    UpdateData transData(player->GetMapId());
    for (Transport* transport : _transports)
    {
        if (player->m_visibleTransports.count(transport->GetGUID()) && transport != player->GetTransport())
        {
            transport->BuildOutOfRangeUpdateBlock(&transData);
            player->m_visibleTransports.erase(transport->GetGUID());
        }
    }
    WorldPacket packet;
    transData.BuildPacket(&packet);
    player->SendDirectMessage(&packet);
}
void Map::SendUpdateTransportVisibility(Player* player)
{
    // Hack to send out transports
    UpdateData transData(player->GetMapId());
    for (Transport* transport : _transports)
    {
        if (!transport->IsInWorld())
            continue;
        auto transportItr = player->m_visibleTransports.find(transport->GetGUID());
        if (player->InSamePhase(transport))
        {
            if (transportItr == player->m_visibleTransports.end())
            {
                transport->BuildCreateUpdateBlockForPlayer(&transData, player);
                player->m_visibleTransports.insert(transport->GetGUID());
            }
        }
        else if (transportItr != player->m_visibleTransports.end())
        {
            transport->BuildOutOfRangeUpdateBlock(&transData);
            player->m_visibleTransports.erase(transportItr);
        }
    }
    WorldPacket packet;
    transData.BuildPacket(&packet);
    player->GetSession()->SendPacket(&packet);
}
inline void Map::setNGrid(NGridType *grid, uint32 x, uint32 y)
{
    if (x >= MAX_NUMBER_OF_GRIDS || y >= MAX_NUMBER_OF_GRIDS)
    {
        TC_LOG_ERROR("maps", "map::setNGrid() Invalid grid coordinates found: {}, {}!", x, y);
        ABORT();
    }
    i_grids[x][y] = grid;
}
void Map::SendObjectUpdates()
{
    UpdateDataMapType update_players;
    while (!_updateObjects.empty())
    {
        Object* obj = *_updateObjects.begin();
        ASSERT(obj->IsInWorld());
        _updateObjects.erase(_updateObjects.begin());
        obj->BuildUpdate(update_players);
    }
    WorldPacket packet;                                     // here we allocate a std::vector with a size of 0x10000
    for (UpdateDataMapType::iterator iter = update_players.begin(); iter != update_players.end(); ++iter)
    {
        iter->second.BuildPacket(&packet);
        iter->first->SendDirectMessage(&packet);
        packet.clear();                                     // clean the string
    }
}
// CheckRespawn MUST do one of the following:
//  -) return true
//  -) set info->respawnTime to zero, which indicates the respawn time should be deleted (and will never be processed again without outside intervention)
//  -) set info->respawnTime to a new respawn time, which must be strictly GREATER than the current time (GameTime::GetGameTime())
bool Map::CheckRespawn(RespawnInfo* info)
{
    SpawnData const* data = sObjectMgr->GetSpawnData(info->type, info->spawnId);
    ASSERT(data, "Invalid respawn info with type %u, spawnID " UI64FMTD " in respawn queue.", info->type, info->spawnId);
    // First, check if this creature's spawn group is inactive
    if (!IsSpawnGroupActive(data->spawnGroupData->groupId))
    {
        info->respawnTime = 0;
        return false;
    }
    // Next, check if there's already an instance of this object that would block the respawn
    bool alreadyExists = false;
    switch (info->type)
    {
        case SPAWN_TYPE_CREATURE:
        {
            // escort check for creatures only (if the world config boolean is set)
            bool const isEscort = (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && data->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC);
            auto range = _creatureBySpawnIdStore.equal_range(info->spawnId);
            for (auto it = range.first; it != range.second; ++it)
            {
                Creature* creature = it->second;
                if (!creature->IsAlive())
                    continue;
                // escort NPCs are allowed to respawn as long as all other instances are already escorting
                if (isEscort && creature->IsEscorted())
                    continue;
                alreadyExists = true;
                break;
            }
            break;
        }
        case SPAWN_TYPE_GAMEOBJECT:
            // gameobject check is simpler - they cannot be dead or escorting
            if (_gameobjectBySpawnIdStore.find(info->spawnId) != _gameobjectBySpawnIdStore.end())
                alreadyExists = true;
            break;
        default:
            ABORT_MSG("Invalid spawn type %u with spawnId " UI64FMTD " on map %u", uint32(info->type), info->spawnId, GetId());
            return true;
    }
    if (alreadyExists)
    {
        info->respawnTime = 0;
        return false;
    }
    // next, check linked respawn time
    ObjectGuid thisGUID = info->type == SPAWN_TYPE_GAMEOBJECT
        ? ObjectGuid::Create(GetId(), info->entry, info->spawnId)
        : ObjectGuid::Create(GetId(), info->entry, info->spawnId);
    if (time_t linkedTime = GetLinkedRespawnTime(thisGUID))
    {
        time_t now = GameTime::GetGameTime();
        time_t respawnTime;
        if (linkedTime == std::numeric_limits::max())
            respawnTime = linkedTime;
        else if (sObjectMgr->GetLinkedRespawnGuid(thisGUID) == thisGUID) // never respawn, save "something" in DB
            respawnTime = now + WEEK;
        else // set us to check again shortly after linked unit
            respawnTime = std::max(now, linkedTime) + urand(5, 15);
        info->respawnTime = respawnTime;
        return false;
    }
    // everything ok, let's spawn
    return true;
}
void Map::Respawn(RespawnInfo* info, CharacterDatabaseTransaction dbTrans)
{
    if (info->respawnTime <= GameTime::GetGameTime())
        return;
    info->respawnTime = GameTime::GetGameTime();
    _respawnTimes->increase(static_cast(info)->handle);
    SaveRespawnInfoDB(*info, dbTrans);
}
size_t Map::DespawnAll(SpawnObjectType type, ObjectGuid::LowType spawnId)
{
    std::vector toUnload;
    switch (type)
    {
        case SPAWN_TYPE_CREATURE:
            for (auto const& pair : Trinity::Containers::MapEqualRange(GetCreatureBySpawnIdStore(), spawnId))
                toUnload.push_back(pair.second);
            break;
        case SPAWN_TYPE_GAMEOBJECT:
            for (auto const& pair : Trinity::Containers::MapEqualRange(GetGameObjectBySpawnIdStore(), spawnId))
                toUnload.push_back(pair.second);
            break;
        default:
            break;
    }
    for (WorldObject* o : toUnload)
        AddObjectToRemoveList(o);
    return toUnload.size();
}
bool Map::AddRespawnInfo(RespawnInfo const& info)
{
    if (!info.spawnId)
    {
        TC_LOG_ERROR("maps", "Attempt to insert respawn info for zero spawn id (type {})", uint32(info.type));
        return false;
    }
    RespawnInfoMap* bySpawnIdMap = GetRespawnMapForType(info.type);
    if (!bySpawnIdMap)
        return false;
    // check if we already have the maximum possible number of respawns scheduled
    if (SpawnData::TypeHasData(info.type))
    {
        auto it = bySpawnIdMap->find(info.spawnId);
        if (it != bySpawnIdMap->end()) // spawnid already has a respawn scheduled
        {
            RespawnInfo* const existing = it->second;
            if (info.respawnTime <= existing->respawnTime) // delete existing in this case
                DeleteRespawnInfo(existing);
            else
                return false;
        }
        ASSERT(bySpawnIdMap->find(info.spawnId) == bySpawnIdMap->end(), "Insertion of respawn info with id (%u," UI64FMTD ") into spawn id map failed - state desync.", uint32(info.type), info.spawnId);
    }
    else
        ABORT_MSG("Invalid respawn info for spawn id (%u," UI64FMTD ") being inserted", uint32(info.type), info.spawnId);
    RespawnInfoWithHandle* ri = new RespawnInfoWithHandle(info);
    ri->handle = _respawnTimes->push(ri);
    bySpawnIdMap->emplace(ri->spawnId, ri);
    return true;
}
static void PushRespawnInfoFrom(std::vector& data, RespawnInfoMap const& map)
{
    data.reserve(data.size() + map.size());
    for (auto const& pair : map)
        data.push_back(pair.second);
}
void Map::GetRespawnInfo(std::vector& respawnData, SpawnObjectTypeMask types) const
{
    if (types & SPAWN_TYPEMASK_CREATURE)
        PushRespawnInfoFrom(respawnData, _creatureRespawnTimesBySpawnId);
    if (types & SPAWN_TYPEMASK_GAMEOBJECT)
        PushRespawnInfoFrom(respawnData, _gameObjectRespawnTimesBySpawnId);
}
RespawnInfo* Map::GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const
{
    RespawnInfoMap const* map = GetRespawnMapForType(type);
    if (!map)
        return nullptr;
    auto it = map->find(spawnId);
    if (it == map->end())
        return nullptr;
    return it->second;
}
void Map::UnloadAllRespawnInfos() // delete everything from memory
{
    for (RespawnInfo* info : *_respawnTimes)
        delete info;
    _respawnTimes->clear();
    _creatureRespawnTimesBySpawnId.clear();
    _gameObjectRespawnTimesBySpawnId.clear();
}
void Map::DeleteRespawnInfo(RespawnInfo* info, CharacterDatabaseTransaction dbTrans)
{
    // Delete from all relevant containers to ensure consistency
    ASSERT(info);
    // spawnid store
    auto spawnMap = GetRespawnMapForType(info->type);
    if (!spawnMap)
        return;
    auto range = spawnMap->equal_range(info->spawnId);
    auto it = std::find_if(range.first, range.second, [info](RespawnInfoMap::value_type const& pair) { return (pair.second == info); });
    ASSERT(it != range.second, "Respawn stores inconsistent for map %u, spawnid " UI64FMTD " (type %u)", GetId(), info->spawnId, uint32(info->type));
    spawnMap->erase(it);
    // respawn heap
    _respawnTimes->erase(static_cast(info)->handle);
    // database
    DeleteRespawnInfoFromDB(info->type, info->spawnId, dbTrans);
    // then cleanup the object
    delete info;
}
void Map::DeleteRespawnInfoFromDB(SpawnObjectType type, ObjectGuid::LowType spawnId, CharacterDatabaseTransaction dbTrans)
{
    if (Instanceable())
        return;
    CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESPAWN);
    stmt->setUInt16(0, type);
    stmt->setUInt64(1, spawnId);
    stmt->setUInt16(2, GetId());
    stmt->setUInt32(3, GetInstanceId());
    CharacterDatabase.ExecuteOrAppend(dbTrans, stmt);
}
void Map::DoRespawn(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 gridId)
{
    if (!IsGridLoaded(gridId)) // if grid isn't loaded, this will be processed in grid load handler
        return;
    switch (type)
    {
        case SPAWN_TYPE_CREATURE:
        {
            Creature* obj = new Creature();
            if (!obj->LoadFromDB(spawnId, this, true, true))
                delete obj;
            break;
        }
        case SPAWN_TYPE_GAMEOBJECT:
        {
            GameObject* obj = new GameObject();
            if (!obj->LoadFromDB(spawnId, this, true))
                delete obj;
            break;
        }
        default:
            ABORT_MSG("Invalid spawn type %u (spawnid " UI64FMTD ") on map %u", uint32(type), spawnId, GetId());
    }
}
void Map::ProcessRespawns()
{
    time_t now = GameTime::GetGameTime();
    while (!_respawnTimes->empty())
    {
        RespawnInfoWithHandle* next = _respawnTimes->top();
        if (now < next->respawnTime) // done for this tick
            break;
        if (uint32 poolId = sPoolMgr->IsPartOfAPool(next->type, next->spawnId)) // is this part of a pool?
        { // if yes, respawn will be handled by (external) pooling logic, just delete the respawn time
            // step 1: remove entry from maps to avoid it being reachable by outside logic
            _respawnTimes->pop();
            ASSERT_NOTNULL(GetRespawnMapForType(next->type))->erase(next->spawnId);
            // step 2: tell pooling logic to do its thing
            sPoolMgr->UpdatePool(GetPoolData(), poolId, next->type, next->spawnId);
            // step 3: get rid of the actual entry
            RemoveRespawnTime(next->type, next->spawnId, nullptr, true);
            delete next;
        }
        else if (CheckRespawn(next)) // see if we're allowed to respawn
        { // ok, respawn
            // step 1: remove entry from maps to avoid it being reachable by outside logic
            _respawnTimes->pop();
            ASSERT_NOTNULL(GetRespawnMapForType(next->type))->erase(next->spawnId);
            // step 2: do the respawn, which involves external logic
            DoRespawn(next->type, next->spawnId, next->gridId);
            // step 3: get rid of the actual entry
            RemoveRespawnTime(next->type, next->spawnId, nullptr, true);
            delete next;
        }
        else if (!next->respawnTime)
        { // just remove this respawn entry without rescheduling
            _respawnTimes->pop();
            ASSERT_NOTNULL(GetRespawnMapForType(next->type))->erase(next->spawnId);
            RemoveRespawnTime(next->type, next->spawnId, nullptr, true);
            delete next;
        }
        else
        { // new respawn time, update heap position
            ASSERT(now < next->respawnTime); // infinite loop guard
            _respawnTimes->decrease(next->handle);
            SaveRespawnInfoDB(*next);
        }
    }
}
void Map::ApplyDynamicModeRespawnScaling(WorldObject const* obj, ObjectGuid::LowType spawnId, uint32& respawnDelay, uint32 mode) const
{
    ASSERT(mode == 1);
    ASSERT(obj->GetMap() == this);
    if (IsBattlegroundOrArena())
        return;
    SpawnObjectType type;
    switch (obj->GetTypeId())
    {
        case TYPEID_UNIT:
            type = SPAWN_TYPE_CREATURE;
            break;
        case TYPEID_GAMEOBJECT:
            type = SPAWN_TYPE_GAMEOBJECT;
            break;
        default:
            return;
    }
    SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(type, spawnId);
    if (!data)
        return;
    if (!(data->spawnGroupData->flags & SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE))
        return;
    auto it = _zonePlayerCountMap.find(obj->GetZoneId());
    if (it == _zonePlayerCountMap.end())
        return;
    uint32 const playerCount = it->second;
    if (!playerCount)
        return;
    double const adjustFactor = sWorld->getFloatConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICRATE_CREATURE) / playerCount;
    if (adjustFactor >= 1.0) // nothing to do here
        return;
    uint32 const timeMinimum = sWorld->getIntConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE);
    if (respawnDelay <= timeMinimum)
        return;
    respawnDelay = std::max(ceil(respawnDelay * adjustFactor), timeMinimum);
}
bool Map::ShouldBeSpawnedOnGridLoad(SpawnObjectType type, ObjectGuid::LowType spawnId) const
{
    ASSERT(SpawnData::TypeHasData(type));
    // check if the object is on its respawn timer
    if (GetRespawnTime(type, spawnId))
        return false;
    SpawnMetadata const* spawnData = ASSERT_NOTNULL(sObjectMgr->GetSpawnMetadata(type, spawnId));
    // check if the object is part of a spawn group
    SpawnGroupTemplateData const* spawnGroup = ASSERT_NOTNULL(spawnData->spawnGroupData);
    if (!(spawnGroup->flags & SPAWNGROUP_FLAG_SYSTEM))
        if (!IsSpawnGroupActive(spawnGroup->groupId))
            return false;
    if (spawnData->ToSpawnData()->poolId)
        if (!GetPoolData().IsSpawnedObject(type, spawnId))
            return false;
    return true;
}
SpawnGroupTemplateData const* Map::GetSpawnGroupData(uint32 groupId) const
{
    SpawnGroupTemplateData const* data = sObjectMgr->GetSpawnGroupData(groupId);
    if (data && (data->flags & SPAWNGROUP_FLAG_SYSTEM || data->mapId == GetId()))
        return data;
    return nullptr;
}
bool Map::SpawnGroupSpawn(uint32 groupId, bool ignoreRespawn, bool force, std::vector* spawnedObjects)
{
    SpawnGroupTemplateData const* groupData = GetSpawnGroupData(groupId);
    if (!groupData || groupData->flags & SPAWNGROUP_FLAG_SYSTEM)
    {
        TC_LOG_ERROR("maps", "Tried to spawn non-existing (or system) spawn group {} on map {}. Blocked.", groupId, GetId());
        return false;
    }
    SetSpawnGroupActive(groupId, true); // start processing respawns for the group
    std::vector toSpawn;
    for (auto& pair : sObjectMgr->GetSpawnMetadataForGroup(groupId))
    {
        SpawnMetadata const* data = pair.second;
        ASSERT(groupData->mapId == data->mapId);
        auto respawnMap = GetRespawnMapForType(data->type);
        if (!respawnMap)
            continue;
        if (force || ignoreRespawn)
            RemoveRespawnTime(data->type, data->spawnId);
        uint32 nRespawnTimers = respawnMap->count(data->spawnId);
        if (SpawnData::TypeHasData(data->type))
        {
            // has a respawn timer
            if (nRespawnTimers)
                continue;
            // has a spawn already active
            if (!force)
                if (WorldObject* obj = GetWorldObjectBySpawnId(data->type, data->spawnId))
                    if ((data->type != SPAWN_TYPE_CREATURE) || obj->ToCreature()->IsAlive())
                        continue;
            toSpawn.push_back(ASSERT_NOTNULL(data->ToSpawnData()));
        }
    }
    for (SpawnData const* data : toSpawn)
    {
        // don't spawn if the current map difficulty is not used by the spawn
        if (std::find(data->spawnDifficulties.begin(), data->spawnDifficulties.end(), GetDifficultyID()) == data->spawnDifficulties.end())
            continue;
        // don't spawn if the grid isn't loaded (will be handled in grid loader)
        if (!IsGridLoaded(data->spawnPoint))
            continue;
        // now do the actual (re)spawn
        switch (data->type)
        {
            case SPAWN_TYPE_CREATURE:
            {
                Creature* creature = new Creature();
                if (!creature->LoadFromDB(data->spawnId, this, true, force))
                    delete creature;
                else if (spawnedObjects)
                    spawnedObjects->push_back(creature);
                break;
            }
            case SPAWN_TYPE_GAMEOBJECT:
            {
                GameObject* gameobject = new GameObject();
                if (!gameobject->LoadFromDB(data->spawnId, this, true))
                    delete gameobject;
                else if (spawnedObjects)
                    spawnedObjects->push_back(gameobject);
                break;
            }
            case SPAWN_TYPE_AREATRIGGER:
            {
                AreaTrigger* areaTrigger = new AreaTrigger();
                if (!areaTrigger->LoadFromDB(data->spawnId, this, true, false))
                    delete areaTrigger;
                else if (spawnedObjects)
                    spawnedObjects->push_back(areaTrigger);
                break;
            }
            default:
                ABORT_MSG("Invalid spawn type %u with spawnId " UI64FMTD, uint32(data->type), data->spawnId);
                return false;
        }
    }
    return true;
}
bool Map::SpawnGroupDespawn(uint32 groupId, bool deleteRespawnTimes, size_t* count)
{
    SpawnGroupTemplateData const* groupData = GetSpawnGroupData(groupId);
    if (!groupData || groupData->flags & SPAWNGROUP_FLAG_SYSTEM)
    {
        TC_LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group {} on map {}. Blocked.", groupId, GetId());
        return false;
    }
    for (auto const& pair : sObjectMgr->GetSpawnMetadataForGroup(groupId))
    {
        SpawnMetadata const* data = pair.second;
        ASSERT(groupData->mapId == data->mapId);
        if (deleteRespawnTimes)
            RemoveRespawnTime(data->type, data->spawnId);
        size_t c = DespawnAll(data->type, data->spawnId);
        if (count)
            *count += c;
    }
    SetSpawnGroupActive(groupId, false); // stop processing respawns for the group, too
    return true;
}
void Map::SetSpawnGroupActive(uint32 groupId, bool state)
{
    SpawnGroupTemplateData const* const data = GetSpawnGroupData(groupId);
    if (!data || data->flags & SPAWNGROUP_FLAG_SYSTEM)
    {
        TC_LOG_ERROR("maps", "Tried to set non-existing (or system) spawn group {} to {} on map {}. Blocked.", groupId, state ? "active" : "inactive", GetId());
        return;
    }
    if (state != !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN)) // toggled
        _toggledSpawnGroupIds.insert(groupId);
    else
        _toggledSpawnGroupIds.erase(groupId);
}
bool Map::IsSpawnGroupActive(uint32 groupId) const
{
    SpawnGroupTemplateData const* const data = GetSpawnGroupData(groupId);
    if (!data)
    {
        TC_LOG_ERROR("maps", "Tried to query state of non-existing spawn group {} on map {}.", groupId, GetId());
        return false;
    }
    if (data->flags & SPAWNGROUP_FLAG_SYSTEM)
        return true;
    // either manual spawn group and toggled, or not manual spawn group and not toggled...
    return (_toggledSpawnGroupIds.find(groupId) != _toggledSpawnGroupIds.end()) != !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN);
}
void Map::InitSpawnGroupState()
{
    std::vector const* spawnGroups = sObjectMgr->GetSpawnGroupsForMap(GetId());
    if (!spawnGroups)
        return;
    for (uint32 spawnGroupId : *spawnGroups)
    {
        SpawnGroupTemplateData const* spawnGroupTemplate = ASSERT_NOTNULL(GetSpawnGroupData(spawnGroupId));
        if (spawnGroupTemplate->flags & SPAWNGROUP_FLAG_SYSTEM)
            continue;
        SetSpawnGroupActive(spawnGroupId, sConditionMgr->IsMapMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_SPAWN_GROUP, spawnGroupId, this));
    }
}
void Map::UpdateSpawnGroupConditions()
{
    std::vector const* spawnGroups = sObjectMgr->GetSpawnGroupsForMap(GetId());
    if (!spawnGroups)
        return;
    for (uint32 spawnGroupId : *spawnGroups)
    {
        SpawnGroupTemplateData const* spawnGroupTemplate = ASSERT_NOTNULL(GetSpawnGroupData(spawnGroupId));
        bool isActive = IsSpawnGroupActive(spawnGroupId);
        bool shouldBeActive = sConditionMgr->IsMapMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_SPAWN_GROUP, spawnGroupId, this);
        if (spawnGroupTemplate->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN)
        {
            // Only despawn the group if it isn't meeting conditions
            if (isActive && !shouldBeActive && spawnGroupTemplate->flags & SPAWNGROUP_FLAG_DESPAWN_ON_CONDITION_FAILURE)
                SpawnGroupDespawn(spawnGroupId, true);
            continue;
        }
        if (isActive == shouldBeActive)
            continue;
        if (shouldBeActive)
            SpawnGroupSpawn(spawnGroupId);
        else if (spawnGroupTemplate->flags & SPAWNGROUP_FLAG_DESPAWN_ON_CONDITION_FAILURE)
            SpawnGroupDespawn(spawnGroupId, true);
        else
            SetSpawnGroupInactive(spawnGroupId);
    }
}
ObjectGuidGenerator& Map::GetGuidSequenceGenerator(HighGuid high)
{
    auto itr = _guidGenerators.find(high);
    if (itr == _guidGenerators.end())
        itr = _guidGenerators.insert(std::make_pair(high, std::make_unique(high))).first;
    return *itr->second;
}
void Map::AddFarSpellCallback(FarSpellCallback&& callback)
{
    _farSpellCallbacks.Enqueue(new FarSpellCallback(std::move(callback)));
}
void Map::DelayedUpdate(uint32 t_diff)
{
    {
        FarSpellCallback* callback;
        while (_farSpellCallbacks.Dequeue(callback))
        {
            (*callback)(this);
            delete callback;
        }
    }
    RemoveAllObjectsInRemoveList();
    // Don't unload grids if it's battleground, since we may have manually added GOs, creatures, those doesn't load from DB at grid re-load !
    // This isn't really bother us, since as soon as we have instanced BG-s, the whole map unloads as the BG gets ended
    if (!IsBattlegroundOrArena())
    {
        for (GridRefManager::iterator i = GridRefManager::begin(); i != GridRefManager::end();)
        {
            NGridType *grid = i->GetSource();
            GridInfo* info = i->GetSource()->getGridInfoRef();
            ++i;                                                // The update might delete the map and we need the next map before the iterator gets invalid
            ASSERT(grid->GetGridState() >= 0 && grid->GetGridState() < MAX_GRID_STATE);
            si_GridStates[grid->GetGridState()]->Update(*this, *grid, *info, t_diff);
        }
    }
}
void Map::AddObjectToRemoveList(WorldObject* obj)
{
    ASSERT(obj->GetMapId() == GetId() && obj->GetInstanceId() == GetInstanceId());
    obj->SetDestroyedObject(true);
    obj->CleanupsBeforeDelete(false);                            // remove or simplify at least cross referenced links
    i_objectsToRemove.insert(obj);
}
void Map::AddObjectToSwitchList(WorldObject* obj, bool on)
{
    ASSERT(obj->GetMapId() == GetId() && obj->GetInstanceId() == GetInstanceId());
    // i_objectsToSwitch is iterated only in Map::RemoveAllObjectsInRemoveList() and it uses
    // the contained objects only if GetTypeId() == TYPEID_UNIT , so we can return in all other cases
    if (obj->GetTypeId() != TYPEID_UNIT)
        return;
    std::map::iterator itr = i_objectsToSwitch.find(obj);
    if (itr == i_objectsToSwitch.end())
        i_objectsToSwitch.insert(itr, std::make_pair(obj, on));
    else if (itr->second != on)
        i_objectsToSwitch.erase(itr);
    else
        ABORT();
}
void Map::RemoveAllObjectsInRemoveList()
{
    while (!i_objectsToSwitch.empty())
    {
        std::map::iterator itr = i_objectsToSwitch.begin();
        WorldObject* obj = itr->first;
        bool on = itr->second;
        i_objectsToSwitch.erase(itr);
        if (!obj->IsPermanentWorldObject())
        {
            switch (obj->GetTypeId())
            {
                case TYPEID_UNIT:
                    SwitchGridContainers(obj->ToCreature(), on);
                    break;
                default:
                    break;
            }
        }
    }
    //TC_LOG_DEBUG("maps", "Object remover 1 check.");
    while (!i_objectsToRemove.empty())
    {
        std::set::iterator itr = i_objectsToRemove.begin();
        WorldObject* obj = *itr;
        switch (obj->GetTypeId())
        {
            case TYPEID_CORPSE:
            {
                Corpse* corpse = ObjectAccessor::GetCorpse(*obj, obj->GetGUID());
                if (!corpse)
                    TC_LOG_ERROR("maps", "Tried to delete corpse/bones {} that is not in map.", obj->GetGUID().ToString());
                else
                    RemoveFromMap(corpse, true);
                break;
            }
            case TYPEID_DYNAMICOBJECT:
                RemoveFromMap(obj->ToDynObject(), true);
                break;
            case TYPEID_AREATRIGGER:
                RemoveFromMap((AreaTrigger*)obj, true);
                break;
            case TYPEID_CONVERSATION:
                RemoveFromMap((Conversation*)obj, true);
                break;
            case TYPEID_GAMEOBJECT:
            {
                GameObject* go = obj->ToGameObject();
                if (Transport* transport = go->ToTransport())
                    RemoveFromMap(transport, true);
                else
                    RemoveFromMap(go, true);
                break;
            }
            case TYPEID_UNIT:
                // in case triggered sequence some spell can continue casting after prev CleanupsBeforeDelete call
                // make sure that like sources auras/etc removed before destructor start
                obj->ToCreature()->CleanupsBeforeDelete();
                RemoveFromMap(obj->ToCreature(), true);
                break;
            default:
                TC_LOG_ERROR("maps", "Non-grid object (TypeId: {}) is in grid object remove list, ignored.", obj->GetTypeId());
                break;
        }
        i_objectsToRemove.erase(itr);
    }
    //TC_LOG_DEBUG("maps", "Object remover 2 check.");
}
uint32 Map::GetPlayersCountExceptGMs() const
{
    uint32 count = 0;
    for (MapRefManager::const_iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr)
        if (!itr->GetSource()->IsGameMaster())
            ++count;
    return count;
}
void Map::SendToPlayers(WorldPacket const* data) const
{
    for (MapRefManager::const_iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr)
        itr->GetSource()->SendDirectMessage(data);
}
bool Map::ActiveObjectsNearGrid(NGridType const& ngrid) const
{
    CellCoord cell_min(ngrid.getX() * MAX_NUMBER_OF_CELLS, ngrid.getY() * MAX_NUMBER_OF_CELLS);
    CellCoord cell_max(cell_min.x_coord + MAX_NUMBER_OF_CELLS, cell_min.y_coord+MAX_NUMBER_OF_CELLS);
    //we must find visible range in cells so we unload only non-visible cells...
    float viewDist = GetVisibilityRange();
    int cell_range = (int)ceilf(viewDist / SIZE_OF_GRID_CELL) + 1;
    cell_min.dec_x(cell_range);
    cell_min.dec_y(cell_range);
    cell_max.inc_x(cell_range);
    cell_max.inc_y(cell_range);
    for (MapRefManager::const_iterator iter = m_mapRefManager.begin(); iter != m_mapRefManager.end(); ++iter)
    {
        Player* player = iter->GetSource();
        CellCoord p = Trinity::ComputeCellCoord(player->GetPositionX(), player->GetPositionY());
        if ((cell_min.x_coord <= p.x_coord && p.x_coord <= cell_max.x_coord) &&
            (cell_min.y_coord <= p.y_coord && p.y_coord <= cell_max.y_coord))
            return true;
    }
    for (ActiveNonPlayers::const_iterator iter = m_activeNonPlayers.begin(); iter != m_activeNonPlayers.end(); ++iter)
    {
        WorldObject* obj = *iter;
        CellCoord p = Trinity::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
        if ((cell_min.x_coord <= p.x_coord && p.x_coord <= cell_max.x_coord) &&
            (cell_min.y_coord <= p.y_coord && p.y_coord <= cell_max.y_coord))
            return true;
    }
    return false;
}
void Map::AddToActive(WorldObject* obj)
{
    AddToActiveHelper(obj);
    Optional respawnLocation;
    switch (obj->GetTypeId())
    {
        case TYPEID_UNIT:
            if (Creature* creature = obj->ToCreature(); !creature->IsPet() && creature->GetSpawnId())
            {
                respawnLocation.emplace();
                creature->GetRespawnPosition(respawnLocation->m_positionX, respawnLocation->m_positionY, respawnLocation->m_positionZ);
            }
            break;
        case TYPEID_GAMEOBJECT:
            if (GameObject* gameObject = obj->ToGameObject(); gameObject->GetSpawnId())
            {
                respawnLocation.emplace();
                gameObject->GetRespawnPosition(respawnLocation->m_positionX, respawnLocation->m_positionY, respawnLocation->m_positionZ);
            }
            break;
        default:
            break;
    }
    if (respawnLocation)
    {
        GridCoord p = Trinity::ComputeGridCoord(respawnLocation->GetPositionX(), respawnLocation->GetPositionY());
        if (getNGrid(p.x_coord, p.y_coord))
            getNGrid(p.x_coord, p.y_coord)->incUnloadActiveLock();
        else
        {
            GridCoord p2 = Trinity::ComputeGridCoord(obj->GetPositionX(), obj->GetPositionY());
            TC_LOG_ERROR("maps", "Active object {} added to grid[{}, {}] but spawn grid[{}, {}] was not loaded.",
                obj->GetGUID().ToString(), p.x_coord, p.y_coord, p2.x_coord, p2.y_coord);
        }
    }
}
void Map::RemoveFromActive(WorldObject* obj)
{
    RemoveFromActiveHelper(obj);
    Optional respawnLocation;
    switch (obj->GetTypeId())
    {
        case TYPEID_UNIT:
            if (Creature* creature = obj->ToCreature(); !creature->IsPet() && creature->GetSpawnId())
            {
                respawnLocation.emplace();
                creature->GetRespawnPosition(respawnLocation->m_positionX, respawnLocation->m_positionY, respawnLocation->m_positionZ);
            }
            break;
        case TYPEID_GAMEOBJECT:
            if (GameObject* gameObject = obj->ToGameObject(); gameObject->GetSpawnId())
            {
                respawnLocation.emplace();
                gameObject->GetRespawnPosition(respawnLocation->m_positionX, respawnLocation->m_positionY, respawnLocation->m_positionZ);
            }
            break;
        default:
            break;
    }
    if (respawnLocation)
    {
        GridCoord p = Trinity::ComputeGridCoord(respawnLocation->GetPositionX(), respawnLocation->GetPositionY());
        if (getNGrid(p.x_coord, p.y_coord))
            getNGrid(p.x_coord, p.y_coord)->decUnloadActiveLock();
        else
        {
            GridCoord p2 = Trinity::ComputeGridCoord(obj->GetPositionX(), obj->GetPositionY());
            TC_LOG_ERROR("maps", "Active object {} removed from to grid[{}, {}] but spawn grid[{}, {}] was not loaded.",
                obj->GetGUID().ToString(), p.x_coord, p.y_coord, p2.x_coord, p2.y_coord);
        }
    }
}
template TC_GAME_API bool Map::AddToMap(Corpse*);
template TC_GAME_API bool Map::AddToMap(Creature*);
template TC_GAME_API bool Map::AddToMap(GameObject*);
template TC_GAME_API bool Map::AddToMap(DynamicObject*);
template TC_GAME_API bool Map::AddToMap(AreaTrigger*);
template TC_GAME_API bool Map::AddToMap(SceneObject*);
template TC_GAME_API bool Map::AddToMap(Conversation*);
template TC_GAME_API void Map::RemoveFromMap(Corpse*, bool);
template TC_GAME_API void Map::RemoveFromMap(Creature*, bool);
template TC_GAME_API void Map::RemoveFromMap(GameObject*, bool);
template TC_GAME_API void Map::RemoveFromMap(DynamicObject*, bool);
template TC_GAME_API void Map::RemoveFromMap(AreaTrigger*, bool);
template TC_GAME_API void Map::RemoveFromMap(SceneObject*, bool);
template TC_GAME_API void Map::RemoveFromMap(Conversation*, bool);
/* ******* Dungeon Instance Maps ******* */
InstanceMap::InstanceMap(uint32 id, time_t expiry, uint32 InstanceId, Difficulty SpawnMode, TeamId InstanceTeam, InstanceLock* instanceLock)
  : Map(id, expiry, InstanceId, SpawnMode),
    i_data(nullptr), i_script_id(0), i_scenario(nullptr), i_instanceLock(instanceLock)
{
    //lets initialize visibility distance for dungeons
    InstanceMap::InitVisibilityDistance();
    // the timer is started by default, and stopped when the first player joins
    // this make sure it gets unloaded if for some reason no player joins
    m_unloadTimer = std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY);
    sWorldStateMgr->SetValue(WS_TEAM_IN_INSTANCE_ALLIANCE, InstanceTeam == TEAM_ALLIANCE, false, this);
    sWorldStateMgr->SetValue(WS_TEAM_IN_INSTANCE_HORDE, InstanceTeam == TEAM_HORDE, false, this);
    if (i_instanceLock)
    {
        i_instanceLock->SetInUse(true);
        i_instanceExpireEvent = i_instanceLock->GetExpiryTime(); // ignore extension state for reset event (will ask players to accept extended save on expiration)
    }
}
InstanceMap::~InstanceMap()
{
    if (i_instanceLock)
        i_instanceLock->SetInUse(false);
    delete i_data;
    delete i_scenario;
}
void InstanceMap::InitVisibilityDistance()
{
    //init visibility distance for instances
    m_VisibleDistance = World::GetMaxVisibleDistanceInInstances();
    m_VisibilityNotifyPeriod = World::GetVisibilityNotifyPeriodInInstances();
}
/*
    Do map specific checks to see if the player can enter
*/
TransferAbortParams InstanceMap::CannotEnter(Player* player)
{
    if (player->GetMapRef().getTarget() == this)
    {
        TC_LOG_ERROR("maps", "InstanceMap::CannotEnter - player {} {} already in map {}, {}, {}!", player->GetName(), player->GetGUID().ToString(), GetId(), GetInstanceId(), GetDifficultyID());
        ABORT();
        return TRANSFER_ABORT_ERROR;
    }
    // allow GM's to enter
    if (player->IsGameMaster())
        return Map::CannotEnter(player);
    // cannot enter if the instance is full (player cap), GMs don't count
    uint32 maxPlayers = GetMaxPlayers();
    if (GetPlayersCountExceptGMs() >= maxPlayers)
    {
        TC_LOG_WARN("maps", "MAP: Instance '{}' of map '{}' cannot have more than '{}' players. Player '{}' rejected", GetInstanceId(), GetMapName(), maxPlayers, player->GetName());
        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 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_NONE)
            return lockError;
    }
    return Map::CannotEnter(player);
}
/*
    Do map specific checks and add the player to the map if successful.
*/
bool InstanceMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
{
    // increase current instances (hourly limit)
    player->AddInstanceEnterTime(GetInstanceId(), GameTime::GetGameTime());
    MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
    if (entries.MapDifficulty->HasResetSchedule() && i_instanceLock && !i_instanceLock->IsNew() && i_data)
    {
        if (!entries.MapDifficulty->IsUsingEncounterLocks())
        {
            InstanceLock const* playerLock = sInstanceLockMgr.FindActiveInstanceLock(player->GetGUID(), entries);
            if (!playerLock || (playerLock->IsExpired() && playerLock->IsExtended()) ||
                playerLock->GetData()->CompletedEncountersMask != i_instanceLock->GetData()->CompletedEncountersMask)
            {
                WorldPackets::Instance::PendingRaidLock pendingRaidLock;
                pendingRaidLock.TimeUntilLock = 60000;
                pendingRaidLock.CompletedMask = i_instanceLock->GetData()->CompletedEncountersMask;
                pendingRaidLock.Extending = playerLock && playerLock->IsExtended();
                pendingRaidLock.WarningOnly = entries.Map->IsFlexLocking(); // events it triggers:  1 : INSTANCE_LOCK_WARNING   0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START
                player->GetSession()->SendPacket(pendingRaidLock.Write());
                if (!entries.Map->IsFlexLocking())
                    player->SetPendingBind(GetInstanceId(), 60000);
            }
        }
    }
    TC_LOG_DEBUG("maps", "MAP: Player '{}' entered instance '{}' of map '{}'", player->GetName(), GetInstanceId(), GetMapName());
    // initialize unload state
    m_unloadTimer = 0;
    // this will acquire the same mutex so it cannot be in the previous block
    Map::AddPlayerToMap(player, initPlayer);
    if (i_data)
        i_data->OnPlayerEnter(player);
    if (i_scenario)
        i_scenario->OnPlayerEnter(player);
    return true;
}
void InstanceMap::Update(uint32 t_diff)
{
    Map::Update(t_diff);
    if (i_data)
    {
        i_data->Update(t_diff);
        i_data->UpdateCombatResurrection(t_diff);
    }
    if (i_scenario)
        i_scenario->Update(t_diff);
    if (i_instanceExpireEvent && i_instanceExpireEvent < GameTime::GetSystemTime())
    {
        Reset(InstanceResetMethod::Expire);
        i_instanceExpireEvent = sInstanceLockMgr.GetNextResetTime({ GetEntry(), GetMapDifficulty() });
    }
}
void InstanceMap::RemovePlayerFromMap(Player* player, bool remove)
{
    TC_LOG_DEBUG("maps", "MAP: Removing player '{}' from instance '{}' of map '{}' before relocating to another map", player->GetName(), GetInstanceId(), GetMapName());
    if (i_data)
        i_data->OnPlayerLeave(player);
    // if last player set unload timer
    if (!m_unloadTimer && m_mapRefManager.getSize() == 1)
        m_unloadTimer = (i_instanceLock && i_instanceLock->IsExpired()) ? MIN_UNLOAD_DELAY : std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY);
    if (i_scenario)
        i_scenario->OnPlayerExit(player);
    Map::RemovePlayerFromMap(player, remove);
}
void InstanceMap::CreateInstanceData()
{
    if (i_data != nullptr)
        return;
    InstanceTemplate const* mInstance = sObjectMgr->GetInstanceTemplate(GetId());
    if (mInstance)
    {
        i_script_id = mInstance->ScriptId;
        i_data = sScriptMgr->CreateInstanceData(this);
    }
    if (!i_data)
        return;
    if (!i_instanceLock || i_instanceLock->IsNew())
    {
        i_data->Create();
        return;
    }
    MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
    if (!entries.IsInstanceIdBound() && !IsRaid() && !entries.MapDifficulty->IsRestoringDungeonState() && i_owningGroupRef.isValid())
    {
        i_data->Create();
        return;
    }
    InstanceLockData const* lockData = i_instanceLock->GetInstanceInitializationData();
    i_data->SetEntranceLocation(lockData->EntranceWorldSafeLocId);
    if (!lockData->Data.empty())
    {
        TC_LOG_DEBUG("maps", "Loading instance data for `{}` with id {}", sObjectMgr->GetScriptName(i_script_id), i_InstanceId);
        i_data->Load(lockData->Data.c_str());
    }
    else
        i_data->Create();
}
void InstanceMap::TrySetOwningGroup(Group* group)
{
    if (!i_owningGroupRef.isValid())
        i_owningGroupRef.link(group, this);
}
/*
    Returns true if there are no players in the instance
*/
InstanceResetResult InstanceMap::Reset(InstanceResetMethod method)
{
    // raids can be reset if no boss was killed
    if (method != InstanceResetMethod::Expire && i_instanceLock && !i_instanceLock->IsNew())
        return InstanceResetResult::CannotReset;
    if (HavePlayers())
    {
        switch (method)
        {
            case InstanceResetMethod::Manual:
                // notify the players to leave the instance so it can be reset
                for (MapReference const& ref : m_mapRefManager)
                    ref.GetSource()->SendResetFailedNotify(GetId());
                break;
            case InstanceResetMethod::OnChangeDifficulty:
                // no client notification
                break;
            case InstanceResetMethod::Expire:
            {
                WorldPackets::Instance::RaidInstanceMessage raidInstanceMessage;
                raidInstanceMessage.Type = RAID_INSTANCE_EXPIRED;
                raidInstanceMessage.MapID = GetId();
                raidInstanceMessage.DifficultyID = GetDifficultyID();
                raidInstanceMessage.Write();
                for (MapReference const& ref : m_mapRefManager)
                    ref.GetSource()->SendDirectMessage(raidInstanceMessage.GetRawPacket());
                if (i_data)
                {
                    WorldPackets::Instance::PendingRaidLock pendingRaidLock;
                    pendingRaidLock.TimeUntilLock = 60000;
                    pendingRaidLock.CompletedMask = i_instanceLock->GetData()->CompletedEncountersMask;
                    pendingRaidLock.Extending = true;
                    pendingRaidLock.WarningOnly = GetEntry()->IsFlexLocking();
                    pendingRaidLock.Write();
                    for (MapReference const& ref : m_mapRefManager)
                    {
                        ref.GetSource()->SendDirectMessage(pendingRaidLock.GetRawPacket());
                        if (!pendingRaidLock.WarningOnly)
                            ref.GetSource()->SetPendingBind(GetInstanceId(), 60000);
                    }
                }
                break;
            }
            default:
                break;
        }
        return InstanceResetResult::NotEmpty;
    }
    else
    {
        // unloaded at next update
        m_unloadTimer = MIN_UNLOAD_DELAY;
    }
    return InstanceResetResult::Success;
}
std::string const& InstanceMap::GetScriptName() const
{
    return sObjectMgr->GetScriptName(i_script_id);
}
void InstanceMap::UpdateInstanceLock(UpdateBossStateSaveDataEvent const& updateSaveDataEvent)
{
    if (i_instanceLock)
    {
        uint32 instanceCompletedEncounters = i_instanceLock->GetData()->CompletedEncountersMask | (1u << updateSaveDataEvent.DungeonEncounter->Bit);
        MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
        CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
        if (entries.IsInstanceIdBound())
            sInstanceLockMgr.UpdateSharedInstanceLock(trans, InstanceLockUpdateEvent(GetInstanceId(), i_data->GetSaveData(),
                instanceCompletedEncounters, updateSaveDataEvent.DungeonEncounter, i_data->GetEntranceLocationForCompletedEncounters(instanceCompletedEncounters)));
        for (MapReference& mapReference : m_mapRefManager)
        {
            Player* player = mapReference.GetSource();
            // never instance bind GMs with GM mode enabled
            if (player->IsGameMaster())
                continue;
            InstanceLock const* playerLock = sInstanceLockMgr.FindActiveInstanceLock(player->GetGUID(), entries);
            std::string const* oldData = nullptr;
            uint32 playerCompletedEncounters = 0;
            if (playerLock)
            {
                oldData = &playerLock->GetData()->Data;
                playerCompletedEncounters = playerLock->GetData()->CompletedEncountersMask | (1u << updateSaveDataEvent.DungeonEncounter->Bit);
            }
            bool isNewLock = !playerLock || playerLock->IsNew() || playerLock->IsExpired();
            InstanceLock const* newLock = sInstanceLockMgr.UpdateInstanceLockForPlayer(trans, player->GetGUID(), entries,
                InstanceLockUpdateEvent(GetInstanceId(), i_data->UpdateBossStateSaveData(oldData ? *oldData : "", updateSaveDataEvent),
                    instanceCompletedEncounters, updateSaveDataEvent.DungeonEncounter, i_data->GetEntranceLocationForCompletedEncounters(playerCompletedEncounters)));
            if (isNewLock)
            {
                WorldPackets::Instance::InstanceSaveCreated data;
                data.Gm = player->IsGameMaster();
                player->SendDirectMessage(data.Write());
                player->GetSession()->SendCalendarRaidLockoutAdded(newLock);
            }
        }
        CharacterDatabase.CommitTransaction(trans);
    }
}
void InstanceMap::UpdateInstanceLock(UpdateAdditionalSaveDataEvent const& updateSaveDataEvent)
{
    if (i_instanceLock)
    {
        uint32 instanceCompletedEncounters = i_instanceLock->GetData()->CompletedEncountersMask;
        MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
        CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
        if (entries.IsInstanceIdBound())
            sInstanceLockMgr.UpdateSharedInstanceLock(trans, InstanceLockUpdateEvent(GetInstanceId(), i_data->GetSaveData(),
                instanceCompletedEncounters, nullptr, {}));
        for (MapReference& mapReference : m_mapRefManager)
        {
            Player* player = mapReference.GetSource();
            // never instance bind GMs with GM mode enabled
            if (player->IsGameMaster())
                continue;
            InstanceLock const* playerLock = sInstanceLockMgr.FindActiveInstanceLock(player->GetGUID(), entries);
            std::string const* oldData = nullptr;
            if (playerLock)
                oldData = &playerLock->GetData()->Data;
            bool isNewLock = !playerLock || playerLock->IsNew() || playerLock->IsExpired();
            InstanceLock const* newLock = sInstanceLockMgr.UpdateInstanceLockForPlayer(trans, player->GetGUID(), entries,
                InstanceLockUpdateEvent(GetInstanceId(), i_data->UpdateAdditionalSaveData(oldData ? *oldData : "", updateSaveDataEvent),
                    instanceCompletedEncounters, nullptr, {}));
            if (isNewLock)
            {
                WorldPackets::Instance::InstanceSaveCreated data;
                data.Gm = player->IsGameMaster();
                player->SendDirectMessage(data.Write());
                player->GetSession()->SendCalendarRaidLockoutAdded(newLock);
            }
        }
        CharacterDatabase.CommitTransaction(trans);
    }
}
void InstanceMap::CreateInstanceLockForPlayer(Player* player)
{
    MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
    InstanceLock const* playerLock = sInstanceLockMgr.FindActiveInstanceLock(player->GetGUID(), entries);
    bool isNewLock = !playerLock || playerLock->IsNew() || playerLock->IsExpired();
    CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
    InstanceLock const* newLock = sInstanceLockMgr.UpdateInstanceLockForPlayer(trans, player->GetGUID(), entries,
        InstanceLockUpdateEvent(GetInstanceId(), i_data->GetSaveData(), i_instanceLock->GetData()->CompletedEncountersMask, nullptr, {}));
    CharacterDatabase.CommitTransaction(trans);
    if (isNewLock)
    {
        WorldPackets::Instance::InstanceSaveCreated data;
        data.Gm = player->IsGameMaster();
        player->SendDirectMessage(data.Write());
        player->GetSession()->SendCalendarRaidLockoutAdded(newLock);
    }
}
MapDifficultyEntry const* Map::GetMapDifficulty() const
{
    return sDB2Manager.GetMapDifficultyData(GetId(), GetDifficultyID());
}
uint32 Map::GetId() const
{
    return i_mapEntry->ID;
}
bool Map::Instanceable() const
{
    return i_mapEntry && i_mapEntry->Instanceable();
}
bool Map::IsDungeon() const
{
    return i_mapEntry && i_mapEntry->IsDungeon();
}
bool Map::IsNonRaidDungeon() const
{
    return i_mapEntry && i_mapEntry->IsNonRaidDungeon();
}
bool Map::IsRaid() const
{
    return i_mapEntry && i_mapEntry->IsRaid();
}
bool Map::IsLFR() const
{
    switch (i_spawnMode)
    {
        case DIFFICULTY_LFR:
        case DIFFICULTY_LFR_NEW:
        case DIFFICULTY_LFR_15TH_ANNIVERSARY:
            return true;
        default:
            return false;
    }
}
bool Map::IsNormal() const
{
    switch (i_spawnMode)
    {
        case DIFFICULTY_NORMAL:
        case DIFFICULTY_10_N:
        case DIFFICULTY_25_N:
        case DIFFICULTY_NORMAL_RAID:
        case DIFFICULTY_NORMAL_ISLAND:
        case DIFFICULTY_NORMAL_WARFRONT:
            return true;
        default:
            return false;
    }
}
bool Map::IsHeroic() const
{
    if (DifficultyEntry const* difficulty = sDifficultyStore.LookupEntry(i_spawnMode))
    {
        if (difficulty->Flags & DIFFICULTY_FLAG_DISPLAY_HEROIC)
            return true;
    }
    // compatibility purposes of old difficulties
    switch (i_spawnMode)
    {
        case DIFFICULTY_10_HC:
        case DIFFICULTY_25_HC:
        case DIFFICULTY_HEROIC:
        case DIFFICULTY_3_MAN_SCENARIO_HC:
            return true;
        default:
            return false;
    }
}
bool Map::IsMythic() const
{
    if (DifficultyEntry const* difficulty = sDifficultyStore.LookupEntry(i_spawnMode))
        return difficulty->Flags & DIFFICULTY_FLAG_DISPLAY_MYTHIC;
    return false;
}
bool Map::IsMythicPlus() const
{
    return IsDungeon() && i_spawnMode == DIFFICULTY_MYTHIC_KEYSTONE;
}
bool Map::IsHeroicOrHigher() const
{
    return IsHeroic() || IsMythic() || IsMythicPlus();
}
bool Map::Is25ManRaid() const
{
    return IsRaid() && (i_spawnMode == DIFFICULTY_25_N || i_spawnMode == DIFFICULTY_25_HC);
}
bool Map::IsTimewalking() const
{
    return (IsDungeon() && i_spawnMode == DIFFICULTY_TIMEWALKING) || (IsRaid() && i_spawnMode == DIFFICULTY_TIMEWALKING_RAID);
}
bool Map::IsBattleground() const
{
    return i_mapEntry && i_mapEntry->IsBattleground();
}
bool Map::IsBattleArena() const
{
    return i_mapEntry && i_mapEntry->IsBattleArena();
}
bool Map::IsBattlegroundOrArena() const
{
    return i_mapEntry && i_mapEntry->IsBattlegroundOrArena();
}
bool Map::IsScenario() const
{
    return i_mapEntry && i_mapEntry->IsScenario();
}
bool Map::IsGarrison() const
{
    return i_mapEntry && i_mapEntry->IsGarrison();
}
bool Map::IsAlwaysActive() const
{
    return IsBattlegroundOrArena();
}
bool Map::GetEntrancePos(int32 &mapid, float &x, float &y)
{
    if (!i_mapEntry)
        return false;
    return i_mapEntry->GetEntrancePos(mapid, x, y);
}
uint32 InstanceMap::GetMaxPlayers() const
{
    MapDifficultyEntry const* mapDiff = GetMapDifficulty();
    if (mapDiff && mapDiff->MaxPlayers)
        return mapDiff->MaxPlayers;
    return GetEntry()->MaxPlayers;
}
TeamId InstanceMap::GetTeamIdInInstance() const
{
    if (sWorldStateMgr->GetValue(WS_TEAM_IN_INSTANCE_ALLIANCE, this))
        return TEAM_ALLIANCE;
    if (sWorldStateMgr->GetValue(WS_TEAM_IN_INSTANCE_HORDE, this))
        return TEAM_HORDE;
    return TEAM_NEUTRAL;
}
/* ******* Battleground Instance Maps ******* */
BattlegroundMap::BattlegroundMap(uint32 id, time_t expiry, uint32 InstanceId, Difficulty spawnMode)
  : Map(id, expiry, InstanceId, spawnMode), m_bg(nullptr)
{
    //lets initialize visibility distance for BG/Arenas
    BattlegroundMap::InitVisibilityDistance();
}
BattlegroundMap::~BattlegroundMap()
{
    if (m_bg)
    {
        //unlink to prevent crash, always unlink all pointer reference before destruction
        m_bg->SetBgMap(nullptr);
        m_bg = nullptr;
    }
}
void BattlegroundMap::InitVisibilityDistance()
{
    //init visibility distance for BG/Arenas
    m_VisibleDistance        = IsBattleArena() ? World::GetMaxVisibleDistanceInArenas() : World::GetMaxVisibleDistanceInBG();
    m_VisibilityNotifyPeriod = IsBattleArena() ? World::GetVisibilityNotifyPeriodInArenas() : World::GetVisibilityNotifyPeriodInBG();
}
TransferAbortParams BattlegroundMap::CannotEnter(Player* player)
{
    if (player->GetMapRef().getTarget() == this)
    {
        TC_LOG_ERROR("maps", "BGMap::CannotEnter - player {} is already in map!", player->GetGUID().ToString());
        ABORT();
        return TRANSFER_ABORT_ERROR;
    }
    if (player->GetBattlegroundId() != GetInstanceId())
        return TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE;
    // player number limit is checked in bgmgr, no need to do it here
    return Map::CannotEnter(player);
}
bool BattlegroundMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
{
    player->m_InstanceValid = true;
    return Map::AddPlayerToMap(player, initPlayer);
}
void BattlegroundMap::RemovePlayerFromMap(Player* player, bool remove)
{
    TC_LOG_DEBUG("maps", "MAP: Removing player '{}' from bg '{}' of map '{}' before relocating to another map", player->GetName(), GetInstanceId(), GetMapName());
    Map::RemovePlayerFromMap(player, remove);
}
void BattlegroundMap::SetUnload()
{
    m_unloadTimer = MIN_UNLOAD_DELAY;
}
void BattlegroundMap::RemoveAllPlayers()
{
    if (HavePlayers())
        for (MapRefManager::iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr)
            if (Player* player = itr->GetSource())
                if (!player->IsBeingTeleportedFar())
                    player->TeleportTo(player->GetBattlegroundEntryPoint());
}
AreaTrigger* Map::GetAreaTrigger(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
SceneObject* Map::GetSceneObject(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
Conversation* Map::GetConversation(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
Player* Map::GetPlayer(ObjectGuid const& guid)
{
    return ObjectAccessor::GetPlayer(this, guid);
}
Corpse* Map::GetCorpse(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
Creature* Map::GetCreature(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
DynamicObject* Map::GetDynamicObject(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
GameObject* Map::GetGameObject(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
Pet* Map::GetPet(ObjectGuid const& guid)
{
    return _objectsStore.Find(guid);
}
Transport* Map::GetTransport(ObjectGuid const& guid)
{
    if (!guid.IsMOTransport())
        return nullptr;
    GameObject* go = GetGameObject(guid);
    return go ? go->ToTransport() : nullptr;
}
Creature* Map::GetCreatureBySpawnId(ObjectGuid::LowType spawnId) const
{
    auto const bounds = GetCreatureBySpawnIdStore().equal_range(spawnId);
    if (bounds.first == bounds.second)
        return nullptr;
    std::unordered_multimap::const_iterator creatureItr = std::find_if(bounds.first, bounds.second, [](Map::CreatureBySpawnIdContainer::value_type const& pair)
    {
        return pair.second->IsAlive();
    });
    return creatureItr != bounds.second ? creatureItr->second : bounds.first->second;
}
GameObject* Map::GetGameObjectBySpawnId(ObjectGuid::LowType spawnId) const
{
    auto const bounds = GetGameObjectBySpawnIdStore().equal_range(spawnId);
    if (bounds.first == bounds.second)
        return nullptr;
    std::unordered_multimap::const_iterator creatureItr = std::find_if(bounds.first, bounds.second, [](Map::GameObjectBySpawnIdContainer::value_type const& pair)
    {
        return pair.second->isSpawned();
    });
    return creatureItr != bounds.second ? creatureItr->second : bounds.first->second;
}
AreaTrigger* Map::GetAreaTriggerBySpawnId(ObjectGuid::LowType spawnId) const
{
    auto const bounds = GetAreaTriggerBySpawnIdStore().equal_range(spawnId);
    if (bounds.first == bounds.second)
        return nullptr;
    return bounds.first->second;
}
void Map::UpdateIteratorBack(Player* player)
{
    if (&*m_mapRefIter == &player->GetMapRef())
        m_mapRefIter = m_mapRefIter->nocheck_prev();
}
void Map::SaveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 entry, time_t respawnTime, uint32 gridId, CharacterDatabaseTransaction dbTrans, bool startup)
{
    SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(type, spawnId);
    if (!data)
    {
        TC_LOG_ERROR("maps", "Map {} attempt to save respawn time for nonexistant spawnid ({},{}).", GetId(), type, spawnId);
        return;
    }
    if (!respawnTime)
    {
        // Delete only
        RemoveRespawnTime(data->type, data->spawnId, dbTrans);
        return;
    }
    RespawnInfo ri;
    ri.type = data->type;
    ri.spawnId = data->spawnId;
    ri.entry = entry;
    ri.respawnTime = respawnTime;
    ri.gridId = gridId;
    bool success = AddRespawnInfo(ri);
    if (startup)
    {
        if (!success)
            TC_LOG_ERROR("maps", "Attempt to load saved respawn {} for ({},{}) failed - duplicate respawn? Skipped.", respawnTime, uint32(type), spawnId);
    }
    else if (success)
        SaveRespawnInfoDB(ri, dbTrans);
}
void Map::SaveRespawnInfoDB(RespawnInfo const& info, CharacterDatabaseTransaction dbTrans)
{
    if (Instanceable())
        return;
    CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_RESPAWN);
    stmt->setUInt16(0, info.type);
    stmt->setUInt64(1, info.spawnId);
    stmt->setInt64(2, info.respawnTime);
    stmt->setUInt16(3, GetId());
    stmt->setUInt32(4, GetInstanceId());
    CharacterDatabase.ExecuteOrAppend(dbTrans, stmt);
}
void Map::LoadRespawnTimes()
{
    if (Instanceable())
        return;
    CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_RESPAWNS);
    stmt->setUInt16(0, GetId());
    stmt->setUInt32(1, GetInstanceId());
    if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
    {
        do
        {
            Field* fields = result->Fetch();
            SpawnObjectType type = SpawnObjectType(fields[0].GetUInt16());
            ObjectGuid::LowType spawnId = fields[1].GetUInt64();
            time_t respawnTime = fields[2].GetInt64();
            if (SpawnData::TypeHasData(type))
            {
                if (SpawnData const* data = sObjectMgr->GetSpawnData(type, spawnId))
                    SaveRespawnTime(type, spawnId, data->id, time_t(respawnTime), Trinity::ComputeGridCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()).GetId(), nullptr, true);
                else
                    TC_LOG_ERROR("maps", "Loading saved respawn time of {} for spawnid ({},{}) - spawn does not exist, ignoring", respawnTime, uint32(type), spawnId);
            }
            else
            {
                TC_LOG_ERROR("maps", "Loading saved respawn time of {} for spawnid ({},{}) - invalid spawn type, ignoring", respawnTime, uint32(type), spawnId);
            }
        } while (result->NextRow());
    }
}
void Map::DeleteRespawnTimesInDB()
{
    if (Instanceable())
        return;
    CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_RESPAWNS);
    stmt->setUInt16(0, GetId());
    stmt->setUInt32(1, GetInstanceId());
    CharacterDatabase.Execute(stmt);
}
time_t Map::GetLinkedRespawnTime(ObjectGuid guid) const
{
    ObjectGuid linkedGuid = sObjectMgr->GetLinkedRespawnGuid(guid);
    switch (linkedGuid.GetHigh())
    {
        case HighGuid::Creature:
            return GetCreatureRespawnTime(linkedGuid.GetCounter());
        case HighGuid::GameObject:
            return GetGORespawnTime(linkedGuid.GetCounter());
        default:
            break;
    }
    return time_t(0);
}
void Map::LoadCorpseData()
{
    CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSES);
    stmt->setUInt32(0, GetId());
    stmt->setUInt32(1, GetInstanceId());
    //        0     1     2     3            4      5          6          7     8      9       10     11        12    13          14          15
    // SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, race, class, gender, flags, dynFlags, time, corpseType, instanceId, guid FROM corpse WHERE mapId = ? AND instanceId = ?
    PreparedQueryResult result = CharacterDatabase.Query(stmt);
    if (!result)
        return;
    std::unordered_map> phases;
    std::unordered_map> customizations;
    stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSE_PHASES);
    stmt->setUInt32(0, GetId());
    stmt->setUInt32(1, GetInstanceId());
    //        0          1
    // SELECT OwnerGuid, PhaseId FROM corpse_phases cp LEFT JOIN corpse c ON cp.OwnerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ?
    if (PreparedQueryResult phaseResult = CharacterDatabase.Query(stmt))
    {
        do
        {
            Field* fields = phaseResult->Fetch();
            ObjectGuid::LowType guid = fields[0].GetUInt64();
            uint32 phaseId = fields[1].GetUInt32();
            phases[guid].insert(phaseId);
        } while (phaseResult->NextRow());
    }
    stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSE_CUSTOMIZATIONS);
    stmt->setUInt32(0, GetId());
    stmt->setUInt32(1, GetInstanceId());
    //        0             1                            2
    // SELECT cc.ownerGuid, cc.chrCustomizationOptionID, cc.chrCustomizationChoiceID FROM corpse_customizations cc LEFT JOIN corpse c ON cc.ownerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ?
    if (PreparedQueryResult customizationResult = CharacterDatabase.Query(stmt))
    {
        do
        {
            Field* fields = customizationResult->Fetch();
            ObjectGuid::LowType guid = fields[0].GetUInt64();
            std::vector& customizationsForCorpse = customizations[guid];
            customizationsForCorpse.emplace_back();
            UF::ChrCustomizationChoice& choice = customizationsForCorpse.back();
            choice.ChrCustomizationOptionID = fields[1].GetUInt32();
            choice.ChrCustomizationChoiceID = fields[2].GetUInt32();
        } while (customizationResult->NextRow());
    }
    do
    {
        Field* fields = result->Fetch();
        CorpseType type = CorpseType(fields[13].GetUInt8());
        ObjectGuid::LowType guid = fields[15].GetUInt64();
        if (type >= MAX_CORPSE_TYPE || type == CORPSE_BONES)
        {
            TC_LOG_ERROR("misc", "Corpse (guid: {}) have wrong corpse type ({}), not loading.", guid, type);
            continue;
        }
        Corpse* corpse = new Corpse(type);
        if (!corpse->LoadCorpseFromDB(GenerateLowGuid(), fields))
        {
            delete corpse;
            continue;
        }
        for (uint32 phaseId : phases[guid])
            PhasingHandler::AddPhase(corpse, phaseId, false);
        corpse->SetCustomizations(Trinity::Containers::MakeIteratorPair(customizations[guid].begin(), customizations[guid].end()));
        AddCorpse(corpse);
    } while (result->NextRow());
}
void Map::DeleteCorpseData()
{
    // DELETE cp, c FROM corpse_phases cp INNER JOIN corpse c ON cp.OwnerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ?
    CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CORPSES_FROM_MAP);
    stmt->setUInt32(0, GetId());
    stmt->setUInt32(1, GetInstanceId());
    CharacterDatabase.Execute(stmt);
}
void Map::AddCorpse(Corpse* corpse)
{
    corpse->SetMap(this);
    _corpsesByCell[corpse->GetCellCoord().GetId()].insert(corpse);
    if (corpse->GetType() != CORPSE_BONES)
        _corpsesByPlayer[corpse->GetOwnerGUID()] = corpse;
    else
        _corpseBones.insert(corpse);
}
void Map::RemoveCorpse(Corpse* corpse)
{
    ASSERT(corpse);
    corpse->UpdateObjectVisibilityOnDestroy();
    if (corpse->IsInGrid())
        RemoveFromMap(corpse, false);
    else
    {
        corpse->RemoveFromWorld();
        corpse->ResetMap();
    }
    _corpsesByCell[corpse->GetCellCoord().GetId()].erase(corpse);
    if (corpse->GetType() != CORPSE_BONES)
        _corpsesByPlayer.erase(corpse->GetOwnerGUID());
    else
        _corpseBones.erase(corpse);
}
Corpse* Map::ConvertCorpseToBones(ObjectGuid const& ownerGuid, bool insignia /*= false*/)
{
    Corpse* corpse = GetCorpseByPlayer(ownerGuid);
    if (!corpse)
        return nullptr;
    RemoveCorpse(corpse);
    // remove corpse from DB
    CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
    corpse->DeleteFromDB(trans);
    CharacterDatabase.CommitTransaction(trans);
    Corpse* bones = nullptr;
    // create the bones only if the map and the grid is loaded at the corpse's location
    // ignore bones creating option in case insignia
    if ((insignia ||
        (IsBattlegroundOrArena() ? sWorld->getBoolConfig(CONFIG_DEATH_BONES_BG_OR_ARENA) : sWorld->getBoolConfig(CONFIG_DEATH_BONES_WORLD))) &&
        !IsRemovalGrid(corpse->GetPositionX(), corpse->GetPositionY()))
    {
        // Create bones, don't change Corpse
        bones = new Corpse();
        bones->Create(corpse->GetGUID().GetCounter(), this);
        bones->ReplaceAllCorpseDynamicFlags(corpse->GetCorpseDynamicFlags());
        bones->SetOwnerGUID(corpse->m_corpseData->Owner);
        bones->SetPartyGUID(corpse->m_corpseData->PartyGUID);
        bones->SetGuildGUID(corpse->m_corpseData->GuildGUID);
        bones->SetDisplayId(corpse->m_corpseData->DisplayID);
        bones->SetRace(corpse->m_corpseData->RaceID);
        bones->SetSex(corpse->m_corpseData->Sex);
        bones->SetClass(corpse->m_corpseData->Class);
        bones->SetCustomizations(Trinity::Containers::MakeIteratorPair(corpse->m_corpseData->Customizations.begin(), corpse->m_corpseData->Customizations.end()));
        bones->ReplaceAllFlags(corpse->m_corpseData->Flags | CORPSE_FLAG_BONES);
        bones->SetFactionTemplate(corpse->m_corpseData->FactionTemplate);
        bones->SetCellCoord(corpse->GetCellCoord());
        bones->Relocate(corpse->GetPositionX(), corpse->GetPositionY(), corpse->GetPositionZ(), corpse->GetOrientation());
        PhasingHandler::InheritPhaseShift(bones, corpse);
        AddCorpse(bones);
        bones->UpdatePositionData();
        bones->SetZoneScript();
        // add bones in grid store if grid loaded where corpse placed
        AddToMap(bones);
    }
    // all references to the corpse should be removed at this point
    delete corpse;
    return bones;
}
void Map::RemoveOldCorpses()
{
    time_t now = GameTime::GetGameTime();
    std::vector corpses;
    corpses.reserve(_corpsesByPlayer.size());
    for (auto const& p : _corpsesByPlayer)
        if (p.second->IsExpired(now))
            corpses.push_back(p.first);
    for (ObjectGuid const& ownerGuid : corpses)
        ConvertCorpseToBones(ownerGuid);
    std::vector expiredBones;
    for (Corpse* bones : _corpseBones)
        if (bones->IsExpired(now))
            expiredBones.push_back(bones);
    for (Corpse* bones : expiredBones)
    {
        RemoveCorpse(bones);
        delete bones;
    }
}
void Map::SendZoneDynamicInfo(uint32 zoneId, Player* player) const
{
    auto itr = _zoneDynamicInfo.find(zoneId);
    if (itr == _zoneDynamicInfo.end())
        return;
    if (uint32 music = itr->second.MusicId)
        player->SendDirectMessage(WorldPackets::Misc::PlayMusic(music).Write());
    SendZoneWeather(itr->second, player);
    for (ZoneDynamicInfo::LightOverride const& lightOverride : itr->second.LightOverrides)
    {
        WorldPackets::Misc::OverrideLight overrideLight;
        overrideLight.AreaLightID = lightOverride.AreaLightId;
        overrideLight.OverrideLightID = lightOverride.OverrideLightId;
        overrideLight.TransitionMilliseconds = lightOverride.TransitionMilliseconds;
        player->SendDirectMessage(overrideLight.Write());
    }
}
void Map::SendZoneWeather(uint32 zoneId, Player* player) const
{
    if (!player->HasAuraType(SPELL_AURA_FORCE_WEATHER))
    {
        auto itr = _zoneDynamicInfo.find(zoneId);
        if (itr == _zoneDynamicInfo.end())
            return;
        SendZoneWeather(itr->second, player);
    }
}
void Map::SendZoneWeather(ZoneDynamicInfo const& zoneDynamicInfo, Player* player) const
{
    if (WeatherState weatherId = zoneDynamicInfo.WeatherId)
    {
        WorldPackets::Misc::Weather weather(weatherId, zoneDynamicInfo.Intensity);
        player->SendDirectMessage(weather.Write());
    }
    else if (zoneDynamicInfo.DefaultWeather)
    {
        zoneDynamicInfo.DefaultWeather->SendWeatherUpdateToPlayer(player);
    }
    else
        Weather::SendFineWeatherUpdateToPlayer(player);
}
void Map::SetZoneMusic(uint32 zoneId, uint32 musicId)
{
    _zoneDynamicInfo[zoneId].MusicId = musicId;
    Map::PlayerList const& players = GetPlayers();
    if (!players.isEmpty())
    {
        WorldPackets::Misc::PlayMusic playMusic(musicId);
        playMusic.Write();
        for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
            if (Player* player = itr->GetSource())
                if (player->GetZoneId() == zoneId && !player->HasAuraType(SPELL_AURA_FORCE_WEATHER))
                    player->SendDirectMessage(playMusic.GetRawPacket());
    }
}
Weather* Map::GetOrGenerateZoneDefaultWeather(uint32 zoneId)
{
    WeatherData const* weatherData = WeatherMgr::GetWeatherData(zoneId);
    if (!weatherData)
        return nullptr;
    ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
    if (!info.DefaultWeather)
    {
        info.DefaultWeather = std::make_unique(zoneId, weatherData);
        info.DefaultWeather->ReGenerate();
        info.DefaultWeather->UpdateWeather();
    }
    return info.DefaultWeather.get();
}
WeatherState Map::GetZoneWeather(uint32 zoneId) const
{
    ZoneDynamicInfo const* zoneDynamicInfo = Trinity::Containers::MapGetValuePtr(_zoneDynamicInfo, zoneId);
    if (zoneDynamicInfo)
    {
        if (WeatherState weatherId = zoneDynamicInfo->WeatherId)
            return weatherId;
        if (zoneDynamicInfo->DefaultWeather)
            return zoneDynamicInfo->DefaultWeather->GetWeatherState();
    }
    return WEATHER_STATE_FINE;
}
void Map::SetZoneWeather(uint32 zoneId, WeatherState weatherId, float intensity)
{
    ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
    info.WeatherId = weatherId;
    info.Intensity = intensity;
    Map::PlayerList const& players = GetPlayers();
    if (!players.isEmpty())
    {
        WorldPackets::Misc::Weather weather(weatherId, intensity);
        weather.Write();
        for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
            if (Player* player = itr->GetSource())
                if (player->GetZoneId() == zoneId)
                    player->SendDirectMessage(weather.GetRawPacket());
    }
}
void Map::SetZoneOverrideLight(uint32 zoneId, uint32 areaLightId, uint32 overrideLightId, Milliseconds transitionTime)
{
    ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
    // client can support only one override for each light (zone independent)
    info.LightOverrides.erase(std::remove_if(info.LightOverrides.begin(), info.LightOverrides.end(), [areaLightId](ZoneDynamicInfo::LightOverride const& lightOverride)
    {
        return lightOverride.AreaLightId == areaLightId;
    }), info.LightOverrides.end());
    // set new override (if any)
    if (overrideLightId)
    {
        ZoneDynamicInfo::LightOverride& lightOverride = info.LightOverrides.emplace_back();
        lightOverride.AreaLightId = areaLightId;
        lightOverride.OverrideLightId = overrideLightId;
        lightOverride.TransitionMilliseconds = static_cast(transitionTime.count());
    }
    Map::PlayerList const& players = GetPlayers();
    if (!players.isEmpty())
    {
        WorldPackets::Misc::OverrideLight overrideLight;
        overrideLight.AreaLightID = areaLightId;
        overrideLight.OverrideLightID = overrideLightId;
        overrideLight.TransitionMilliseconds = static_cast(transitionTime.count());
        overrideLight.Write();
        for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
            if (Player* player = itr->GetSource())
                if (player->GetZoneId() == zoneId)
                    player->SendDirectMessage(overrideLight.GetRawPacket());
    }
}
void Map::UpdateAreaDependentAuras()
{
    Map::PlayerList const& players = GetPlayers();
    for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
    {
        if (Player* player = itr->GetSource())
        {
            if (player->IsInWorld())
            {
                player->UpdateAreaDependentAuras(player->GetAreaId());
                player->UpdateZoneDependentAuras(player->GetZoneId());
            }
        }
    }
}
std::string Map::GetDebugInfo() const
{
    std::stringstream sstr;
    sstr << std::boolalpha
        << "Id: " << GetId() << " InstanceId: " << GetInstanceId() << " Difficulty: " << std::to_string(GetDifficultyID())
        << " HasPlayers: " << HavePlayers();
    return sstr.str();
}
std::string InstanceMap::GetDebugInfo() const
{
    std::stringstream sstr;
    sstr << Map::GetDebugInfo() << "\n"
        << std::boolalpha
        << "ScriptId: " << GetScriptId() << " ScriptName: " << GetScriptName();
    return sstr.str();
}
template class TC_GAME_API TypeUnorderedMapContainer;