/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * 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 3 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 <http://www.gnu.org/licenses/>.
 */

using Framework.Constants;
using Framework.Database;
using Framework.GameMath;
using Game.BattleGrounds;
using Game.Collision;
using Game.Combat;
using Game.DataStorage;
using Game.Entities;
using Game.Groups;
using Game.Network;
using Game.Network.Packets;
using Game.Scenarios;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;

namespace Game.Maps
{
    public class Map
    {
        public Map(uint id, long expiry, uint instanceId, Difficulty spawnmode, Map parent = null)
        {
            i_mapRecord = CliDB.MapStorage.LookupByKey(id);
            i_spawnMode = spawnmode;
            i_InstanceId = instanceId;
            m_VisibleDistance = SharedConst.DefaultVisibilityDistance;
            m_VisibilityNotifyPeriod = SharedConst.DefaultVisibilityNotifyPeriod;
            i_gridExpiry = expiry;
            _defaultLight = Global.DB2Mgr.GetDefaultMapLight(id);

            m_parentMap = parent ?? this;

            for (uint x = 0; x < MapConst.MaxGrids; ++x)
            {
                i_grids[x] = new Grid[MapConst.MaxGrids];
                GridMaps[x] = new GridMap[MapConst.MaxGrids];
                for (uint y = 0; y < MapConst.MaxGrids; ++y)
                {
                    //z code
                    GridMaps[x][y] = null;
                    setGrid(null, x, y);
                }
            }

            //lets initialize visibility distance for map
            InitVisibilityDistance();

            GetGuidSequenceGenerator(HighGuid.Transport).Set(Global.ObjectMgr.GetGenerator(HighGuid.Transport).GetNextAfterMaxUsed());

            Global.ScriptMgr.OnCreateMap(this);
        }

        public static bool ExistMap(uint mapid, uint gx, uint gy)
        {
            string fileName = string.Format("{0}/maps/{1:D4}_{2:D2}_{3:D2}.map", Global.WorldMgr.GetDataPath(), mapid, gx, gy);
            if (!File.Exists(fileName))
            {
                Log.outError(LogFilter.Maps, "Map file '{0}': does not exist!", fileName);
                return false;
            }

            using (var reader = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)))
            {
                var header = reader.ReadStruct<mapFileHeader>();
                if (new string(header.mapMagic) != MapConst.MapMagic || new string(header.versionMagic) != MapConst.MapVersionMagic)
                {
                    Log.outError(LogFilter.Maps,
                        "Map file '{0}' is from an incompatible map version ({1}), {2} is expected. Please recreate using the mapextractor.",
                        fileName, Convert.ToString(header.versionMagic), MapConst.MapVersionMagic);
                    return false;
                }
                return true;
            }
        }

        public static bool ExistVMap(uint mapid, uint gx, uint gy)
        {
            if (Global.VMapMgr.isMapLoadingEnabled())
            {
                bool exists = Global.VMapMgr.existsMap(mapid, gx, gy);
                if (!exists)
                {
                    string name = VMapManager.getMapFileName(mapid);
                    Log.outError(LogFilter.Maps, "VMap file '{0}' is missing or points to wrong version of vmap file. Redo vmaps with latest version of vmap_assembler.exe.",
                        Global.WorldMgr.GetDataPath() + "vmaps/" + name);
                    return false;
                }
            }
            return true;
        }

        void LoadMMap(uint gx, uint gy)
        {
            if (Global.MMapMgr.loadMap(GetId(), (int)gx, (int)gy))
                Log.outInfo(LogFilter.Maps, "MMAP loaded name:{0}, id:{1}, x:{2}, y:{3} (mmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy);
            else
                Log.outInfo(LogFilter.Maps, "Could not load MMAP name:{0}, id:{1}, x:{2}, y:{3} (mmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy);
        }

        void LoadVMap(uint gx, uint gy)
        {
            // x and y are swapped !!
            VMAPLoadResult vmapLoadResult = Global.VMapMgr.loadMap(GetId(), gx, gy);
            switch (vmapLoadResult)
            {
                case VMAPLoadResult.OK:
                    Log.outInfo(LogFilter.Maps, "VMAP loaded name:{0}, id:{1}, x:{2}, y:{3} (vmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy);
                    break;
                case VMAPLoadResult.Error:
                    Log.outInfo(LogFilter.Maps, "Could not load VMAP name:{0}, id:{1}, x:{2}, y:{3} (vmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy);
                    break;
                case VMAPLoadResult.Ignored:
                    Log.outDebug(LogFilter.Maps, "Ignored VMAP name:{0}, id:{1}, x:{2}, y:{3} (vmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy);
                    break;
            }
        }

        void LoadMap(uint gx, uint gy, bool reload = false)
        {
            if (i_InstanceId != 0)
            {
                if (GridMaps[gx][gy] != null)
                    return;

                // load grid map for base map
                if (m_parentMap.GridMaps[gx][gy] == null)
                    m_parentMap.EnsureGridCreated(new GridCoord(63 - gx, 63 - gy));

                ((MapInstanced)m_parentMap).AddGridMapReference(new GridCoord(gx, gy));
                GridMaps[gx][gy] = m_parentMap.GridMaps[gx][gy];
                return;
            }

            if (GridMaps[gx][gy] != null && !reload)
                return;

            if (GridMaps[gx][gy] != null)
            {
                Log.outInfo(LogFilter.Maps, "Unloading previously loaded map {0} before reloading.", GetId());
                Global.ScriptMgr.OnUnloadGridMap(this, GridMaps[gx][gy], gx, gy);

                GridMaps[gx][gy] = null;
            }
            // map file name
            string filename = string.Format("{0}/maps/{1:D4}_{2:D2}_{3:D2}.map", Global.WorldMgr.GetDataPath(), GetId(), gx, gy);
            Log.outInfo(LogFilter.Maps, "Loading map {0}", filename);
            // loading data
            GridMaps[gx][gy] = new GridMap();
            if (!GridMaps[gx][gy].loadData(filename))
                Log.outError(LogFilter.Maps, "Error loading map file: {0}", filename);

            Global.ScriptMgr.OnLoadGridMap(this, GridMaps[gx][gy], gx, gy);
        }

        void LoadMapAndVMap(uint gx, uint gy)
        {
            LoadMap(gx, gy);
            // Only load the data for the base map
            if (i_InstanceId == 0)
            {
                LoadVMap(gx, gy);
                LoadMMap(gx, gy);
            }
        }

        public void LoadAllCells()
        {
            for (uint cellX = 0; cellX < MapConst.TotalCellsPerMap; cellX++)
                for (uint cellY = 0; cellY < MapConst.TotalCellsPerMap; cellY++)
                    LoadGrid((cellX + 0.5f - MapConst.CenterGridCellId) * MapConst.SizeofCells, (cellY + 0.5f - MapConst.CenterGridCellId) * MapConst.SizeofCells);
        }

        public virtual void InitVisibilityDistance()
        {
            //init visibility for continents
            m_VisibleDistance = Global.WorldMgr.GetMaxVisibleDistanceOnContinents();
            m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodOnContinents();
        }

        public void AddToGrid<T>(T obj, Cell cell)where T : WorldObject
        {
            Grid grid = getGrid(cell.GetGridX(), cell.GetGridY());
            switch (obj.GetTypeId())
            {
                case TypeId.Corpse:
                    if (grid.isGridObjectDataLoaded())
                    {
                        // 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 (obj.IsWorldObject())
                        {
                            obj.SetCurrentCell(cell);
                            grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddWorldObject(obj);
                        }
                        else
                            grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddGridObject(obj);
                    }
                    return;
                case TypeId.GameObject:
                case TypeId.AreaTrigger:
                case TypeId.DynamicObject:
                    grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddGridObject(obj);
                    break;
                default:
                    if (obj.IsWorldObject())
                        grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddWorldObject(obj);
                    else
                        grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddGridObject(obj);
                    break;
            }

            obj.SetCurrentCell(cell);
        }

        public void RemoveFromGrid(WorldObject obj, Cell cell)
        {
            if (cell == null)
                return;

            Grid grid = getGrid(cell.GetGridX(), cell.GetGridY());
            if (grid == null)
                return;

            if (obj.IsWorldObject())
                grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).RemoveWorldObject(obj);
            else
                grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).RemoveGridObject(obj);

            obj.SetCurrentCell(null);
        }

        void SwitchGridContainers(WorldObject obj, bool on)
        {
            if (obj.IsPermanentWorldObject())
                return;

            CellCoord p = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY());
            if (!p.IsCoordValid())
            {
                Log.outError(LogFilter.Maps, "Map.SwitchGridContainers: Object {0} has invalid coordinates X:{1} Y:{2} grid cell [{3}:{4}]",
                    obj.GetGUID(), obj.GetPositionX(), obj.GetPositionY(), p.x_coord, p.y_coord);
                return;
            }

            var cell = new Cell(p);
            if (!IsGridLoaded(new GridCoord(cell.GetGridX(), cell.GetGridY())))
                return;

            Log.outDebug(LogFilter.Maps, "Switch object {0} from grid[{1}, {2}] {3}", obj.GetGUID(), cell.GetGridX(), cell.GetGridY(), on);
            Grid ngrid = getGrid(cell.GetGridX(), cell.GetGridY());
            Contract.Assert(ngrid != null);

            GridCell grid = ngrid.GetGridCell(cell.GetCellX(), cell.GetCellY());

            RemoveFromGrid(obj, cell); //This step is not really necessary but we want to do ASSERT in remove/add
            AddToGrid(obj, cell);
            if (on)
            {
                grid.AddWorldObject(obj);
                AddWorldObject(obj);
            }
            else
            {
                grid.AddGridObject(obj);
                RemoveWorldObject(obj);
            }
            if (obj.IsTypeId(TypeId.Unit))
                obj.ToCreature().m_isTempWorldObject = on;
        }

        void DeleteFromWorld(Player player)
        {
            Global.ObjAccessor.RemoveObject(player);
            RemoveUpdateObject(player); /// @todo I do not know why we need this, it should be removed in ~Object anyway
            player.Dispose();
        }

        void DeleteFromWorld(Transport transport)
        {
            Global.ObjAccessor.RemoveObject(transport);
            transport.Dispose();
        }

        void DeleteFromWorld(WorldObject obj)
        {
            obj.Dispose();
        }

        void EnsureGridCreated(GridCoord p)
        {
            lock (this)
            {
                EnsureGridCreated_i(p);
            }
        }

        void EnsureGridCreated_i(GridCoord p)
        {
            if (getGrid(p.x_coord, p.y_coord) == null)
            {
                Log.outDebug(LogFilter.Maps, "Creating grid[{0}, {1}] for map {2} instance {3}", p.x_coord, p.y_coord,
                    GetId(), i_InstanceId);

                setGrid(new Grid(p.x_coord * MapConst.MaxGrids + p.y_coord, p.x_coord, p.y_coord, i_gridExpiry, WorldConfig.GetBoolValue(WorldCfg.GridUnload)),
                    p.x_coord, p.y_coord);

                getGrid(p.x_coord, p.y_coord).SetGridState(GridState.Idle);

                //z coord
                uint gx = (MapConst.MaxGrids - 1) - p.x_coord;
                uint gy = (MapConst.MaxGrids - 1) - p.y_coord;

                if (GridMaps[gx][gy] == null)
                    LoadMapAndVMap(gx, gy);
            }
        }

        void EnsureGridLoadedForActiveObject(Cell cell, WorldObject obj)
        {
            EnsureGridLoaded(cell);
            Grid grid = getGrid(cell.GetGridX(), cell.GetGridY());

            // refresh grid state & timer
            if (grid.GetGridState() != GridState.Active)
            {
                Log.outDebug(LogFilter.Maps, "Active object {0} triggers loading of grid [{1}, {2}] on map {3}",
                    obj.GetGUID(), cell.GetGridX(), cell.GetGridY(), GetId());
                ResetGridExpiry(grid, 0.1f);
                grid.SetGridState(GridState.Active);
            }
        }

        private bool EnsureGridLoaded(Cell cell)
        {
            EnsureGridCreated(new GridCoord(cell.GetGridX(), cell.GetGridY()));
            Grid grid = getGrid(cell.GetGridX(), cell.GetGridY());

            if (!isGridObjectDataLoaded(cell.GetGridX(), cell.GetGridY()))
            {
                Log.outDebug(LogFilter.Maps, "Loading grid[{0}, {1}] for map {2} instance {3}", cell.GetGridX(),
                    cell.GetGridY(), GetId(), i_InstanceId);

                setGridObjectDataLoaded(true, cell.GetGridX(), cell.GetGridY());

                LoadGridObjects(grid, cell);

                Balance();
                return true;
            }

            return false;
        }

        public virtual void LoadGridObjects(Grid grid, Cell cell)
        {
            ObjectGridLoader loader = new ObjectGridLoader(grid, this, cell);
            loader.LoadN();
        }

        public void LoadGrid(float x, float y)
        {
            EnsureGridLoaded(new Cell(x, y));
        }

        public virtual bool AddPlayerToMap(Player player, bool initPlayer = true)
        {
            CellCoord cellCoord = GridDefines.ComputeCellCoord(player.GetPositionX(), player.GetPositionY());
            if (!cellCoord.IsCoordValid())
            {
                Log.outError(LogFilter.Maps, "Map.AddPlayer (GUID: {0}) has invalid coordinates X:{1} Y:{2}",
                    player.GetGUID().ToString(), player.GetPositionX(), player.GetPositionY());
                return false;
            }
            var cell = new Cell(cellCoord);
            EnsureGridLoadedForActiveObject(cell, player);
            AddToGrid(player, cell);

            Contract.Assert(player.GetMap() == this);
            player.SetMap(this);
            player.AddToWorld();

            if (initPlayer)
                SendInitSelf(player);

            SendInitTransports(player);
            SendZoneDynamicInfo(player);

            if (initPlayer)
                player.m_clientGUIDs.Clear();

            player.UpdateObjectVisibility(false);
            player.SendUpdatePhasing();

            if (player.IsAlive())
                ConvertCorpseToBones(player.GetGUID());

            m_activePlayers.Add(player);

            Global.ScriptMgr.OnPlayerEnterMap(this, player);
            return true;
        }

        void InitializeObject(WorldObject obj)
        {
            if (!obj.IsTypeId(TypeId.Unit) || !obj.IsTypeId(TypeId.GameObject))
                return;
            obj._moveState = ObjectCellMoveState.None;
        }

        public bool AddToMap(WorldObject obj)
        {
            //TODO: Needs clean up. An object should not be added to map twice.
            if (obj.IsInWorld)
            {
                obj.UpdateObjectVisibility(true);
                return true;
            }

            CellCoord cellCoord = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY());
            if (!cellCoord.IsCoordValid())
            {
                Log.outError(LogFilter.Maps,
                    "Map.Add: Object {0} has invalid coordinates X:{1} Y:{2} grid cell [{3}:{4}]", obj.GetGUID(),
                    obj.GetPositionX(), obj.GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
                return false; //Should delete object
            }

            var cell = new Cell(cellCoord);
            if (obj.isActiveObject())
                EnsureGridLoadedForActiveObject(cell, obj);
            else
                EnsureGridCreated(new GridCoord(cell.GetGridX(), cell.GetGridY()));
            AddToGrid(obj, cell);
            Log.outDebug(LogFilter.Maps, "Object {0} enters grid[{1}, {2}]", obj.GetGUID().ToString(), cell.GetGridX(), cell.GetGridY());

            obj.AddToWorld();

            InitializeObject(obj);

            if (obj.isActiveObject())
                AddToActive(obj);

            obj.RebuildTerrainSwaps();

            //something, such as vehicle, needs to be update immediately
            //also, trigger needs to cast spell, if not update, cannot see visual
            obj.UpdateObjectVisibilityOnCreate();
            return true;
        }

        public bool AddToMap(Transport obj)
        {
            //TODO: Needs clean up. An object should not be added to map twice.
            if (obj.IsInWorld)
                return true;

            CellCoord cellCoord = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY());
            if (!cellCoord.IsCoordValid())
            {
                Log.outError(LogFilter.Maps,
                    "Map.Add: Object {0} has invalid coordinates X:{1} Y:{2} grid cell [{3}:{4}]", obj.GetGUID(),
                    obj.GetPositionX(), obj.GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
                return false; //Should delete object
            }

            obj.AddToWorld();
            _transports.Add(obj);

            // Broadcast creation to players
            foreach (Player player in GetPlayers())
            {
                if (player.GetTransport() != obj)
                {
                    var data = new UpdateData(GetId());
                    obj.BuildCreateUpdateBlockForPlayer(data, player);
                    UpdateObject packet;
                    data.BuildPacket(out packet);
                    player.SendPacket(packet);
                }
            }

            return true;
        }

        public bool IsGridLoaded(float x, float y)
        {
            return IsGridLoaded(GridDefines.ComputeGridCoord(x, y));
        }

        public bool IsGridLoaded(GridCoord p)
        {
            return (getGrid(p.x_coord, p.y_coord) != null && isGridObjectDataLoaded(p.x_coord, p.y_coord));
        }

        void VisitNearbyCellsOf(WorldObject obj, Visitor gridVisitor, Visitor 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 (uint x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x)
            {
                for (uint 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
                    uint cell_id = (y * MapConst.TotalCellsPerMap) + x;
                    if (isCellMarked(cell_id))
                        continue;

                    markCell(cell_id);
                    var pair = new CellCoord(x, y);
                    var cell = new Cell(pair);
                    cell.SetNoCreate();
                    Visit(cell, gridVisitor);
                    Visit(cell, worldVisitor);
                }
            }
        }

        public virtual void Update(uint diff)
        {
            _dynamicTree.update(diff);
            
            // update worldsessions for existing players
            foreach (Player player in m_activePlayers.ToList())
            {
                if (player.IsInWorld)
                {
                    WorldSession session = player.GetSession();
                    var updater = new MapSessionFilter(session);
                    session.Update(diff, updater);
                }
            }
            // update active cells around players and active objects
            resetMarkedCells();
            
            var update = new UpdaterNotifier(diff);

            var grid_object_update = new Visitor(update, GridMapTypeMask.AllGrid);
            var world_object_update = new Visitor(update, GridMapTypeMask.AllWorld);

            foreach (Player player in m_activePlayers.ToList())
            {
                if (!player.IsInWorld)
                    continue;
                
                // update players at tick
                player.Update(diff);
                
                VisitNearbyCellsOf(player, grid_object_update, world_object_update);

                // If player is using far sight, visit that object too
                WorldObject viewPoint = player.GetViewpoint();
                if (viewPoint)
                {
                    if (viewPoint.IsTypeId(TypeId.Unit))
                        VisitNearbyCellsOf(viewPoint.ToCreature(), grid_object_update, world_object_update);
                    else if (viewPoint.IsTypeId(TypeId.DynamicObject))
                        VisitNearbyCellsOf(viewPoint.ToDynamicObject(), grid_object_update, world_object_update);
                }

                // Handle updates for creatures in combat with player and are more than 60 yards away
                if (player.IsInCombat())
                {
                    List<Creature> updateList = new List<Creature>();
                    HostileReference refe = player.getHostileRefManager().getFirst();

                    while (refe != null)
                    {
                        Unit unit = refe.GetSource().GetOwner();
                        if (unit)
                            if (unit.ToCreature() && unit.GetMapId() == player.GetMapId() && !unit.IsWithinDistInMap(player, GetVisibilityRange(), false))
                                updateList.Add(unit.ToCreature());

                        refe = refe.next();
                    }

                    // Process deferred update list for player
                    foreach (Creature c in updateList)
                        VisitNearbyCellsOf(c, grid_object_update, world_object_update);
                }
            }
       
            foreach (WorldObject obj in m_activeNonPlayers.ToList())
            {
                if (!obj.IsInWorld)
                    continue;

                VisitNearbyCellsOf(obj, grid_object_update, world_object_update);
            }
            
            foreach (Transport transport in _transports.ToList())
            {
                if (!transport || !transport.IsInWorld)
                    continue;

                transport.Update(diff);
            }
            
            SendObjectUpdates();

            // Process necessary scripts
            if (!m_scriptSchedule.Empty())
            {
                i_scriptLock = true;
                ScriptsProcess();
                i_scriptLock = false;
            }
            
            MoveAllCreaturesInMoveList();
            MoveAllGameObjectsInMoveList();
            MoveAllAreaTriggersInMoveList();

            if (!m_activePlayers.Empty() || !m_activeNonPlayers.Empty())
                ProcessRelocationNotifies(diff);

            Global.ScriptMgr.OnMapUpdate(this, diff);
        }

        void ProcessRelocationNotifies(uint diff)
        {
            for (uint x = 0; x < MapConst.MaxGrids; ++x)
            {
                for (uint y = 0; y < MapConst.MaxGrids; ++y)
                {
                    Grid grid = getGrid(x, y);
                    if (grid == null)
                        continue;

                    if (grid.GetGridState() != GridState.Active)
                        continue;

                    grid.getGridInfoRef().getRelocationTimer().TUpdate((int)diff);
                    if (!grid.getGridInfoRef().getRelocationTimer().TPassed())
                        continue;

                    uint gx = grid.getX();
                    uint gy = grid.getY();

                    var cell_min = new CellCoord(gx * MapConst.MaxCells, gy * MapConst.MaxCells);
                    var cell_max = new CellCoord(cell_min.x_coord + MapConst.MaxCells, cell_min.y_coord + MapConst.MaxCells);

                    for (uint xx = cell_min.x_coord; xx < cell_max.x_coord; ++xx)
                    {
                        for (uint yy = cell_min.y_coord; yy < cell_max.y_coord; ++yy)
                        {
                            uint cell_id = (yy * MapConst.TotalCellsPerMap) + xx;
                            if (!isCellMarked(cell_id))
                                continue;

                            var pair = new CellCoord(xx, yy);
                            var cell = new Cell(pair);
                            cell.SetNoCreate();

                            var cell_relocation = new DelayedUnitRelocation(cell, pair, this, SharedConst.MaxVisibilityDistance);
                            var grid_object_relocation = new Visitor(cell_relocation, GridMapTypeMask.AllGrid);
                            var world_object_relocation = new Visitor(cell_relocation, GridMapTypeMask.AllWorld);

                            Visit(cell, grid_object_relocation);
                            Visit(cell, world_object_relocation);
                        }
                    }
                }
            }
            var reset = new ResetNotifier();
            var grid_notifier = new Visitor(reset, GridMapTypeMask.AllGrid);
            var world_notifier = new Visitor(reset, GridMapTypeMask.AllWorld);

            for (uint x = 0; x < MapConst.MaxGrids; ++x)
            {
                for (uint y = 0; y < MapConst.MaxGrids; ++y)
                {
                    Grid grid = getGrid(x, y);
                    if (grid == null)
                        continue;

                    if (grid.GetGridState() != GridState.Active)
                        continue;

                    if (!grid.getGridInfoRef().getRelocationTimer().TPassed())
                        continue;

                    grid.getGridInfoRef().getRelocationTimer().TReset((int)diff, m_VisibilityNotifyPeriod);

                    uint gx = grid.getX();
                    uint gy = grid.getY();

                    var cell_min = new CellCoord(gx * MapConst.MaxCells, gy * MapConst.MaxCells);
                    var cell_max = new CellCoord(cell_min.x_coord + MapConst.MaxCells,
                        cell_min.y_coord + MapConst.MaxCells);

                    for (uint xx = cell_min.x_coord; xx < cell_max.x_coord; ++xx)
                    {
                        for (uint yy = cell_min.y_coord; yy < cell_max.y_coord; ++yy)
                        {
                            uint cell_id = (yy * MapConst.TotalCellsPerMap) + xx;
                            if (!isCellMarked(cell_id))
                                continue;

                            var pair = new CellCoord(xx, yy);
                            var cell = new Cell(pair);
                            cell.SetNoCreate();
                            Visit(cell, grid_notifier);
                            Visit(cell, world_notifier);
                        }
                    }
                }
            }
        }

        public virtual void RemovePlayerFromMap(Player player, bool remove)
        {
            Global.ScriptMgr.OnPlayerLeaveMap(this, player);

            player.RemoveFromWorld();
            SendRemoveTransports(player);

            player.UpdateObjectVisibility(true);
            Cell cell = player.GetCurrentCell();
            RemoveFromGrid(player, cell);

            m_activePlayers.Remove(player);

            if (remove)
                DeleteFromWorld(player);
        }

        public void RemoveFromMap(WorldObject obj, bool remove)
        {
            obj.RemoveFromWorld();
            if (obj.isActiveObject())
                RemoveFromActive(obj);

            obj.UpdateObjectVisibility(true);
            Cell cell = obj.GetCurrentCell();
            RemoveFromGrid(obj, cell);

            obj.ResetMap();

            if (remove)
            {
                // if option set then object already saved at this moment
                if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately))
                    obj.SaveRespawnTime();
                DeleteFromWorld(obj);
            }
        }

        public void RemoveFromMap(Transport obj, bool remove)
        {
            obj.RemoveFromWorld();

            var players = GetPlayers();
            if (!players.Empty())
            {
                UpdateData data = new UpdateData(GetId());
                obj.BuildOutOfRangeUpdateBlock(data);
                UpdateObject packet;
                data.BuildPacket(out packet);
                packet.Write();
                foreach (var pl in players)
                    if (pl.GetTransport() != obj)
                        pl.SendPacket(packet, false);
            }

            if (!_transports.Contains(obj))
                return;

            _transports.Remove(obj);

            obj.ResetMap();
            if (remove)
            {
                // if option set then object already saved at this moment
                if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately))
                    obj.SaveRespawnTime();
                DeleteFromWorld(obj);
            }
        }

        public void PlayerRelocation(Player player, float x, float y, float z, float orientation)
        {
            var oldcell = new Cell(player.GetPositionX(), player.GetPositionY());
            var newcell = new Cell(x, y);

            //! If hovering, always increase our server-side Z position
            //! Client automatically projects correct position based on Z coord sent in monster move
            //! and UNIT_FIELD_HOVERHEIGHT sent in object updates
            if (player.HasUnitMovementFlag(MovementFlag.Hover))
                z += player.GetFloatValue(UnitFields.HoverHeight);

            player.Relocate(x, y, z, orientation);
            if (player.IsVehicle())
                player.GetVehicleKit().RelocatePassengers();

            if (oldcell.DiffGrid(newcell) || oldcell.DiffCell(newcell))
            {
                Log.outDebug(LogFilter.Maps,
                    "Player {0} relocation grid[{1}, {2}]cell[{3}, {4}].grid[{5}, {6}]cell[{7}, {8}]",
                    player.GetName(), oldcell.GetGridX(), oldcell.GetGridY(), oldcell.GetCellX(), oldcell.GetCellY(),
                    newcell.GetGridX(), newcell.GetGridY(), newcell.GetCellX(), newcell.GetCellY());

                RemoveFromGrid(player, oldcell);
                if (oldcell.DiffGrid(newcell))
                    EnsureGridLoadedForActiveObject(newcell, player);

                AddToGrid(player, newcell);
            }

            player.UpdateObjectVisibility(false);
        }

        public void CreatureRelocation(Creature creature, float x, float y, float z, float ang, bool respawnRelocationOnFail = true)
        {
            CheckGridIntegrity(creature, false);

            Cell old_cell = creature.GetCurrentCell();
            var new_cell = new Cell(x, y);

            if (!respawnRelocationOnFail && getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null)
                return;

            //! If hovering, always increase our server-side Z position
            //! Client automatically projects correct position based on Z coord sent in monster move
            //! and UNIT_FIELD_HOVERHEIGHT sent in object updates
            if (creature.HasUnitMovementFlag(MovementFlag.Hover))
                z += creature.GetFloatValue(UnitFields.HoverHeight);

            // delay creature move for grid/cell to grid/cell moves
            if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
            {
                AddCreatureToMoveList(creature, x, y, z, ang);
                // in diffcell/diffgrid case notifiers called at finishing move creature in MoveAllCreaturesInMoveList
            }
            else
            {
                creature.Relocate(x, y, z, ang);
                if (creature.IsVehicle())
                    creature.GetVehicleKit().RelocatePassengers();
                creature.UpdateObjectVisibility(false);
                RemoveCreatureFromMoveList(creature);
            }

            CheckGridIntegrity(creature, true);
        }

        public void GameObjectRelocation(GameObject go, float x, float y, float z, float orientation, bool respawnRelocationOnFail = true)
        {
            var integrity_check = new Cell(go.GetPositionX(), go.GetPositionY());
            Cell old_cell = go.GetCurrentCell();

            var new_cell = new Cell(x, y);
            if (!respawnRelocationOnFail && getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null)
                return;

            // delay creature move for grid/cell to grid/cell moves
            if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
            {
                Log.outDebug(LogFilter.Maps,
                    "GameObject (GUID: {0} Entry: {1}) added to moving list from grid[{2}, {3}]cell[{4}, {5}] to grid[{6}, {7}]cell[{8}, {9}].",
                    go.GetGUID().ToString(), go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(),
                    old_cell.GetCellY(), new_cell.GetGridX(), new_cell.GetGridY(), new_cell.GetCellX(),
                    new_cell.GetCellY());
                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.UpdateModelPosition();
                go.UpdateObjectVisibility(false);
                RemoveGameObjectFromMoveList(go);
            }

            old_cell = go.GetCurrentCell();
            integrity_check = new Cell(go.GetPositionX(), go.GetPositionY());
        }

        public void DynamicObjectRelocation(DynamicObject dynObj, float x, float y, float z, float orientation)
        {
            Cell integrity_check = new Cell(dynObj.GetPositionX(), dynObj.GetPositionY());
            Cell old_cell = dynObj.GetCurrentCell();

            Contract.Assert(integrity_check == old_cell);
            Cell new_cell = new Cell(x, y);

            if (getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null)
                return;

            // delay creature move for grid/cell to grid/cell moves
            if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
            {

                Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) added to moving list from grid[{1}, {2}]cell[{3}, {4}] to grid[{5}, {6}]cell[{7}, {8}].",
                    dynObj.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetGridX(), new_cell.GetGridY(), new_cell.GetCellX(), new_cell.GetCellY());

                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.UpdateObjectVisibility(false);
                RemoveDynamicObjectFromMoveList(dynObj);
            }

            old_cell = dynObj.GetCurrentCell();
            integrity_check = new Cell(dynObj.GetPositionX(), dynObj.GetPositionY());
            Contract.Assert(integrity_check == old_cell);
        }

        public void AreaTriggerRelocation(AreaTrigger at, float x, float y, float z, float orientation)
        {
            Cell integrity_check = new Cell(at.GetPositionX(), at.GetPositionY());
            Cell old_cell = at.GetCurrentCell();

            Contract.Assert(integrity_check == old_cell);
            Cell new_cell = new Cell(x, y);

            if (getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null)
                return;

            // delay areatrigger move for grid/cell to grid/cell moves
            if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell))
            {
                Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) added to moving list from {1} to {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString());

                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);
            }

            old_cell = at.GetCurrentCell();
            integrity_check = new Cell(at.GetPositionX(), at.GetPositionY());
            Contract.Assert(integrity_check == old_cell);
        }

        void AddCreatureToMoveList(Creature c, float x, float y, float z, float ang)
        {
            if (_creatureToMoveLock) //can this happen?
                return;

            if (c._moveState == ObjectCellMoveState.None)
                creaturesToMove.Add(c);
            c.SetNewCellPosition(x, y, z, ang);
        }

        void AddGameObjectToMoveList(GameObject go, float x, float y, float z, float ang)
        {
            if (_gameObjectsToMoveLock) //can this happen?
                return;

            if (go._moveState == ObjectCellMoveState.None)
                _gameObjectsToMove.Add(go);
            go.SetNewCellPosition(x, y, z, ang);
        }

        void RemoveGameObjectFromMoveList(GameObject go)
        {
            if (_gameObjectsToMoveLock) //can this happen?
                return;

            if (go._moveState == ObjectCellMoveState.Active)
                go._moveState = ObjectCellMoveState.Inactive;
        }

        void RemoveCreatureFromMoveList(Creature c)
        {
            if (_creatureToMoveLock) //can this happen?
                return;

            if (c._moveState == ObjectCellMoveState.Active)
                c._moveState = ObjectCellMoveState.Inactive;
        }

        void AddDynamicObjectToMoveList(DynamicObject dynObj, float x, float y, float z, float ang)
        {
            if (_dynamicObjectsToMoveLock) //can this happen?
                return;

            if (dynObj._moveState == ObjectCellMoveState.None)
                _dynamicObjectsToMove.Add(dynObj);
            dynObj.SetNewCellPosition(x, y, z, ang);
        }

        void RemoveDynamicObjectFromMoveList(DynamicObject dynObj)
        {
            if (_dynamicObjectsToMoveLock) //can this happen?
                return;

            if (dynObj._moveState == ObjectCellMoveState.Active)
                dynObj._moveState = ObjectCellMoveState.Inactive;
        }

        void AddAreaTriggerToMoveList(AreaTrigger at, float x, float y, float z, float ang)
        {
            if (_areaTriggersToMoveLock) //can this happen?
                return;

            if (at._moveState == ObjectCellMoveState.None)
                _areaTriggersToMove.Add(at);
            at.SetNewCellPosition(x, y, z, ang);
        }

        void RemoveAreaTriggerFromMoveList(AreaTrigger at)
        {
            if (_areaTriggersToMoveLock) //can this happen?
                return;

            if (at._moveState == ObjectCellMoveState.Active)
                at._moveState = ObjectCellMoveState.Inactive;
        }

        void MoveAllCreaturesInMoveList()
        {
            _creatureToMoveLock = true;
            foreach (Creature creature in creaturesToMove.ToList())
            {
                if (creature.GetMap() != this) //pet is teleported to another map
                    continue;

                if (creature._moveState != ObjectCellMoveState.Active)
                {
                    creature._moveState = ObjectCellMoveState.None;
                    continue;
                }

                creature._moveState = ObjectCellMoveState.None;
                if (!creature.IsInWorld)
                    continue;

                // do move or do move to respawn or remove creature if previous all fail
                if (CreatureCellRelocation(creature, new Cell(creature._newPosition.posX, creature._newPosition.posY)))
                {
                    // update pos
                    creature.Relocate(creature._newPosition);
                    if (creature.IsVehicle())
                        creature.GetVehicleKit().RelocatePassengers();
                    creature.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(creature, false))
                    {
                        // ... or unload (if respawn grid also not loaded)
                        //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 (creature.IsPet())
                            ((Pet)creature).Remove(PetSaveMode.NotInSlot, true);
                        else
                            AddObjectToRemoveList(creature);
                    }
                }
            }
            creaturesToMove.Clear();
            _creatureToMoveLock = false;
        }

        void MoveAllGameObjectsInMoveList()
        {
            _gameObjectsToMoveLock = true;
            foreach (GameObject go in _gameObjectsToMove)
            {
                if (go.GetMap() != this) //transport is teleported to another map
                    continue;

                if (go._moveState != ObjectCellMoveState.Active)
                {
                    go._moveState = ObjectCellMoveState.None;
                    continue;
                }

                go._moveState = ObjectCellMoveState.None;
                if (!go.IsInWorld)
                    continue;

                // do move or do move to respawn or remove creature if previous all fail
                if (GameObjectCellRelocation(go, new Cell(go._newPosition.posX, go._newPosition.posY)))
                {
                    // update pos
                    go.Relocate(go._newPosition);
                    go.UpdateModelPosition();
                    go.UpdateObjectVisibility(false);
                }
                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)
                        Log.outDebug(LogFilter.Maps,
                            "GameObject (GUID: {0} Entry: {1}) cannot be move to unloaded respawn grid.",
                            go.GetGUID().ToString(), go.GetEntry());
                        AddObjectToRemoveList(go);
                    }
                }
            }
            _gameObjectsToMove.Clear();
            _gameObjectsToMoveLock = false;
        }

        void MoveAllDynamicObjectsInMoveList()
        {
            _dynamicObjectsToMoveLock = true;
            foreach (var dynObj in _dynamicObjectsToMove)
            {
                if (dynObj.GetMap() != this) //transport is teleported to another map
                    continue;

                if (dynObj._moveState != ObjectCellMoveState.Active)
                {
                    dynObj._moveState = ObjectCellMoveState.None;
                    continue;
                }

                dynObj._moveState = ObjectCellMoveState.None;
                if (!dynObj.IsInWorld)
                    continue;

                // do move or do move to respawn or remove creature if previous all fail
                if (DynamicObjectCellRelocation(dynObj, new Cell(dynObj._newPosition.posX, dynObj._newPosition.posY)))
                {
                    // update pos
                    dynObj.Relocate(dynObj._newPosition);
                    dynObj.UpdateObjectVisibility(false);
                }
                else
                    Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) cannot be moved to unloaded grid.", dynObj.GetGUID().ToString());
            }

            _dynamicObjectsToMove.Clear();
            _dynamicObjectsToMoveLock = false;
        }

        void MoveAllAreaTriggersInMoveList()
        {
            _areaTriggersToMoveLock = true;
            foreach (AreaTrigger at in _areaTriggersToMove)
            {
                if (at.GetMap() != this) //transport is teleported to another map
                    continue;

                if (at._moveState != ObjectCellMoveState.Active)
                {
                    at._moveState = ObjectCellMoveState.None;
                    continue;
                }

                at._moveState = ObjectCellMoveState.None;
                if (!at.IsInWorld)
                    continue;

                // do move or do move to respawn or remove creature if previous all fail
                if (AreaTriggerCellRelocation(at, new Cell(at._newPosition.posX, at._newPosition.posY)))
                {
                    // update pos
                    at.Relocate(at._newPosition);
                    at.UpdateObjectVisibility(false);
                }
                else
                {
                    Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) cannot be moved to unloaded grid.", at.GetGUID().ToString());
                }
            }

            _areaTriggersToMove.Clear();
            _areaTriggersToMoveLock = false;
        }

        private bool CreatureCellRelocation(Creature c, Cell new_cell)
        {
            Cell old_cell = c.GetCurrentCell();
            if (!old_cell.DiffGrid(new_cell)) // in same grid
            {
                // if in same cell then none do
                if (old_cell.DiffCell(new_cell))
                {
                    RemoveFromGrid(c, old_cell);
                    AddToGrid(c, new_cell);
                }

                return true;
            }

            // in diff. grids but active creature
            if (c.isActiveObject())
            {
                EnsureGridLoadedForActiveObject(new_cell, c);

                Log.outDebug(LogFilter.Maps,
                    "Active creature (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to grid[{4}, {5}].",
                    c.GetGUID().ToString(), c.GetEntry(), old_cell.GetGridX(),
                    old_cell.GetGridY(), new_cell.GetGridX(), new_cell.GetGridY());
                RemoveFromGrid(c, old_cell);
                AddToGrid(c, new_cell);

                return true;
            }

            // in diff. loaded grid normal creature
            var grid = new GridCoord(new_cell.GetGridX(), new_cell.GetGridY());
            if (IsGridLoaded(grid))
            {
                RemoveFromGrid(c, old_cell);
                EnsureGridCreated(grid);
                AddToGrid(c, new_cell);
                return true;
            }

            // fail to move: normal creature attempt move to unloaded grid
            return false;
        }

        private bool GameObjectCellRelocation(GameObject go, Cell new_cell)
        {
            Cell old_cell = go.GetCurrentCell();
            if (!old_cell.DiffGrid(new_cell)) // in same grid
            {
                // if in same cell then none do
                if (old_cell.DiffCell(new_cell))
                {
                    RemoveFromGrid(go, old_cell);
                    AddToGrid(go, new_cell);
                }

                return true;
            }

            // in diff. grids but active GameObject
            if (go.isActiveObject())
            {
                EnsureGridLoadedForActiveObject(new_cell, go);

                Log.outDebug(LogFilter.Maps,
                    "Active GameObject (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to grid[{4}, {5}].",
                    go.GetGUID().ToString(), go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), new_cell.GetGridX(),
                    new_cell.GetGridY());

                RemoveFromGrid(go, old_cell);
                AddToGrid(go, new_cell);

                return true;
            }

            // in diff. loaded grid normal GameObject
            if (IsGridLoaded(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY())))
            {
                Log.outDebug(LogFilter.Maps,
                    "GameObject (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to grid[{4}, {5}].", go.GetGUID().ToString(),
                    go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), new_cell.GetGridX(), new_cell.GetGridY());

                RemoveFromGrid(go, old_cell);
                EnsureGridCreated(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY()));
                AddToGrid(go, new_cell);

                return true;
            }

            // fail to move: normal GameObject attempt move to unloaded grid
            Log.outDebug(LogFilter.Maps,
                "GameObject (GUID: {0} Entry: {1}) attempted to move from grid[{2}, {3}] to unloaded grid[{4}, {5}].",
                go.GetGUID().ToString(), go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), new_cell.GetGridX(),
                new_cell.GetGridY());
            return false;
        }

        private bool DynamicObjectCellRelocation(DynamicObject go, Cell new_cell)
        {
            Cell old_cell = go.GetCurrentCell();
            if (!old_cell.DiffGrid(new_cell))                       // in same grid
            {
                // if in same cell then none do
                if (old_cell.DiffCell(new_cell))
                {
                    Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) moved in grid[{1}, {2}] from cell[{3}, {4}] to cell[{5}, {6}].", go.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetCellX(), new_cell.GetCellY());

                    RemoveFromGrid(go, old_cell);
                    AddToGrid(go, new_cell);
                }
                else
                    Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) moved in same {1}.", go.GetGUID().ToString(), old_cell.GetGridCellString());

                return true;
            }

            // in diff. grids but active GameObject
            if (go.isActiveObject())
            {
                EnsureGridLoadedForActiveObject(new_cell, go);

                Log.outDebug(LogFilter.Maps, "Active DynamicObject (GUID: {0}) moved from {1} to {2}.", go.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString());

                RemoveFromGrid(go, old_cell);
                AddToGrid(go, new_cell);

                return true;
            }

            // in diff. loaded grid normal GameObject
            if (IsGridLoaded(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY())))
            {
                Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) moved from {1} to {2}.", go.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString());

                RemoveFromGrid(go, old_cell);
                EnsureGridCreated(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY()));
                AddToGrid(go, new_cell);

                return true;
            }

            // fail to move: normal GameObject attempt move to unloaded grid
            Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) attempted to move from {1} to unloaded {2}.", go.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString());
            return false;
        }

        bool AreaTriggerCellRelocation(AreaTrigger at, Cell new_cell)
        {
            Cell old_cell = at.GetCurrentCell();
            if (!old_cell.DiffGrid(new_cell))                       // in same grid
            {
                // if in same cell then none do
                if (old_cell.DiffCell(new_cell))
                {
                    Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in grid[{0}, {1}] from cell[{2}, {3}] to cell[{4}, {5}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), 
                        old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetCellX(), new_cell.GetCellY());

                    RemoveFromGrid(at, old_cell);
                    AddToGrid(at, new_cell);
                }
                else
                {
                    Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in same grid[{1}, {2}]cell[{3}, {4}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), old_cell.GetCellY());
                }

                return true;
            }

            // in diff. grids but active AreaTrigger
            if (at.isActiveObject())
            {
                EnsureGridLoadedForActiveObject(new_cell, at);

                Log.outDebug(LogFilter.Maps, "Active AreaTrigger ({0}) moved from {1} to {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString());

                RemoveFromGrid(at, old_cell);
                AddToGrid(at, new_cell);

                return true;
            }

            // in diff. loaded grid normal AreaTrigger
            if (IsGridLoaded(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY())))
            {
                Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved from {1} to {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString());

                RemoveFromGrid(at, old_cell);
                EnsureGridCreated(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY()));
                AddToGrid(at, new_cell);

                return true;
            }

            // fail to move: normal AreaTrigger attempt move to unloaded grid
            Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) attempted to move from {1} to unloaded {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString());
            return false;
        }

        public bool CreatureRespawnRelocation(Creature c, bool diffGridOnly)
        {
            float resp_x, resp_y, resp_z, resp_o;
            c.GetRespawnPosition(out resp_x, out resp_y, out resp_z, out resp_o);
            var resp_cell = new 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();

            // 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
                c.UpdateObjectVisibility(false);
                return true;
            }

            return false;
        }

        public bool GameObjectRespawnRelocation(GameObject go, bool diffGridOnly)
        {
            float resp_x, resp_y, resp_z, resp_o;
            go.GetRespawnPosition(out resp_x, out resp_y, out resp_z, out resp_o);
            var resp_cell = new Cell(resp_x, resp_y);

            //GameObject will be unloaded with grid
            if (diffGridOnly && !go.GetCurrentCell().DiffGrid(resp_cell))
                return true;

            Log.outDebug(LogFilter.Maps,
                "GameObject (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to respawn grid[{4}, {5}].",
                go.GetGUID().ToString(), go.GetEntry(), go.GetCurrentCell().GetGridX(), go.GetCurrentCell().GetGridY(),
                resp_cell.GetGridX(), resp_cell.GetGridY());

            // 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.UpdateObjectVisibility(false);
                return true;
            }
            return false;
        }

        public bool UnloadGrid(Grid grid, bool unloadAll)
        {
            uint x = grid.getX();
            uint y = grid.getY();

            if (!unloadAll)
            {
                //pets, possessed creatures (must be active), transport passengers
                if (grid.GetWorldObjectCountInNGrid<Creature>() != 0)
                    return false;

                if (ActiveObjectsNearGrid(grid))
                    return false;
            }

            Log.outDebug(LogFilter.Maps, "Unloading grid[{0}, {1}] for map {2}", 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 = new ObjectGridEvacuator();
                var visitor = new Visitor(worker, GridMapTypeMask.AllGrid);
                grid.VisitAllGrids(visitor);

                // Finish creature moves, remove and delete all creatures with delayed remove before unload
                MoveAllCreaturesInMoveList();
                MoveAllGameObjectsInMoveList();
                MoveAllAreaTriggersInMoveList();
            }

            {
                ObjectGridCleaner worker = new ObjectGridCleaner();
                var visitor = new Visitor(worker, GridMapTypeMask.AllGrid);
                grid.VisitAllGrids(visitor);
            }

            RemoveAllObjectsInRemoveList();

            {
                ObjectGridUnloader worker = new ObjectGridUnloader();
                var visitor = new Visitor(worker, GridMapTypeMask.AllGrid);
                grid.VisitAllGrids(visitor);
            }

            Contract.Assert(i_objectsToRemove.Empty());
            setGrid(null, x, y);

            uint gx = (MapConst.MaxGrids - 1) - x;
            uint gy = (MapConst.MaxGrids - 1) - y;

            {
                if (i_InstanceId == 0)
                {
                    if (GridMaps[gx][gy] != null)
                    {
                        GridMaps[gx][gy].unloadData();
                    }
                    Global.VMapMgr.unloadMap(GetId(), gx, gy);
                    Global.MMapMgr.unloadMap(GetId(), gx, gy);
                }
                else
                    ((MapInstanced)m_parentMap).RemoveGridMapReference(new GridCoord(gx, gy));

                GridMaps[gx][gy] = null;
            }
            Log.outDebug(LogFilter.Maps, "Unloading grid[{0}, {1}] for map {2} finished", x, y, GetId());
            return true;
        }

        public virtual void RemoveAllPlayers()
        {
            if (HavePlayers())
            {
                foreach (Player pl in m_activePlayers)
                {
                    if (!pl.IsBeingTeleportedFar())
                    {
                        // this is happening for bg
                        Log.outError(LogFilter.Maps,
                            "Map.UnloadAll: player {0} is still in map {1} during unload, this should not happen!",
                            pl.GetName(), GetId());
                        pl.TeleportTo(pl.GetHomebind());
                    }
                }
            }
        }

        public virtual void UnloadAll()
        {
            // clear all delayed moves, useless anyway do this moves before map unload.
            creaturesToMove.Clear();
            _gameObjectsToMove.Clear();

            for (uint x = 0; x < MapConst.MaxGrids; ++x)
            {
                for (uint y = 0; y < MapConst.MaxGrids; ++y)
                {
                    var grid = getGrid(x, y);
                    if (grid == null)
                        continue;

                    UnloadGrid(grid, true); // deletes the grid and removes it from the GridRefManager
                }
            }

            foreach (Transport transport in _transports)
                RemoveFromMap(transport, true);

            _transports.Clear();

            foreach (var corpse in _corpsesByCell.Values.ToList())
            {
                corpse.RemoveFromWorld();
                corpse.ResetMap();
                corpse.Dispose();
            }

            _corpsesByCell.Clear();
            _corpsesByPlayer.Clear();
            _corpseBones.Clear();

            Global.ScriptMgr.OnDestroyMap(this);

            foreach (WorldObject obj in i_worldObjects.ToList())
            {
                Contract.Assert(obj.IsWorldObject());
                obj.RemoveFromWorld();
                obj.ResetMap();
            }

            if (!m_scriptSchedule.Empty())
                Global.MapMgr.DecreaseScheduledScriptCount((uint)m_scriptSchedule.Count);

            Global.MMapMgr.unloadMapInstance(GetId(), i_InstanceId);
        }

        private GridMap GetGridMap(float x, float y)
        {
            // half opt method
            var gx = (uint)(32 - x / MapConst.SizeofGrids); //grid x
            var gy = (uint)(32 - y / MapConst.SizeofGrids); //grid y

            // ensure GridMap is loaded
            EnsureGridCreated(new GridCoord(63 - gx, 63 - gy));

            return GridMaps[gx][gy];
        }

        public float GetWaterOrGroundLevel(List<uint> phases, float x, float y, float z)
        {
            float ground = 0f;
            return GetWaterOrGroundLevel(phases, x, y, z, ref ground);
        }

        public float GetWaterOrGroundLevel(List<uint> phases, float x, float y, float z, ref float ground, bool swim = false)
        {
            if (GetGridMap(x, y) != null)
            {
                // we need ground level (including grid height version) for proper return water level in point
                float ground_z = GetHeight(phases, x, y, z, true, 50.0f);
                ground = ground_z;

                LiquidData liquid_status;

                ZLiquidStatus res = getLiquidStatus(x, y, ground_z, MapConst.MapAllLiquidTypes, out liquid_status);
                return res != ZLiquidStatus.NoWater ? liquid_status.level : ground_z;
            }

            return MapConst.VMAPInvalidHeightValue;
        }

        public float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = MapConst.DefaultHeightSearch)
        {
            // find raw .map surface under Z coordinates
            float mapHeight = MapConst.VMAPInvalidHeightValue;
            GridMap gmap = GetGridMap(x, y);
            if (gmap != null)
            {
                float gridHeight = gmap.getHeight(x, y);
                // look from a bit higher pos to find the floor, ignore under surface case
                if (z + 2.0f > gridHeight)
                    mapHeight = gridHeight;
            }

            float vmapHeight = MapConst.VMAPInvalidHeightValue;
            if (checkVMap)
            {
                if (Global.VMapMgr.isHeightCalcEnabled())
                    vmapHeight = Global.VMapMgr.getHeight(GetId(), x, y, z + 2.0f, maxSearchDist);
                // look from a bit higher pos to find the floor
            }

            // mapHeight set for any above raw ground Z or <= INVALID_HEIGHT
            // vmapheight set for any under Z value or <= INVALID_HEIGHT
            if (vmapHeight > MapConst.InvalidHeight)
            {
                if (mapHeight > MapConst.InvalidHeight)
                {
                    // we have mapheight and vmapheight and must select more appropriate

                    // vmap height above map height
                    // or if the distance of the vmap height is less the land height distance
                    if (vmapHeight > mapHeight || Math.Abs(mapHeight - z) > Math.Abs(vmapHeight - z))
                        return vmapHeight;
                    return mapHeight; // better use .map surface height
                }
                return vmapHeight; // we have only vmapHeight (if have)
            }

            return mapHeight; // explicitly use map data
        }

        public float GetMinHeight(float x, float y)
        {
            GridMap grid = GetGridMap(x, y);
            if (grid != null)
                return grid.getMinHeight(x, y);

            return -500.0f;
        }

        private bool IsOutdoorWMO(uint mogpFlags, int adtId, int rootId, int groupId, WMOAreaTableRecord wmoEntry, AreaTableRecord atEntry)
        {
            bool outdoor = true;

            if (wmoEntry != null && atEntry != null)
            {
                if (atEntry.Flags[0].HasAnyFlag(AreaFlags.Outside))
                    return true;
                if (atEntry.Flags[0].HasAnyFlag(AreaFlags.Inside))
                    return false;
            }

            outdoor = Convert.ToBoolean(mogpFlags & 0x8);

            if (wmoEntry != null)
            {
                if (Convert.ToBoolean(wmoEntry.Flags & 4))
                    return true;
                if ((wmoEntry.Flags & 2) != 0)
                    outdoor = false;
            }
            return outdoor;
        }

        public bool IsOutdoors(float x, float y, float z)
        {
            uint mogpFlags;
            int adtId, rootId, groupId;

            // no wmo found? . outside by default
            if (!GetAreaInfo(x, y, z, out mogpFlags, out adtId, out rootId, out groupId))
                return true;

            AreaTableRecord atEntry = null;
            WMOAreaTableRecord wmoEntry = Global.DB2Mgr.GetWMOAreaTable(rootId, adtId, groupId);
            if (wmoEntry != null)
            {
                Log.outDebug(LogFilter.Maps, "Got WMOAreaTableEntry! flag {0}, areaid {1}", wmoEntry.Flags,
                    wmoEntry.AreaTableID);
                atEntry = CliDB.AreaTableStorage.LookupByKey(wmoEntry.AreaTableID);
            }
            return IsOutdoorWMO(mogpFlags, adtId, rootId, groupId, wmoEntry, atEntry);
        }

        private bool GetAreaInfo(float x, float y, float z, out uint flags, out int adtId, out int rootId, out int groupId)
        {
            flags = 0;
            adtId = 0;
            rootId = 0;
            groupId = 0;

            float vmap_z = z;
            if (Global.VMapMgr.getAreaInfo(GetId(), x, y, ref vmap_z, out flags, out adtId, out rootId, out groupId))
            {
                // check if there's terrain between player height and object height
                GridMap gmap = GetGridMap(x, y);
                if (gmap != null)
                {
                    float _mapheight = gmap.getHeight(x, y);
                    // z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice...
                    if (z + 2.0f > _mapheight && _mapheight > vmap_z)
                        return false;
                }
                return true;
            }

            return false;
        }

        public uint GetAreaId(float x, float y, float z)
        {
            bool throwaway;
            return GetAreaId(x, y, z, out throwaway);
        }

        public uint GetAreaId(float x, float y, float z, out bool isOutdoors)
        {
            uint mogpFlags;
            int adtId, rootId, groupId;
            WMOAreaTableRecord wmoEntry = null;
            AreaTableRecord atEntry = null;
            bool haveAreaInfo = false;
            uint areaId = 0;

            if (GetAreaInfo(x, y, z, out mogpFlags, out adtId, out rootId, out groupId))
            {
                haveAreaInfo = true;
                wmoEntry = Global.DB2Mgr.GetWMOAreaTable(rootId, adtId, groupId);
                if (wmoEntry != null)
                {
                    areaId = wmoEntry.AreaTableID;
                    atEntry = CliDB.AreaTableStorage.LookupByKey(wmoEntry.AreaTableID);
                }
            }

            if (areaId == 0)
            {
                GridMap gmap = GetGridMap(x, y);
                if (gmap != null)
                    areaId = gmap.getArea(x, y);

                // this used while not all *.map files generated (instances)
                if (areaId == 0)
                    areaId = i_mapRecord.AreaTableID;
            }

            if (haveAreaInfo)
                isOutdoors = IsOutdoorWMO(mogpFlags, adtId, rootId, groupId, wmoEntry, atEntry);
            else
                isOutdoors = true;

            return areaId;
        }

        public uint GetZoneId(float x, float y, float z)
        {
            uint areaId = GetAreaId(x, y, z);
            AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(areaId);
            if (area != null)
                if (area.ParentAreaID != 0)
                    return area.ParentAreaID;

            return areaId;
        }

        public void GetZoneAndAreaId(out uint zoneid, out uint areaid, float x, float y, float z)
        {
            areaid = zoneid = GetAreaId(x, y, z);
            AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(areaid);
            if (area != null)
                if (area.ParentAreaID != 0)
                    zoneid = area.ParentAreaID;
        }

        private byte GetTerrainType(float x, float y)
        {
            GridMap gmap = GetGridMap(x, y);
            if (gmap != null)
                return gmap.getTerrainType(x, y);
            return 0;
        }

        public ZLiquidStatus getLiquidStatus(float x, float y, float z, byte ReqLiquidType)
        {
            LiquidData throwaway;
            return getLiquidStatus(x, y, z, ReqLiquidType, out throwaway);
        }

        public ZLiquidStatus getLiquidStatus(float x, float y, float z, byte ReqLiquidType, out LiquidData data)
        {
            data = new LiquidData();
            var result = ZLiquidStatus.NoWater;
            float liquid_level = MapConst.InvalidHeight;
            float ground_level = MapConst.InvalidHeight;
            uint liquid_type = 0;

            if (Global.VMapMgr.GetLiquidLevel(GetId(), x, y, z, ReqLiquidType, ref liquid_level, ref ground_level, ref liquid_type))
            {
                Log.outDebug(LogFilter.Maps, "getLiquidStatus(): vmap liquid level: {0} ground: {1} type: {2}",
                    liquid_level, ground_level, liquid_type);
                // Check water level and ground level
                if (liquid_level > ground_level && z > ground_level - 2)
                {
                    // All ok in water . store data
                    // hardcoded in client like this
                    if (GetId() == 530 && liquid_type == 2)
                        liquid_type = 15;

                    uint liquidFlagType = 0;
                    LiquidTypeRecord liq = CliDB.LiquidTypeStorage.LookupByKey(liquid_type);
                    if (liq != null)
                        liquidFlagType = liq.LiquidType;

                    if (liquid_type != 0 && liquid_type < 21)
                    {
                        AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(GetAreaId(x, y, z));
                        if (area != null)
                        {
                            uint overrideLiquid = area.LiquidTypeID[liquidFlagType];
                            if (overrideLiquid == 0 && area.ParentAreaID != 0)
                            {
                                area = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID);
                                if (area != null)
                                    overrideLiquid = area.LiquidTypeID[liquidFlagType];
                            }

                            liq = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid);
                            if (liq != null)
                            {
                                liquid_type = overrideLiquid;
                                liquidFlagType = liq.LiquidType;
                            }
                        }
                    }

                    data.level = liquid_level;
                    data.depth_level = ground_level;

                    data.entry = liquid_type;
                    data.type_flags = (uint)(1 << (int)liquidFlagType);

                    float delta = liquid_level - z;

                    // Get position delta
                    if (delta > 2.0f) // Under water
                        return ZLiquidStatus.UnderWater;
                    if (delta > 0.0f) // In water
                        return ZLiquidStatus.InWater;
                    if (delta > -0.1f) // Walk on water
                        return ZLiquidStatus.WaterWalk;
                    result = ZLiquidStatus.AboveWater;
                }
            }

            GridMap gmap = GetGridMap(x, y);
            if (gmap != null)
            {
                var map_data = new LiquidData();
                ZLiquidStatus map_result = gmap.getLiquidStatus(x, y, z, ReqLiquidType, map_data);
                // Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER:
                if (map_result != ZLiquidStatus.NoWater && (map_data.level > ground_level))
                {
                    // hardcoded in client like this
                    if (GetId() == 530 && map_data.entry == 2)
                        map_data.entry = 15;

                    data = map_data;

                    return map_result;
                }
            }
            return result;
        }

        public float GetWaterLevel(float x, float y)
        {
            GridMap gmap = GetGridMap(x, y);
            if (gmap != null)
                return gmap.getLiquidLevel(x, y);
            return 0;
        }

        public bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, List<uint> phases)
        {
            return Global.VMapMgr.isInLineOfSight(GetId(), x1, y1, z1, x2, y2, z2)
                   && _dynamicTree.isInLineOfSight(new Vector3(x1, y1, z1), new Vector3(x2, y2, z2), phases);
        }

        public bool getObjectHitPos(List<uint> phases, float x1, float y1, float z1, float x2, float y2, float z2, out float rx, out float ry, out float rz, float modifyDist)
        {
            var startPos = new Vector3(x1, y1, z1);
            var dstPos = new Vector3(x2, y2, z2);

            var resultPos = new Vector3();
            bool result = _dynamicTree.getObjectHitPos(phases, startPos, dstPos, ref resultPos, modifyDist);

            rx = resultPos.X;
            ry = resultPos.Y;
            rz = resultPos.Z;
            return result;
        }

        public float GetHeight(List<uint> phases, float x, float y, float z, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch)
        {
            return Math.Max(GetHeight(x, y, z, vmap, maxSearchDist), _dynamicTree.getHeight(x, y, z, maxSearchDist, phases));
        }

        public bool IsInWater(float x, float y, float pZ)
        {
            return Convert.ToBoolean(getLiquidStatus(x, y, pZ, MapConst.MapAllLiquidTypes) &
                                  (ZLiquidStatus.InWater | ZLiquidStatus.UnderWater));
        }

        public bool IsUnderWater(float x, float y, float z)
        {
            return
                Convert.ToBoolean(getLiquidStatus(x, y, z, MapConst.MapLiquidTypeWater | MapConst.MapLiquidTypeOcean) &
                                  ZLiquidStatus.UnderWater);
        }

        private bool CheckGridIntegrity(Creature c, bool moved)
        {
            Cell cur_cell = c.GetCurrentCell();
            var xy_cell = new Cell(c.GetPositionX(), c.GetPositionY());
            if (xy_cell != cur_cell)
                return true;

            return true;
        }

        public string GetMapName()
        {
            return i_mapRecord != null ? i_mapRecord.MapName[Global.WorldMgr.GetDefaultDbcLocale()] : "UNNAMEDMAP";
        }

        public void SendInitSelf(Player pl)
        {
            var data = new UpdateData(pl.GetMapId());

            // attach to player data current transport data
            Transport transport = pl.GetTransport();
            if (transport != null)
                transport.BuildCreateUpdateBlockForPlayer(data, pl);

            pl.BuildCreateUpdateBlockForPlayer(data, pl);

            // 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 != null)
            {
                foreach (WorldObject obj in transport.GetPassengers())
                {
                    if (pl != obj && pl.HaveAtClient(obj))
                        obj.BuildCreateUpdateBlockForPlayer(data, pl);
                }
            }
            UpdateObject packet;
            data.BuildPacket(out packet);
            pl.SendPacket(packet);
        }

        void SendInitTransports(Player player)
        {
            var transData = new UpdateData(player.GetMapId());

            foreach (Transport transport in _transports)
            {
                if (transport != player.GetTransport() && player.IsInPhase(transport))
                    transport.BuildCreateUpdateBlockForPlayer(transData, player);
            }

            UpdateObject packet;
            transData.BuildPacket(out packet);
            player.SendPacket(packet);
        }

        void SendRemoveTransports(Player player)
        {
            var transData = new UpdateData(player.GetMapId());
            foreach (Transport transport in _transports)
                if (transport != player.GetTransport())
                    transport.BuildOutOfRangeUpdateBlock(transData);

            UpdateObject packet;
            transData.BuildPacket(out packet);
            player.SendPacket(packet);
        }

        public void SendUpdateTransportVisibility(Player player, List<uint> previousPhases)
        {
            // Hack to send out transports
            UpdateData transData = new UpdateData(player.GetMapId());
            foreach (var trans in _transports)
            {
                if (trans == player.GetTransport())
                    continue;

                if (player.IsInPhase(trans) && !previousPhases.Intersect(trans.GetPhases()).Any())
                    trans.BuildCreateUpdateBlockForPlayer(transData, player);
                else if (!player.IsInPhase(trans))
                    trans.BuildOutOfRangeUpdateBlock(transData);
            }

            UpdateObject packet;
            transData.BuildPacket(out packet);
            player.SendPacket(packet);
        }

        void setGrid(Grid grid, uint x, uint y)
        {
            if (x >= MapConst.MaxGrids || y >= MapConst.MaxGrids)
            {
                Log.outError(LogFilter.Maps, "Map.setNGrid Invalid grid coordinates found: {0}, {1}!", x, y);
                return;
            }
            i_grids[x][y] = grid;
        }

        void SendObjectUpdates()
        {
            Dictionary<Player, UpdateData> update_players = new Dictionary<Player, UpdateData>();

            while (!_updateObjects.Empty())
            {
                WorldObject obj = _updateObjects[0];
                Contract.Assert(obj.IsInWorld);
                _updateObjects.RemoveAt(0);
                obj.BuildUpdate(update_players);
            }

            UpdateObject packet;
            foreach (var iter in update_players)
            {
                iter.Value.BuildPacket(out packet);
                iter.Key.SendPacket(packet);
            }
        }

        public virtual void DelayedUpdate(uint diff)
        {
            foreach (var transport in _transports.ToList())
            {
                if (!transport.IsInWorld)
                    continue;

                transport.DelayedUpdate(diff);
            }

            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 (uint x = 0; x < MapConst.MaxGrids; ++x)
                {
                    for (uint y = 0; y < MapConst.MaxGrids; ++y)
                    {
                        Grid grid = getGrid(x, y);
                        if (grid != null)
                            grid.Update(this, diff);
                    }
                }
            }
        }

        public void AddObjectToRemoveList(WorldObject obj)
        {
            Contract.Assert(obj.GetMapId() == GetId() && obj.GetInstanceId() == GetInstanceId());

            obj.CleanupsBeforeDelete(false); // remove or simplify at least cross referenced links

            i_objectsToRemove.Add(obj);
        }

        public void AddObjectToSwitchList(WorldObject obj, bool on)
        {
            Contract.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.IsTypeId(TypeId.Unit) && !obj.IsTypeId(TypeId.GameObject))
                return;

            if (!i_objectsToSwitch.ContainsKey(obj))
                i_objectsToSwitch.Add(obj, on);
            else if (i_objectsToSwitch[obj] != on)
                i_objectsToSwitch.Remove(obj);
            else
                Contract.Assert(false);
        }

        void RemoveAllObjectsInRemoveList()
        {
            while (!i_objectsToSwitch.Empty())
            {
                KeyValuePair<WorldObject, bool> pair = i_objectsToSwitch.First();
                WorldObject obj = pair.Key;
                bool on = pair.Value;
                i_objectsToSwitch.Remove(pair.Key);

                if (!obj.IsPermanentWorldObject())
                {
                    switch (obj.GetTypeId())
                    {
                        case TypeId.Unit:
                            SwitchGridContainers(obj.ToCreature(), on);
                            break;
                        case TypeId.GameObject:
                            SwitchGridContainers(obj.ToGameObject(), on);
                            break;
                        default:
                            break;
                    }
                }
            }

            while (!i_objectsToRemove.Empty())
            {
                WorldObject obj = i_objectsToRemove.First();

                switch (obj.GetTypeId())
                {
                    case TypeId.Corpse:
                        {
                            Corpse corpse = ObjectAccessor.GetCorpse(obj, obj.GetGUID());
                            if (corpse == null)
                                Log.outError(LogFilter.Maps, "Tried to delete corpse/bones {0} that is not in map.", obj.GetGUID().ToString());
                            else
                                RemoveFromMap(corpse, true);
                            break;
                        }
                    case TypeId.DynamicObject:
                        RemoveFromMap(obj, true);
                        break;
                    case TypeId.AreaTrigger:
                        RemoveFromMap(obj, true);
                        break;
                    case TypeId.Conversation:
                        RemoveFromMap(obj, true);
                        break;
                    case TypeId.GameObject:
                        GameObject go = obj.ToGameObject();
                        if (go.IsTransport())
                            RemoveFromMap(go.ToTransport(), 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:
                        Log.outError(LogFilter.Maps, "Non-grid object (TypeId: {0}) is in grid object remove list, ignored.", obj.GetTypeId());
                        break;
                }

                i_objectsToRemove.Remove(obj);
            }
        }

        public uint GetPlayersCountExceptGMs()
        {
            uint count = 0;
            foreach (Player pl in m_activePlayers)
                if (!pl.IsGameMaster())
                    ++count;
            return count;
        }

        public void SendToPlayers(ServerPacket data)
        {
            data.Write();
            foreach (Player pl in m_activePlayers)
                pl.SendPacket(data, false);
        }

        public bool ActiveObjectsNearGrid(Grid grid)
        {
            var cell_min = new CellCoord(grid.getX() * MapConst.MaxCells,
                grid.getY() * MapConst.MaxCells);
            var cell_max = new CellCoord(cell_min.x_coord + MapConst.MaxCells,
                cell_min.y_coord + MapConst.MaxCells);

            //we must find visible range in cells so we unload only non-visible cells...
            float viewDist = GetVisibilityRange();
            uint cell_range = (uint)Math.Ceiling(viewDist / MapConst.SizeofCells) + 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);

            foreach (Player pl in m_activePlayers)
            {
                CellCoord p = GridDefines.ComputeCellCoord(pl.GetPositionX(), pl.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;
            }

            foreach (WorldObject obj in m_activeNonPlayers)
            {
                CellCoord p = GridDefines.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;
        }

        public void AddToActive(WorldObject obj)
        {
            AddToActiveHelper(obj);

            if (obj.IsTypeId(TypeId.Unit))
            {
                Creature c = obj.ToCreature();
                // also not allow unloading spawn grid to prevent creating creature clone at load
                if (!c.IsPet() && c.GetSpawnId() != 0)
                {
                    float x, y, z;
                    c.GetRespawnPosition(out x, out y, out z);
                    GridCoord p = GridDefines.ComputeGridCoord(x, y);
                    if (getGrid(p.x_coord, p.y_coord) != null)
                        getGrid(p.x_coord, p.y_coord).incUnloadActiveLock();
                    else
                    {
                        GridCoord p2 = GridDefines.ComputeGridCoord(c.GetPositionX(), c.GetPositionY());
                        Log.outError(LogFilter.Maps,
                            "Active creature (GUID: {0} Entry: {1}) added to grid[{2}, {3}] but spawn grid[{4}, {5}] was not loaded.",
                            c.GetGUID().ToString(), c.GetEntry(), p.x_coord, p.y_coord, p2.x_coord, p2.y_coord);
                    }
                }
            }
        }

        void AddToActiveHelper(WorldObject obj)
        {
            m_activeNonPlayers.Add(obj);
        }

        public void RemoveFromActive(WorldObject obj)
        {
            RemoveFromActiveHelper(obj);

            if (obj.IsTypeId(TypeId.Unit))
            {
                Creature c = obj.ToCreature();
                // also allow unloading spawn grid
                if (!c.IsPet() && c.GetSpawnId() != 0)
                {
                    float x, y, z;
                    c.GetRespawnPosition(out x, out y, out z);
                    GridCoord p = GridDefines.ComputeGridCoord(x, y);
                    if (getGrid(p.x_coord, p.y_coord) != null)
                        getGrid(p.x_coord, p.y_coord).decUnloadActiveLock();
                    else
                    {
                        GridCoord p2 = GridDefines.ComputeGridCoord(c.GetPositionX(), c.GetPositionY());
                        Log.outDebug(LogFilter.Maps,
                            "Active creature (GUID: {0} Entry: {1}) removed from grid[{2}, {3}] but spawn grid[{4}, {5}] was not loaded.",
                            c.GetGUID().ToString(), c.GetEntry(), p.x_coord, p.y_coord, p2.x_coord, p2.y_coord);
                    }
                }
            }
        }

        void RemoveFromActiveHelper(WorldObject obj)
        {
            m_activeNonPlayers.Remove(obj);
        }

        public void SaveCreatureRespawnTime(ulong dbGuid, long respawnTime)
        {
            if (respawnTime == 0)
            {
                // Delete only
                RemoveCreatureRespawnTime(dbGuid);
                return;
            }

            _creatureRespawnTimes[dbGuid] = respawnTime;

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CREATURE_RESPAWN);
            stmt.AddValue(0, dbGuid);
            stmt.AddValue(1, respawnTime);
            stmt.AddValue(2, GetId());
            stmt.AddValue(3, GetInstanceId());
            DB.Characters.Execute(stmt);
        }

        public void RemoveCreatureRespawnTime(ulong dbGuid)
        {
            _creatureRespawnTimes.Remove(dbGuid);

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN);
            stmt.AddValue(0, dbGuid);
            stmt.AddValue(1, GetId());
            stmt.AddValue(2, GetInstanceId());
            DB.Characters.Execute(stmt);
        }

        public void SaveGORespawnTime(ulong dbGuid, long respawnTime)
        {
            if (respawnTime == 0)
            {
                // Delete only
                RemoveGORespawnTime(dbGuid);
                return;
            }

            _goRespawnTimes[dbGuid] = respawnTime;

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GO_RESPAWN);
            stmt.AddValue(0, dbGuid);
            stmt.AddValue(1, respawnTime);
            stmt.AddValue(2, GetId());
            stmt.AddValue(3, GetInstanceId());
            DB.Characters.Execute(stmt);
        }

        public void RemoveGORespawnTime(ulong dbGuid)
        {
            _goRespawnTimes.Remove(dbGuid);

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN);
            stmt.AddValue(0, dbGuid);
            stmt.AddValue(1, GetId());
            stmt.AddValue(2, GetInstanceId());
            DB.Characters.Execute(stmt);
        }

        public void LoadRespawnTimes()
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CREATURE_RESPAWNS);
            stmt.AddValue(0, GetId());
            stmt.AddValue(1, GetInstanceId());
            SQLResult result = DB.Characters.Query(stmt);
            if (!result.IsEmpty())
            {
                do
                {
                    var loguid = result.Read<uint>(0);
                    var respawnTime = result.Read<uint>(1);

                    _creatureRespawnTimes[loguid] = respawnTime;
                } while (result.NextRow());
            }

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GO_RESPAWNS);
            stmt.AddValue(0, GetId());
            stmt.AddValue(1, GetInstanceId());
            result = DB.Characters.Query(stmt);
            if (!result.IsEmpty())
            {
                do
                {
                    var loguid = result.Read<uint>(0);
                    var respawnTime = result.Read<uint>(1);

                    _goRespawnTimes[loguid] = respawnTime;
                } while (result.NextRow());
            }
        }

        public void DeleteRespawnTimes()
        {
            _creatureRespawnTimes.Clear();
            _goRespawnTimes.Clear();

            DeleteRespawnTimesInDB(GetId(), GetInstanceId());
        }

        public static void DeleteRespawnTimesInDB(uint mapId, uint instanceId)
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN_BY_INSTANCE);
            stmt.AddValue(0, mapId);
            stmt.AddValue(1, instanceId);
            DB.Characters.Execute(stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN_BY_INSTANCE);
            stmt.AddValue(0, mapId);
            stmt.AddValue(1, instanceId);
            DB.Characters.Execute(stmt);
        }

        public long GetLinkedRespawnTime(ObjectGuid guid)
        {
            ObjectGuid linkedGuid = Global.ObjectMgr.GetLinkedRespawnGuid(guid);
            switch (linkedGuid.GetHigh())
            {
                case HighGuid.Creature:
                    return GetCreatureRespawnTime(linkedGuid.GetCounter());
                case HighGuid.GameObject:
                    return GetGORespawnTime(linkedGuid.GetCounter());
                default:
                    break;
            }

            return 0L;
        }

        public void LoadCorpseData()
        {
            MultiMap<ulong, uint> phases = new MultiMap<ulong, uint>();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CORPSE_PHASES);
            stmt.AddValue(0, GetId());
            stmt.AddValue(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 = ?
            SQLResult phaseResult = DB.Characters.Query(stmt);
            if (!phaseResult.IsEmpty())
            {
                do
                {
                    ulong guid = phaseResult.Read<ulong>(0);
                    uint phaseId = phaseResult.Read<uint>(1);

                    phases.Add(guid, phaseId);

                } while (phaseResult.NextRow());
            }

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CORPSES);
            stmt.AddValue(0, GetId());
            stmt.AddValue(1, GetInstanceId());

            //        0     1     2     3            4      5          6          7       8       9      10        11    12          13          14
            // SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, flags, dynFlags, time, corpseType, instanceId, guid FROM corpse WHERE mapId = ? AND instanceId = ?
            SQLResult result = DB.Characters.Query(stmt);
            if (result.IsEmpty())
                return;

            do
            {
                CorpseType type = (CorpseType)result.Read<byte>(12);
                ulong guid = result.Read<ulong>(14);
                if (type >= CorpseType.Max || type == CorpseType.Bones)
                {
                    Log.outError(LogFilter.Maps, "Corpse (guid: {0}) have wrong corpse type ({1}), not loading.", guid, type);
                    continue;
                }

                Corpse corpse = new Corpse(type);
                if (!corpse.LoadCorpseFromDB(GenerateLowGuid(HighGuid.Corpse), result.GetFields()))
                    continue;

                foreach (var phaseId in phases[guid])
                    corpse.SetInPhase(phaseId, false, true);

                AddCorpse(corpse);
            } while (result.NextRow());
        }

        public void DeleteCorpseData()
        {
            // DELETE cp, c FROM corpse_phases cp INNER JOIN corpse c ON cp.OwnerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ?
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CORPSES_FROM_MAP);
            stmt.AddValue(0, GetId());
            stmt.AddValue(1, GetInstanceId());
            DB.Characters.Execute(stmt);
        }

        public void AddCorpse(Corpse corpse)
        {
            corpse.SetMap(this);

            _corpsesByCell.Add(corpse.GetCellCoord().GetId(), corpse);
            if (corpse.GetCorpseType() != CorpseType.Bones)
                _corpsesByPlayer[corpse.GetOwnerGUID()] = corpse;
            else
                _corpseBones.Add(corpse);
        }

        void RemoveCorpse(Corpse corpse)
        {
            Contract.Assert(corpse);

            corpse.DestroyForNearbyPlayers();
            if (corpse.GetCurrentCell() != null)
                RemoveFromMap(corpse, false);
            else
            {
                corpse.RemoveFromWorld();
                corpse.ResetMap();
            }

            _corpsesByCell.Remove(corpse.GetCellCoord().GetId(), corpse);
            if (corpse.GetCorpseType() != CorpseType.Bones)
                _corpsesByPlayer.Remove(corpse.GetOwnerGUID());
            else
                _corpseBones.Remove(corpse);
        }

        public Corpse ConvertCorpseToBones(ObjectGuid ownerGuid, bool insignia = false)
        {
            Corpse corpse = GetCorpseByPlayer(ownerGuid);
            if (!corpse)
                return null;

            RemoveCorpse(corpse);

            // remove corpse from DB
            SQLTransaction trans = new SQLTransaction();
            corpse.DeleteFromDB(trans);
            DB.Characters.CommitTransaction(trans);

            Corpse bones = null;

            // 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() ? WorldConfig.GetBoolValue(WorldCfg.DeathBonesBgOrArena) : WorldConfig.GetBoolValue(WorldCfg.DeathBonesWorld))) &&
                !IsRemovalGrid(corpse.GetPositionX(), corpse.GetPositionY()))
            {
                // Create bones, don't change Corpse
                bones = new Corpse();
                bones.Create(corpse.GetGUID().GetCounter(), this);

                for (byte i = (int)ObjectFields.Guid + 4; i < (int)CorpseFields.End; ++i)                    // don't overwrite guid
                    bones.SetUInt32Value(i, corpse.GetUInt32Value(i));

                bones.SetCellCoord(corpse.GetCellCoord());
                bones.Relocate(corpse.GetPositionX(), corpse.GetPositionY(), corpse.GetPositionZ(), corpse.GetOrientation());

                bones.SetUInt32Value(CorpseFields.Flags, corpse.GetUInt32Value(CorpseFields.Flags) | (uint)CorpseFlags.Bones);

                bones.CopyPhaseFrom(corpse);

                AddCorpse(bones);

                // add bones in grid store if grid loaded where corpse placed
                AddToMap(bones);
            }

            // all references to the corpse should be removed at this point
            corpse.Dispose();

            return bones;
        }

        public void RemoveOldCorpses()
        {
            long now = Time.UnixTime;

            List<ObjectGuid> corpses = new List<ObjectGuid>();

            foreach (var p in _corpsesByPlayer)
                if (p.Value.IsExpired(now))
                    corpses.Add(p.Key);

            foreach (ObjectGuid ownerGuid in corpses)
                ConvertCorpseToBones(ownerGuid);

            List<Corpse> expiredBones = new List<Corpse>();
            foreach (Corpse bones in _corpseBones)
                if (bones.IsExpired(now))
                    expiredBones.Add(bones);

            foreach (Corpse bones in expiredBones)
            {
                RemoveCorpse(bones);
                bones.Dispose();
            }
        }

        void SendZoneDynamicInfo(Player player)
        {
            uint zoneId = GetZoneId(player.GetPositionX(), player.GetPositionY(), player.GetPositionZ());
            var zoneInfo = _zoneDynamicInfo.LookupByKey(zoneId);
            if (zoneInfo == null)
                return;

            uint music = zoneInfo.MusicId;
            if (music != 0)
                player.SendPacket(new PlayMusic(music));

            WeatherState weatherId = zoneInfo.WeatherId;
            if (weatherId != 0)
            {
                WeatherPkt weather = new WeatherPkt(weatherId, zoneInfo.WeatherGrade);
                player.SendPacket(weather);
            }

            uint overrideLightId = zoneInfo.OverrideLightId;
            if (overrideLightId != 0)
            {
                OverrideLight overrideLight = new OverrideLight();
                overrideLight.AreaLightID = _defaultLight;
                overrideLight.OverrideLightID = overrideLightId;
                overrideLight.TransitionMilliseconds = zoneInfo.LightFadeInTime;
                player.SendPacket(overrideLight);
            }
        }

        public void SetZoneMusic(uint zoneId, uint musicId)
        {
            if (!_zoneDynamicInfo.ContainsKey(zoneId))
                _zoneDynamicInfo[zoneId] = new ZoneDynamicInfo();

            _zoneDynamicInfo[zoneId].MusicId = musicId;

            var players = GetPlayers();
            if (!players.Empty())
            {
                PlayMusic data = new PlayMusic(musicId);
                data.Write();

                foreach (var player in players)
                    if (player.GetZoneId() == zoneId)
                        player.SendPacket(data, false);
            }
        }

        void SetZoneWeather(uint zoneId, WeatherState weatherId, float weatherGrade)
        {
            if (!_zoneDynamicInfo.ContainsKey(zoneId))
                _zoneDynamicInfo[zoneId] = new ZoneDynamicInfo();

            ZoneDynamicInfo info = _zoneDynamicInfo[zoneId];
            info.WeatherId = weatherId;
            info.WeatherGrade = weatherGrade;
            var players = GetPlayers();

            if (!players.Empty())
            {
                WeatherPkt weather = new WeatherPkt(weatherId, weatherGrade);
                weather.Write();
                foreach (var player in players)
                    if (player.GetZoneId() == zoneId)
                        player.SendPacket(weather, false);
            }
        }

        void SetZoneOverrideLight(uint zoneId, uint lightId, uint fadeInTime)
        {
            if (!_zoneDynamicInfo.ContainsKey(zoneId))
                _zoneDynamicInfo[zoneId] = new ZoneDynamicInfo();

            ZoneDynamicInfo info = _zoneDynamicInfo[zoneId];
            info.OverrideLightId = lightId;
            info.LightFadeInTime = fadeInTime;
            var players = GetPlayers();

            if (!players.Empty())
            {
                OverrideLight overrideLight = new OverrideLight();
                overrideLight.AreaLightID = _defaultLight;
                overrideLight.OverrideLightID = lightId;
                overrideLight.TransitionMilliseconds = fadeInTime;
                overrideLight.Write();

                foreach (var player in players)
                    if (player.GetZoneId() == zoneId)
                        player.SendPacket(overrideLight, false);
            }
        }

        public MapRecord GetEntry()
        {
            return i_mapRecord;
        }

        public bool CanUnload(uint diff)
        {
            if (m_unloadTimer == 0)
                return false;

            if (m_unloadTimer <= diff)
                return true;

            m_unloadTimer -= diff;
            return false;
        }

        public float GetVisibilityRange()
        {
            return m_VisibleDistance;
        }

        public bool IsRemovalGrid(float x, float y)
        {
            GridCoord p = GridDefines.ComputeGridCoord(x, y);
            return getGrid(p.x_coord, p.y_coord) == null ||
                   getGrid(p.x_coord, p.y_coord).GetGridState() == GridState.Removal;
        }

        private bool GetUnloadLock(GridCoord p)
        {
            return getGrid(p.x_coord, p.y_coord).getUnloadLock();
        }

        void SetUnloadLock(GridCoord p, bool on)
        {
            getGrid(p.x_coord, p.y_coord).setUnloadExplicitLock(on);
        }

        public void ResetGridExpiry(Grid grid, float factor = 1)
        {
            grid.ResetTimeTracker((long)(i_gridExpiry * factor));
        }

        public long GetGridExpiry()
        {
            return i_gridExpiry;
        }

        private Map GetParent()
        {
            return m_parentMap;
        }

        public uint GetInstanceId()
        {
            return i_InstanceId;
        }

        public Difficulty GetSpawnMode()
        {
            return i_spawnMode;
        }

        public virtual EnterState CannotEnter(Player player) { return EnterState.CanEnter; }

        public Difficulty GetDifficultyID()
        {
            return GetSpawnMode();
        }

        public MapDifficultyRecord GetMapDifficulty()
        {
            return Global.DB2Mgr.GetMapDifficultyData(GetId(), GetDifficultyID());
        }

        public byte GetDifficultyLootBonusTreeMod()
        {
            MapDifficultyRecord mapDifficulty = GetMapDifficulty();
            if (mapDifficulty != null && mapDifficulty.ItemBonusTreeModID != 0)
                return mapDifficulty.ItemBonusTreeModID;

            DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(GetDifficultyID());
            if (difficulty != null)
                return difficulty.ItemBonusTreeModID;

            return 0;
        }

        public uint GetId()
        {
            return i_mapRecord.Id;
        }

        public bool Instanceable()
        {
            return i_mapRecord != null && i_mapRecord.Instanceable();
        }

        public bool IsDungeon()
        {
            return i_mapRecord != null && i_mapRecord.IsDungeon();
        }

        public bool IsNonRaidDungeon()
        {
            return i_mapRecord != null && i_mapRecord.IsNonRaidDungeon();
        }

        public bool IsRaid()
        {
            return i_mapRecord != null && i_mapRecord.IsRaid();
        }

        public bool IsRaidOrHeroicDungeon()
        {
            return IsRaid() || IsHeroic();
        }

        public bool IsHeroic()
        {
            DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(i_spawnMode);
            if (difficulty != null)
                return difficulty.Flags.HasAnyFlag(DifficultyFlags.Heroic);
            return false;
        }

        public bool Is25ManRaid()
        {
            // since 25man difficulties are 1 and 3, we can check them like that
            return IsRaid() && (i_spawnMode == Difficulty.Raid25N || i_spawnMode == Difficulty.Raid25HC);
        }

        public bool IsBattleground()
        {
            return i_mapRecord != null && i_mapRecord.IsBattleground();
        }

        public bool IsBattleArena()
        {
            return i_mapRecord != null && i_mapRecord.IsBattleArena();
        }

        public bool IsBattlegroundOrArena()
        {
            return i_mapRecord != null && i_mapRecord.IsBattlegroundOrArena();
        }

        public bool IsGarrison()
        {
            return i_mapRecord != null && i_mapRecord.IsGarrison();
        }

        private bool GetEntrancePos(out uint mapid, out float x, out float y)
        {
            mapid = 0;
            x = 0;
            y = 0;

            if (i_mapRecord == null)
                return false;

            return i_mapRecord.GetEntrancePos(out mapid, out x, out y);
        }

        void resetMarkedCells()
        {
            marked_cells.SetAll(false);
        }

        private bool isCellMarked(uint pCellId)
        {
            return marked_cells.Get((int)pCellId);
        }

        void markCell(uint pCellId)
        {
            marked_cells.Set((int)pCellId, true);
        }

        public bool HavePlayers()
        {
            return !m_activePlayers.Empty();
        }

        public void AddWorldObject(WorldObject obj)
        {
            i_worldObjects.Add(obj);
        }

        public void RemoveWorldObject(WorldObject obj)
        {
            i_worldObjects.Remove(obj);
        }

        public List<Player> GetPlayers()
        {
            return m_activePlayers;
        }

        public Dictionary<ObjectGuid, WorldObject> GetObjectsStore() { return _objectsStore; }

        public MultiMap<ulong, Creature> GetCreatureBySpawnIdStore() { return _creatureBySpawnIdStore; }

        public MultiMap<ulong, GameObject> GetGameObjectBySpawnIdStore() { return _gameobjectBySpawnIdStore; }

        public List<Corpse> GetCorpsesInCell(uint cellId)
        {
            return _corpsesByCell.LookupByKey(cellId);
        }

        public Corpse GetCorpseByPlayer(ObjectGuid ownerGuid)
        {
            return _corpsesByPlayer.LookupByKey(ownerGuid);
        }

        public MapInstanced ToMapInstanced() { return Instanceable() ? (this as MapInstanced) : null; }
        public InstanceMap ToInstanceMap() { return IsDungeon() ? (this as InstanceMap) : null; }
        public BattlegroundMap ToBattlegroundMap() { return IsBattlegroundOrArena() ? (this as BattlegroundMap) : null; }

        void Balance()
        {
            _dynamicTree.balance();
        }

        public void RemoveGameObjectModel(GameObjectModel model)
        {
            _dynamicTree.remove(model);
        }

        public void InsertGameObjectModel(GameObjectModel model)
        {
            _dynamicTree.insert(model);
        }

        public bool ContainsGameObjectModel(GameObjectModel model)
        {
            return _dynamicTree.contains(model);
        }

        public virtual uint GetOwnerGuildId(Team team = Team.Other)
        {
            return 0;
        }

        public long GetCreatureRespawnTime(ulong dbGuid)
        {
            return _creatureRespawnTimes.LookupByKey(dbGuid);
        }

        public long GetGORespawnTime(ulong dbGuid)
        {
            return _goRespawnTimes.LookupByKey(dbGuid);
        }

        void SetTimer(uint t)
        {
            i_gridExpiry = t < MapConst.MinGridDelay ? MapConst.MinGridDelay : t;
        }

        private Grid getGrid(uint x, uint y)
        {
            return i_grids[x][y];
        }

        private bool isGridObjectDataLoaded(uint x, uint y)
        {
            return getGrid(x, y).isGridObjectDataLoaded();
        }

        void setGridObjectDataLoaded(bool pLoaded, uint x, uint y)
        {
            getGrid(x, y).setGridObjectDataLoaded(pLoaded);
        }

        public void SetUnloadReferenceLock(GridCoord p, bool on)
        {
            getGrid(p.x_coord, p.y_coord).setUnloadReferenceLock(on);
        }

        public AreaTrigger GetAreaTrigger(ObjectGuid guid)
        {
            return (AreaTrigger)_objectsStore.LookupByKey(guid);
        }

        public Conversation GetConversation(ObjectGuid guid)
        {
            return (Conversation)_objectsStore.LookupByKey(guid);
        }

        public Corpse GetCorpse(ObjectGuid guid)
        {
            return (Corpse)_objectsStore.LookupByKey(guid);
        }

        public Creature GetCreature(ObjectGuid guid)
        {
            return (Creature)_objectsStore.LookupByKey(guid);
        }

        public DynamicObject GetDynamicObject(ObjectGuid guid)
        {
            return (DynamicObject)_objectsStore.LookupByKey(guid);
        }

        public GameObject GetGameObject(ObjectGuid guid)
        {
            return (GameObject)_objectsStore.LookupByKey(guid);
        }

        public Pet GetPet(ObjectGuid guid)
        {
            return (Pet)_objectsStore.LookupByKey(guid);
        }

        public Transport GetTransport(ObjectGuid guid)
        {
            if (!guid.IsMOTransport())
                return null;

            GameObject go = GetGameObject(guid);
            return go ? go.ToTransport() : null;
        }

        public void Visit(Cell cell, Visitor visitor)
        {
            uint x = cell.GetGridX();
            uint y = cell.GetGridY();
            uint cell_x = cell.GetCellX();
            uint cell_y = cell.GetCellY();

            if (!cell.NoCreate() || IsGridLoaded(new GridCoord(x, y)))
            {
                EnsureGridLoaded(cell);
                getGrid(x, y).VisitGrid(cell_x, cell_y, visitor);
            }
        }

        public TempSummon SummonCreature(uint entry, Position pos, SummonPropertiesRecord properties = null, uint duration = 0, Unit summoner = null, uint spellId = 0, uint vehId = 0)
        {
            var mask = UnitTypeMask.Summon;
            if (properties != null)
            {
                switch (properties.Category)
                {
                    case SummonCategory.Pet:
                        mask = UnitTypeMask.Guardian;
                        break;
                    case SummonCategory.Puppet:
                        mask = UnitTypeMask.Puppet;
                        break;
                    case SummonCategory.Vehicle:
                        mask = UnitTypeMask.Minion;
                        break;
                    case SummonCategory.Wild:
                    case SummonCategory.Ally:
                    case SummonCategory.Unk:
                        {
                            switch (properties.Type)
                            {
                                case SummonType.Minion:
                                case SummonType.Guardian:
                                case SummonType.Guardian2:
                                    mask = UnitTypeMask.Guardian;
                                    break;
                                case SummonType.Totem:
                                case SummonType.LightWell:
                                    mask = UnitTypeMask.Totem;
                                    break;
                                case SummonType.Vehicle:
                                case SummonType.Vehicle2:
                                    mask = UnitTypeMask.Summon;
                                    break;
                                case SummonType.Minipet:
                                    mask = UnitTypeMask.Minion;
                                    break;
                                default:
                                    if (Convert.ToBoolean(properties.Flags & 512)) // Mirror Image, Summon Gargoyle
                                        mask = UnitTypeMask.Guardian;
                                    break;
                            }
                            break;
                        }
                    default:
                        return null;
                }
            }

            TempSummon summon = null;
            switch (mask)
            {
                case UnitTypeMask.Summon:
                    summon = new TempSummon(properties, summoner, false);
                    break;
                case UnitTypeMask.Guardian:
                    summon = new Guardian(properties, summoner, false);
                    break;
                case UnitTypeMask.Puppet:
                    summon = new Puppet(properties, summoner);
                    break;
                case UnitTypeMask.Totem:
                    summon = new Totem(properties, summoner);
                    break;
                case UnitTypeMask.Minion:
                    summon = new Minion(properties, summoner, false);
                    break;
                default:
                    return null;
            }

            if (!summon.Create(GenerateLowGuid(HighGuid.Creature), this, 0, entry, pos.posX, pos.posY, pos.posZ, pos.Orientation, null, vehId))
                return null;

            // Set the summon to the summoner's phase
            summon.CopyPhaseFrom(summoner);

            summon.SetUInt32Value(UnitFields.CreatedBySpell, spellId);

            summon.SetHomePosition(pos);

            summon.InitStats(duration);
            AddToMap(summon.ToCreature());
            summon.InitSummon();

            // call MoveInLineOfSight for nearby creatures
            AIRelocationNotifier notifier = new AIRelocationNotifier(summon);
            Cell.VisitAllObjects(summon, notifier, GetVisibilityRange());

            return summon;
        }

        public ulong GenerateLowGuid(HighGuid high)
        {
            //Contract.Assert(!ObjectGuid.IsMapSpecific(high), "Only map specific guid can be generated in Map context");

            return GetGuidSequenceGenerator(high).Generate();
        }

        ObjectGuidGenerator GetGuidSequenceGenerator(HighGuid high)
        {
            if (!_guidGenerators.ContainsKey(high))
                _guidGenerators[high] = new ObjectGuidGenerator(high);

            return _guidGenerators[high];
        }

        public void AddUpdateObject(WorldObject obj)
        {
            _updateObjects.Add(obj);
        }

        public void RemoveUpdateObject(WorldObject obj)
        {
            _updateObjects.Remove(obj);
        }

        public static implicit operator bool (Map map)
        {
            return map != null;
        }

        #region Scripts

        // Put scripts in the execution queue
        public void ScriptsStart(ScriptsType scriptsType, uint id, WorldObject source, WorldObject target)
        {
            var scripts = Global.ObjectMgr.GetScriptsMapByType(scriptsType);

            // Find the script map
            MultiMap<uint, ScriptInfo> list = scripts.LookupByKey(id);
            if (list == null)
                return;

            // prepare static data
            ObjectGuid sourceGUID = source != null ? source.GetGUID() : ObjectGuid.Empty; //some script commands doesn't have source
            ObjectGuid targetGUID = target != null ? target.GetGUID() : ObjectGuid.Empty;
            ObjectGuid ownerGUID = (source != null && source.GetTypeId() == TypeId.Item) ? ((Item)source).GetOwnerGUID() : ObjectGuid.Empty;

            // Schedule script execution for all scripts in the script map
            bool immedScript = false;
            foreach (var script in list)
            {
                ScriptAction sa;
                sa.sourceGUID = sourceGUID;
                sa.targetGUID = targetGUID;
                sa.ownerGUID = ownerGUID;

                sa.script = script.Value;
                m_scriptSchedule.Add(Global.WorldMgr.GetGameTime() + script.Key, sa);
                if (script.Key == 0)
                    immedScript = true;

                Global.MapMgr.IncreaseScheduledScriptsCount();
            }
            // If one of the effects should be immediate, launch the script execution
            if (immedScript && !i_scriptLock)
            {
                i_scriptLock = true;
                ScriptsProcess();
                i_scriptLock = false;
            }
        }

        public void ScriptCommandStart(ScriptInfo script, uint delay, WorldObject source, WorldObject target)
        {
            // NOTE: script record _must_ exist until command executed

            // prepare static data
            ObjectGuid sourceGUID = source != null ? source.GetGUID() : ObjectGuid.Empty;
            ObjectGuid targetGUID = target != null ? target.GetGUID() : ObjectGuid.Empty;
            ObjectGuid ownerGUID = (source != null && source.GetTypeId() == TypeId.Item) ? ((Item)source).GetOwnerGUID() : ObjectGuid.Empty;

            var sa = new ScriptAction();
            sa.sourceGUID = sourceGUID;
            sa.targetGUID = targetGUID;
            sa.ownerGUID = ownerGUID;

            sa.script = script;
            m_scriptSchedule.Add(Global.WorldMgr.GetGameTime() + delay, sa);

            Global.MapMgr.IncreaseScheduledScriptsCount();

            // If effects should be immediate, launch the script execution
            if (delay == 0 && !i_scriptLock)
            {
                i_scriptLock = true;
                ScriptsProcess();
                i_scriptLock = false;
            }
        }

        // Helpers for ScriptProcess method.
        private Player _GetScriptPlayerSourceOrTarget(WorldObject source, WorldObject target, ScriptInfo scriptInfo)
        {
            Player player = null;
            if (source == null && target == null)
                Log.outError(LogFilter.Scripts, "{0} source and target objects are NULL.", scriptInfo.GetDebugInfo());
            else
            {
                // Check target first, then source.
                if (target != null)
                    player = target.ToPlayer();
                if (player == null && source != null)
                    player = source.ToPlayer();

                if (player == null)
                    Log.outError(LogFilter.Scripts, "{0} neither source nor target object is player (source: TypeId: {1}, Entry: {2}, {3}; target: TypeId: {4}, Entry: {5}, {6}), skipping.",
                        scriptInfo.GetDebugInfo(), source ? source.GetTypeId() : 0, source ? source.GetEntry() : 0, source ? source.GetGUID().ToString() : "",
                        target ? target.GetTypeId() : 0, target ? target.GetEntry() : 0, target ? target.GetGUID().ToString() : "");
            }
            return player;
        }

        private Creature _GetScriptCreatureSourceOrTarget(WorldObject source, WorldObject target, ScriptInfo scriptInfo, bool bReverse = false)
        {
            Creature creature = null;
            if (source == null && target == null)
                Log.outError(LogFilter.Scripts, "{0} source and target objects are NULL.", scriptInfo.GetDebugInfo());
            else
            {
                if (bReverse)
                {
                    // Check target first, then source.
                    if (target != null)
                        creature = target.ToCreature();
                    if (creature == null && source != null)
                        creature = source.ToCreature();
                }
                else
                {
                    // Check source first, then target.
                    if (source != null)
                        creature = source.ToCreature();
                    if (creature == null && target != null)
                        creature = target.ToCreature();
                }

                if (creature == null)
                    Log.outError(LogFilter.Scripts, "{0} neither source nor target are creatures (source: TypeId: {1}, Entry: {2}, {3}; target: TypeId: {4}, Entry: {5}, {6}), skipping.",
                        scriptInfo.GetDebugInfo(), source ? source.GetTypeId() : 0, source ? source.GetEntry() : 0, source ? source.GetGUID().ToString() : "",
                        target ? target.GetTypeId() : 0, target ? target.GetEntry() : 0, target ? target.GetGUID().ToString() : "");
            }
            return creature;
        }

        private Unit _GetScriptUnit(WorldObject obj, bool isSource, ScriptInfo scriptInfo)
        {
            Unit unit = null;
            if (obj == null)
                Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(),
                    isSource ? "source" : "target");
            else if (!obj.isTypeMask(TypeMask.Unit))
                Log.outError(LogFilter.Scripts,
                    "{0} {1} object is not unit (TypeId: {2}, Entry: {3}, GUID: {4}), skipping.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString());
            else
            {
                unit = obj.ToUnit();
                if (unit == null)
                    Log.outError(LogFilter.Scripts, "{0} {1} object could not be casted to unit.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target");
            }
            return unit;
        }

        private Player _GetScriptPlayer(WorldObject obj, bool isSource, ScriptInfo scriptInfo)
        {
            Player player = null;
            if (obj == null)
                Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(),
                    isSource ? "source" : "target");
            else
            {
                player = obj.ToPlayer();
                if (player == null)
                    Log.outError(LogFilter.Scripts, "{0} {1} object is not a player (TypeId: {2}, Entry: {3}, GUID: {4}).",
                        scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString());
            }
            return player;
        }

        private Creature _GetScriptCreature(WorldObject obj, bool isSource, ScriptInfo scriptInfo)
        {
            Creature creature = null;
            if (obj == null)
                Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target");
            else
            {
                creature = obj.ToCreature();
                if (creature == null)
                    Log.outError(LogFilter.Scripts,
                        "{0} {1} object is not a creature (TypeId: {2}, Entry: {3}, GUID: {4}).", scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString());
            }
            return creature;
        }

        private WorldObject _GetScriptWorldObject(WorldObject obj, bool isSource, ScriptInfo scriptInfo)
        {
            WorldObject pWorldObject = null;
            if (obj == null)
                Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target");
            else
            {
                pWorldObject = obj;
                if (pWorldObject == null)
                    Log.outError(LogFilter.Scripts,
                        "{0} {1} object is not a world object (TypeId: {2}, Entry: {3}, GUID: {4}).", scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString());
            }
            return pWorldObject;
        }

        void _ScriptProcessDoor(WorldObject source, WorldObject target, ScriptInfo scriptInfo)
        {
            bool bOpen = false;
            ulong guid = scriptInfo.ToggleDoor.GOGuid;
            int nTimeToToggle = Math.Max(15, (int)scriptInfo.ToggleDoor.ResetDelay);
            switch (scriptInfo.command)
            {
                case ScriptCommands.OpenDoor:
                    bOpen = true;
                    break;
                case ScriptCommands.CloseDoor:
                    break;
                default:
                    Log.outError(LogFilter.Scripts, "{0} unknown command for _ScriptProcessDoor.", scriptInfo.GetDebugInfo());
                    return;
            }
            if (guid == 0)
                Log.outError(LogFilter.Scripts, "{0} door guid is not specified.", scriptInfo.GetDebugInfo());
            else if (source == null)
                Log.outError(LogFilter.Scripts, "{0} source object is NULL.", scriptInfo.GetDebugInfo());
            else if (!source.isTypeMask(TypeMask.Unit))
                Log.outError(LogFilter.Scripts,
                    "{0} source object is not unit (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", scriptInfo.GetDebugInfo(), source.GetTypeId(), source.GetEntry(), source.GetGUID().ToString());
            else
            {
                if (source == null)
                    Log.outError(LogFilter.Scripts,
                        "{0} source object could not be casted to world object (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", scriptInfo.GetDebugInfo(), source.GetTypeId(), source.GetEntry(), source.GetGUID().ToString());
                else
                {
                    GameObject pDoor = _FindGameObject(source, guid);
                    if (pDoor == null)
                        Log.outError(LogFilter.Scripts, "{0} gameobject was not found (guid: {1}).", scriptInfo.GetDebugInfo(), guid);
                    else if (pDoor.GetGoType() != GameObjectTypes.Door)
                        Log.outError(LogFilter.Scripts, "{0} gameobject is not a door (GoType: {1}, Entry: {2}, GUID: {3}).", scriptInfo.GetDebugInfo(), pDoor.GetGoType(), pDoor.GetEntry(), pDoor.GetGUID().ToString());
                    else if (bOpen == (pDoor.GetGoState() == GameObjectState.Ready))
                    {
                        pDoor.UseDoorOrButton((uint)nTimeToToggle);

                        if (target != null && target.isTypeMask(TypeMask.GameObject))
                        {
                            GameObject goTarget = target.ToGameObject();
                            if (goTarget != null && goTarget.GetGoType() == GameObjectTypes.Button)
                                goTarget.UseDoorOrButton((uint)nTimeToToggle);
                        }
                    }
                }
            }
        }

        private GameObject _FindGameObject(WorldObject searchObject, ulong guid)
        {
            var bounds = searchObject.GetMap().GetGameObjectBySpawnIdStore().LookupByKey(guid);
            if (bounds.Empty())
                return null;

            return bounds[0];
        }

        // Process queued scripts
        void ScriptsProcess()
        {
            if (m_scriptSchedule.Empty())
                return;

            // Process overdue queued scripts
            KeyValuePair<long, ScriptAction> iter = m_scriptSchedule.First();
            while (!m_scriptSchedule.Empty() && (iter.Key <= Global.WorldMgr.GetGameTime()))
            {
                ScriptAction step = iter.Value;

                WorldObject source = null;
                if (!step.sourceGUID.IsEmpty())
                {
                    switch (step.sourceGUID.GetHigh())
                    {
                        case HighGuid.Item: // as well as HIGHGUID_CONTAINER
                            Player player = Global.ObjAccessor.FindPlayer(step.ownerGUID);
                            if (player != null)
                                source = player.GetItemByGuid(step.sourceGUID);
                            break;
                        case HighGuid.Creature:
                        case HighGuid.Vehicle:
                            source = GetCreature(step.sourceGUID);
                            break;
                        case HighGuid.Pet:
                            source = GetPet(step.sourceGUID);
                            break;
                        case HighGuid.Player:
                            source = Global.ObjAccessor.FindPlayer(step.sourceGUID);
                            break;
                        case HighGuid.GameObject:
                        case HighGuid.Transport:
                            source = GetGameObject(step.sourceGUID);
                            break;
                        case HighGuid.Corpse:
                            source = GetCorpse(step.sourceGUID);
                            break;
                        default:
                            Log.outError(LogFilter.Scripts, "{0} source with unsupported high guid (GUID: {1}, high guid: {2}).",
                                step.script.GetDebugInfo(), step.sourceGUID, step.sourceGUID.ToString());
                            break;
                    }
                }

                WorldObject target = null;
                if (!step.targetGUID.IsEmpty())
                {
                    switch (step.targetGUID.GetHigh())
                    {
                        case HighGuid.Creature:
                        case HighGuid.Vehicle:
                            target = GetCreature(step.targetGUID);
                            break;
                        case HighGuid.Pet:
                            target = GetPet(step.targetGUID);
                            break;
                        case HighGuid.Player:
                            target = Global.ObjAccessor.FindPlayer(step.targetGUID);
                            break;
                        case HighGuid.GameObject:
                        case HighGuid.Transport:
                            target = GetGameObject(step.targetGUID);
                            break;
                        case HighGuid.Corpse:
                            target = GetCorpse(step.targetGUID);
                            break;
                        default:
                            Log.outError(LogFilter.Scripts, "{0} target with unsupported high guid {1}.", step.script.GetDebugInfo(), step.targetGUID.ToString());
                            break;
                    }
                }

                switch (step.script.command)
                {
                    case ScriptCommands.Talk:
                        {
                            if (step.script.Talk.ChatType > ChatMsg.Whisper && step.script.Talk.ChatType != ChatMsg.RaidBossWhisper)
                            {
                                Log.outError(LogFilter.Scripts, "{0} invalid chat type ({1}) specified, skipping.",
                                    step.script.GetDebugInfo(), step.script.Talk.ChatType);
                                break;
                            }

                            if (step.script.Talk.Flags.HasAnyFlag(eScriptFlags.TalkUsePlayer))
                                source = _GetScriptPlayerSourceOrTarget(source, target, step.script);
                            else
                                source = _GetScriptCreatureSourceOrTarget(source, target, step.script);

                            if (source)
                            {
                                Unit sourceUnit = source.ToUnit();
                                if (!sourceUnit)
                                {
                                    Log.outError(LogFilter.Scripts, "{0} source object ({1}) is not an unit, skipping.", step.script.GetDebugInfo(), source.GetGUID().ToString());
                                    break;
                                }

                                switch (step.script.Talk.ChatType)
                                {
                                    case ChatMsg.Say:
                                        sourceUnit.Say((uint)step.script.Talk.TextID, target);
                                        break;
                                    case ChatMsg.Yell:
                                        sourceUnit.Yell((uint)step.script.Talk.TextID, target);
                                        break;
                                    case ChatMsg.Emote:
                                    case ChatMsg.RaidBossEmote:
                                        sourceUnit.TextEmote((uint)step.script.Talk.TextID, target, step.script.Talk.ChatType == ChatMsg.RaidBossEmote);
                                        break;
                                    case ChatMsg.Whisper:
                                    case ChatMsg.RaidBossWhisper:
                                        {
                                            Player receiver = target ? target.ToPlayer() : null;
                                            if (!receiver)
                                                Log.outError(LogFilter.Scripts, "{0} attempt to whisper to non-player unit, skipping.", step.script.GetDebugInfo());
                                            else
                                                sourceUnit.Whisper((uint)step.script.Talk.TextID, receiver, step.script.Talk.ChatType == ChatMsg.RaidBossWhisper);
                                            break;
                                        }
                                    default:
                                        break; // must be already checked at load
                                }
                            }
                            break;
                        }
                    case ScriptCommands.Emote:
                        {
                            // Source or target must be Creature.
                            Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script);
                            if (cSource)
                            {
                                if (step.script.Emote.Flags.HasAnyFlag(eScriptFlags.EmoteUseState))
                                    cSource.SetUInt32Value(UnitFields.NpcEmotestate, step.script.Emote.EmoteID);
                                else
                                    cSource.HandleEmoteCommand((Emote)step.script.Emote.EmoteID);
                            }
                            break;
                        }
                    case ScriptCommands.FieldSet:
                        {
                            // Source or target must be Creature.
                            Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script);
                            if (cSource)
                            {
                                // Validate field number.
                                if (step.script.FieldSet.FieldID <= (int)ObjectFields.Entry ||
                                    step.script.FieldSet.FieldID >= cSource.valuesCount)
                                    Log.outError(LogFilter.Scripts,
                                        "{0} wrong field {1} (max count: {2}) in object (TypeId: {3}, Entry: {4}, GUID: {5}) specified, skipping.",
                                        step.script.GetDebugInfo(), step.script.FieldSet.FieldID,
                                        cSource.valuesCount, cSource.GetTypeId(), cSource.GetEntry(), cSource.GetGUID().ToString());
                                else
                                    cSource.SetUInt32Value(step.script.FieldSet.FieldID, step.script.FieldSet.FieldValue);
                            }
                            break;
                        }
                    case ScriptCommands.MoveTo:
                        {
                            // Source or target must be Creature.
                            Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script);
                            if (cSource)
                            {
                                Unit unit = cSource.ToUnit();
                                if (step.script.MoveTo.TravelTime != 0)
                                {
                                    float speed =
                                        unit.GetDistance(step.script.MoveTo.DestX, step.script.MoveTo.DestY,
                                            step.script.MoveTo.DestZ) / (step.script.MoveTo.TravelTime * 0.001f);
                                    unit.MonsterMoveWithSpeed(step.script.MoveTo.DestX, step.script.MoveTo.DestY,
                                        step.script.MoveTo.DestZ, speed);
                                }
                                else
                                    unit.NearTeleportTo(step.script.MoveTo.DestX, step.script.MoveTo.DestY,
                                        step.script.MoveTo.DestZ, unit.GetOrientation());
                            }
                            break;
                        }
                    case ScriptCommands.FlagSet:
                        {
                            // Source or target must be Creature.
                            Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script);
                            if (cSource)
                            {
                                // Validate field number.
                                if (step.script.FlagToggle.FieldID <= (int)ObjectFields.Entry ||
                                    step.script.FlagToggle.FieldID >= cSource.valuesCount)
                                    Log.outError(LogFilter.Scripts,
                                        "{0} wrong field {1} (max count: {2}) in object (TypeId: {3}, Entry: {4}, GUID: {5}) specified, skipping.",
                                        step.script.GetDebugInfo(), step.script.FlagToggle.FieldID,
                                        cSource.valuesCount, cSource.GetTypeId(), cSource.GetEntry(), cSource.GetGUID().ToString());
                                else
                                    cSource.SetFlag(step.script.FlagToggle.FieldID, step.script.FlagToggle.FieldValue);
                            }
                            break;
                        }
                    case ScriptCommands.FlagRemove:
                        {
                            // Source or target must be Creature.
                            Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script);
                            if (cSource)
                            {
                                // Validate field number.
                                if (step.script.FlagToggle.FieldID <= (int)ObjectFields.Entry ||
                                    step.script.FlagToggle.FieldID >= cSource.valuesCount)
                                    Log.outError(LogFilter.Scripts,
                                        "{0} wrong field {1} (max count: {2}) in object (TypeId: {3}, Entry: {4}, GUID: {5}) specified, skipping.",
                                        step.script.GetDebugInfo(), step.script.FlagToggle.FieldID,
                                        cSource.valuesCount, cSource.GetTypeId(), cSource.GetEntry(), cSource.GetGUID().ToString());
                                else
                                    cSource.RemoveFlag(step.script.FlagToggle.FieldID, step.script.FlagToggle.FieldValue);
                            }
                            break;
                        }
                    case ScriptCommands.TeleportTo:
                        {
                            if (step.script.TeleportTo.Flags.HasAnyFlag(eScriptFlags.TeleportUseCreature))
                            {
                                // Source or target must be Creature.
                                Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script);
                                if (cSource)
                                    cSource.NearTeleportTo(step.script.TeleportTo.DestX, step.script.TeleportTo.DestY,
                                        step.script.TeleportTo.DestZ, step.script.TeleportTo.Orientation);
                            }
                            else
                            {
                                // Source or target must be Player.
                                Player player = _GetScriptPlayerSourceOrTarget(source, target, step.script);
                                if (player)
                                    player.TeleportTo(step.script.TeleportTo.MapID, step.script.TeleportTo.DestX,
                                        step.script.TeleportTo.DestY, step.script.TeleportTo.DestZ, step.script.TeleportTo.Orientation);
                            }
                            break;
                        }
                    case ScriptCommands.QuestExplored:
                        {
                            if (!source)
                            {
                                Log.outError(LogFilter.Scripts, "{0} source object is NULL.", step.script.GetDebugInfo());
                                break;
                            }
                            if (!target)
                            {
                                Log.outError(LogFilter.Scripts, "{0} target object is NULL.", step.script.GetDebugInfo());
                                break;
                            }

                            // when script called for item spell casting then target == (unit or GO) and source is player
                            WorldObject worldObject;
                            Player player = target.ToPlayer();
                            if (player != null)
                            {
                                if (!source.IsTypeId(TypeId.Unit) && !source.IsTypeId(TypeId.GameObject) && !source.IsTypeId(TypeId.Player))
                                {
                                    Log.outError(LogFilter.Scripts, "{0} source is not unit, gameobject or player (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.",
                                        step.script.GetDebugInfo(), source.GetTypeId(), source.GetEntry(), source.GetGUID().ToString());
                                    break;
                                }
                                worldObject = source;
                            }
                            else
                            {
                                player = source.ToPlayer();
                                if (player != null)
                                {
                                    if (!target.IsTypeId(TypeId.Unit) && !target.IsTypeId(TypeId.GameObject) && !target.IsTypeId(TypeId.Player))
                                    {
                                        Log.outError(LogFilter.Scripts,
                                            "{0} target is not unit, gameobject or player (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", step.script.GetDebugInfo(), target.GetTypeId(), target.GetEntry(), target.GetGUID().ToString());
                                        break;
                                    }
                                    worldObject = target;
                                }
                                else
                                {
                                    Log.outError(LogFilter.Scripts, "{0} neither source nor target is player (Entry: {0}, GUID: {1}; target: Entry: {2}, GUID: {3}), skipping.",
                                        step.script.GetDebugInfo(), source.GetEntry(), source.GetGUID().ToString(), target.GetEntry(), target.GetGUID().ToString());
                                    break;
                                }
                            }

                            // quest id and flags checked at script loading
                            if ((!worldObject.IsTypeId(TypeId.Unit) || worldObject.ToUnit().IsAlive()) &&
                                (step.script.QuestExplored.Distance == 0 ||
                                 worldObject.IsWithinDistInMap(player, step.script.QuestExplored.Distance)))
                                player.AreaExploredOrEventHappens(step.script.QuestExplored.QuestID);
                            else
                                player.FailQuest(step.script.QuestExplored.QuestID);

                            break;
                        }

                    case ScriptCommands.KillCredit:
                        {
                            // Source or target must be Player.
                            Player player = _GetScriptPlayerSourceOrTarget(source, target, step.script);
                            if (player)
                            {
                                if (step.script.KillCredit.Flags.HasAnyFlag(eScriptFlags.KillcreditRewardGroup))
                                    player.RewardPlayerAndGroupAtEvent(step.script.KillCredit.CreatureEntry, player);
                                else
                                    player.KilledMonsterCredit(step.script.KillCredit.CreatureEntry, ObjectGuid.Empty);
                            }
                            break;
                        }
                    case ScriptCommands.RespawnGameobject:
                        {
                            if (step.script.RespawnGameObject.GOGuid == 0)
                            {
                                Log.outError(LogFilter.Scripts, "{0} gameobject guid (datalong) is not specified.", step.script.GetDebugInfo());
                                break;
                            }

                            // Source or target must be WorldObject.
                            WorldObject pSummoner = _GetScriptWorldObject(source, true, step.script);
                            if (pSummoner)
                            {
                                GameObject pGO = _FindGameObject(pSummoner, step.script.RespawnGameObject.GOGuid);
                                if (pGO == null)
                                {
                                    Log.outError(LogFilter.Scripts, "{0} gameobject was not found (guid: {1}).", step.script.GetDebugInfo(), step.script.RespawnGameObject.GOGuid);
                                    break;
                                }

                                if (pGO.GetGoType() == GameObjectTypes.FishingNode ||
                                    pGO.GetGoType() == GameObjectTypes.Door || pGO.GetGoType() == GameObjectTypes.Button ||
                                    pGO.GetGoType() == GameObjectTypes.Trap)
                                {
                                    Log.outError(LogFilter.Scripts,
                                        "{0} can not be used with gameobject of type {1} (guid: {2}).", step.script.GetDebugInfo(), pGO.GetGoType(), step.script.RespawnGameObject.GOGuid);
                                    break;
                                }

                                // Check that GO is not spawned
                                if (!pGO.isSpawned())
                                {
                                    int nTimeToDespawn = Math.Max(5, (int)step.script.RespawnGameObject.DespawnDelay);
                                    pGO.SetLootState(LootState.Ready);
                                    pGO.SetRespawnTime(nTimeToDespawn);

                                    pGO.GetMap().AddToMap(pGO);
                                }
                            }
                            break;
                        }
                    case ScriptCommands.TempSummonCreature:
                        {
                            // Source must be WorldObject.
                            WorldObject pSummoner = _GetScriptWorldObject(source, true, step.script);
                            if (pSummoner)
                            {
                                if (step.script.TempSummonCreature.CreatureEntry == 0)
                                    Log.outError(LogFilter.Scripts, "{0} creature entry (datalong) is not specified.", step.script.GetDebugInfo());
                                else
                                {
                                    float x = step.script.TempSummonCreature.PosX;
                                    float y = step.script.TempSummonCreature.PosY;
                                    float z = step.script.TempSummonCreature.PosZ;
                                    float o = step.script.TempSummonCreature.Orientation;

                                    if (pSummoner.SummonCreature(step.script.TempSummonCreature.CreatureEntry, x, y, z, o, TempSummonType.TimedOrDeadDespawn, step.script.TempSummonCreature.DespawnDelay) == null)
                                        Log.outError(LogFilter.Scripts, "{0} creature was not spawned (entry: {1}).", step.script.GetDebugInfo(), step.script.TempSummonCreature.CreatureEntry);
                                }
                            }
                            break;
                        }

                    case ScriptCommands.OpenDoor:
                    case ScriptCommands.CloseDoor:
                        _ScriptProcessDoor(source, target, step.script);
                        break;
                    case ScriptCommands.ActivateObject:
                        {
                            // Source must be Unit.
                            Unit unit = _GetScriptUnit(source, true, step.script);
                            if (unit)
                            {
                                // Target must be GameObject.
                                if (target != null)
                                {
                                    Log.outError(LogFilter.Scripts, "{0} target object is NULL.", step.script.GetDebugInfo());
                                    break;
                                }

                                if (!target.IsTypeId(TypeId.GameObject))
                                {
                                    Log.outError(LogFilter.Scripts,
                                        "{0} target object is not gameobject (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", step.script.GetDebugInfo(), target.GetTypeId(), target.GetEntry(),
                                        target.GetGUID().ToString());
                                    break;
                                }
                                GameObject pGO = target.ToGameObject();
                                if (pGO)
                                    pGO.Use(unit);
                            }
                            break;
                        }
                    case ScriptCommands.RemoveAura:
                        {
                            // Source (datalong2 != 0) or target (datalong2 == 0) must be Unit.
                            bool bReverse = step.script.RemoveAura.Flags.HasAnyFlag(eScriptFlags.RemoveauraReverse);
                            Unit unit = _GetScriptUnit(bReverse ? source : target, bReverse, step.script);
                            if (unit)
                                unit.RemoveAurasDueToSpell(step.script.RemoveAura.SpellID);
                            break;
                        }
                    case ScriptCommands.CastSpell:
                        {
                            //@todo Allow gameobjects to be targets and casters
                            if (source == null && target == null)
                            {
                                Log.outError(LogFilter.Scripts, "{0} source and target objects are NULL.", step.script.GetDebugInfo());
                                break;
                            }

                            Unit uSource = null;
                            Unit uTarget = null;
                            // source/target cast spell at target/source (script.datalong2: 0: s.t 1: s.s 2: t.t 3: t.s
                            switch (step.script.CastSpell.Flags)
                            {
                                case eScriptFlags.CastspellSourceToTarget: // source . target
                                    uSource = source?.ToUnit();
                                    uTarget = target?.ToUnit();
                                    break;
                                case eScriptFlags.CastspellSourceToSource: // source . source
                                    uSource =source?.ToUnit();
                                    uTarget = uSource;
                                    break;
                                case eScriptFlags.CastspellTargetToTarget: // target . target
                                    uSource = target?.ToUnit();
                                    uTarget = uSource;
                                    break;
                                case eScriptFlags.CastspellTargetToSource: // target . source
                                    uSource = target?.ToUnit();
                                    uTarget = source?.ToUnit();
                                    break;
                                case eScriptFlags.CastspellSearchCreature: // source . creature with entry
                                    uSource = source?.ToUnit();
                                    uTarget = uSource?.FindNearestCreature((uint)Math.Abs(step.script.CastSpell.CreatureEntry), step.script.CastSpell.SearchRadius);
                                    break;
                            }

                            if (uSource == null || !uSource.isTypeMask(TypeMask.Unit))
                            {
                                Log.outError(LogFilter.Scripts, "{0} no source unit found for spell {1}", step.script.GetDebugInfo(), step.script.CastSpell.SpellID);
                                break;
                            }

                            if (uTarget == null || !uTarget.isTypeMask(TypeMask.Unit))
                            {
                                Log.outError(LogFilter.Scripts, "{0} no target unit found for spell {1}", step.script.GetDebugInfo(), step.script.CastSpell.SpellID);
                                break;
                            }

                            bool triggered = ((int)step.script.CastSpell.Flags != 4)
                                ? step.script.CastSpell.CreatureEntry.HasAnyFlag((int)eScriptFlags.CastspellTriggered)
                                : step.script.CastSpell.CreatureEntry < 0;
                            uSource.CastSpell(uTarget, step.script.CastSpell.SpellID, triggered);
                            break;
                        }

                    case ScriptCommands.PlaySound:
                        // Source must be WorldObject.
                        WorldObject obj = _GetScriptWorldObject(source, true, step.script);
                        if (obj)
                        {
                            // PlaySound.Flags bitmask: 0/1=anyone/target
                            Player player2 = null;
                            if (step.script.PlaySound.Flags.HasAnyFlag(eScriptFlags.PlaysoundTargetPlayer))
                            {
                                // Target must be Player.
                                player2 = _GetScriptPlayer(target, false, step.script);
                                if (target == null)
                                    break;
                            }

                            // PlaySound.Flags bitmask: 0/2=without/with distance dependent
                            if (step.script.PlaySound.Flags.HasAnyFlag(eScriptFlags.PlaysoundDistanceSound))
                                obj.PlayDistanceSound(step.script.PlaySound.SoundID, player2);
                            else
                                obj.PlayDirectSound(step.script.PlaySound.SoundID, player2);
                        }
                        break;

                    case ScriptCommands.CreateItem:
                        // Target or source must be Player.
                        Player pReceiver = _GetScriptPlayerSourceOrTarget(source, target, step.script);
                        if (pReceiver)
                        {
                            var dest = new List<ItemPosCount>();
                            InventoryResult msg = pReceiver.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, step.script.CreateItem.ItemEntry, step.script.CreateItem.Amount);
                            if (msg == InventoryResult.Ok)
                            {
                                Item item = pReceiver.StoreNewItem(dest, step.script.CreateItem.ItemEntry, true);
                                if (item != null)
                                    pReceiver.SendNewItem(item, step.script.CreateItem.Amount, false, true);
                            }
                            else
                                pReceiver.SendEquipError(msg, null, null, step.script.CreateItem.ItemEntry);
                        }
                        break;

                    case ScriptCommands.DespawnSelf:
                        {
                            // Target or source must be Creature.
                            Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script, true);
                            if (cSource)
                                cSource.DespawnOrUnsummon(step.script.DespawnSelf.DespawnDelay);
                            break;
                        }
                    case ScriptCommands.LoadPath:
                        {
                            // Source must be Unit.
                            Unit unit = _GetScriptUnit(source, true, step.script);
                            if (unit)
                            {
                                if (Global.WaypointMgr.GetPath(step.script.LoadPath.PathID) == null)
                                    Log.outError(LogFilter.Scripts, "{0} source object has an invalid path ({1}), skipping.", step.script.GetDebugInfo(), step.script.LoadPath.PathID);
                                else
                                    unit.GetMotionMaster().MovePath(step.script.LoadPath.PathID, step.script.LoadPath.IsRepeatable != 0);
                            }
                            break;
                        }
                    case ScriptCommands.CallscriptToUnit:
                        {
                            if (step.script.CallScript.CreatureEntry == 0)
                            {
                                Log.outError(LogFilter.Scripts, "{0} creature entry is not specified, skipping.", step.script.GetDebugInfo());
                                break;
                            }
                            if (step.script.CallScript.ScriptID == 0)
                            {
                                Log.outError(LogFilter.Scripts, "{0} script id is not specified, skipping.", step.script.GetDebugInfo());
                                break;
                            }

                            Creature cTarget = null;
                            var creatureBounds = _creatureBySpawnIdStore.LookupByKey(step.script.CallScript.CreatureEntry);
                            if (!creatureBounds.Empty())
                            {
                                // Prefer alive (last respawned) creature
                                var foundCreature = creatureBounds.Find(creature => { return creature.IsAlive(); });

                                cTarget = foundCreature ?? creatureBounds[0];
                            }

                            if (cTarget == null)
                            {
                                Log.outError(LogFilter.Scripts, "{0} target was not found (entry: {1})", step.script.GetDebugInfo(), step.script.CallScript.CreatureEntry);
                                break;
                            }

                            // Insert script into schedule but do not start it
                            ScriptsStart((ScriptsType)step.script.CallScript.ScriptType, step.script.CallScript.ScriptID, cTarget, null);
                            break;
                        }

                    case ScriptCommands.Kill:
                        {
                            // Source or target must be Creature.
                            Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script);
                            if (cSource)
                            {
                                if (cSource.IsDead())
                                    Log.outError(LogFilter.Scripts, "{0} creature is already dead (Entry: {1}, GUID: {2})", step.script.GetDebugInfo(), cSource.GetEntry(), cSource.GetGUID().ToString());
                                else
                                {
                                    cSource.setDeathState(DeathState.JustDied);
                                    if (step.script.Kill.RemoveCorpse == 1)
                                        cSource.RemoveCorpse();
                                }
                            }
                            break;
                        }
                    case ScriptCommands.Orientation:
                        {
                            // Source must be Unit.
                            Unit sourceUnit = _GetScriptUnit(source, true, step.script);
                            if (sourceUnit)
                            {
                                if (step.script.Orientation.Flags.HasAnyFlag(eScriptFlags.OrientationFaceTarget))
                                {
                                    // Target must be Unit.
                                    Unit targetUnit = _GetScriptUnit(target, false, step.script);
                                    if (targetUnit == null)
                                        break;

                                    sourceUnit.SetFacingToObject(targetUnit);
                                }
                                else
                                    sourceUnit.SetFacingTo(step.script.Orientation._Orientation);
                            }
                            break;
                        }
                    case ScriptCommands.Equip:
                        {
                            // Source must be Creature.
                            Creature cSource = _GetScriptCreature(source, target, step.script);
                            if (cSource)
                                cSource.LoadEquipment((int)step.script.Equip.EquipmentID);
                            break;
                        }
                    case ScriptCommands.Model:
                        {
                            // Source must be Creature.
                            Creature cSource = _GetScriptCreature(source, target, step.script);
                            if (cSource)
                                cSource.SetDisplayId(step.script.Model.ModelID);
                            break;
                        }
                    case ScriptCommands.CloseGossip:
                        {
                            // Source must be Player.
                            Player player = _GetScriptPlayer(source, true, step.script);
                            if (player != null)
                                player.PlayerTalkClass.SendCloseGossip();
                            break;
                        }
                    case ScriptCommands.Playmovie:
                        {
                            // Source must be Player.
                            Player player = _GetScriptPlayer(source, true, step.script);
                            if (player)
                                player.SendMovieStart(step.script.PlayMovie.MovieID);
                            break;
                        }
                    case ScriptCommands.Movement:
                        {
                            // Source must be Creature.
                            Creature cSource = _GetScriptCreature(source, true, step.script);
                            if (cSource)
                            {
                                if (!cSource.IsAlive())
                                    return;

                                cSource.GetMotionMaster().MovementExpired();
                                cSource.GetMotionMaster().MoveIdle();

                                switch ((MovementGeneratorType)step.script.Movement.MovementType)
                                {
                                    case MovementGeneratorType.Random:
                                        cSource.GetMotionMaster().MoveRandom(step.script.Movement.MovementDistance);
                                        break;
                                    case MovementGeneratorType.Waypoint:
                                        cSource.GetMotionMaster().MovePath((uint)step.script.Movement.Path, false);
                                        break;
                                }
                            }
                            break;
                        }
                    case ScriptCommands.PlayAnimkit:
                        {
                            // Source must be Creature.
                            Creature cSource = _GetScriptCreature(source, true, step.script);
                            if (cSource)
                                cSource.PlayOneShotAnimKitId((ushort)step.script.PlayAnimKit.AnimKitID);
                            break;
                        }
                    default:
                        Log.outError(LogFilter.Scripts, "Unknown script command {0}.", step.script.GetDebugInfo());
                        break;
                }

                m_scriptSchedule.Remove(iter);
                iter = m_scriptSchedule.FirstOrDefault();
                Global.MapMgr.DecreaseScheduledScriptCount();
            }
        }

        #endregion

        #region Fields
        bool _creatureToMoveLock;
        List<Creature> creaturesToMove = new List<Creature>();

        bool _gameObjectsToMoveLock;
        List<GameObject> _gameObjectsToMove = new List<GameObject>();

        bool _dynamicObjectsToMoveLock;
        List<DynamicObject> _dynamicObjectsToMove = new List<DynamicObject>();

        bool _areaTriggersToMoveLock;
        List<AreaTrigger> _areaTriggersToMove = new List<AreaTrigger>();

        GridMap[][] GridMaps = new GridMap[MapConst.MaxGrids][];
        Dictionary<ulong, long> _creatureRespawnTimes = new Dictionary<ulong, long>();
        DynamicMapTree _dynamicTree = new DynamicMapTree();

        Dictionary<ulong, long> _goRespawnTimes = new Dictionary<ulong, long>();
        List<Transport> _transports = new List<Transport>();
        Grid[][] i_grids = new Grid[MapConst.MaxGrids][];
        MapRecord i_mapRecord;
        List<WorldObject> i_objectsToRemove = new List<WorldObject>();
        Dictionary<WorldObject, bool> i_objectsToSwitch = new Dictionary<WorldObject, bool>();
        Difficulty i_spawnMode;
        List<WorldObject> i_worldObjects = new List<WorldObject>();
        protected List<WorldObject> m_activeNonPlayers = new List<WorldObject>();
        protected List<Player> m_activePlayers = new List<Player>();
        Map m_parentMap;
        SortedMultiMap<long, ScriptAction> m_scriptSchedule = new SortedMultiMap<long, ScriptAction>();

        BitArray marked_cells = new BitArray(MapConst.TotalCellsPerMap * MapConst.TotalCellsPerMap);
        public Dictionary<uint, CreatureGroup> CreatureGroupHolder = new Dictionary<uint, CreatureGroup>();
        internal uint i_InstanceId;
        long i_gridExpiry;
        List<WorldObject> i_objects = new List<WorldObject>();
        bool i_scriptLock;

        public int m_VisibilityNotifyPeriod;
        public float m_VisibleDistance;
        internal uint m_unloadTimer;

        Dictionary<uint, ZoneDynamicInfo> _zoneDynamicInfo = new Dictionary<uint, ZoneDynamicInfo>();
        uint _defaultLight;
        Dictionary<HighGuid, ObjectGuidGenerator> _guidGenerators = new Dictionary<HighGuid, ObjectGuidGenerator>();
        Dictionary<ObjectGuid, WorldObject> _objectsStore = new Dictionary<ObjectGuid, WorldObject>();
        MultiMap<ulong, Creature> _creatureBySpawnIdStore = new MultiMap<ulong, Creature>();
        MultiMap<ulong, GameObject> _gameobjectBySpawnIdStore = new MultiMap<ulong, GameObject>();
        MultiMap<uint, Corpse> _corpsesByCell = new MultiMap<uint, Corpse>();
        Dictionary<ObjectGuid, Corpse> _corpsesByPlayer = new Dictionary<ObjectGuid, Corpse>();
        List<Corpse> _corpseBones = new List<Corpse>();

        List<WorldObject> _updateObjects = new List<WorldObject>();
        #endregion
    }

    public class InstanceMap : Map
    {
        public InstanceMap(uint id, long expiry, uint InstanceId, Difficulty spawnMode, Map _parent)
            : base(id, expiry, InstanceId, spawnMode, _parent)
        {
            //lets initialize visibility distance for dungeons
            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 = (uint)Math.Max(WorldConfig.GetIntValue(WorldCfg.InstanceUnloadDelay), 1);
        }

        public override void InitVisibilityDistance()
        {
            //init visibility distance for instances
            m_VisibleDistance = Global.WorldMgr.GetMaxVisibleDistanceInInstances();
            m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodInInstances();
        }

        public override EnterState CannotEnter(Player player)
        {
            if (player.GetMap() == this)
            {
                Log.outError(LogFilter.Maps, "InstanceMap:CannotEnter - player {0} ({1}) already in map {2}, {3}, {4}!", player.GetName(), player.GetGUID().ToString(), GetId(), GetInstanceId(), GetSpawnMode());
                Contract.Assert(false);
                return EnterState.CannotEnterAlreadyInMap;
            }

            // allow GM's to enter
            if (player.IsGameMaster())
                return base.CannotEnter(player);

            // cannot enter if the instance is full (player cap), GMs don't count
            uint maxPlayers = GetMaxPlayers();
            if (GetPlayersCountExceptGMs() >= maxPlayers)
            {
                Log.outInfo(LogFilter.Maps, "MAP: Instance '{0}' of map '{1}' cannot have more than '{2}' players. Player '{3}' rejected", GetInstanceId(), GetMapName(), maxPlayers, player.GetName());
                return EnterState.CannotEnterMaxPlayers;
            }

            // cannot enter while an encounter is in progress (unless this is a relog, in which case it is permitted)
            if (!player.IsLoading() && IsRaid() && GetInstanceScript() != null && GetInstanceScript().IsEncounterInProgress())
                return EnterState.CannotEnterZoneInCombat;

            // cannot enter if player is permanent saved to a different instance id
            InstanceBind playerBind = player.GetBoundInstance(GetId(), GetDifficultyID());
            if (playerBind != null)
                if (playerBind.perm && playerBind.save != null)
                    if (playerBind.save.GetInstanceId() != GetInstanceId())
                        return EnterState.CannotEnterInstanceBindMismatch;

            return base.CannotEnter(player);
        }

        public override bool AddPlayerToMap(Player player, bool initPlayer = true)
        {
            /// @todo Not sure about checking player level: already done in HandleAreaTriggerOpcode
            // GMs still can teleport player in instance.
            // Is it needed?
            {
                // Dungeon only code
                if (IsDungeon())
                {
                    Group group = player.GetGroup();

                    // increase current instances (hourly limit)
                    if (!group || !group.isLFGGroup())
                        player.AddInstanceEnterTime(GetInstanceId(), Time.UnixTime);

                    // get or create an instance save for the map
                    InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId());
                    if (mapSave == null)
                    {
                        Log.outInfo(LogFilter.Maps, "InstanceMap.Add: creating instance save for map {0} spawnmode {1} with instance id {2}", GetId(), GetSpawnMode(), GetInstanceId());
                        mapSave = Global.InstanceSaveMgr.AddInstanceSave(GetId(), GetInstanceId(), GetSpawnMode(), 0, 0, true);
                    }

                    Contract.Assert(mapSave != null);

                    // check for existing instance binds
                    InstanceBind playerBind = player.GetBoundInstance(GetId(), GetSpawnMode());
                    if (playerBind != null && playerBind.perm)
                    {
                        // cannot enter other instances if bound permanently
                        if (playerBind.save != mapSave)
                        {
                            Log.outError(LogFilter.Maps, "InstanceMap.Add: player {0}({1}) is permanently bound to instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is being put into instance {9} {10}, {11}, {12}, {13}, {14}, {15}",
                                player.GetName(), player.GetGUID().ToString(), GetMapName(), playerBind.save.GetMapId(),
                                playerBind.save.GetInstanceId(), playerBind.save.GetDifficultyID(),
                                playerBind.save.GetPlayerCount(), playerBind.save.GetGroupCount(),
                                playerBind.save.CanReset(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(),
                                mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(),
                                mapSave.CanReset());
                            return false;
                        }
                    }
                    else
                    {
                        if (group)
                        {
                            // solo saves should have been reset when the map was loaded
                            InstanceBind groupBind = group.GetBoundInstance(this);
                            if (playerBind != null && playerBind.save != mapSave)
                            {
                                Log.outError(LogFilter.Maps,
                                    "InstanceMapAdd: player {0}({1}) is being put into instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is in group {9} and is bound to instance {10}, {11}, {12}, {13}, {14}, {15}!",
                                    player.GetName(), player.GetGUID().ToString(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(),
                                    mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(),
                                    mapSave.CanReset(), group.GetLeaderGUID().ToString(),
                                    playerBind.save.GetMapId(), playerBind.save.GetInstanceId(),
                                    playerBind.save.GetDifficultyID(), playerBind.save.GetPlayerCount(),
                                    playerBind.save.GetGroupCount(), playerBind.save.CanReset());
                                if (groupBind != null)
                                    Log.outError(LogFilter.Maps,
                                        "InstanceMap.Add: the group is bound to the instance {0} {1}, {2}, {3}, {4}, {5}, {6}",
                                        GetMapName(), groupBind.save.GetMapId(), groupBind.save.GetInstanceId(),
                                        groupBind.save.GetDifficultyID(), groupBind.save.GetPlayerCount(),
                                        groupBind.save.GetGroupCount(), groupBind.save.CanReset());
                                Contract.Assert(false);
                                return false;
                            }
                            // bind to the group or keep using the group save
                            if (groupBind == null)
                                group.BindToInstance(mapSave, false);
                            else
                            {
                                // cannot jump to a different instance without resetting it
                                if (groupBind.save != mapSave)
                                {
                                    Log.outError(LogFilter.Maps,
                                        "InstanceMap.Add: player {0}({1}) is being put into instance {2}, {3}, {4} but he is in group {5} which is bound to instance {6}, {7}, {8}!",
                                        player.GetName(), player.GetGUID().ToString(), mapSave.GetMapId(), mapSave.GetInstanceId(),
                                        mapSave.GetDifficultyID(), group.GetLeaderGUID().ToString(),
                                        groupBind.save.GetMapId(), groupBind.save.GetInstanceId(),
                                        groupBind.save.GetDifficultyID());
                                    Log.outError(LogFilter.Maps, "MapSave players: {0}, group count: {1}",
                                        mapSave.GetPlayerCount(), mapSave.GetGroupCount());
                                    if (groupBind.save != null)
                                        Log.outError(LogFilter.Maps, "GroupBind save players: {0}, group count: {1}",
                                            groupBind.save.GetPlayerCount(), groupBind.save.GetGroupCount());
                                    else
                                        Log.outError(LogFilter.Maps, "GroupBind save NULL");
                                    return false;
                                }
                                // if the group/leader is permanently bound to the instance
                                // players also become permanently bound when they enter
                                if (groupBind.perm)
                                {
                                    PendingRaidLock pendingRaidLock = new PendingRaidLock();
                                    pendingRaidLock.TimeUntilLock = 60000;
                                    pendingRaidLock.CompletedMask = i_data != null ? i_data.GetCompletedEncounterMask() : 0;
                                    pendingRaidLock.Extending = false;
                                    pendingRaidLock.WarningOnly = false; // events it throws:  1 : INSTANCE_LOCK_WARNING   0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START
                                    player.SendPacket(pendingRaidLock);
                                    player.SetPendingBind(mapSave.GetInstanceId(), 60000);
                                }
                            }
                        }
                        else
                        {
                            // set up a solo bind or continue using it
                            if (playerBind == null)
                                player.BindToInstance(mapSave, false);
                            else
                                // cannot jump to a different instance without resetting it
                                Contract.Assert(playerBind.save == mapSave);
                        }
                    }
                }

                // for normal instances cancel the reset schedule when the
                // first player enters (no players yet)
                SetResetSchedule(false);

                Log.outInfo(LogFilter.Maps, "MAP: Player '{0}' entered instance '{1}' of map '{2}'", player.GetName(),
                    GetInstanceId(), GetMapName());
                // initialize unload state
                m_unloadTimer = 0;
                m_resetAfterUnload = false;
                m_unloadWhenEmpty = false;
            }

            // this will acquire the same mutex so it cannot be in the previous block
            base.AddPlayerToMap(player, initPlayer);

            if (i_data != null)
                i_data.OnPlayerEnter(player);

            if (i_scenario != null)
                i_scenario.OnPlayerEnter(player);

            return true;
        }

        public override void Update(uint diff)
        {
            base.Update(diff);

            if (i_data != null)
            {
                i_data.Update(diff);
                i_data.UpdateCombatResurrection(diff);
            }

            if (i_scenario != null)
                i_scenario.Update(diff);
        }

        public override void RemovePlayerFromMap(Player player, bool remove)
        {
            Log.outInfo(LogFilter.Maps,
                "MAP: Removing player '{0}' from instance '{1}' of map '{2}' before relocating to another map",
                player.GetName(), GetInstanceId(), GetMapName());
            //if last player set unload timer
            if (m_unloadTimer == 0 && GetPlayers().Count == 1)
                m_unloadTimer = m_unloadWhenEmpty ? 1 : (uint)Math.Max(WorldConfig.GetIntValue(WorldCfg.InstanceUnloadDelay), 1);

            if (i_scenario != null)
                i_scenario.OnPlayerExit(player);

            base.RemovePlayerFromMap(player, remove);
            // for normal instances schedule the reset after all players have left
            SetResetSchedule(true);
        }

        public void CreateInstanceData(bool load)
        {
            if (i_data != null)
                return;

            InstanceTemplate mInstance = Global.ObjectMgr.GetInstanceTemplate(GetId());
            if (mInstance != null)
            {
                i_script_id = mInstance.ScriptId;
                i_data = Global.ScriptMgr.CreateInstanceData(this);
            }

            if (i_data == null)
                return;

            i_data.Initialize();

            if (load)
            {
                /// @todo make a global storage for this
                PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_INSTANCE);
                stmt.AddValue(0, GetId());
                stmt.AddValue(1, i_InstanceId);
                SQLResult result = DB.Characters.Query(stmt);

                if (!result.IsEmpty())
                {
                    var data = result.Read<string>(0);
                    i_data.SetCompletedEncountersMask(result.Read<uint>(1));
                    if (data != "")
                    {
                        Log.outDebug(LogFilter.Maps, "Loading instance data for `{0}` with id {1}",
                            Global.ObjectMgr.GetScriptName(i_script_id), i_InstanceId);
                        i_data.Load(data);
                    }
                }
            }
        }

        public bool Reset(InstanceResetMethod method)
        {
            // note: since the map may not be loaded when the instance needs to be reset
            // the instance must be deleted from the DB by InstanceSaveManager

            if (HavePlayers())
            {
                if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty)
                {
                    // notify the players to leave the instance so it can be reset
                    foreach (Player player in GetPlayers())
                        player.SendResetFailedNotify(GetId());
                }
                else
                {
                    bool doUnload = true;
                    if (method == InstanceResetMethod.Global)
                    {
                        // set the homebind timer for players inside (1 minute)
                        foreach (Player player in GetPlayers())
                        {
                            InstanceBind bind = player.GetBoundInstance(GetId(), GetDifficultyID());
                            if (bind != null && bind.extendState != 0 && bind.save.GetInstanceId() == GetInstanceId())
                                doUnload = false;
                            else
                                player.m_InstanceValid = false;
                        }

                        if (doUnload && HasPermBoundPlayers()) // check if any unloaded players have a nonexpired save to this
                            doUnload = false;
                    }

                    if (doUnload)
                    {
                        // the unload timer is not started
                        // instead the map will unload immediately after the players have left
                        m_unloadWhenEmpty = true;
                        m_resetAfterUnload = true;
                    }
                }
            }
            else
            {
                // unloaded at next update
                m_unloadTimer = 1;
                m_resetAfterUnload = !(method == InstanceResetMethod.Global && HasPermBoundPlayers());
            }

            return GetPlayers().Empty();
        }

        public string GetScriptName()
        {
            return Global.ObjectMgr.GetScriptName(i_script_id);
        }

        public void PermBindAllPlayers()
        {
            if (!IsDungeon())
                return;

            InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId());
            if (save == null)
            {
                Log.outError(LogFilter.Maps, "Cannot bind players to instance map (Name: {0}, Entry: {1}, Difficulty: {2}, ID: {3}) because no instance save is available!", GetMapName(), GetId(), GetDifficultyID(), GetInstanceId());
                return;
            }

            // perm bind all players that are currently inside the instance
            foreach (Player player in GetPlayers())
            {
                // never instance bind GMs with GM mode enabled
                if (player.IsGameMaster())
                    continue;

                InstanceBind bind = player.GetBoundInstance(save.GetMapId(), save.GetDifficultyID());
                if (bind != null && bind.perm)
                {
                    if (bind.save != null && bind.save.GetInstanceId() != save.GetInstanceId())
                    {
                        Log.outError(LogFilter.Maps, "Player (GUID: {0}, Name: {1}) is in instance map (Name: {2}, Entry: {3}, Difficulty: {4}, ID: {5}) that is being bound, but already has a save for the map on ID {6}!", 
                            player.GetGUID().GetCounter(), player.GetName(), GetMapName(), save.GetMapId(), save.GetDifficultyID(), save.GetInstanceId(), bind.save.GetInstanceId());
                    }
                    else if (bind.save == null)
                    {
                        Log.outError(LogFilter.Maps, "Player (GUID: {0}, Name: {1}) is in instance map (Name: {2}, Entry: {3}, Difficulty: {4}, ID: {5}) that is being bound, but already has a bind (without associated save) for the map!",
                            player.GetGUID().GetCounter(), player.GetName(), GetMapName(), save.GetMapId(), save.GetDifficultyID(), save.GetInstanceId());
                    }
                }
                else
                {
                    player.BindToInstance(save, true);
                    InstanceSaveCreated data = new InstanceSaveCreated();
                    data.Gm = player.IsGameMaster();
                    player.SendPacket(data);

                    player.GetSession().SendCalendarRaidLockout(save, true);

                    // if group leader is in instance, group also gets bound
                    Group group = player.GetGroup();
                    if (group)
                        if (group.GetLeaderGUID() == player.GetGUID())
                            group.BindToInstance(save, true);
                }
            }
        }

        public override void UnloadAll()
        {
            Contract.Assert(!HavePlayers());

            if (m_resetAfterUnload)
            {
                DeleteRespawnTimes();
                DeleteCorpseData();
            }

            base.UnloadAll();
        }

        public void SendResetWarnings(uint timeLeft)
        {
            foreach (Player player in GetPlayers())
                player.SendInstanceResetWarning(GetId(), player.GetDifficultyID(GetEntry()), timeLeft, true);
        }

        void SetResetSchedule(bool on)
        {
            // only for normal instances
            // the reset time is only scheduled when there are no payers inside
            // it is assumed that the reset time will rarely (if ever) change while the reset is scheduled
            if (IsDungeon() && !HavePlayers() && !IsRaidOrHeroicDungeon())
            {
                InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId());
                if (save != null)
                    Global.InstanceSaveMgr.ScheduleReset(on, save.GetResetTime(),
                        new InstanceSaveManager.InstResetEvent(0, GetId(), GetSpawnMode(), GetInstanceId()));
                else
                    Log.outError(LogFilter.Maps,
                        "InstanceMap.SetResetSchedule: cannot turn schedule {0}, there is no save information for instance (map [id: {1}, name: {2}], instance id: {3}, difficulty: {4})",
                        on ? "on" : "off", GetId(), GetMapName(), GetInstanceId(), GetSpawnMode());
            }
        }

        bool HasPermBoundPlayers()
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PERM_BIND_BY_INSTANCE);
            stmt.AddValue(0, GetInstanceId());
            return !DB.Characters.Query(stmt).IsEmpty();
        }

        public uint GetMaxPlayers()
        {
            MapDifficultyRecord mapDiff = GetMapDifficulty();
            if (mapDiff != null && mapDiff.MaxPlayers != 0)
                return mapDiff.MaxPlayers;

            return GetEntry().MaxPlayers;
        }

        private uint GetMaxResetDelay()
        {
            MapDifficultyRecord mapDiff = GetMapDifficulty();
            return mapDiff != null ? mapDiff.GetRaidDuration() : 0;
        }

        public uint GetScriptId()
        {
            return i_script_id;
        }

        public InstanceScript GetInstanceScript()
        {
            return i_data;
        }

        public InstanceScenario GetInstanceScenario() { return i_scenario; }
        public void SetInstanceScenario(InstanceScenario scenario) { i_scenario = scenario; }

        InstanceScript i_data;
        uint i_script_id;
        InstanceScenario i_scenario;
        bool m_resetAfterUnload;
        bool m_unloadWhenEmpty;
    }

    public class BattlegroundMap : Map
    {
        public BattlegroundMap(uint id, uint expiry, uint InstanceId, Map _parent, Difficulty spawnMode)
            : base(id, expiry, InstanceId, spawnMode, _parent)
        {
            InitVisibilityDistance();
        }

        public override void InitVisibilityDistance()
        {
            m_VisibleDistance = Global.WorldMgr.GetMaxVisibleDistanceInBGArenas();
            m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodInBGArenas();
        }

        public override EnterState CannotEnter(Player player)
        {
            if (player.GetMap() == this)
            {
                Log.outError(LogFilter.Maps, "BGMap:CannotEnter - player {0} is already in map!", player.GetGUID().ToString());
                Contract.Assert(false);
                return EnterState.CannotEnterAlreadyInMap;
            }

            if (player.GetBattlegroundId() != GetInstanceId())
                return EnterState.CannotEnterInstanceBindMismatch;

            return base.CannotEnter(player);
        }

        public override bool AddPlayerToMap(Player player, bool initPlayer = true)
        {
            player.m_InstanceValid = true;
            return base.AddPlayerToMap(player, initPlayer);
        }

        public override void RemovePlayerFromMap(Player player, bool remove)
        {
            Log.outInfo(LogFilter.Maps,
                "MAP: Removing player '{0}' from bg '{1}' of map '{2}' before relocating to another map", player.GetName(),
                GetInstanceId(), GetMapName());
            base.RemovePlayerFromMap(player, remove);
        }

        public void SetUnload()
        {
            m_unloadTimer = 1;
        }

        public override void RemoveAllPlayers()
        {
            if (HavePlayers())
                foreach (Player player in m_activePlayers)
                    if (!player.IsBeingTeleportedFar())
                        player.TeleportTo(player.GetBattlegroundEntryPoint());
        }

        public Battleground GetBG() { return m_bg; }
        public void SetBG(Battleground bg) { m_bg = bg; }

        Battleground m_bg;
    }

    public struct ScriptAction
    {
        public ObjectGuid ownerGUID;

        // owner of source if source is item
        public ScriptInfo script;

        public ObjectGuid sourceGUID;
        public ObjectGuid targetGUID;
    }

    public class ZoneDynamicInfo
    {
        public uint MusicId;
        public WeatherState WeatherId = WeatherState.Fine;
        public float WeatherGrade;
        public uint OverrideLightId;
        public uint LightFadeInTime;
    }
}