f7ae161e3d
Port From (https://github.com/TrinityCore/TrinityCore/commit/86725ed9267803506d8a5cab04c2c5641a234dd9)
301 lines
12 KiB
C#
301 lines
12 KiB
C#
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
|
|
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
|
|
|
|
using Framework.Constants;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace Game
|
|
{
|
|
public class MMapManager : Singleton<MMapManager>
|
|
{
|
|
MMapManager() { }
|
|
|
|
const string MAP_FILE_NAME_FORMAT = "{0}/mmaps/{1:D4}.mmap";
|
|
const string TILE_FILE_NAME_FORMAT = "{0}/mmaps/{1:D4}{2:D2}{3:D2}.mmtile";
|
|
|
|
public void Initialize(MultiMap<uint, uint> mapData)
|
|
{
|
|
foreach (var pair in mapData)
|
|
parentMapData[pair.Value] = pair.Key;
|
|
}
|
|
|
|
MMapData GetMMapData(uint mapId)
|
|
{
|
|
return loadedMMaps.LookupByKey(mapId);
|
|
}
|
|
|
|
bool LoadMapData(string basePath, uint mapId)
|
|
{
|
|
// we already have this map loaded?
|
|
if (loadedMMaps.ContainsKey(mapId) && loadedMMaps[mapId] != null)
|
|
return true;
|
|
|
|
// load and init dtNavMesh - read parameters from file
|
|
string filename = string.Format(MAP_FILE_NAME_FORMAT, basePath, mapId);
|
|
if (!File.Exists(filename))
|
|
{
|
|
Log.outError(LogFilter.Maps, "Could not open mmap file {0}", filename);
|
|
return false;
|
|
}
|
|
|
|
using BinaryReader reader = new(new FileStream(filename, FileMode.Open, FileAccess.Read), Encoding.UTF8);
|
|
Detour.dtNavMeshParams Params = new();
|
|
Params.orig[0] = reader.ReadSingle();
|
|
Params.orig[1] = reader.ReadSingle();
|
|
Params.orig[2] = reader.ReadSingle();
|
|
|
|
Params.tileWidth = reader.ReadSingle();
|
|
Params.tileHeight = reader.ReadSingle();
|
|
Params.maxTiles = reader.ReadInt32();
|
|
Params.maxPolys = reader.ReadInt32();
|
|
|
|
Detour.dtNavMesh mesh = new();
|
|
if (Detour.dtStatusFailed(mesh.init(Params)))
|
|
{
|
|
Log.outError(LogFilter.Maps, "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {0:D4} from file {1}", mapId, filename);
|
|
return false;
|
|
}
|
|
|
|
Log.outInfo(LogFilter.Maps, "MMAP:loadMapData: Loaded {0:D4}.mmap", mapId);
|
|
|
|
// store inside our map list
|
|
loadedMMaps[mapId] = new MMapData(mesh);
|
|
return true;
|
|
}
|
|
|
|
uint PackTileID(int x, int y)
|
|
{
|
|
return (uint)(x << 16 | y);
|
|
}
|
|
|
|
public bool LoadMap(string basePath, uint mapId, int x, int y)
|
|
{
|
|
// make sure the mmap is loaded and ready to load tiles
|
|
if (!LoadMapData(basePath, mapId))
|
|
return false;
|
|
|
|
// get this mmap data
|
|
MMapData mmap = loadedMMaps[mapId];
|
|
Cypher.Assert(mmap.navMesh != null);
|
|
|
|
// check if we already have this tile loaded
|
|
uint packedGridPos = PackTileID(x, y);
|
|
if (mmap.loadedTileRefs.ContainsKey(packedGridPos))
|
|
return false;
|
|
|
|
// load this tile . mmaps/MMMXXYY.mmtile
|
|
string fileName = string.Format(TILE_FILE_NAME_FORMAT, basePath, mapId, x, y);
|
|
if (!File.Exists(fileName))
|
|
{
|
|
if (parentMapData.ContainsKey(mapId))
|
|
fileName = string.Format(TILE_FILE_NAME_FORMAT, basePath, parentMapData[mapId], x, y);
|
|
}
|
|
|
|
if (!File.Exists(fileName))
|
|
{
|
|
Log.outDebug(LogFilter.Maps, "MMAP:loadMap: Could not open mmtile file '{0}'", fileName);
|
|
return false;
|
|
}
|
|
|
|
using BinaryReader reader = new(new FileStream(fileName, FileMode.Open, FileAccess.Read));
|
|
MmapTileHeader fileHeader = reader.Read<MmapTileHeader>();
|
|
if (fileHeader.mmapMagic != MapConst.mmapMagic)
|
|
{
|
|
Log.outError(LogFilter.Maps, "MMAP:loadMap: Bad header in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
|
return false;
|
|
}
|
|
if (fileHeader.mmapVersion != MapConst.mmapVersion)
|
|
{
|
|
Log.outError(LogFilter.Maps, "MMAP:loadMap: {0:D4}{1:D2}{2:D2}.mmtile was built with generator v{3}, expected v{4}",
|
|
mapId, x, y, fileHeader.mmapVersion, MapConst.mmapVersion);
|
|
return false;
|
|
}
|
|
|
|
var bytes = reader.ReadBytes((int)fileHeader.size);
|
|
Detour.dtRawTileData data = new();
|
|
data.FromBytes(bytes, 0);
|
|
|
|
ulong tileRef = 0;
|
|
// memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
|
|
if (Detour.dtStatusSucceed(mmap.navMesh.addTile(data, 1, 0, ref tileRef)))
|
|
{
|
|
mmap.loadedTileRefs.Add(packedGridPos, tileRef);
|
|
++loadedTiles;
|
|
Log.outInfo(LogFilter.Maps, "MMAP:loadMap: Loaded mmtile {0:D4}[{1:D2}, {2:D2}]", mapId, x, y);
|
|
return true;
|
|
}
|
|
|
|
Log.outError(LogFilter.Maps, "MMAP:loadMap: Could not load {0:D4}{1:D2}{2:D2}.mmtile into navmesh", mapId, x, y);
|
|
return false;
|
|
}
|
|
|
|
public bool LoadMapInstance(string basePath, uint meshMapId, uint instanceMapId, uint instanceId)
|
|
{
|
|
if (!LoadMapData(basePath, meshMapId))
|
|
return false;
|
|
|
|
MMapData mmap = loadedMMaps[meshMapId];
|
|
if (mmap.navMeshQueries.ContainsKey((instanceMapId, instanceId)))
|
|
return true;
|
|
|
|
// allocate mesh query
|
|
Detour.dtNavMeshQuery query = new();
|
|
if (Detour.dtStatusFailed(query.init(mmap.navMesh, 1024)))
|
|
{
|
|
Log.outError(LogFilter.Maps, $"MMAP.GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {instanceMapId:D4} instanceId {instanceId}");
|
|
return false;
|
|
}
|
|
|
|
Log.outDebug(LogFilter.Maps, $"MMAP.GetNavMeshQuery: created dtNavMeshQuery for mapId {instanceMapId:D4} instanceId {instanceId}");
|
|
mmap.navMeshQueries.Add((instanceMapId, instanceId), query);
|
|
return true;
|
|
}
|
|
|
|
public bool UnloadMap(uint mapId, int x, int y)
|
|
{
|
|
// check if we have this map loaded
|
|
MMapData mmap = GetMMapData(mapId);
|
|
if (mmap == null)
|
|
{
|
|
// file may not exist, therefore not loaded
|
|
Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
|
return false;
|
|
}
|
|
|
|
// check if we have this tile loaded
|
|
uint packedGridPos = PackTileID(x, y);
|
|
if (!mmap.loadedTileRefs.ContainsKey(packedGridPos))
|
|
{
|
|
// file may not exist, therefore not loaded
|
|
Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh tile. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
|
return false;
|
|
}
|
|
|
|
ulong tileRef = mmap.loadedTileRefs[packedGridPos];
|
|
|
|
// unload, and mark as non loaded
|
|
if (Detour.dtStatusFailed(mmap.navMesh.removeTile(tileRef, out _)))
|
|
{
|
|
// this is technically a memory leak
|
|
// if the grid is later reloaded, dtNavMesh.addTile will return error but no extra memory is used
|
|
// we cannot recover from this error - assert out
|
|
Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y);
|
|
Cypher.Assert(false);
|
|
}
|
|
else
|
|
{
|
|
mmap.loadedTileRefs.Remove(packedGridPos);
|
|
--loadedTiles;
|
|
Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4}[{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool UnloadMap(uint mapId)
|
|
{
|
|
if (!loadedMMaps.ContainsKey(mapId))
|
|
{
|
|
// file may not exist, therefore not loaded
|
|
Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map {0:D4}", mapId);
|
|
return false;
|
|
}
|
|
|
|
// unload all tiles from given map
|
|
MMapData mmap = loadedMMaps.LookupByKey(mapId);
|
|
foreach (var i in mmap.loadedTileRefs)
|
|
{
|
|
uint x = (i.Key >> 16);
|
|
uint y = (i.Key & 0x0000FFFF);
|
|
if (Detour.dtStatusFailed(mmap.navMesh.removeTile(i.Value, out _)))
|
|
Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y);
|
|
else
|
|
{
|
|
--loadedTiles;
|
|
Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4} [{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId);
|
|
}
|
|
}
|
|
|
|
loadedMMaps.Remove(mapId);
|
|
Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded {0:D4}.mmap", mapId);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool UnloadMapInstance(uint meshMapId, uint instanceMapId, uint instanceId)
|
|
{
|
|
// check if we have this map loaded
|
|
MMapData mmap = GetMMapData(meshMapId);
|
|
if (mmap == null)
|
|
{
|
|
// file may not exist, therefore not loaded
|
|
Log.outDebug(LogFilter.Maps, $"MMAP:unloadMapInstance: Asked to unload not loaded navmesh map {meshMapId}");
|
|
return false;
|
|
}
|
|
|
|
if (!mmap.navMeshQueries.ContainsKey((instanceMapId, instanceId)))
|
|
{
|
|
Log.outDebug(LogFilter.Maps, $"MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {instanceMapId} instanceId {instanceId}");
|
|
return false;
|
|
}
|
|
|
|
mmap.navMeshQueries.Remove((instanceMapId, instanceId));
|
|
Log.outInfo(LogFilter.Maps, $"MMAP:unloadMapInstance: Unloaded mapId {instanceMapId} instanceId {instanceId}");
|
|
|
|
return true;
|
|
}
|
|
|
|
public Detour.dtNavMesh GetNavMesh(uint mapId)
|
|
{
|
|
MMapData mmap = GetMMapData(mapId);
|
|
if (mmap == null)
|
|
return null;
|
|
|
|
return mmap.navMesh;
|
|
}
|
|
|
|
public Detour.dtNavMeshQuery GetNavMeshQuery(uint meshMapId, uint instanceMapId, uint instanceId)
|
|
{
|
|
MMapData mmap = GetMMapData(meshMapId);
|
|
if (mmap == null)
|
|
return null;
|
|
|
|
return mmap.navMeshQueries.LookupByKey((instanceMapId, instanceId));
|
|
}
|
|
|
|
public uint GetLoadedTilesCount() { return loadedTiles; }
|
|
public int GetLoadedMapsCount() { return loadedMMaps.Count; }
|
|
|
|
Dictionary<uint, MMapData> loadedMMaps = new();
|
|
uint loadedTiles;
|
|
|
|
Dictionary<uint, uint> parentMapData = new();
|
|
}
|
|
|
|
public class MMapData
|
|
{
|
|
public MMapData(Detour.dtNavMesh mesh)
|
|
{
|
|
navMesh = mesh;
|
|
}
|
|
|
|
public Dictionary<(uint, uint), Detour.dtNavMeshQuery> navMeshQueries = new(); // instanceId to query
|
|
|
|
public Detour.dtNavMesh navMesh;
|
|
public Dictionary<uint, ulong> loadedTileRefs = new(); // maps [map grid coords] to [dtTile]
|
|
}
|
|
|
|
public struct MmapTileHeader
|
|
{
|
|
public uint mmapMagic;
|
|
public uint dtVersion;
|
|
public uint mmapVersion;
|
|
public uint size;
|
|
public byte usesLiquids;
|
|
}
|
|
}
|