#region References using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #endregion namespace Server { public class TileMatrix { private readonly StaticTile[][][][][] m_StaticTiles; private readonly LandTile[][][] m_LandTiles; private readonly LandTile[] m_InvalidLandBlock; private readonly StaticTile[][][] m_EmptyStaticBlock; private FileStream m_Map; private readonly UOPIndex m_MapIndex; private FileStream m_Index; private BinaryReader m_IndexReader; private FileStream m_Statics; private readonly int m_FileIndex; private readonly int m_BlockWidth; private readonly int m_BlockHeight; private readonly int m_Width, m_Height; private readonly Map m_Owner; private readonly TileMatrixPatch m_Patch; private readonly int[][] m_StaticPatches; private readonly int[][] m_LandPatches; public TileMatrixPatch Patch => m_Patch; public int BlockWidth => m_BlockWidth; public int BlockHeight => m_BlockHeight; public FileStream MapStream { get => m_Map; set => m_Map = value; } public FileStream IndexStream { get => m_Index; set => m_Index = value; } public FileStream DataStream { get => m_Statics; set => m_Statics = value; } public BinaryReader IndexReader { get => m_IndexReader; set => m_IndexReader = value; } public bool Exists => m_Map != null && m_Index != null && m_Statics != null; private static readonly List m_Instances = new List(); private readonly List m_FileShare = new List(); public TileMatrix(Map owner, int fileIndex, int mapID, int width, int height) { lock (m_Instances) { for (var i = 0; i < m_Instances.Count; ++i) { var tm = m_Instances[i]; if (tm.m_FileIndex == fileIndex) { lock (m_FileShare) { lock (tm.m_FileShare) { tm.m_FileShare.Add(this); m_FileShare.Add(tm); } } } } m_Instances.Add(this); } m_FileIndex = fileIndex; m_Width = width; m_Height = height; m_BlockWidth = width >> 3; m_BlockHeight = height >> 3; m_Owner = owner; if (fileIndex != 0x7F) { var mapPath = Core.FindDataFile("map{0}.mul", fileIndex); if (File.Exists(mapPath)) { m_Map = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } else { mapPath = Core.FindDataFile("map{0}LegacyMUL.uop", fileIndex); if (File.Exists(mapPath)) { m_Map = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); m_MapIndex = new UOPIndex(m_Map); } } var indexPath = Core.FindDataFile("staidx{0}.mul", fileIndex); if (File.Exists(indexPath)) { m_Index = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); m_IndexReader = new BinaryReader(m_Index); } var staticsPath = Core.FindDataFile("statics{0}.mul", fileIndex); if (File.Exists(staticsPath)) { m_Statics = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } } m_EmptyStaticBlock = new StaticTile[8][][]; for (var i = 0; i < 8; ++i) { m_EmptyStaticBlock[i] = new StaticTile[8][]; for (var j = 0; j < 8; ++j) { m_EmptyStaticBlock[i][j] = new StaticTile[0]; } } m_InvalidLandBlock = new LandTile[196]; m_LandTiles = new LandTile[m_BlockWidth][][]; m_StaticTiles = new StaticTile[m_BlockWidth][][][][]; m_StaticPatches = new int[m_BlockWidth][]; m_LandPatches = new int[m_BlockWidth][]; m_Patch = new TileMatrixPatch(this, mapID); } public StaticTile[][][] EmptyStaticBlock => m_EmptyStaticBlock; [MethodImpl(MethodImplOptions.Synchronized)] public void SetStaticBlock(int x, int y, StaticTile[][][] value) { if (x < 0 || y < 0 || x >= m_BlockWidth || y >= m_BlockHeight) { return; } if (m_StaticTiles[x] == null) { m_StaticTiles[x] = new StaticTile[m_BlockHeight][][][]; } m_StaticTiles[x][y] = value; if (m_StaticPatches[x] == null) { m_StaticPatches[x] = new int[(m_BlockHeight + 31) >> 5]; } m_StaticPatches[x][y >> 5] |= 1 << (y & 0x1F); } [MethodImpl(MethodImplOptions.Synchronized)] public StaticTile[][][] GetStaticBlock(int x, int y) { if (x < 0 || y < 0 || x >= m_BlockWidth || y >= m_BlockHeight || m_Statics == null || m_Index == null) { return m_EmptyStaticBlock; } if (m_StaticTiles[x] == null) { m_StaticTiles[x] = new StaticTile[m_BlockHeight][][][]; } var tiles = m_StaticTiles[x][y]; if (tiles == null) { lock (m_FileShare) { for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) { var shared = m_FileShare[i]; lock (shared) { if (x >= 0 && x < shared.m_BlockWidth && y >= 0 && y < shared.m_BlockHeight) { var theirTiles = shared.m_StaticTiles[x]; if (theirTiles != null) { tiles = theirTiles[y]; } if (tiles != null) { var theirBits = shared.m_StaticPatches[x]; if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) { tiles = null; } } } } } } if (tiles == null) { tiles = ReadStaticBlock(x, y); } m_StaticTiles[x][y] = tiles; } return tiles; } public StaticTile[] GetStaticTiles(int x, int y) { var tiles = GetStaticBlock(x >> 3, y >> 3); return tiles[x & 0x7][y & 0x7]; } private readonly TileList m_TilesList = new TileList(); [MethodImpl(MethodImplOptions.Synchronized)] public StaticTile[] GetStaticTiles(int x, int y, bool multis) { var tiles = GetStaticBlock(x >> 3, y >> 3); if (multis) { var eable = m_Owner.GetMultiTilesAt(x, y); if (eable == Map.NullEnumerable.Instance) { return tiles[x & 0x7][y & 0x7]; } var any = false; foreach (var multiTiles in eable) { if (!any) { any = true; } m_TilesList.AddRange(multiTiles); } eable.Free(); if (!any) { return tiles[x & 0x7][y & 0x7]; } m_TilesList.AddRange(tiles[x & 0x7][y & 0x7]); return m_TilesList.ToArray(); } else { return tiles[x & 0x7][y & 0x7]; } } [MethodImpl(MethodImplOptions.Synchronized)] public void SetLandBlock(int x, int y, LandTile[] value) { if (x < 0 || y < 0 || x >= m_BlockWidth || y >= m_BlockHeight) { return; } if (m_LandTiles[x] == null) { m_LandTiles[x] = new LandTile[m_BlockHeight][]; } m_LandTiles[x][y] = value; if (m_LandPatches[x] == null) { m_LandPatches[x] = new int[(m_BlockHeight + 31) >> 5]; } m_LandPatches[x][y >> 5] |= 1 << (y & 0x1F); } [MethodImpl(MethodImplOptions.Synchronized)] public LandTile[] GetLandBlock(int x, int y) { if (x < 0 || y < 0 || x >= m_BlockWidth || y >= m_BlockHeight || m_Map == null) { return m_InvalidLandBlock; } if (m_LandTiles[x] == null) { m_LandTiles[x] = new LandTile[m_BlockHeight][]; } var tiles = m_LandTiles[x][y]; if (tiles == null) { lock (m_FileShare) { for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) { var shared = m_FileShare[i]; lock (shared) { if (x >= 0 && x < shared.m_BlockWidth && y >= 0 && y < shared.m_BlockHeight) { var theirTiles = shared.m_LandTiles[x]; if (theirTiles != null) { tiles = theirTiles[y]; } if (tiles != null) { var theirBits = shared.m_LandPatches[x]; if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) { tiles = null; } } } } } } if (tiles == null) { tiles = ReadLandBlock(x, y); } m_LandTiles[x][y] = tiles; } return tiles; } public LandTile GetLandTile(int x, int y) { var tiles = GetLandBlock(x >> 3, y >> 3); return tiles[((y & 0x7) << 3) + (x & 0x7)]; } private TileList[][] m_Lists; private StaticTile[] m_TileBuffer = new StaticTile[128]; [MethodImpl(MethodImplOptions.Synchronized)] private unsafe StaticTile[][][] ReadStaticBlock(int x, int y) { try { m_IndexReader.BaseStream.Seek(((x * m_BlockHeight) + y) * 12, SeekOrigin.Begin); var lookup = m_IndexReader.ReadInt32(); var length = m_IndexReader.ReadInt32(); if (lookup < 0 || length <= 0) { return m_EmptyStaticBlock; } else { var count = length / 7; m_Statics.Seek(lookup, SeekOrigin.Begin); if (m_TileBuffer.Length < count) { m_TileBuffer = new StaticTile[count]; } var staTiles = m_TileBuffer; //new StaticTile[tileCount]; fixed (StaticTile* pTiles = staTiles) { NativeReader.Read(m_Statics, pTiles, length); if (m_Lists == null) { m_Lists = new TileList[8][]; for (var i = 0; i < 8; ++i) { m_Lists[i] = new TileList[8]; for (var j = 0; j < 8; ++j) { m_Lists[i][j] = new TileList(); } } } var lists = m_Lists; StaticTile* pCur = pTiles, pEnd = pTiles + count; while (pCur < pEnd) { lists[pCur->m_X & 0x7][pCur->m_Y & 0x7].Add(pCur->m_ID, pCur->m_Z); pCur = pCur + 1; } var tiles = new StaticTile[8][][]; for (var i = 0; i < 8; ++i) { tiles[i] = new StaticTile[8][]; for (var j = 0; j < 8; ++j) { tiles[i][j] = lists[i][j].ToArray(); } } return tiles; } } } catch (EndOfStreamException ex) { Diagnostics.ExceptionLogging.LogException(ex); if (DateTime.UtcNow >= m_NextStaticWarning) { Console.WriteLine("Warning: Static EOS for {0} ({1}, {2})", m_Owner, x, y); m_NextStaticWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); } return m_EmptyStaticBlock; } } private DateTime m_NextStaticWarning; private DateTime m_NextLandWarning; public void Force() { if (ScriptCompiler.Assemblies == null || ScriptCompiler.Assemblies.Length == 0) { throw new Exception(); } } [MethodImpl(MethodImplOptions.Synchronized)] private unsafe LandTile[] ReadLandBlock(int x, int y) { try { var offset = ((x * m_BlockHeight) + y) * 196 + 4; if (m_MapIndex != null) { offset = m_MapIndex.Lookup(offset); } m_Map.Seek(offset, SeekOrigin.Begin); var tiles = new LandTile[64]; fixed (LandTile* pTiles = tiles) { NativeReader.Read(m_Map, pTiles, 192); } return tiles; } catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); if (DateTime.UtcNow >= m_NextLandWarning) { Console.WriteLine("Warning: Land EOS for {0} ({1}, {2})", m_Owner, x, y); m_NextLandWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); } return m_InvalidLandBlock; } } public void Dispose() { if (m_MapIndex != null) { m_MapIndex.Close(); } else if (m_Map != null) { m_Map.Close(); } if (m_Statics != null) { m_Statics.Close(); } if (m_IndexReader != null) { m_IndexReader.Close(); } } } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct LandTile { internal short m_ID; internal sbyte m_Z; public int ID => m_ID; public int Z { get => m_Z; set => m_Z = (sbyte)value; } public int Height => 0; public bool Ignored => m_ID == 2 || m_ID == 0x1DB || (m_ID >= 0x1AE && m_ID <= 0x1B5); public LandTile(short id, sbyte z) { m_ID = id; m_Z = z; } public void Set(short id, sbyte z) { m_ID = id; m_Z = z; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct StaticTile { internal ushort m_ID; internal byte m_X; internal byte m_Y; internal sbyte m_Z; internal short m_Hue; public int ID => m_ID; public int X { get => m_X; set => m_X = (byte)value; } public int Y { get => m_Y; set => m_Y = (byte)value; } public int Z { get => m_Z; set => m_Z = (sbyte)value; } public int Hue { get => m_Hue; set => m_Hue = (short)value; } public int Height => TileData.ItemTable[m_ID & TileData.MaxItemValue].Height; public StaticTile(ushort id, sbyte z) { m_ID = id; m_Z = z; m_X = 0; m_Y = 0; m_Hue = 0; } public StaticTile(ushort id, byte x, byte y, sbyte z, short hue) { m_ID = id; m_X = x; m_Y = y; m_Z = z; m_Hue = hue; } public void Set(ushort id, sbyte z) { m_ID = id; m_Z = z; } public void Set(ushort id, byte x, byte y, sbyte z, short hue) { m_ID = id; m_X = x; m_Y = y; m_Z = z; m_Hue = hue; } } public class UOPIndex { private class UOPEntry : IComparable { public int m_Offset; public readonly int m_Length; public int m_Order; public UOPEntry(int offset, int length) { m_Offset = offset; m_Length = length; m_Order = 0; } public int CompareTo(UOPEntry other) { return m_Order.CompareTo(other.m_Order); } } private class OffsetComparer : IComparer { public static readonly IComparer Instance = new OffsetComparer(); public int Compare(UOPEntry x, UOPEntry y) { return x.m_Offset.CompareTo(y.m_Offset); } } private readonly BinaryReader m_Reader; private readonly int m_Length; private readonly int m_Version; private readonly UOPEntry[] m_Entries; public int Version => m_Version; public UOPIndex(FileStream stream) { m_Reader = new BinaryReader(stream); m_Length = (int)stream.Length; if (m_Reader.ReadInt32() != 0x50594D) { throw new ArgumentException("Invalid UOP file."); } m_Version = m_Reader.ReadInt32(); m_Reader.ReadInt32(); var nextTable = m_Reader.ReadInt32(); var entries = new List(); do { stream.Seek(nextTable, SeekOrigin.Begin); var count = m_Reader.ReadInt32(); nextTable = m_Reader.ReadInt32(); m_Reader.ReadInt32(); for (var i = 0; i < count; ++i) { var offset = m_Reader.ReadInt32(); if (offset == 0) { stream.Seek(30, SeekOrigin.Current); continue; } m_Reader.ReadInt64(); var length = m_Reader.ReadInt32(); entries.Add(new UOPEntry(offset, length)); stream.Seek(18, SeekOrigin.Current); } } while (nextTable != 0 && nextTable < m_Length); entries.Sort(OffsetComparer.Instance); for (var i = 0; i < entries.Count; ++i) { stream.Seek(entries[i].m_Offset + 2, SeekOrigin.Begin); int dataOffset = m_Reader.ReadInt16(); entries[i].m_Offset += 4 + dataOffset; stream.Seek(dataOffset, SeekOrigin.Current); entries[i].m_Order = m_Reader.ReadInt32(); } entries.Sort(); m_Entries = entries.ToArray(); } public int Lookup(int offset) { var total = 0; for (var i = 0; i < m_Entries.Length; ++i) { var newTotal = total + m_Entries[i].m_Length; if (offset < newTotal) { return m_Entries[i].m_Offset + (offset - total); } total = newTotal; } return m_Length; } public void Close() { m_Reader.Close(); } } }