context: #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<TileMatrix> m_Instances = new List<TileMatrix>();
  		private readonly List<TileMatrix> m_FileShare = new List<TileMatrix>();
  
  		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<StaticTile[]>.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<UOPEntry>
  		{
  			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<UOPEntry>
  		{
  			public static readonly IComparer<UOPEntry> 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<UOPEntry>();
  
  			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)
  			{
  
gt: 				var newTotal = total + m_Entries[i].m_Length;