Files
CypherCore/Source/Game/Maps/GridMap.cs
T

710 lines
26 KiB
C#

/*
* Copyright (C) 2012-2020 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.GameMath;
using Game.DataStorage;
using System;
using System.Collections.Generic;
using System.IO;
using Game.Collision;
namespace Game.Maps
{
public class GridMap
{
public GridMap()
{
// Height level data
_gridHeight = MapConst.InvalidHeight;
_gridGetHeight = GetHeightFromFlat;
// Liquid data
_liquidLevel = MapConst.InvalidHeight;
}
public LoadResult LoadData(string filename)
{
UnloadData();
if (!File.Exists(filename))
return LoadResult.FileNotFound;
using BinaryReader reader = new(new FileStream(filename, FileMode.Open, FileAccess.Read));
MapFileHeader header = reader.Read<MapFileHeader>();
if (header.mapMagic != MapConst.MapMagic || (header.versionMagic != MapConst.MapVersionMagic && header.versionMagic != MapConst.MapVersionMagic2)) // Hack for some different extractors using v2.0 header
{
Log.outError(LogFilter.Maps, $"Map file '{filename}' is from an incompatible map version. Please recreate using the mapextractor.");
return LoadResult.ReadFromFileFailed;
}
if (header.areaMapOffset != 0 && !LoadAreaData(reader, header.areaMapOffset))
{
Log.outError(LogFilter.Maps, "Error loading map area data");
return LoadResult.ReadFromFileFailed;
}
if (header.heightMapOffset != 0 && !LoadHeightData(reader, header.heightMapOffset))
{
Log.outError(LogFilter.Maps, "Error loading map height data");
return LoadResult.ReadFromFileFailed;
}
if (header.liquidMapOffset != 0 && !LoadLiquidData(reader, header.liquidMapOffset))
{
Log.outError(LogFilter.Maps, "Error loading map liquids data");
return LoadResult.ReadFromFileFailed;
}
if (header.holesSize != 0 && !LoadHolesData(reader, header.holesOffset))
{
Log.outError(LogFilter.Maps, "Error loading map holes data");
return LoadResult.ReadFromFileFailed;
}
return LoadResult.Success;
}
public void UnloadData()
{
_areaMap = null;
m_V9 = null;
m_V8 = null;
_liquidEntry = null;
_liquidFlags = null;
_liquidMap = null;
_gridGetHeight = GetHeightFromFlat;
}
bool LoadAreaData(BinaryReader reader, uint offset)
{
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
MapAreaHeader areaHeader = reader.Read<MapAreaHeader>();
if (areaHeader.fourcc != MapConst.MapAreaMagic)
return false;
_gridArea = areaHeader.gridArea;
if (!areaHeader.flags.HasAnyFlag(AreaHeaderFlags.NoArea))
_areaMap = reader.ReadArray<ushort>(16 * 16);
return true;
}
bool LoadHeightData(BinaryReader reader, uint offset)
{
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
MapHeightHeader mapHeader = reader.Read<MapHeightHeader>();
if (mapHeader.fourcc != MapConst.MapHeightMagic)
return false;
_gridHeight = mapHeader.gridHeight;
_flags = (uint)mapHeader.flags;
if (!mapHeader.flags.HasAnyFlag(HeightHeaderFlags.NoHeight))
{
if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightAsInt16))
{
m_uint16_V9 = reader.ReadArray<ushort>(129 * 129);
m_uint16_V8 = reader.ReadArray<ushort>(128 * 128);
_gridIntHeightMultiplier = (mapHeader.gridMaxHeight - mapHeader.gridHeight) / 65535;
_gridGetHeight = GetHeightFromUint16;
}
else if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightAsInt8))
{
m_ubyte_V9 = reader.ReadBytes(129 * 129);
m_ubyte_V8 = reader.ReadBytes(128 * 128);
_gridIntHeightMultiplier = (mapHeader.gridMaxHeight - mapHeader.gridHeight) / 255;
_gridGetHeight = GetHeightFromUint8;
}
else
{
m_V9 = reader.ReadArray<float>(129 * 129);
m_V8 = reader.ReadArray<float>(128 * 128);
_gridGetHeight = GetHeightFromFloat;
}
}
else
_gridGetHeight = GetHeightFromFlat;
if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HasFlightBounds))
{
short[] maxHeights = reader.ReadArray<short>(3 * 3);
short[] minHeights = reader.ReadArray<short>(3 * 3);
uint[][] indices =
{
new uint[] { 3, 0, 4 },
new uint[] { 0, 1, 4 },
new uint[] { 1, 2, 4 },
new uint[] { 2, 5, 4 },
new uint[] { 5, 8, 4 },
new uint[] { 8, 7, 4 },
new uint[] { 7, 6, 4 },
new uint[] { 6, 3, 4 }
};
float[][] boundGridCoords =
{
new [] { 0.0f, 0.0f },
new [] { 0.0f, -266.66666f },
new [] { 0.0f, -533.33331f },
new [] { -266.66666f, 0.0f },
new [] { -266.66666f, -266.66666f },
new [] { -266.66666f, -533.33331f },
new [] { -533.33331f, 0.0f },
new [] { -533.33331f, -266.66666f },
new [] { -533.33331f, -533.33331f }
};
_minHeightPlanes = new Plane[8];
for (uint quarterIndex = 0; quarterIndex < 8; ++quarterIndex)
_minHeightPlanes[quarterIndex] = new Plane(
new Vector3(boundGridCoords[indices[quarterIndex][0]][0], boundGridCoords[indices[quarterIndex][0]][1], minHeights[indices[quarterIndex][0]]),
new Vector3(boundGridCoords[indices[quarterIndex][1]][0], boundGridCoords[indices[quarterIndex][1]][1], minHeights[indices[quarterIndex][1]]),
new Vector3(boundGridCoords[indices[quarterIndex][2]][0], boundGridCoords[indices[quarterIndex][2]][1], minHeights[indices[quarterIndex][2]])
);
}
return true;
}
bool LoadLiquidData(BinaryReader reader, uint offset)
{
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
MapLiquidHeader liquidHeader = reader.Read<MapLiquidHeader>();
if (liquidHeader.fourcc != MapConst.MapLiquidMagic)
return false;
_liquidGlobalEntry = liquidHeader.liquidType;
_liquidGlobalFlags = (LiquidHeaderTypeFlags)liquidHeader.liquidFlags;
_liquidOffX = liquidHeader.offsetX;
_liquidOffY = liquidHeader.offsetY;
_liquidWidth = liquidHeader.width;
_liquidHeight = liquidHeader.height;
_liquidLevel = liquidHeader.liquidLevel;
if (!liquidHeader.flags.HasAnyFlag(LiquidHeaderFlags.NoType))
{
_liquidEntry = reader.ReadArray<ushort>(16 * 16);
_liquidFlags = reader.ReadBytes(16 * 16);
}
if (!liquidHeader.flags.HasAnyFlag(LiquidHeaderFlags.NoHeight))
_liquidMap = reader.ReadArray<float>((uint)(_liquidWidth * _liquidHeight));
return true;
}
bool LoadHolesData(BinaryReader reader, uint offset)
{
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
_holes = reader.ReadArray<byte>(16 * 16 * 8);
return true;
}
public ushort GetArea(float x, float y)
{
if (_areaMap == null)
return _gridArea;
x = 16 * (32 - x / MapConst.SizeofGrids);
y = 16 * (32 - y / MapConst.SizeofGrids);
int lx = (int)x & 15;
int ly = (int)y & 15;
return _areaMap[lx * 16 + ly];
}
float GetHeightFromFlat(float x, float y)
{
return _gridHeight;
}
float GetHeightFromFloat(float x, float y)
{
if (m_uint16_V8 == null || m_uint16_V9 == null)
return _gridHeight;
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
int x_int = (int)x;
int y_int = (int)y;
x -= x_int;
y -= y_int;
x_int &= (MapConst.MapResolution - 1);
y_int &= (MapConst.MapResolution - 1);
if (IsHole(x_int, y_int))
return MapConst.InvalidHeight;
float a, b, c;
if (x + y < 1)
{
if (x > y)
{
// 1 triangle (h1, h2, h5 points)
float h1 = m_V9[(x_int) * 129 + y_int];
float h2 = m_V9[(x_int + 1) * 129 + y_int];
float h5 = 2 * m_V8[x_int * 128 + y_int];
a = h2 - h1;
b = h5 - h1 - h2;
c = h1;
}
else
{
// 2 triangle (h1, h3, h5 points)
float h1 = m_V9[x_int * 129 + y_int];
float h3 = m_V9[x_int * 129 + y_int + 1];
float h5 = 2 * m_V8[x_int * 128 + y_int];
a = h5 - h1 - h3;
b = h3 - h1;
c = h1;
}
}
else
{
if (x > y)
{
// 3 triangle (h2, h4, h5 points)
float h2 = m_V9[(x_int + 1) * 129 + y_int];
float h4 = m_V9[(x_int + 1) * 129 + y_int + 1];
float h5 = 2 * m_V8[x_int * 128 + y_int];
a = h2 + h4 - h5;
b = h4 - h2;
c = h5 - h4;
}
else
{
// 4 triangle (h3, h4, h5 points)
float h3 = m_V9[(x_int) * 129 + y_int + 1];
float h4 = m_V9[(x_int + 1) * 129 + y_int + 1];
float h5 = 2 * m_V8[x_int * 128 + y_int];
a = h4 - h3;
b = h3 + h4 - h5;
c = h5 - h4;
}
}
// Calculate height
return a * x + b * y + c;
}
float GetHeightFromUint8(float x, float y)
{
if (m_ubyte_V8 == null || m_ubyte_V9 == null)
return _gridHeight;
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
int x_int = (int)x;
int y_int = (int)y;
x -= x_int;
y -= y_int;
x_int &= (MapConst.MapResolution - 1);
y_int &= (MapConst.MapResolution - 1);
if (IsHole(x_int, y_int))
return MapConst.InvalidHeight;
int a, b, c;
unsafe
{
fixed (byte* V9 = m_ubyte_V9)
{
byte* V9_h1_ptr = &V9[x_int * 128 + x_int + y_int];
if (x + y < 1)
{
if (x > y)
{
// 1 triangle (h1, h2, h5 points)
int h1 = V9_h1_ptr[0];
int h2 = V9_h1_ptr[129];
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
a = h2 - h1;
b = h5 - h1 - h2;
c = h1;
}
else
{
// 2 triangle (h1, h3, h5 points)
int h1 = V9_h1_ptr[0];
int h3 = V9_h1_ptr[1];
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
a = h5 - h1 - h3;
b = h3 - h1;
c = h1;
}
}
else
{
if (x > y)
{
// 3 triangle (h2, h4, h5 points)
int h2 = V9_h1_ptr[129];
int h4 = V9_h1_ptr[130];
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
a = h2 + h4 - h5;
b = h4 - h2;
c = h5 - h4;
}
else
{
// 4 triangle (h3, h4, h5 points)
int h3 = V9_h1_ptr[1];
int h4 = V9_h1_ptr[130];
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
a = h4 - h3;
b = h3 + h4 - h5;
c = h5 - h4;
}
}
// Calculate height
return ((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight;
}
}
}
float GetHeightFromUint16(float x, float y)
{
if (m_uint16_V8 == null || m_uint16_V9 == null)
return _gridHeight;
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
int x_int = (int)x;
int y_int = (int)y;
x -= x_int;
y -= y_int;
x_int &= (MapConst.MapResolution - 1);
y_int &= (MapConst.MapResolution - 1);
if (IsHole(x_int, y_int))
return MapConst.InvalidHeight;
int a, b, c;
unsafe
{
fixed (ushort* V9 = m_uint16_V9)
{
ushort* V9_h1_ptr = &V9[x_int * 128 + x_int + y_int];
if (x + y < 1)
{
if (x > y)
{
// 1 triangle (h1, h2, h5 points)
int h1 = V9_h1_ptr[0];
int h2 = V9_h1_ptr[129];
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
a = h2 - h1;
b = h5 - h1 - h2;
c = h1;
}
else
{
// 2 triangle (h1, h3, h5 points)
int h1 = V9_h1_ptr[0];
int h3 = V9_h1_ptr[1];
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
a = h5 - h1 - h3;
b = h3 - h1;
c = h1;
}
}
else
{
if (x > y)
{
// 3 triangle (h2, h4, h5 points)
int h2 = V9_h1_ptr[129];
int h4 = V9_h1_ptr[130];
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
a = h2 + h4 - h5;
b = h4 - h2;
c = h5 - h4;
}
else
{
// 4 triangle (h3, h4, h5 points)
int h3 = V9_h1_ptr[1];
int h4 = V9_h1_ptr[130];
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
a = h4 - h3;
b = h3 + h4 - h5;
c = h5 - h4;
}
}
// Calculate height
return ((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight;
}
}
}
bool IsHole(int row, int col)
{
if (_holes == null)
return false;
int cellRow = row / 8; // 8 squares per cell
int cellCol = col / 8;
int holeRow = row % 8;
int holeCol = col % 8;
return (_holes[cellRow * 16 * 8 + cellCol * 8 + holeRow] & (1 << holeCol)) != 0;
}
public float GetMinHeight(float x, float y)
{
if (_minHeightPlanes == null)
return -500.0f;
GridCoord gridCoord = GridDefines.ComputeGridCoord(x, y);
int doubleGridX = (int)(Math.Floor(-(x - MapConst.MapHalfSize) / MapConst.CenterGridOffset));
int doubleGridY = (int)(Math.Floor(-(y - MapConst.MapHalfSize) / MapConst.CenterGridOffset));
float gx = x - ((int)gridCoord.X_coord - MapConst.CenterGridId + 1) * MapConst.SizeofGrids;
float gy = y - ((int)gridCoord.Y_coord - MapConst.CenterGridId + 1) * MapConst.SizeofGrids;
uint quarterIndex;
if (Convert.ToBoolean(doubleGridY & 1))
{
if (Convert.ToBoolean(doubleGridX & 1))
quarterIndex = 4 + (gx <= gy ? 1 : 0u);
else
quarterIndex = (2 + ((-MapConst.SizeofGrids - gx) > gy ? 1u : 0));
}
else if (Convert.ToBoolean(doubleGridX & 1))
quarterIndex = 6 + ((-MapConst.SizeofGrids - gx) <= gy ? 1u : 0);
else
quarterIndex = gx > gy ? 1u : 0;
Ray ray = new(new Vector3(gx, gy, 0.0f), Vector3.ZAxis);
return ray.intersection(_minHeightPlanes[quarterIndex]).Z;
}
public float GetLiquidLevel(float x, float y)
{
if (_liquidMap == null)
return _liquidLevel;
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
int cx_int = ((int)x & (MapConst.MapResolution - 1)) - _liquidOffY;
int cy_int = ((int)y & (MapConst.MapResolution - 1)) - _liquidOffX;
if (cx_int < 0 || cx_int >= _liquidHeight)
return MapConst.InvalidHeight;
if (cy_int < 0 || cy_int >= _liquidWidth)
return MapConst.InvalidHeight;
return _liquidMap[cx_int * _liquidWidth + cy_int];
}
// Get water state on map
public ZLiquidStatus GetLiquidStatus(float x, float y, float z, LiquidHeaderTypeFlags? reqLiquidType, LiquidData data, float collisionHeight)
{
// Check water type (if no water return)
if (_liquidGlobalFlags == LiquidHeaderTypeFlags.NoWater && _liquidFlags == null)
return ZLiquidStatus.NoWater;
// Get cell
float cx = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
float cy = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
int x_int = (int)cx & (MapConst.MapResolution - 1);
int y_int = (int)cy & (MapConst.MapResolution - 1);
// Check water type in cell
int idx = (x_int >> 3) * 16 + (y_int >> 3);
LiquidHeaderTypeFlags type = _liquidFlags != null ? (LiquidHeaderTypeFlags)_liquidFlags[idx] : _liquidGlobalFlags;
uint entry = _liquidEntry != null ? _liquidEntry[idx] : _liquidGlobalEntry;
LiquidTypeRecord liquidEntry = CliDB.LiquidTypeStorage.LookupByKey(entry);
if (liquidEntry != null)
{
type &= LiquidHeaderTypeFlags.DarkWater;
uint liqTypeIdx = liquidEntry.SoundBank;
if (entry < 21)
{
var area = CliDB.AreaTableStorage.LookupByKey(GetArea(x, y));
if (area != null)
{
uint overrideLiquid = area.LiquidTypeID[liquidEntry.SoundBank];
if (overrideLiquid == 0 && area.ParentAreaID == 0)
{
area = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID);
if (area != null)
overrideLiquid = area.LiquidTypeID[liquidEntry.SoundBank];
}
var liq = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid);
if (liq != null)
{
entry = overrideLiquid;
liqTypeIdx = liq.SoundBank;
}
}
}
type |= (LiquidHeaderTypeFlags)(1 << (int)liqTypeIdx);
}
if (type == LiquidHeaderTypeFlags.NoWater)
return ZLiquidStatus.NoWater;
// Check req liquid type mask
if (reqLiquidType.HasValue && (reqLiquidType & type) == LiquidHeaderTypeFlags.NoWater)
return ZLiquidStatus.NoWater;
// Check water level:
// Check water height map
int lx_int = x_int - _liquidOffY;
int ly_int = y_int - _liquidOffX;
if (lx_int < 0 || lx_int >= _liquidHeight)
return ZLiquidStatus.NoWater;
if (ly_int < 0 || ly_int >= _liquidWidth)
return ZLiquidStatus.NoWater;
// Get water level
float liquid_level = _liquidMap != null ? _liquidMap[lx_int * _liquidWidth + ly_int] : _liquidLevel;
// Get ground level (sub 0.2 for fix some errors)
float ground_level = GetHeight(x, y);
// Check water level and ground level
if (liquid_level < ground_level || z < ground_level)
return ZLiquidStatus.NoWater;
// All ok in water . store data
if (data != null)
{
data.entry = entry;
data.type_flags = type;
data.level = liquid_level;
data.depth_level = ground_level;
}
// For speed check as int values
float delta = liquid_level - z;
if (delta > collisionHeight) // Under water
return ZLiquidStatus.UnderWater;
if (delta > 0.0f) // In water
return ZLiquidStatus.InWater;
if (delta > -0.1f) // Walk on water
return ZLiquidStatus.WaterWalk;
// Above water
return ZLiquidStatus.AboveWater;
}
public float GetHeight(float x, float y) { return _gridGetHeight(x, y); }
#region Fields
delegate float GetHeightDel(float x, float y);
GetHeightDel _gridGetHeight;
uint _flags;
public float[] m_V9;
public ushort[] m_uint16_V9;
public byte[] m_ubyte_V9;
public float[] m_V8;
public ushort[] m_uint16_V8;
public byte[] m_ubyte_V8;
Plane[] _minHeightPlanes;
float _gridHeight;
float _gridIntHeightMultiplier;
//Area data
public ushort[] _areaMap;
//Liquid Map
float _liquidLevel;
ushort[] _liquidEntry;
byte[] _liquidFlags;
float[] _liquidMap;
ushort _gridArea;
ushort _liquidGlobalEntry;
LiquidHeaderTypeFlags _liquidGlobalFlags;
byte _liquidOffX;
byte _liquidOffY;
byte _liquidWidth;
byte _liquidHeight;
byte[] _holes;
#endregion
}
public struct MapFileHeader
{
public uint mapMagic;
public uint versionMagic;
public uint buildMagic;
public uint areaMapOffset;
public uint areaMapSize;
public uint heightMapOffset;
public uint heightMapSize;
public uint liquidMapOffset;
public uint liquidMapSize;
public uint holesOffset;
public uint holesSize;
}
public struct MapAreaHeader
{
public uint fourcc;
public AreaHeaderFlags flags;
public ushort gridArea;
}
public struct MapHeightHeader
{
public uint fourcc;
public HeightHeaderFlags flags;
public float gridHeight;
public float gridMaxHeight;
}
public struct MapLiquidHeader
{
public uint fourcc;
public LiquidHeaderFlags flags;
public byte liquidFlags;
public ushort liquidType;
public byte offsetX;
public byte offsetY;
public byte width;
public byte height;
public float liquidLevel;
}
public class LiquidData
{
public LiquidHeaderTypeFlags type_flags;
public uint entry;
public float level;
public float depth_level;
}
}