diff --git a/Source/Game/Collision/Management/VMapManager.cs b/Source/Game/Collision/Management/VMapManager.cs index 4fc64b7a4..41802cab2 100644 --- a/Source/Game/Collision/Management/VMapManager.cs +++ b/Source/Game/Collision/Management/VMapManager.cs @@ -33,7 +33,8 @@ namespace Game.Collision { Success, FileNotFound, - VersionMismatch + VersionMismatch, + InvalidFile } public class VMapManager : Singleton diff --git a/Source/Game/Collision/Maps/MapTree.cs b/Source/Game/Collision/Maps/MapTree.cs index a0b513669..8f69b01d7 100644 --- a/Source/Game/Collision/Maps/MapTree.cs +++ b/Source/Game/Collision/Maps/MapTree.cs @@ -233,8 +233,14 @@ namespace Game.Collision if (!File.Exists(tilefile)) { int parentMapId = vm.GetParentMapId(mapID); - if (parentMapId != -1) + while (parentMapId != -1) + { tilefile = vmapPath + GetTileFileName((uint)parentMapId, tileX, tileY); + if (File.Exists(tilefile)) + break; + + parentMapId = vm.GetParentMapId((uint)parentMapId); + } } if (!File.Exists(tilefile)) diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index a65fa9e13..d94fb5d47 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -4692,7 +4692,7 @@ namespace Game.Entities AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(GetAreaId()); // Such zones are considered unreachable as a ghost and the player must be automatically revived - if ((!IsAlive() && zone != null && zone.Flags[0].HasAnyFlag(AreaFlags.NeedFly)) || GetTransport() != null || GetPositionZ() < GetMap().GetMinHeight(GetPositionX(), GetPositionY())) + if ((!IsAlive() && zone != null && zone.Flags[0].HasAnyFlag(AreaFlags.NeedFly)) || GetTransport() != null || GetPositionZ() < GetMap().GetMinHeight(GetPhaseShift(), GetPositionX(), GetPositionY())) { ResurrectPlayer(0.5f); SpawnCorpseBones(); @@ -4729,7 +4729,7 @@ namespace Game.Entities SendPacket(packet); } } - else if (GetPositionZ() < GetMap().GetMinHeight(GetPositionX(), GetPositionY())) + else if (GetPositionZ() < GetMap().GetMinHeight(GetPhaseShift(), GetPositionX(), GetPositionY())) TeleportTo(homebind); RemovePlayerFlag(PlayerFlags.IsOutOfBounds); diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index 87b1389c9..0a18be44d 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -188,7 +188,7 @@ namespace Game plrMover.UpdateFallInformationIfNeed(movementInfo, opcode); - if (movementInfo.Pos.posZ < plrMover.GetMap().GetMinHeight(movementInfo.Pos.GetPositionX(), movementInfo.Pos.GetPositionY())) + if (movementInfo.Pos.posZ < plrMover.GetMap().GetMinHeight(plrMover.GetPhaseShift(), movementInfo.Pos.GetPositionX(), movementInfo.Pos.GetPositionY())) { if (!(plrMover.GetBattleground() && plrMover.GetBattleground().HandlePlayerUnderMap(GetPlayer()))) { diff --git a/Source/Game/Maps/GridMap.cs b/Source/Game/Maps/GridMap.cs index 9563c6e74..81a451c52 100644 --- a/Source/Game/Maps/GridMap.cs +++ b/Source/Game/Maps/GridMap.cs @@ -21,6 +21,7 @@ using Game.DataStorage; using System; using System.Collections.Generic; using System.IO; +using Game.Collision; namespace Game.Maps { @@ -36,41 +37,40 @@ namespace Game.Maps _liquidLevel = MapConst.InvalidHeight; } - public bool LoadData(string filename) + public LoadResult LoadData(string filename) { UnloadData(); if (!File.Exists(filename)) - return true; + return LoadResult.FileNotFound; - _fileExists = true; using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) { MapFileHeader header = reader.Read(); if (header.mapMagic != MapConst.MapMagic || header.versionMagic != MapConst.MapVersionMagic) { Log.outError(LogFilter.Maps, $"Map file '{filename}' is from an incompatible map version. Please recreate using the mapextractor."); - return false; + return LoadResult.InvalidFile; } if (header.areaMapOffset != 0 && !LoadAreaData(reader, header.areaMapOffset)) { Log.outError(LogFilter.Maps, "Error loading map area data"); - return false; + return LoadResult.InvalidFile; } if (header.heightMapOffset != 0 && !LoadHeightData(reader, header.heightMapOffset)) { Log.outError(LogFilter.Maps, "Error loading map height data"); - return false; + return LoadResult.InvalidFile; } if (header.liquidMapOffset != 0 && !LoadLiquidData(reader, header.liquidMapOffset)) { Log.outError(LogFilter.Maps, "Error loading map liquids data"); - return false; + return LoadResult.InvalidFile; } - return true; + return LoadResult.Success; } } @@ -83,7 +83,6 @@ namespace Game.Maps _liquidFlags = null; _liquidMap = null; _gridGetHeight = GetHeightFromFlat; - _fileExists = false; } bool LoadAreaData(BinaryReader reader, uint offset) @@ -596,8 +595,6 @@ namespace Game.Maps public float GetHeight(float x, float y) { return _gridGetHeight(x, y); } - public bool GileExists() { return _fileExists; } - #region Fields delegate float GetHeightDel(float x, float y); @@ -630,7 +627,6 @@ namespace Game.Maps byte _liquidOffY; byte _liquidWidth; byte _liquidHeight; - bool _fileExists; #endregion } diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 97bc65017..8e5da8d29 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -32,6 +32,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; namespace Game.Maps { @@ -106,6 +107,22 @@ namespace Game.Maps Global.MMapMgr.UnloadMapInstance(GetId(), i_InstanceId); } + public void DiscoverGridMapFiles() + { + for (uint gx = 0; gx < MapConst.MaxGrids; ++gx) + for (uint gy = 0; gy < MapConst.MaxGrids; ++gy) + i_gridFileExists[(int)(gx * MapConst.MaxGrids + gy)] = ExistMap(GetId(), gx, gy); + } + + Map GetRootParentTerrainMap() + { + Map map = this; + while (map != map.m_parentTerrainMap) + map = map.m_parentTerrainMap; + + return map; + } + public static bool ExistMap(uint mapid, uint gx, uint gy) { string fileName = $"{Global.WorldMgr.GetDataPath()}/maps/{mapid:D4}_{gx:D2}_{gy:D2}.map"; @@ -195,29 +212,33 @@ namespace Game.Maps if (map.GridMaps[gx][gy] != null) return; - Map parent = map.m_parentMap; - ++parent.GridMapReference[gx][gy]; + // map file name + string fileName = $"{Global.WorldMgr.GetDataPath()}/maps/{map.GetId():D4}_{gx:D2}_{gy:D2}.map"; + Log.outInfo(LogFilter.Maps, "Loading map {0}", fileName); - // load grid map for base map - if (parent != map) + // loading data + GridMap gridMap = new GridMap(); + LoadResult gridMapLoadResult = gridMap.LoadData(fileName); + if (gridMapLoadResult == LoadResult.Success) + map.GridMaps[gx][gy] = gridMap; + else { - GridCoord ngridCoord = new GridCoord((MapConst.MaxGrids - 1) - gx, (MapConst.MaxGrids - 1) - gy); - if (parent.GridMaps[gx][gy] == null) - parent.EnsureGridCreated(ngridCoord); + map.i_gridFileExists[(int)(gx * MapConst.MaxGrids + gy)] = false; + Map parentTerrain = map; + while (parentTerrain != parentTerrain.m_parentTerrainMap) + { + map.GridMaps[gx][gy] = parentTerrain.m_parentTerrainMap.GridMaps[gx][gy]; + if (map.GridMaps[gx][gy] != null) + break; - map.GridMaps[gx][gy] = parent.GridMaps[gx][gy]; - return; + parentTerrain = parentTerrain.m_parentTerrainMap; + } } - // map file name - string filename = $"{Global.WorldMgr.GetDataPath()}/maps/{map.GetId():D4}_{gx:D2}_{gy:D2}.map"; - Log.outInfo(LogFilter.Maps, "Loading map {0}", filename); - // loading data - map.GridMaps[gx][gy] = new GridMap(); - if (!map.GridMaps[gx][gy].LoadData(filename)) - Log.outError(LogFilter.Maps, "Error loading map file: {0}", filename); - - Global.ScriptMgr.OnLoadGridMap(map, map.GridMaps[gx][gy], gx, gy); + if (map.GridMaps[gx][gy] != null) + Global.ScriptMgr.OnLoadGridMap(map, map.GridMaps[gx][gy], gx, gy); + else if (gridMapLoadResult == LoadResult.InvalidFile) + Log.outError(LogFilter.Maps, $"Error loading map file: {fileName}"); } void UnloadMap(uint gx, uint gy) @@ -230,25 +251,18 @@ namespace Game.Maps void UnloadMapImpl(Map map, uint gx, uint gy) { - if (map.GridMaps[gx][gy] != null) - { - Map parent = map.m_parentMap; - - if ((--parent.GridMapReference[gx][gy]) == 0) - { - parent.GridMaps[gx][gy].UnloadData(); - parent.GridMaps[gx][gy] = null; - } - } - map.GridMaps[gx][gy] = null; } void LoadMapAndVMap(uint gx, uint gy) { LoadMap(gx, gy); - LoadVMap(gx, gy); - LoadMMap(gx, gy); + // Only load the data for the base map + if (this == m_parentMap) + { + LoadVMap(gx, gy); + LoadMMap(gx, gy); + } } public void LoadAllCells() @@ -404,7 +418,21 @@ namespace Game.Maps uint gy = (MapConst.MaxGrids - 1) - p.Y_coord; if (GridMaps[gx][gy] == null) - m_parentTerrainMap.LoadMapAndVMap(gx, gy); + { + Map rootParentTerrainMap = m_parentMap.GetRootParentTerrainMap(); + // because LoadMapAndVMap is always entered using rootParentTerrainMap, we can only lock that once and not have to do it for every child map + if (this != rootParentTerrainMap) + Monitor.Enter(rootParentTerrainMap._gridLock); + + if (m_parentMap.GridMaps[gx][gy] == null) + rootParentTerrainMap.LoadMapAndVMap(gx, gy); + + if (m_parentMap.GridMaps[gx][gy] != null) + { + GridMaps[gx][gy] = m_parentMap.GridMaps[gx][gy]; + ++rootParentTerrainMap.GridMapReference[gx][gy]; + } + } } } @@ -1614,9 +1642,13 @@ namespace Game.Maps // delete grid map, but don't delete if it is from parent map (and thus only reference) if (GridMaps[gx][gy] != null) { - m_parentTerrainMap.UnloadMap(gx, gy); - Global.VMapMgr.UnloadMap(m_parentTerrainMap.GetId(), gx, gy); - Global.MMapMgr.UnloadMap(m_parentTerrainMap.GetId(), gx, gy); + Map terrainRoot = m_parentMap.GetRootParentTerrainMap(); + if (--terrainRoot.GridMapReference[gx][gy] == 0) + { + m_parentMap.GetRootParentTerrainMap().UnloadMap(gx, gy); + Global.VMapMgr.UnloadMap(terrainRoot.GetId(), gx, gy); + Global.MMapMgr.UnloadMap(terrainRoot.GetId(), gx, gy); + } } Log.outDebug(LogFilter.Maps, "Unloading grid[{0}, {1}] for map {2} finished", x, y, GetId()); @@ -1690,9 +1722,6 @@ namespace Game.Maps GridMap GetGridMap(uint mapId, float x, float y) { - if (GetId() == mapId) - return GetGridMap(x, y); - // half opt method uint gx = (uint)(MapConst.CenterGridId - x / MapConst.SizeofGrids); //grid x uint gy = (uint)(MapConst.CenterGridId - y / MapConst.SizeofGrids); //grid y @@ -1702,16 +1731,16 @@ namespace Game.Maps GridMap grid = GridMaps[gx][gy]; var childMap = m_childTerrainMaps.Find(childTerrainMap => childTerrainMap.GetId() == mapId); - if (childMap != null && childMap.GridMaps[gx][gy].GileExists()) + if (childMap != null && childMap.GridMaps[gx][gy] != null) grid = childMap.GridMaps[gx][gy]; return grid; } - public bool HasGridMap(uint mapId, uint gx, uint gy) + public bool HasChildMapGridFile(uint mapId, uint gx, uint gy) { var childMap = m_childTerrainMaps.Find(childTerrainMap => childTerrainMap.GetId() == mapId); - return childMap != null && childMap.GridMaps[gx][gy] != null && childMap.GridMaps[gx][gy].GileExists(); + return childMap != null && childMap.i_gridFileExists[(int)(gx * MapConst.MaxGrids + gy)]; } public float GetWaterOrGroundLevel(PhaseShift phaseShift, float x, float y, float z) @@ -1722,7 +1751,7 @@ namespace Game.Maps public float GetWaterOrGroundLevel(PhaseShift phaseShift, float x, float y, float z, ref float ground, bool swim = false) { - if (GetGridMap(x, y) != null) + if (GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y) != null) { // we need ground level (including grid height version) for proper return water level in point float ground_z = GetHeight(phaseShift, x, y, z, true, 50.0f); @@ -1742,7 +1771,7 @@ namespace Game.Maps // find raw .map surface under Z coordinates float mapHeight = MapConst.VMAPInvalidHeightValue; uint terrainMapId = PhasingHandler.GetTerrainMapId(phaseShift, this, x, y); - GridMap gmap = m_parentTerrainMap.GetGridMap(terrainMapId, x, y); + GridMap gmap = GetGridMap(terrainMapId, x, y); if (gmap != null) { float gridHeight = gmap.GetHeight(x, y); @@ -1779,9 +1808,9 @@ namespace Game.Maps return mapHeight; // explicitly use map data } - public float GetMinHeight(float x, float y) + public float GetMinHeight(PhaseShift phaseShift, float x, float y) { - GridMap grid = GetGridMap(x, y); + GridMap grid = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); if (grid != null) return grid.GetMinHeight(x, y); @@ -1887,7 +1916,7 @@ namespace Game.Maps if (hasVmapAreaInfo || hasDynamicAreaInfo) { // check if there's terrain between player height and object height - GridMap gmap = m_parentTerrainMap.GetGridMap(terrainMapId, x, y); + GridMap gmap = GetGridMap(terrainMapId, x, y); if (gmap != null) { float mapHeight = gmap.GetHeight(x, y); @@ -1929,7 +1958,7 @@ namespace Game.Maps if (areaId == 0) { - GridMap gmap = m_parentTerrainMap.GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); + GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); if (gmap != null) areaId = gmap.GetArea(x, y); @@ -1968,7 +1997,7 @@ namespace Game.Maps private byte GetTerrainType(PhaseShift phaseShift, float x, float y) { - GridMap gmap = m_parentTerrainMap.GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); + GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); if (gmap != null) return gmap.GetTerrainType(x, y); return 0; @@ -2046,7 +2075,7 @@ namespace Game.Maps } } - GridMap gmap = m_parentTerrainMap.GetGridMap(terrainMapId, x, y); + GridMap gmap = GetGridMap(terrainMapId, x, y); if (gmap != null) { var map_data = new LiquidData(); @@ -2068,7 +2097,7 @@ namespace Game.Maps public float GetWaterLevel(PhaseShift phaseShift, float x, float y) { - GridMap gmap = m_parentTerrainMap.GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); + GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); if (gmap != null) return gmap.GetLiquidLevel(x, y); return 0; @@ -4320,6 +4349,7 @@ namespace Game.Maps List m_childTerrainMaps = new List(); // contains m_parentMap of maps that have MapEntry::ParentMapID == GetId() SortedMultiMap m_scriptSchedule = new SortedMultiMap(); + BitSet i_gridFileExists = new BitSet(MapConst.MaxGrids * MapConst.MaxGrids); // cache what grids are available for this map (not including parent/child maps) BitSet marked_cells = new BitSet(MapConst.TotalCellsPerMap * MapConst.TotalCellsPerMap); public Dictionary CreatureGroupHolder = new Dictionary(); internal uint i_InstanceId; diff --git a/Source/Game/Maps/MapManager.cs b/Source/Game/Maps/MapManager.cs index de9ee359e..25e122366 100644 --- a/Source/Game/Maps/MapManager.cs +++ b/Source/Game/Maps/MapManager.cs @@ -60,9 +60,9 @@ namespace Game.Entities { var entry = CliDB.MapStorage.LookupByKey(id); Cypher.Assert(entry != null); - if (entry.ParentMapID != -1) + if (entry.ParentMapID != -1 || entry.CosmeticParentMapID != -1) { - CreateBaseMap((uint)entry.ParentMapID); + CreateBaseMap((uint)(entry.ParentMapID != -1 ? entry.ParentMapID : entry.CosmeticParentMapID)); // must have been created by parent map map = FindBaseMap(id); @@ -84,6 +84,8 @@ namespace Game.Entities else map = new Map(mapEntry.Id, i_gridCleanUpDelay, 0, Difficulty.None); + map.DiscoverGridMapFiles(); + i_maps[mapEntry.Id] = map; foreach (uint childMapId in _parentMapData[mapEntry.Id]) @@ -265,16 +267,14 @@ namespace Game.Entities public void UnloadAll() { - // first unlink child maps + // first unload maps foreach (var pair in i_maps) - pair.Value.UnlinkAllChildTerrainMaps(); - - foreach (var pair in i_maps.ToList()) - { pair.Value.UnloadAll(); + + foreach (var pair in i_maps) pair.Value.Dispose(); - i_maps.Remove(pair.Key); - } + + i_maps.Clear(); if (m_updater != null) m_updater.Deactivate(); diff --git a/Source/Game/Phasing/PhasingHandler.cs b/Source/Game/Phasing/PhasingHandler.cs index 1207ba5bf..fbfbdd3d0 100644 --- a/Source/Game/Phasing/PhasingHandler.cs +++ b/Source/Game/Phasing/PhasingHandler.cs @@ -471,19 +471,12 @@ namespace Game return phaseShift.VisibleMapIds.First().Key; GridCoord gridCoord = GridDefines.ComputeGridCoord(x, y); - uint gx = (uint)((MapConst.MaxGrids - 1) - gridCoord.X_coord); - uint gy = (uint)((MapConst.MaxGrids - 1) - gridCoord.Y_coord); + uint gx = ((MapConst.MaxGrids - 1) - gridCoord.X_coord); + uint gy = ((MapConst.MaxGrids - 1) - gridCoord.Y_coord); - uint gxbegin = Math.Max(gx - 1, 0); - uint gxend = Math.Min(gx + 1, MapConst.MaxGrids); - uint gybegin = Math.Max(gy - 1, 0); - uint gyend = Math.Min(gy + 1, MapConst.MaxGrids); - - foreach (var itr in phaseShift.VisibleMapIds) - for (uint gxi = gxbegin; gxi < gxend; ++gxi) - for (uint gyi = gybegin; gyi < gyend; ++gyi) - if (map.HasGridMap(itr.Key, gxi, gyi)) - return itr.Key; + foreach (var visibleMap in phaseShift.VisibleMapIds) + if (map.HasChildMapGridFile(visibleMap.Key, gx, gy)) + return visibleMap.Key; return map.GetId(); } diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/Server/WorldManager.cs index 074715df0..f689d4537 100644 --- a/Source/Game/Server/WorldManager.cs +++ b/Source/Game/Server/WorldManager.cs @@ -360,7 +360,12 @@ namespace Game foreach (MapRecord mapEntry in CliDB.MapStorage.Values) { if (mapEntry.ParentMapID != -1) + { + Cypher.Assert(mapEntry.CosmeticParentMapID == -1 || mapEntry.ParentMapID == mapEntry.CosmeticParentMapID, $"Inconsistent parent map data for map {mapEntry.Id} (ParentMapID = {mapEntry.ParentMapID}, CosmeticParentMapID = {mapEntry.CosmeticParentMapID})"); mapData.Add((uint)mapEntry.ParentMapID, mapEntry.Id); + } + else if (mapEntry.CosmeticParentMapID != -1) + mapData.Add((uint)mapEntry.CosmeticParentMapID, mapEntry.Id); } Global.MapMgr.InitializeParentMapData(mapData);