context: #region Header
  // **********
  // ServUO - MultiData.cs
  // **********
  #endregion
  
  #region References
  using System;
  using System.IO;
  #endregion
  
  namespace Server
  {
  	public static class MultiData
  	{
  		private static readonly MultiComponentList[] m_Components;
  
  		private static readonly FileStream m_Index;
  		private static readonly FileStream m_Stream;
  		private static readonly BinaryReader m_IndexReader;
  		private static readonly BinaryReader m_StreamReader;
  
  		public static MultiComponentList GetComponents(int multiID)
  		{
  			MultiComponentList mcl;
  
  			#region Stygian Abyss
  			multiID &= 0x3FFF; // The value of the actual multi is shifted by 0x4000, so this is left alone.
  			#endregion
  
  			if (multiID >= 0 && multiID < m_Components.Length)
  			{
  				mcl = m_Components[multiID];
  
  				if (mcl == null)
  				{
  					m_Components[multiID] = mcl = Load(multiID);
  				}
  			}
  			else
  			{
  				mcl = MultiComponentList.Empty;
  			}
  
  			return mcl;
  		}
  
  		public static MultiComponentList Load(int multiID)
  		{
  			try
  			{
  				m_IndexReader.BaseStream.Seek(multiID * 12, SeekOrigin.Begin);
  
  				int lookup = m_IndexReader.ReadInt32();
  				int length = m_IndexReader.ReadInt32();
  
  				if (lookup < 0 || length <= 0)
  				{
  					return MultiComponentList.Empty;
  				}
  
  				m_StreamReader.BaseStream.Seek(lookup, SeekOrigin.Begin);
  
  				return new MultiComponentList(m_StreamReader, length / (MultiComponentList.PostHSFormat ? 16 : 12));
  			}
  			catch
  			{
  				return MultiComponentList.Empty;
  			}
  		}
  
  		static MultiData()
  		{
  			string idxPath = Core.FindDataFile("multi.idx");
  			string mulPath = Core.FindDataFile("multi.mul");
  
  			if (File.Exists(idxPath) && File.Exists(mulPath))
  			{
  				m_Index = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read);
  				m_IndexReader = new BinaryReader(m_Index);
  
  				m_Stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
  				m_StreamReader = new BinaryReader(m_Stream);
  
  				m_Components = new MultiComponentList[(int)(m_Index.Length / 12)];
  
  				string vdPath = Core.FindDataFile("verdata.mul");
  
  				if (File.Exists(vdPath))
  				{
  					using (FileStream fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read))
  					{
  						BinaryReader bin = new BinaryReader(fs);
  
  						int count = bin.ReadInt32();
  
  						for (int i = 0; i < count; ++i)
  						{
  							int file = bin.ReadInt32();
  							int index = bin.ReadInt32();
  							int lookup = bin.ReadInt32();
  							int length = bin.ReadInt32();
  							int extra = bin.ReadInt32();
  
  							if (file == 14 && index >= 0 && index < m_Components.Length && lookup >= 0 && length > 0)
  							{
  								bin.BaseStream.Seek(lookup, SeekOrigin.Begin);
  
  								m_Components[index] = new MultiComponentList(bin, length / 12);
  
  								bin.BaseStream.Seek(24 + (i * 20), SeekOrigin.Begin);
  							}
  						}
  
  						bin.Close();
  					}
  				}
  			}
  			else
  			{
  				Console.WriteLine("Warning: Multi data files not found");
  
  				m_Components = new MultiComponentList[0];
  			}
  		}
  	}
  
  	public struct MultiTileEntry
  	{
  		public ushort m_ItemID;
  		public short m_OffsetX, m_OffsetY, m_OffsetZ;
  		public int m_Flags;
  
  		public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, int flags)
  		{
  			m_ItemID = itemID;
  			m_OffsetX = xOffset;
  			m_OffsetY = yOffset;
  			m_OffsetZ = zOffset;
  			m_Flags = flags;
  		}
  	}
  
  	public sealed class MultiComponentList
  	{
  		public static bool PostHSFormat { get { return _PostHSFormat; } set { _PostHSFormat = value; } }
  
  		private static bool _PostHSFormat;
  
  		private Point2D m_Min, m_Max, m_Center;
  		private int m_Width, m_Height;
  		private StaticTile[][][] m_Tiles;
  		private MultiTileEntry[] m_List;
  
  		public static readonly MultiComponentList Empty = new MultiComponentList();
  
  		public Point2D Min { get { return m_Min; } }
  		public Point2D Max { get { return m_Max; } }
  
  		public Point2D Center { get { return m_Center; } }
  
  		public int Width { get { return m_Width; } }
  		public int Height { get { return m_Height; } }
  
  		public StaticTile[][][] Tiles { get { return m_Tiles; } }
  		public MultiTileEntry[] List { get { return m_List; } }
  
  		public void Add(int itemID, int x, int y, int z)
  		{
  			#region Stygian Abyss
  			itemID &= TileData.MaxItemValue;
  			itemID |= 0x10000;
  			#endregion
  
  			int vx = x + m_Center.m_X;
  			int vy = y + m_Center.m_Y;
  
  			if (vx >= 0 && vx < m_Width && vy >= 0 && vy < m_Height)
  			{
  				var oldTiles = m_Tiles[vx][vy];
  
  				for (int i = oldTiles.Length - 1; i >= 0; --i)
  				{
  					ItemData data = TileData.ItemTable[itemID & TileData.MaxItemValue];
  
  					if (oldTiles[i].Z == z && (oldTiles[i].Height > 0 == data.Height > 0))
  					{
  						bool newIsRoof = (data.Flags & TileFlag.Roof) != 0;
  						bool oldIsRoof = (TileData.ItemTable[oldTiles[i].ID & TileData.MaxItemValue].Flags & TileFlag.Roof) != 0;
  
  						if (newIsRoof == oldIsRoof)
  						{
  							Remove(oldTiles[i].ID, x, y, z);
  						}
  					}
  				}
  
  				oldTiles = m_Tiles[vx][vy];
  
  				var newTiles = new StaticTile[oldTiles.Length + 1];
  
  				for (int i = 0; i < oldTiles.Length; ++i)
  				{
  					newTiles[i] = oldTiles[i];
  				}
  
  				newTiles[oldTiles.Length] = new StaticTile((ushort)itemID, (sbyte)z);
  
  				m_Tiles[vx][vy] = newTiles;
  
  				var oldList = m_List;
  				var newList = new MultiTileEntry[oldList.Length + 1];
  
  				for (int i = 0; i < oldList.Length; ++i)
  				{
  					newList[i] = oldList[i];
  				}
  
  				newList[oldList.Length] = new MultiTileEntry((ushort)itemID, (short)x, (short)y, (short)z, 1);
  
  				m_List = newList;
  
  				if (x < m_Min.m_X)
  				{
  					m_Min.m_X = x;
  				}
  
  				if (y < m_Min.m_Y)
  				{
  					m_Min.m_Y = y;
  				}
  
  				if (x > m_Max.m_X)
  				{
  					m_Max.m_X = x;
  				}
  
  				if (y > m_Max.m_Y)
  				{
  					m_Max.m_Y = y;
  				}
  			}
  		}
  
  		public void RemoveXYZH(int x, int y, int z, int minHeight)
  		{
  			int vx = x + m_Center.m_X;
  			int vy = y + m_Center.m_Y;
  
  			if (vx >= 0 && vx < m_Width && vy >= 0 && vy < m_Height)
  			{
  				var oldTiles = m_Tiles[vx][vy];
  
  				for (int i = 0; i < oldTiles.Length; ++i)
  				{
  					StaticTile tile = oldTiles[i];
  
  					if (tile.Z == z && tile.Height >= minHeight)
  					{
  						var newTiles = new StaticTile[oldTiles.Length - 1];
  
  						for (int j = 0; j < i; ++j)
  						{
  							newTiles[j] = oldTiles[j];
  						}
  
  						for (int j = i + 1; j < oldTiles.Length; ++j)
  						{
  							newTiles[j - 1] = oldTiles[j];
  						}
  
  						m_Tiles[vx][vy] = newTiles;
  
  						break;
  					}
  				}
  
  				var oldList = m_List;
  
  				for (int i = 0; i < oldList.Length; ++i)
  				{
  					MultiTileEntry tile = oldList[i];
  
  					if (tile.m_OffsetX == (short)x && tile.m_OffsetY == (short)y && tile.m_OffsetZ == (short)z &&
  						TileData.ItemTable[tile.m_ItemID & TileData.MaxItemValue].Height >= minHeight)
  					{
  						var newList = new MultiTileEntry[oldList.Length - 1];
  
  						for (int j = 0; j < i; ++j)
  						{
  							newList[j] = oldList[j];
  						}
  
  						for (int j = i + 1; j < oldList.Length; ++j)
  						{
  							newList[j - 1] = oldList[j];
  						}
  
  						m_List = newList;
  
  						break;
  					}
  				}
  			}
  		}
  
  		public void Remove(int itemID, int x, int y, int z)
  		{
  			int vx = x + m_Center.m_X;
  			int vy = y + m_Center.m_Y;
  
  			if (vx >= 0 && vx < m_Width && vy >= 0 && vy < m_Height)
  			{
  				var oldTiles = m_Tiles[vx][vy];
  
  				for (int i = 0; i < oldTiles.Length; ++i)
  				{
  					StaticTile tile = oldTiles[i];
  
  					if (tile.ID == itemID && tile.Z == z)
  					{
  						var newTiles = new StaticTile[oldTiles.Length - 1];
  
  						for (int j = 0; j < i; ++j)
  						{
  							newTiles[j] = oldTiles[j];
  						}
  
  						for (int j = i + 1; j < oldTiles.Length; ++j)
  						{
  							newTiles[j - 1] = oldTiles[j];
  						}
  
  						m_Tiles[vx][vy] = newTiles;
  
  						break;
  					}
  				}
  
  				var oldList = m_List;
  
  				for (int i = 0; i < oldList.Length; ++i)
  				{
  					MultiTileEntry tile = oldList[i];
  
  					if (tile.m_ItemID == itemID && tile.m_OffsetX == (short)x && tile.m_OffsetY == (short)y &&
  						tile.m_OffsetZ == (short)z)
  					{
  						var newList = new MultiTileEntry[oldList.Length - 1];
  
  						for (int j = 0; j < i; ++j)
  						{
  							newList[j] = oldList[j];
  						}
  
  						for (int j = i + 1; j < oldList.Length; ++j)
  						{
  							newList[j - 1] = oldList[j];
  						}
  
  						m_List = newList;
  
  						break;
  					}
  				}
  			}
  		}
  
  		public void Resize(int newWidth, int newHeight)
  		{
  			int oldWidth = m_Width, oldHeight = m_Height;
  			var oldTiles = m_Tiles;
  
  			int totalLength = 0;
  
  			var newTiles = new StaticTile[newWidth][][];
  
  			for (int x = 0; x < newWidth; ++x)
  			{
  				newTiles[x] = new StaticTile[newHeight][];
  
  				for (int y = 0; y < newHeight; ++y)
  				{
  					if (x < oldWidth && y < oldHeight)
  					{
  						newTiles[x][y] = oldTiles[x][y];
  					}
  					else
  					{
  						newTiles[x][y] = new StaticTile[0];
  					}
  
  					totalLength += newTiles[x][y].Length;
  				}
  			}
  
  			m_Tiles = newTiles;
  			m_List = new MultiTileEntry[totalLength];
  			m_Width = newWidth;
  			m_Height = newHeight;
  
  			m_Min = Point2D.Zero;
  			m_Max = Point2D.Zero;
  
  			int index = 0;
  
  			for (int x = 0; x < newWidth; ++x)
  			{
  				for (int y = 0; y < newHeight; ++y)
  				{
  					var tiles = newTiles[x][y];
  
  					for (int i = 0; i < tiles.Length; ++i)
  					{
  
gt: 						StaticTile tile = tiles[i];