diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index 1eac5d8e84..d7af5d17ce 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -249,15 +249,13 @@ public static string FindDataFile(string path, bool throwNotFound = true) { fullPath = Path.Combine(p, path); - if (IsLinux) + if (IsLinux && !File.Exists(fullPath)) { var fi = new FileInfo(fullPath); fullPath = fi.Directory!.EnumerateFiles( fi.Name, new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive } ).FirstOrDefault()?.FullName; - - break; } if (File.Exists(fullPath)) diff --git a/Projects/Server/MultiData.cs b/Projects/Server/MultiData.cs index ead7417af2..b815ab0c9b 100644 --- a/Projects/Server/MultiData.cs +++ b/Projects/Server/MultiData.cs @@ -4,990 +4,943 @@ using System.IO; using System.IO.Compression; -namespace Server +namespace Server; + +public static class MultiData { - public static class MultiData + public static void Configure() { - private static readonly BinaryReader m_IndexReader; - private static readonly BinaryReader m_StreamReader; - - public static readonly bool PostHSMulFormat; - public static readonly bool UsingUOPFormat; + var multiUOPPath = Core.FindDataFile("MultiCollection.uop", false); - static MultiData() + if (File.Exists(multiUOPPath)) { - var multiUOPPath = Core.FindDataFile("MultiCollection.uop", false); - - if (File.Exists(multiUOPPath)) - { - LoadUOP(multiUOPPath); - UsingUOPFormat = true; - PostHSMulFormat = false; - return; - } - - // Client version 7.0.9.0+ - PostHSMulFormat = UOClient.ServerClientVersion >= ClientVersion.Version7090; - - var idxPath = Core.FindDataFile("multi.idx"); - var mulPath = Core.FindDataFile("multi.mul"); - - var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read); - m_IndexReader = new BinaryReader(idx); - - var stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read); - m_StreamReader = new BinaryReader(stream); - - var vdPath = Core.FindDataFile("verdata.mul", false); - - if (!File.Exists(vdPath)) - { - return; - } - - using var fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var bin = new BinaryReader(fs); - - var count = bin.ReadInt32(); - - for (var i = 0; i < count; ++i) - { - var file = bin.ReadInt32(); - var index = bin.ReadInt32(); - var lookup = bin.ReadInt32(); - var length = bin.ReadInt32(); - bin.ReadInt32(); // extra + LoadUOP(multiUOPPath); + return; + } - if (file == 14 && index >= 0 && lookup >= 0 && length > 0) - { - bin.BaseStream.Seek(lookup, SeekOrigin.Begin); + // OSI Client 7.0.9.0+ uses 64bit tiledata flags + var postHSMulFormat = ServerConfiguration.GetSetting( + "maps.enablePostHSMultiComponentFormat", + UOClient.ServerClientVersion >= ClientVersion.Version7090 + ); - Components[index] = new MultiComponentList(bin, length / 12); + LoadMul(postHSMulFormat); + } - bin.BaseStream.Seek(24 + i * 20, SeekOrigin.Begin); - } - } + private static Dictionary _components = new(); - bin.Close(); - } + public static MultiComponentList GetComponents(int multiID) => + _components.TryGetValue(multiID & 0x3FFF, out var mcl) ? mcl : MultiComponentList.Empty; - public static Dictionary Components { get; } = new(); + private static void LoadUOP(string path) + { + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var streamReader = new BinaryReader(stream); - public static MultiComponentList GetComponents(int multiID) + // Head Information Start + if (streamReader.ReadInt32() != 0x0050594D) // Not a UOP File { - MultiComponentList mcl; - - multiID &= 0x3FFF; - - if (Components.ContainsKey(multiID)) - { - mcl = Components[multiID]; - } - else if (!UsingUOPFormat) - { - Components[multiID] = mcl = Load(multiID); - } - else - { - mcl = MultiComponentList.Empty; - } - - return mcl; + return; } - public static void LoadUOP(string path) + if (streamReader.ReadInt32() > 5) // Bad Version { - var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); - var streamReader = new BinaryReader(stream); - - // Head Information Start - if (streamReader.ReadInt32() != 0x0050594D) // Not a UOP Files - { - return; - } + return; + } - if (streamReader.ReadInt32() > 5) // Bad Version - { - return; - } + UOPHash.BuildChunkIDs(out var chunkIds); - // Multi ID List Array Start - UOPHash.BuildChunkIDs(out var chunkIds); - // Multi ID List Array End + streamReader.ReadUInt32(); // format timestamp? 0xFD23EC43 + var startAddress = streamReader.ReadInt64(); - streamReader.ReadUInt32(); // format timestamp? 0xFD23EC43 - var startAddress = streamReader.ReadInt64(); + stream.Seek(startAddress, SeekOrigin.Begin); // End of head block - streamReader.ReadInt32(); - streamReader.ReadInt32(); + long nextBlock; - stream.Seek(startAddress, SeekOrigin.Begin); // Head Information End + do + { + var blockFileCount = streamReader.ReadInt32(); + nextBlock = streamReader.ReadInt64(); - long nextBlock; + var index = 0; do { - var blockFileCount = streamReader.ReadInt32(); - nextBlock = streamReader.ReadInt64(); + var offset = streamReader.ReadInt64(); + + var headerSize = streamReader.ReadInt32(); // header length + var compressedSize = streamReader.ReadInt32(); // compressed size + var decompressedSize = streamReader.ReadInt32(); // decompressed size - var index = 0; + var filehash = streamReader.ReadUInt64(); // filename hash (HashLittle2) + streamReader.ReadUInt32(); + var compressionMethod = streamReader.ReadInt16(); // compression method (0 = none, 1 = zlib) - do + index++; + + if (offset == 0 || decompressedSize == 0 || filehash == 0x126D1E99DDEDEE0A) // Exclude housing.bin { - var offset = streamReader.ReadInt64(); + continue; + } - var headerSize = streamReader.ReadInt32(); // header length - var compressedSize = streamReader.ReadInt32(); // compressed size - var decompressedSize = streamReader.ReadInt32(); // decompressed size + chunkIds.TryGetValue(filehash, out var chunkID); - var filehash = streamReader.ReadUInt64(); // filename hash (HashLittle2) - streamReader.ReadUInt32(); - var compressionMethod = streamReader.ReadInt16(); // compression method (0 = none, 1 = zlib) + var position = stream.Position; // save current position - index++; + stream.Seek(offset + headerSize, SeekOrigin.Begin); - if (offset == 0 || decompressedSize == 0 || filehash == 0x126D1E99DDEDEE0A) // Exclude housing.bin - { - continue; - } + Span sourceData = GC.AllocateUninitializedArray(compressedSize); - chunkIds.TryGetValue(filehash, out var chunkID); + if (stream.Read(sourceData) != compressedSize) + { + continue; + } - var position = stream.Position; // save current position + Span data; - stream.Seek(offset + headerSize, SeekOrigin.Begin); + if (compressionMethod == 1) + { + data = GC.AllocateUninitializedArray(decompressedSize); + Zlib.Unpack(data, ref decompressedSize, sourceData, compressedSize); + } + else + { + data = sourceData; + } - Span sourceData = GC.AllocateUninitializedArray(compressedSize); + var tileList = new List(); - if (stream.Read(sourceData) != compressedSize) - { - continue; - } + var reader = new SpanReader(data); + reader.Seek(4, SeekOrigin.Begin); + var count = reader.ReadUInt32LE(); - Span data; + for (uint i = 0; i < count; i++) + { + var itemId = reader.ReadUInt16LE(); + var x = reader.ReadInt16LE(); + var y = reader.ReadInt16LE(); + var z = reader.ReadInt16LE(); + var flagValue = reader.ReadUInt16LE(); - if (compressionMethod == 1) + var tileFlag = flagValue switch { - data = GC.AllocateUninitializedArray(decompressedSize); - Zlib.Unpack(data, ref decompressedSize, sourceData, compressedSize); - } - else - { - data = sourceData; - } + 1 => TileFlag.None, + 257 => TileFlag.Generic, + _ => TileFlag.Background // 0 + }; - var tileList = new List(); + var clilocsCount = reader.ReadUInt32LE(); + var skip = (int)Math.Min(clilocsCount, int.MaxValue) * 4; // bypass binary block + reader.Seek(skip, SeekOrigin.Current); - var reader = new SpanReader(data); - reader.Seek(4, SeekOrigin.Begin); - var count = reader.ReadUInt32LE(); + tileList.Add(new MultiTileEntry(itemId, x, y, z, tileFlag)); + } - for (uint i = 0; i < count; i++) - { - var itemId = reader.ReadUInt16LE(); - var x = reader.ReadInt16LE(); - var y = reader.ReadInt16LE(); - var z = reader.ReadInt16LE(); - var flagValue = reader.ReadUInt16LE(); - - var tileFlag = flagValue switch - { - 1 => TileFlag.None, - 257 => TileFlag.Generic, - _ => TileFlag.Background // 0 - }; - - var clilocsCount = reader.ReadUInt32LE(); - var skip = (int)Math.Min(clilocsCount, int.MaxValue) * 4; // bypass binary block - reader.Seek(skip, SeekOrigin.Current); - - tileList.Add(new MultiTileEntry(itemId, x, y, z, tileFlag)); - } + _components[chunkID] = new MultiComponentList(tileList); - Components[chunkID] = new MultiComponentList(tileList); + stream.Seek(position, SeekOrigin.Begin); // back to position + } while (index < blockFileCount); + } while (stream.Seek(nextBlock, SeekOrigin.Begin) != 0); - stream.Seek(position, SeekOrigin.Begin); // back to position - } while (index < blockFileCount); - } while (stream.Seek(nextBlock, SeekOrigin.Begin) != 0); - } + streamReader.Close(); + } - // TODO: Change this to read the file all during load time - public static MultiComponentList Load(int multiID) - { - try - { - m_IndexReader.BaseStream.Seek(multiID * 12, SeekOrigin.Begin); + private static void LoadMul(bool postHSMulFormat) + { + var idxPath = Core.FindDataFile("multi.idx"); + var mulPath = Core.FindDataFile("multi.mul"); - var lookup = m_IndexReader.ReadInt32(); - var length = m_IndexReader.ReadInt32(); + using var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var idxReader = new BinaryReader(idx); - if (lookup < 0 || length <= 0) - { - return MultiComponentList.Empty; - } + using var stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var bin = new BinaryReader(stream); - m_StreamReader.BaseStream.Seek(lookup, SeekOrigin.Begin); + var count = (int)(idx.Length / 12); + for (var i = 0; i < count; i++) + { + var lookup = idxReader.ReadInt32(); + var length = idxReader.ReadInt32(); + idx.Seek(4, SeekOrigin.Current); // Extra - return new MultiComponentList(m_StreamReader, length / (PostHSMulFormat ? 16 : 12)); - } - catch + if (lookup < 0 || length <= 0) { - return MultiComponentList.Empty; + continue; } + + bin.BaseStream.Seek(lookup, SeekOrigin.Begin); + _components[i] = new MultiComponentList(bin, length, postHSMulFormat); } + + idxReader.Close(); } +} - public struct MultiTileEntry - { - public ushort ItemId { get; set; } - public short OffsetX { get; set; } - public short OffsetY { get; set; } - public short OffsetZ { get; set; } - public TileFlag Flags { get; set; } +public struct MultiTileEntry +{ + public ushort ItemId { get; set; } + public short OffsetX { get; set; } + public short OffsetY { get; set; } + public short OffsetZ { get; set; } + public TileFlag Flags { get; set; } - public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, TileFlag flags) - { - ItemId = itemID; - OffsetX = xOffset; - OffsetY = yOffset; - OffsetZ = zOffset; - Flags = flags; - } + public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, TileFlag flags) + { + ItemId = itemID; + OffsetX = xOffset; + OffsetY = yOffset; + OffsetZ = zOffset; + Flags = flags; } +} - public sealed class MultiComponentList - { - public static readonly MultiComponentList Empty = new(); +public sealed class MultiComponentList +{ + public static readonly MultiComponentList Empty = new(); - private Point2D m_Min, m_Max; + private Point2D m_Min, m_Max; - public MultiComponentList(MultiComponentList toCopy) - { - m_Min = toCopy.m_Min; - m_Max = toCopy.m_Max; + public MultiComponentList(MultiComponentList toCopy) + { + m_Min = toCopy.m_Min; + m_Max = toCopy.m_Max; - Center = toCopy.Center; + Center = toCopy.Center; - Width = toCopy.Width; - Height = toCopy.Height; + Width = toCopy.Width; + Height = toCopy.Height; - Tiles = new StaticTile[Width][][]; + Tiles = new StaticTile[Width][][]; - for (var x = 0; x < Width; ++x) + for (var x = 0; x < Width; ++x) + { + Tiles[x] = new StaticTile[Height][]; + + for (var y = 0; y < Height; ++y) { - Tiles[x] = new StaticTile[Height][]; + Tiles[x][y] = new StaticTile[toCopy.Tiles[x][y].Length]; - for (var y = 0; y < Height; ++y) + for (var i = 0; i < Tiles[x][y].Length; ++i) { - Tiles[x][y] = new StaticTile[toCopy.Tiles[x][y].Length]; - - for (var i = 0; i < Tiles[x][y].Length; ++i) - { - Tiles[x][y][i] = toCopy.Tiles[x][y][i]; - } + Tiles[x][y][i] = toCopy.Tiles[x][y][i]; } } + } - List = new MultiTileEntry[toCopy.List.Length]; + List = new MultiTileEntry[toCopy.List.Length]; - for (var i = 0; i < List.Length; ++i) - { - List[i] = toCopy.List[i]; - } + for (var i = 0; i < List.Length; ++i) + { + List[i] = toCopy.List[i]; } + } - public MultiComponentList(IGenericReader reader) - { - var version = reader.ReadInt(); + public MultiComponentList(IGenericReader reader) + { + var version = reader.ReadInt(); - m_Min = reader.ReadPoint2D(); - m_Max = reader.ReadPoint2D(); - Center = reader.ReadPoint2D(); - Width = reader.ReadInt(); - Height = reader.ReadInt(); + m_Min = reader.ReadPoint2D(); + m_Max = reader.ReadPoint2D(); + Center = reader.ReadPoint2D(); + Width = reader.ReadInt(); + Height = reader.ReadInt(); - var length = reader.ReadInt(); + var length = reader.ReadInt(); - var allTiles = List = new MultiTileEntry[length]; + var allTiles = List = new MultiTileEntry[length]; - if (version == 0) + if (version == 0) + { + for (var i = 0; i < length; ++i) { - for (var i = 0; i < length; ++i) + int id = reader.ReadShort(); + if (id >= 0x4000) { - int id = reader.ReadShort(); - if (id >= 0x4000) - { - id -= 0x4000; - } - - allTiles[i].ItemId = (ushort)id; - allTiles[i].OffsetX = reader.ReadShort(); - allTiles[i].OffsetY = reader.ReadShort(); - allTiles[i].OffsetZ = reader.ReadShort(); - allTiles[i].Flags = (TileFlag)reader.ReadInt(); + id -= 0x4000; } + + allTiles[i].ItemId = (ushort)id; + allTiles[i].OffsetX = reader.ReadShort(); + allTiles[i].OffsetY = reader.ReadShort(); + allTiles[i].OffsetZ = reader.ReadShort(); + allTiles[i].Flags = (TileFlag)reader.ReadInt(); } - else + } + else + { + for (var i = 0; i < length; ++i) { - for (var i = 0; i < length; ++i) - { - allTiles[i].ItemId = reader.ReadUShort(); - allTiles[i].OffsetX = reader.ReadShort(); - allTiles[i].OffsetY = reader.ReadShort(); - allTiles[i].OffsetZ = reader.ReadShort(); - allTiles[i].Flags = (TileFlag)reader.ReadInt(); - } + allTiles[i].ItemId = reader.ReadUShort(); + allTiles[i].OffsetX = reader.ReadShort(); + allTiles[i].OffsetY = reader.ReadShort(); + allTiles[i].OffsetZ = reader.ReadShort(); + allTiles[i].Flags = (TileFlag)reader.ReadInt(); } + } - var tiles = new TileList[Width][]; - Tiles = new StaticTile[Width][][]; + var tiles = new TileList[Width][]; + Tiles = new StaticTile[Width][][]; - for (var x = 0; x < Width; ++x) - { - tiles[x] = new TileList[Height]; - Tiles[x] = new StaticTile[Height][]; + for (var x = 0; x < Width; ++x) + { + tiles[x] = new TileList[Height]; + Tiles[x] = new StaticTile[Height][]; - for (var y = 0; y < Height; ++y) - { - tiles[x][y] = new TileList(); - } + for (var y = 0; y < Height; ++y) + { + tiles[x][y] = new TileList(); } + } - for (var i = 0; i < allTiles.Length; ++i) + for (var i = 0; i < allTiles.Length; ++i) + { + if (i == 0 || allTiles[i].Flags != 0) { - if (i == 0 || allTiles[i].Flags != 0) - { - var xOffset = allTiles[i].OffsetX + Center.m_X; - var yOffset = allTiles[i].OffsetY + Center.m_Y; + var xOffset = allTiles[i].OffsetX + Center.m_X; + var yOffset = allTiles[i].OffsetY + Center.m_Y; - tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); - } + tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); } + } - for (var x = 0; x < Width; ++x) + for (var x = 0; x < Width; ++x) + { + for (var y = 0; y < Height; ++y) { - for (var y = 0; y < Height; ++y) - { - Tiles[x][y] = tiles[x][y].ToArray(); - } + Tiles[x][y] = tiles[x][y].ToArray(); } } + } - public MultiComponentList(BinaryReader reader, int count) - { - var allTiles = List = new MultiTileEntry[count]; + public MultiComponentList(BinaryReader reader, int length, bool postHSFormat) + { + var count = length / (postHSFormat ? 16 : 12); + var allTiles = List = new MultiTileEntry[count]; - for (var i = 0; i < count; ++i) - { - allTiles[i].ItemId = reader.ReadUInt16(); - allTiles[i].OffsetX = reader.ReadInt16(); - allTiles[i].OffsetY = reader.ReadInt16(); - allTiles[i].OffsetZ = reader.ReadInt16(); - allTiles[i].Flags = MultiData.PostHSMulFormat - ? (TileFlag)reader.ReadUInt64() - : (TileFlag)reader.ReadUInt32(); + for (var i = 0; i < count; ++i) + { + allTiles[i].ItemId = reader.ReadUInt16(); + allTiles[i].OffsetX = reader.ReadInt16(); + allTiles[i].OffsetY = reader.ReadInt16(); + allTiles[i].OffsetZ = reader.ReadInt16(); + allTiles[i].Flags = postHSFormat ? (TileFlag)reader.ReadUInt64() : (TileFlag)reader.ReadUInt32(); - var e = allTiles[i]; + var e = allTiles[i]; - if (i == 0 || e.Flags != 0) + if (i == 0 || e.Flags != 0) + { + if (e.OffsetX < m_Min.m_X) { - if (e.OffsetX < m_Min.m_X) - { - m_Min.m_X = e.OffsetX; - } - - if (e.OffsetY < m_Min.m_Y) - { - m_Min.m_Y = e.OffsetY; - } - - if (e.OffsetX > m_Max.m_X) - { - m_Max.m_X = e.OffsetX; - } - - if (e.OffsetY > m_Max.m_Y) - { - m_Max.m_Y = e.OffsetY; - } + m_Min.m_X = e.OffsetX; } - } - Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); - Width = m_Max.m_X - m_Min.m_X + 1; - Height = m_Max.m_Y - m_Min.m_Y + 1; - - var tiles = new TileList[Width][]; - Tiles = new StaticTile[Width][][]; - - for (var x = 0; x < Width; ++x) - { - tiles[x] = new TileList[Height]; - Tiles[x] = new StaticTile[Height][]; - - for (var y = 0; y < Height; ++y) + if (e.OffsetY < m_Min.m_Y) { - tiles[x][y] = new TileList(); + m_Min.m_Y = e.OffsetY; } - } - for (var i = 0; i < allTiles.Length; ++i) - { - if (i == 0 || allTiles[i].Flags != 0) + if (e.OffsetX > m_Max.m_X) { - var xOffset = allTiles[i].OffsetX + Center.m_X; - var yOffset = allTiles[i].OffsetY + Center.m_Y; - - tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); + m_Max.m_X = e.OffsetX; } - } - for (var x = 0; x < Width; ++x) - { - for (var y = 0; y < Height; ++y) + if (e.OffsetY > m_Max.m_Y) { - Tiles[x][y] = tiles[x][y].ToArray(); + m_Max.m_Y = e.OffsetY; } } } - public MultiComponentList(List list) + Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); + Width = m_Max.m_X - m_Min.m_X + 1; + Height = m_Max.m_Y - m_Min.m_Y + 1; + + var tiles = new TileList[Width][]; + Tiles = new StaticTile[Width][][]; + + for (var x = 0; x < Width; ++x) { - var allTiles = List = new MultiTileEntry[list.Count]; + tiles[x] = new TileList[Height]; + Tiles[x] = new StaticTile[Height][]; - for (var i = 0; i < list.Count; ++i) + for (var y = 0; y < Height; ++y) { - allTiles[i].ItemId = list[i].ItemId; - allTiles[i].OffsetX = list[i].OffsetX; - allTiles[i].OffsetY = list[i].OffsetY; - allTiles[i].OffsetZ = list[i].OffsetZ; - - allTiles[i].Flags = list[i].Flags; + tiles[x][y] = new TileList(); + } + } - var e = allTiles[i]; + for (var i = 0; i < allTiles.Length; ++i) + { + if (i == 0 || allTiles[i].Flags != 0) + { + var xOffset = allTiles[i].OffsetX + Center.m_X; + var yOffset = allTiles[i].OffsetY + Center.m_Y; - if (i == 0 || e.Flags != 0) - { - if (e.OffsetX < m_Min.m_X) - { - m_Min.m_X = e.OffsetX; - } + tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); + } + } - if (e.OffsetY < m_Min.m_Y) - { - m_Min.m_Y = e.OffsetY; - } + for (var x = 0; x < Width; ++x) + { + for (var y = 0; y < Height; ++y) + { + Tiles[x][y] = tiles[x][y].ToArray(); + } + } + } - if (e.OffsetX > m_Max.m_X) - { - m_Max.m_X = e.OffsetX; - } + public MultiComponentList(List list) + { + var allTiles = List = new MultiTileEntry[list.Count]; - if (e.OffsetY > m_Max.m_Y) - { - m_Max.m_Y = e.OffsetY; - } - } - } + for (var i = 0; i < list.Count; ++i) + { + allTiles[i].ItemId = list[i].ItemId; + allTiles[i].OffsetX = list[i].OffsetX; + allTiles[i].OffsetY = list[i].OffsetY; + allTiles[i].OffsetZ = list[i].OffsetZ; - Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); - Width = m_Max.m_X - m_Min.m_X + 1; - Height = m_Max.m_Y - m_Min.m_Y + 1; + allTiles[i].Flags = list[i].Flags; - var tiles = new TileList[Width][]; - Tiles = new StaticTile[Width][][]; + var e = allTiles[i]; - for (var x = 0; x < Width; ++x) + if (i == 0 || e.Flags != 0) { - tiles[x] = new TileList[Height]; - Tiles[x] = new StaticTile[Height][]; + if (e.OffsetX < m_Min.m_X) + { + m_Min.m_X = e.OffsetX; + } - for (var y = 0; y < Height; ++y) + if (e.OffsetY < m_Min.m_Y) { - tiles[x][y] = new TileList(); + m_Min.m_Y = e.OffsetY; } - } - for (var i = 0; i < allTiles.Length; ++i) - { - if (i == 0 || allTiles[i].Flags != 0) + if (e.OffsetX > m_Max.m_X) { - var xOffset = allTiles[i].OffsetX + Center.m_X; - var yOffset = allTiles[i].OffsetY + Center.m_Y; - var itemID = (allTiles[i].ItemId & TileData.MaxItemValue) | 0x10000; + m_Max.m_X = e.OffsetX; + } - tiles[xOffset][yOffset].Add((ushort)itemID, (sbyte)allTiles[i].OffsetZ); + if (e.OffsetY > m_Max.m_Y) + { + m_Max.m_Y = e.OffsetY; } } + } + + Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); + Width = m_Max.m_X - m_Min.m_X + 1; + Height = m_Max.m_Y - m_Min.m_Y + 1; + + var tiles = new TileList[Width][]; + Tiles = new StaticTile[Width][][]; + + for (var x = 0; x < Width; ++x) + { + tiles[x] = new TileList[Height]; + Tiles[x] = new StaticTile[Height][]; - for (var x = 0; x < Width; ++x) + for (var y = 0; y < Height; ++y) { - for (var y = 0; y < Height; ++y) - { - Tiles[x][y] = tiles[x][y].ToArray(); - } + tiles[x][y] = new TileList(); } } - private MultiComponentList() + for (var i = 0; i < allTiles.Length; ++i) { - Tiles = Array.Empty(); - List = Array.Empty(); + if (i == 0 || allTiles[i].Flags != 0) + { + var xOffset = allTiles[i].OffsetX + Center.m_X; + var yOffset = allTiles[i].OffsetY + Center.m_Y; + var itemID = (allTiles[i].ItemId & TileData.MaxItemValue) | 0x10000; + + tiles[xOffset][yOffset].Add((ushort)itemID, (sbyte)allTiles[i].OffsetZ); + } } - public static void Configure() + for (var x = 0; x < Width; ++x) { - // OSI Client Patch 7.0.9.0 - PostHSFormat = ServerConfiguration.GetSetting("maps.enablePostHSMultiComponentFormat", true); + for (var y = 0; y < Height; ++y) + { + Tiles[x][y] = tiles[x][y].ToArray(); + } } + } - public static bool PostHSFormat { get; set; } + private MultiComponentList() + { + Tiles = Array.Empty(); + List = Array.Empty(); + } - public Point2D Min => m_Min; - public Point2D Max => m_Max; + public Point2D Min => m_Min; + public Point2D Max => m_Max; - public Point2D Center { get; } + public Point2D Center { get; } - public int Width { get; private set; } + public int Width { get; private set; } - public int Height { get; private set; } + public int Height { get; private set; } - public StaticTile[][][] Tiles { get; private set; } + public StaticTile[][][] Tiles { get; private set; } - public MultiTileEntry[] List { get; private set; } + public MultiTileEntry[] List { get; private set; } - public void Add(int itemID, int x, int y, int z) + public void Add(int itemID, int x, int y, int z) + { + var vx = x + Center.m_X; + var vy = y + Center.m_Y; + + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) { - var vx = x + Center.m_X; - var vy = y + Center.m_Y; + var oldTiles = Tiles[vx][vy]; - if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + for (var i = oldTiles.Length - 1; i >= 0; --i) { - var oldTiles = Tiles[vx][vy]; + var data = TileData.ItemTable[itemID & TileData.MaxItemValue]; - for (var i = oldTiles.Length - 1; i >= 0; --i) + if (oldTiles[i].Z == z && oldTiles[i].Height > 0 == data.Height > 0) { - var data = TileData.ItemTable[itemID & TileData.MaxItemValue]; + var newIsRoof = (data.Flags & TileFlag.Roof) != 0; + var oldIsRoof = + (TileData.ItemTable[oldTiles[i].ID & TileData.MaxItemValue].Flags & TileFlag.Roof) != 0; - if (oldTiles[i].Z == z && oldTiles[i].Height > 0 == data.Height > 0) + if (newIsRoof == oldIsRoof) { - var newIsRoof = (data.Flags & TileFlag.Roof) != 0; - var oldIsRoof = - (TileData.ItemTable[oldTiles[i].ID & TileData.MaxItemValue].Flags & TileFlag.Roof) != 0; - - if (newIsRoof == oldIsRoof) - { - Remove(oldTiles[i].ID, x, y, z); - } + Remove(oldTiles[i].ID, x, y, z); } } + } - oldTiles = Tiles[vx][vy]; + oldTiles = Tiles[vx][vy]; - var newTiles = new StaticTile[oldTiles.Length + 1]; + var newTiles = new StaticTile[oldTiles.Length + 1]; - for (var i = 0; i < oldTiles.Length; ++i) - { - newTiles[i] = oldTiles[i]; - } + for (var i = 0; i < oldTiles.Length; ++i) + { + newTiles[i] = oldTiles[i]; + } - newTiles[oldTiles.Length] = new StaticTile((ushort)itemID, (sbyte)z); + newTiles[oldTiles.Length] = new StaticTile((ushort)itemID, (sbyte)z); - Tiles[vx][vy] = newTiles; + Tiles[vx][vy] = newTiles; - var oldList = List; - var newList = new MultiTileEntry[oldList.Length + 1]; + var oldList = List; + var newList = new MultiTileEntry[oldList.Length + 1]; - for (var i = 0; i < oldList.Length; ++i) - { - newList[i] = oldList[i]; - } + for (var i = 0; i < oldList.Length; ++i) + { + newList[i] = oldList[i]; + } - newList[oldList.Length] = new MultiTileEntry( - (ushort)itemID, - (short)x, - (short)y, - (short)z, - TileFlag.Background - ); + newList[oldList.Length] = new MultiTileEntry( + (ushort)itemID, + (short)x, + (short)y, + (short)z, + TileFlag.Background + ); - List = newList; + List = newList; - if (x < m_Min.m_X) - { - m_Min.m_X = x; - } + if (x < m_Min.m_X) + { + m_Min.m_X = x; + } - if (y < m_Min.m_Y) - { - m_Min.m_Y = y; - } + if (y < m_Min.m_Y) + { + m_Min.m_Y = y; + } - if (x > m_Max.m_X) - { - m_Max.m_X = x; - } + if (x > m_Max.m_X) + { + m_Max.m_X = x; + } - if (y > m_Max.m_Y) - { - m_Max.m_Y = y; - } + if (y > m_Max.m_Y) + { + m_Max.m_Y = y; } } + } + + public void RemoveXYZH(int x, int y, int z, int minHeight) + { + var vx = x + Center.m_X; + var vy = y + Center.m_Y; - public void RemoveXYZH(int x, int y, int z, int minHeight) + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) { - var vx = x + Center.m_X; - var vy = y + Center.m_Y; + var oldTiles = Tiles[vx][vy]; - if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + for (var i = 0; i < oldTiles.Length; ++i) { - var oldTiles = Tiles[vx][vy]; + var tile = oldTiles[i]; - for (var i = 0; i < oldTiles.Length; ++i) + if (tile.Z == z && tile.Height >= minHeight) { - var tile = oldTiles[i]; + var newTiles = new StaticTile[oldTiles.Length - 1]; - if (tile.Z == z && tile.Height >= minHeight) + for (var j = 0; j < i; ++j) { - var newTiles = new StaticTile[oldTiles.Length - 1]; - - for (var j = 0; j < i; ++j) - { - newTiles[j] = oldTiles[j]; - } + newTiles[j] = oldTiles[j]; + } - for (var j = i + 1; j < oldTiles.Length; ++j) - { - newTiles[j - 1] = oldTiles[j]; - } + for (var j = i + 1; j < oldTiles.Length; ++j) + { + newTiles[j - 1] = oldTiles[j]; + } - Tiles[vx][vy] = newTiles; + Tiles[vx][vy] = newTiles; - break; - } + break; } + } - var oldList = List; + var oldList = List; - for (var i = 0; i < oldList.Length; ++i) + for (var i = 0; i < oldList.Length; ++i) + { + var tile = oldList[i]; + + if (tile.OffsetX == (short)x && tile.OffsetY == (short)y && tile.OffsetZ == (short)z && + TileData.ItemTable[tile.ItemId & TileData.MaxItemValue].Height >= minHeight) { - var tile = oldList[i]; + var newList = new MultiTileEntry[oldList.Length - 1]; - if (tile.OffsetX == (short)x && tile.OffsetY == (short)y && tile.OffsetZ == (short)z && - TileData.ItemTable[tile.ItemId & TileData.MaxItemValue].Height >= minHeight) + for (var j = 0; j < i; ++j) { - var newList = new MultiTileEntry[oldList.Length - 1]; - - for (var j = 0; j < i; ++j) - { - newList[j] = oldList[j]; - } + newList[j] = oldList[j]; + } - for (var j = i + 1; j < oldList.Length; ++j) - { - newList[j - 1] = oldList[j]; - } + for (var j = i + 1; j < oldList.Length; ++j) + { + newList[j - 1] = oldList[j]; + } - List = newList; + List = newList; - break; - } + break; } } } + } + + public void Remove(int itemID, int x, int y, int z) + { + var vx = x + Center.m_X; + var vy = y + Center.m_Y; - public void Remove(int itemID, int x, int y, int z) + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) { - var vx = x + Center.m_X; - var vy = y + Center.m_Y; + var oldTiles = Tiles[vx][vy]; - if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + for (var i = 0; i < oldTiles.Length; ++i) { - var oldTiles = Tiles[vx][vy]; + var tile = oldTiles[i]; - for (var i = 0; i < oldTiles.Length; ++i) + if (tile.ID == itemID && tile.Z == z) { - var tile = oldTiles[i]; + var newTiles = new StaticTile[oldTiles.Length - 1]; - if (tile.ID == itemID && tile.Z == z) + for (var j = 0; j < i; ++j) { - var newTiles = new StaticTile[oldTiles.Length - 1]; - - for (var j = 0; j < i; ++j) - { - newTiles[j] = oldTiles[j]; - } + newTiles[j] = oldTiles[j]; + } - for (var j = i + 1; j < oldTiles.Length; ++j) - { - newTiles[j - 1] = oldTiles[j]; - } + for (var j = i + 1; j < oldTiles.Length; ++j) + { + newTiles[j - 1] = oldTiles[j]; + } - Tiles[vx][vy] = newTiles; + Tiles[vx][vy] = newTiles; - break; - } + break; } + } - var oldList = List; + var oldList = List; + + for (var i = 0; i < oldList.Length; ++i) + { + var tile = oldList[i]; - for (var i = 0; i < oldList.Length; ++i) + if (tile.ItemId == itemID && tile.OffsetX == (short)x && tile.OffsetY == (short)y && + tile.OffsetZ == (short)z) { - var tile = oldList[i]; + var newList = new MultiTileEntry[oldList.Length - 1]; - if (tile.ItemId == itemID && tile.OffsetX == (short)x && tile.OffsetY == (short)y && - tile.OffsetZ == (short)z) + for (var j = 0; j < i; ++j) { - var newList = new MultiTileEntry[oldList.Length - 1]; - - for (var j = 0; j < i; ++j) - { - newList[j] = oldList[j]; - } + newList[j] = oldList[j]; + } - for (var j = i + 1; j < oldList.Length; ++j) - { - newList[j - 1] = oldList[j]; - } + for (var j = i + 1; j < oldList.Length; ++j) + { + newList[j - 1] = oldList[j]; + } - List = newList; + List = newList; - break; - } + break; } } } + } - public void Resize(int newWidth, int newHeight) - { - int oldWidth = Width, oldHeight = Height; - var oldTiles = Tiles; + public void Resize(int newWidth, int newHeight) + { + int oldWidth = Width, oldHeight = Height; + var oldTiles = Tiles; - var totalLength = 0; + var totalLength = 0; - var newTiles = new StaticTile[newWidth][][]; + var newTiles = new StaticTile[newWidth][][]; - for (var x = 0; x < newWidth; ++x) - { - newTiles[x] = new StaticTile[newHeight][]; + for (var x = 0; x < newWidth; ++x) + { + newTiles[x] = new StaticTile[newHeight][]; - for (var y = 0; y < newHeight; ++y) + for (var y = 0; y < newHeight; ++y) + { + if (x < oldWidth && y < oldHeight) { - if (x < oldWidth && y < oldHeight) - { - newTiles[x][y] = oldTiles[x][y]; - } - else - { - newTiles[x][y] = Array.Empty(); - } - - totalLength += newTiles[x][y].Length; + newTiles[x][y] = oldTiles[x][y]; + } + else + { + newTiles[x][y] = Array.Empty(); } + + totalLength += newTiles[x][y].Length; } + } - Tiles = newTiles; - List = new MultiTileEntry[totalLength]; - Width = newWidth; - Height = newHeight; + Tiles = newTiles; + List = new MultiTileEntry[totalLength]; + Width = newWidth; + Height = newHeight; - m_Min = Point2D.Zero; - m_Max = Point2D.Zero; + m_Min = Point2D.Zero; + m_Max = Point2D.Zero; - var index = 0; + var index = 0; - for (var x = 0; x < newWidth; ++x) + for (var x = 0; x < newWidth; ++x) + { + for (var y = 0; y < newHeight; ++y) { - for (var y = 0; y < newHeight; ++y) + var tiles = newTiles[x][y]; + + for (var i = 0; i < tiles.Length; ++i) { - var tiles = newTiles[x][y]; + var tile = tiles[i]; - for (var i = 0; i < tiles.Length; ++i) + var vx = x - Center.X; + var vy = y - Center.Y; + + if (vx < m_Min.m_X) { - var tile = tiles[i]; - - var vx = x - Center.X; - var vy = y - Center.Y; - - if (vx < m_Min.m_X) - { - m_Min.m_X = vx; - } - - if (vy < m_Min.m_Y) - { - m_Min.m_Y = vy; - } - - if (vx > m_Max.m_X) - { - m_Max.m_X = vx; - } - - if (vy > m_Max.m_Y) - { - m_Max.m_Y = vy; - } - - List[index++] = new MultiTileEntry( - (ushort)tile.ID, - (short)vx, - (short)vy, - (short)tile.Z, - TileFlag.Background - ); + m_Min.m_X = vx; } + + if (vy < m_Min.m_Y) + { + m_Min.m_Y = vy; + } + + if (vx > m_Max.m_X) + { + m_Max.m_X = vx; + } + + if (vy > m_Max.m_Y) + { + m_Max.m_Y = vy; + } + + List[index++] = new MultiTileEntry( + (ushort)tile.ID, + (short)vx, + (short)vy, + (short)tile.Z, + TileFlag.Background + ); } } } + } - public void Serialize(IGenericWriter writer) - { - writer.Write(1); // version; + public void Serialize(IGenericWriter writer) + { + writer.Write(1); // version; - writer.Write(m_Min); - writer.Write(m_Max); - writer.Write(Center); + writer.Write(m_Min); + writer.Write(m_Max); + writer.Write(Center); - writer.Write(Width); - writer.Write(Height); + writer.Write(Width); + writer.Write(Height); - writer.Write(List.Length); + writer.Write(List.Length); - for (var i = 0; i < List.Length; ++i) - { - var ent = List[i]; + for (var i = 0; i < List.Length; ++i) + { + var ent = List[i]; - writer.Write(ent.ItemId); - writer.Write(ent.OffsetX); - writer.Write(ent.OffsetY); - writer.Write(ent.OffsetZ); - writer.Write((int)ent.Flags); - } + writer.Write(ent.ItemId); + writer.Write(ent.OffsetX); + writer.Write(ent.OffsetY); + writer.Write(ent.OffsetZ); + writer.Write((int)ent.Flags); } } +} - public static class UOPHash +public static class UOPHash +{ + public static void BuildChunkIDs(out Dictionary chunkIds) { - public static void BuildChunkIDs(out Dictionary chunkIds) - { - const int maxId = 0x10000; + const int maxId = 0x10000; - chunkIds = new Dictionary(); + chunkIds = new Dictionary(); - for (var i = 0; i < maxId; ++i) - { - chunkIds[HashLittle2($"build/multicollection/{i:000000}.bin")] = i; - } + for (var i = 0; i < maxId; ++i) + { + chunkIds[HashLittle2($"build/multicollection/{i:000000}.bin")] = i; } + } - private static ulong HashLittle2(string s) - { - var length = s.Length; + public static ulong HashLittle2(ReadOnlySpan s) + { + var length = s.Length; - uint b, c; - var a = b = c = 0xDEADBEEF + (uint)length; + uint b, c; + var a = b = c = 0xDEADBEEF + (uint)length; - var k = 0; + var k = 0; - while (length > 12) - { - a += s[k]; - a += (uint)s[k + 1] << 8; - a += (uint)s[k + 2] << 16; - a += (uint)s[k + 3] << 24; - b += s[k + 4]; - b += (uint)s[k + 5] << 8; - b += (uint)s[k + 6] << 16; - b += (uint)s[k + 7] << 24; - c += s[k + 8]; - c += (uint)s[k + 9] << 8; - c += (uint)s[k + 10] << 16; - c += (uint)s[k + 11] << 24; - - a -= c; - a ^= (c << 4) | (c >> 28); - c += b; - b -= a; - b ^= (a << 6) | (a >> 26); - a += c; - c -= b; - c ^= (b << 8) | (b >> 24); - b += a; - a -= c; - a ^= (c << 16) | (c >> 16); - c += b; - b -= a; - b ^= (a << 19) | (a >> 13); - a += c; - c -= b; - c ^= (b << 4) | (b >> 28); - b += a; - - length -= 12; - k += 12; - } + while (length > 12) + { + a += s[k]; + a += (uint)s[k + 1] << 8; + a += (uint)s[k + 2] << 16; + a += (uint)s[k + 3] << 24; + b += s[k + 4]; + b += (uint)s[k + 5] << 8; + b += (uint)s[k + 6] << 16; + b += (uint)s[k + 7] << 24; + c += s[k + 8]; + c += (uint)s[k + 9] << 8; + c += (uint)s[k + 10] << 16; + c += (uint)s[k + 11] << 24; + + a -= c; + a ^= (c << 4) | (c >> 28); + c += b; + b -= a; + b ^= (a << 6) | (a >> 26); + a += c; + c -= b; + c ^= (b << 8) | (b >> 24); + b += a; + a -= c; + a ^= (c << 16) | (c >> 16); + c += b; + b -= a; + b ^= (a << 19) | (a >> 13); + a += c; + c -= b; + c ^= (b << 4) | (b >> 28); + b += a; + + length -= 12; + k += 12; + } - if (length != 0) + if (length != 0) + { + switch (length) { - switch (length) - { - case 12: + case 12: + { c += (uint)s[k + 11] << 24; goto case 11; - case 11: + } + case 11: + { c += (uint)s[k + 10] << 16; goto case 10; - case 10: + } + case 10: + { c += (uint)s[k + 9] << 8; goto case 9; - case 9: + } + case 9: + { c += s[k + 8]; goto case 8; - case 8: + } + case 8: + { b += (uint)s[k + 7] << 24; goto case 7; - case 7: + } + case 7: + { b += (uint)s[k + 6] << 16; goto case 6; - case 6: + } + case 6: + { b += (uint)s[k + 5] << 8; goto case 5; - case 5: + } + case 5: + { b += s[k + 4]; goto case 4; - case 4: + } + case 4: + { a += (uint)s[k + 3] << 24; goto case 3; - case 3: + } + case 3: + { a += (uint)s[k + 2] << 16; goto case 2; - case 2: + } + case 2: + { a += (uint)s[k + 1] << 8; goto case 1; - case 1: + } + case 1: + { a += s[k]; break; - } - - c ^= b; - c -= (b << 14) | (b >> 18); - a ^= c; - a -= (c << 11) | (c >> 21); - b ^= a; - b -= (a << 25) | (a >> 7); - c ^= b; - c -= (b << 16) | (b >> 16); - a ^= c; - a -= (c << 4) | (c >> 28); - b ^= a; - b -= (a << 14) | (a >> 18); - c ^= b; - c -= (b << 24) | (b >> 8); + } } - return ((ulong)b << 32) | c; + c ^= b; + c -= (b << 14) | (b >> 18); + a ^= c; + a -= (c << 11) | (c >> 21); + b ^= a; + b -= (a << 25) | (a >> 7); + c ^= b; + c -= (b << 16) | (b >> 16); + a ^= c; + a -= (c << 4) | (c >> 28); + b ^= a; + b -= (a << 14) | (a >> 18); + c ^= b; + c -= (b << 24) | (b >> 8); } + + return ((ulong)b << 32) | c; } }