Files
CypherCore/Source/Game/Maps/MMapManager.cs
T
Fabian a3dc7b3f48 Ported .Net Core commits:
hondacrx:
- Initial commit: Switch to .Net Core 2.0
- Fix build and removed not needed files
Fabi:
- Updated solution platforms.
- Changed folder structure.
- Change library target framework to netstandard2.0.
- Updated solution platforms again...
- Removed windows specific kernel32 function usage (Ctrl-C handler).
2017-10-26 17:23:44 +02:00

582 lines
24 KiB
C#

/*
* 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 Game.DataStorage;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
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()
{
foreach (MapRecord mapEntry in CliDB.MapStorage.Values)
{
if (mapEntry.ParentMapID != -1)
phaseMapData.Add((uint)mapEntry.ParentMapID, mapEntry.Id);
}
}
MMapData GetMMapData(uint mapId)
{
return loadedMMaps.LookupByKey(mapId);
}
bool loadMapData(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, Global.WorldMgr.GetDataPath(), mapId);
if (!File.Exists(filename))
{
Log.outError(LogFilter.Maps, "Could not open mmap file {0}", filename);
return false;
}
using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read), Encoding.UTF8))
{
Detour.dtNavMeshParams Params = new Detour.dtNavMeshParams();
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 Detour.dtNavMesh();
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, mapId);
return true;
}
}
uint packTileID(int x, int y)
{
return (uint)(x << 16 | y);
}
public bool loadMap(uint mapId, int x, int y)
{
// make sure the mmap is loaded and ready to load tiles
if (!loadMapData(mapId))
return false;
// get this mmap data
MMapData mmap = loadedMMaps[mapId];
Contract.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, Global.WorldMgr.GetDataPath(), 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 BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
{
MmapTileHeader fileHeader = reader.ReadStruct<MmapTileHeader>();
Array.Reverse(fileHeader.mmapMagic);
if (new string(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 Detour.dtRawTileData();
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, 0, 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);
var phasedMaps = phaseMapData.LookupByKey(mapId);
if (!phasedMaps.Empty())
{
mmap.AddBaseTile(packedGridPos, data, fileHeader, fileHeader.size);
LoadPhaseTiles(phasedMaps, 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;
}
}
PhasedTile LoadTile(uint mapId, int x, int y)
{
// load this tile . mmaps/MMMXXYY.mmtile
string filename = string.Format(TILE_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId, x, y);
if (!File.Exists(filename))
{
// Not all tiles have phased versions, don't flood this msg
return null;
}
PhasedTile pTile = new PhasedTile();
using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
{
// read header
pTile.fileHeader = reader.ReadStruct<MmapTileHeader>();
Array.Reverse(pTile.fileHeader.mmapMagic);
if (new string(pTile.fileHeader.mmapMagic) != MapConst.mmapMagic)
{
Log.outError(LogFilter.Maps, "MMAP.LoadTile: Bad header in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
return null;
}
if (pTile.fileHeader.mmapVersion != MapConst.mmapVersion)
{
Log.outError(LogFilter.Maps, "MMAP:LoadTile: {0:D4}{1:D2}{2:D2}.mmtile was built with generator v{3}, expected v{4}", mapId, x, y, pTile.fileHeader.mmapVersion, MapConst.mmapVersion);
return null;
}
pTile.data = new Detour.dtRawTileData();
pTile.data.FromBytes(reader.ReadBytes((int)pTile.fileHeader.size), 0);
if (pTile.data.ToBytes().Length == 0)
{
Log.outError(LogFilter.Maps, "MMAP.LoadTile: Bad header or data in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
return null;
}
}
return pTile;
}
void LoadPhaseTiles(List<uint> phasedMapData, int x, int y)
{
Log.outDebug(LogFilter.Maps, "MMAP.LoadPhaseTiles: Loading phased mmtiles for map {0}, X: {1}, Y: {2}", phasedMapData.FirstOrDefault(), x, y);
uint packedGridPos = packTileID(x, y);
foreach (uint phaseMapId in phasedMapData)
{
PhasedTile data = LoadTile(phaseMapId, x, y);
// only a few tiles have terrain swaps, do not write error for them
if (data != null)
{
Log.outDebug(LogFilter.Maps, "MMAP.LoadPhaseTiles: Loaded phased {0:D4}{1:D2}{2:D2}.mmtile for root phase map {3}", phaseMapId, x, y, phasedMapData.FirstOrDefault());
if (!_phaseTiles.ContainsKey(phaseMapId))
_phaseTiles[phaseMapId] = new Dictionary<uint, PhasedTile>();
_phaseTiles[phaseMapId][packedGridPos] = data;
}
}
}
void UnloadPhaseTile(List<uint> phasedMapData, int x, int y)
{
Log.outDebug(LogFilter.Maps, "MMAP.UnloadPhaseTile: Unloading phased mmtile for map {0}, X: {1}, Y: {2}", phasedMapData.FirstOrDefault(), x, y);
uint packedGridPos = packTileID(x, y);
foreach (uint phaseMapId in phasedMapData.ToList())
{
var phasedTileDic = _phaseTiles.LookupByKey(phaseMapId);
if (phasedTileDic == null)
continue;
var phaseTile = phasedTileDic.LookupByKey(packedGridPos);
if (phaseTile != null)
{
Log.outDebug(LogFilter.Maps, "MMAP.UnloadPhaseTile: Unloaded phased {0:D4}{1:D2}{2:D2}.mmtile for root phase map {3}", phaseMapId, x, y, phasedMapData.FirstOrDefault());
phasedTileDic.Remove(packedGridPos);
}
}
}
public bool unloadMap(uint mapId, uint x, uint 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((int)x, (int)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.LookupByKey(packedGridPos);
// unload, and mark as non loaded
Detour.dtRawTileData data;
if (Detour.dtStatusFailed(mmap.navMesh.removeTile(tileRef, out data)))
{
// 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);
Contract.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);
var phasedMaps = phaseMapData.LookupByKey(mapId);
if (!phasedMaps.Empty())
{
mmap.DeleteBaseTile(packedGridPos);
UnloadPhaseTile(phasedMaps, (int)x, (int)y);
}
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);
Detour.dtRawTileData data;
if (Detour.dtStatusFailed(mmap.navMesh.removeTile(i.Value, out data)))
Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y);
else
{
var phasedMaps = phaseMapData.LookupByKey(mapId);
if (!phasedMaps.Empty())
{
mmap.DeleteBaseTile(i.Key);
UnloadPhaseTile(phasedMaps, (int)x, (int)y);
}
--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 mapId, uint instanceId)
{
// 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:unloadMapInstance: Asked to unload not loaded navmesh map {0}", mapId);
return false;
}
if (!mmap.navMeshQueries.ContainsKey(instanceId))
{
Log.outDebug(LogFilter.Maps, "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {0} instanceId {1}", mapId, instanceId);
return false;
}
mmap.navMeshQueries.Remove(instanceId);
Log.outInfo(LogFilter.Maps, "MMAP:unloadMapInstance: Unloaded mapId {0} instanceId {1}", mapId, instanceId);
return true;
}
public Detour.dtNavMesh GetNavMesh(uint mapId, List<uint> swaps)
{
MMapData mmap = GetMMapData(mapId);
if (mmap == null)
return null;
return mmap.GetNavMesh(swaps);
}
public Detour.dtNavMeshQuery GetNavMeshQuery(uint mapId, uint instanceId, List<uint> swaps)
{
MMapData mmap = GetMMapData(mapId);
if (mmap == null)
return null;
if (!mmap.navMeshQueries.ContainsKey(instanceId))
{
// allocate mesh query
Detour.dtNavMeshQuery query = new Detour.dtNavMeshQuery();
if (Detour.dtStatusFailed(query.init(mmap.GetNavMesh(swaps), 1024)))
{
Log.outError(LogFilter.Maps, "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId);
return null;
}
Log.outInfo(LogFilter.Maps, "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId);
mmap.navMeshQueries.Add(instanceId, query);
}
return mmap.navMeshQueries[instanceId];
}
public uint getLoadedTilesCount() { return loadedTiles; }
public int getLoadedMapsCount() { return loadedMMaps.Count; }
public Dictionary<uint, PhasedTile> GetPhaseTileContainer(uint mapId) { return _phaseTiles.LookupByKey(mapId); }
Dictionary<uint, MMapData> loadedMMaps = new Dictionary<uint, MMapData>();
MultiMap<uint, uint> phaseMapData = new MultiMap<uint, uint>();
Dictionary<uint, Dictionary<uint, PhasedTile>> _phaseTiles = new Dictionary<uint, Dictionary<uint, PhasedTile>>();
uint loadedTiles;
}
public class MMapData
{
public MMapData(Detour.dtNavMesh mesh, uint mapId)
{
navMesh = mesh;
_mapId = mapId;
}
void RemoveSwap(PhasedTile ptile, uint swap, uint packedXY)
{
uint x = (packedXY >> 16);
uint y = (packedXY & 0x0000FFFF);
if (!loadedPhasedTiles[swap].Contains(packedXY))
{
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: mmtile {0:D4}[{1:D2}, {2:D2}] unload skipped, due to not loaded", swap, x, y);
return;
}
Detour.dtMeshHeader header = ptile.data.header;
Detour.dtRawTileData data;
// remove old tile
if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data)))
Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not unload phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y);
else
{
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Unloaded phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y);
// restore base tile
ulong loadedRef = 0;
if (Detour.dtStatusSucceed(navMesh.addTile(_baseTiles[packedXY].data, 0, 0, ref loadedRef)))
{
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Loaded base mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", _mapId, x, y, _mapId, header.x, header.y);
}
else
Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not load base {0:D4}{1:D2}{2:D2}.mmtile to navmesh", _mapId, x, y);
loadedTileRefs[packedXY] = loadedRef;
}
loadedPhasedTiles.Remove(swap, packedXY);
if (loadedPhasedTiles[swap].Empty())
{
_activeSwaps.Remove(swap);
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Fully removed swap {0} from map {1}", swap, _mapId);
}
}
void AddSwap(PhasedTile ptile, uint swap, uint packedXY)
{
uint x = (packedXY >> 16);
uint y = (packedXY & 0x0000FFFF);
if (!loadedTileRefs.ContainsKey(packedXY))
{
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile on map {3}", swap, x, y, _mapId);
return;
}
if (loadedPhasedTiles[swap].Contains(packedXY))
{
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: WARNING! phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to already loaded on map {3}", swap, x, y, _mapId);
return;
}
Detour.dtMeshHeader header = ptile.data.header;
Detour.dtMeshTile oldTile = navMesh.getTileByRef(loadedTileRefs[packedXY]);
if (oldTile == null)
{
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile ref on map {3}", swap, x, y, _mapId);
return;
}
// header xy is based on the swap map's tile set, wich doesn't have all the same tiles as root map, so copy the xy from the orignal header
header.x = oldTile.header.x;
header.y = oldTile.header.y;
Detour.dtRawTileData data;
// remove old tile
if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data)))
Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y);
else
{
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Unloaded {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y);
_activeSwaps.Add(swap);
loadedPhasedTiles.Add(swap, packedXY);
// add new swapped tile
ulong loadedRef = 0;
if (Detour.dtStatusSucceed(navMesh.addTile(ptile.data, 0, 0, ref loadedRef)))
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Loaded phased mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", swap, x, y, _mapId, header.x, header.y);
else
Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not load {0:D4}{1:D2}{2:D2}.mmtile to navmesh", swap, x, y);
loadedTileRefs[packedXY] = loadedRef;
}
}
public Detour.dtNavMesh GetNavMesh(List<uint> swaps)
{
foreach (uint swap in _activeSwaps)
{
if (!swaps.Contains(swap)) // swap not active
{
var ptc = Global.MMapMgr.GetPhaseTileContainer(swap);
foreach (var pair in ptc)
RemoveSwap(pair.Value, swap, pair.Key); // remove swap
}
}
// for each of the calling unit's terrain swaps
foreach (uint swap in swaps)
{
if (!_activeSwaps.Contains(swap)) // swap not active
{
// for each of the terrain swap's xy tiles
var ptc = Global.MMapMgr.GetPhaseTileContainer(swap);
if (ptc != null)
{
foreach (var pair in ptc)
AddSwap(pair.Value, swap, pair.Key); // add swap
}
}
}
return navMesh;
}
public void AddBaseTile(uint packedGridPos, Detour.dtRawTileData data, MmapTileHeader fileHeader, uint dataSize)
{
if (!_baseTiles.ContainsKey(packedGridPos))
{
PhasedTile phasedTile = new PhasedTile();
phasedTile.data = data;
phasedTile.fileHeader = fileHeader;
phasedTile.dataSize = (int)dataSize;
_baseTiles[packedGridPos] = phasedTile;
}
}
public void DeleteBaseTile(uint packedGridPos)
{
var phaseTile = _baseTiles.LookupByKey(packedGridPos);
if (phaseTile != null)
{
_baseTiles.Remove(packedGridPos);
}
}
public Dictionary<uint, Detour.dtNavMeshQuery> navMeshQueries = new Dictionary<uint, Detour.dtNavMeshQuery>(); // instanceId to query
public Detour.dtNavMesh navMesh;
public Dictionary<uint, ulong> loadedTileRefs = new Dictionary<uint, ulong>();
MultiMap<uint, uint> loadedPhasedTiles = new MultiMap<uint, uint>();
uint _mapId;
Dictionary<uint, PhasedTile> _baseTiles = new Dictionary<uint, PhasedTile>();
List<uint> _activeSwaps = new List<uint>();
}
public class PhasedTile
{
public Detour.dtRawTileData data;
public MmapTileHeader fileHeader;
public int dataSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct MmapTileHeader
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public char[] mmapMagic;
public uint dtVersion;
public uint mmapVersion;
public uint size;
public byte usesLiquids;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] padding;
}
}