diff --git a/src/Prima.Core.Server/Compression/Deflate.cs b/src/Prima.Core.Server/Compression/Deflate.cs new file mode 100644 index 0000000..420ee96 --- /dev/null +++ b/src/Prima.Core.Server/Compression/Deflate.cs @@ -0,0 +1,11 @@ +using System.IO.Compression; + +namespace Prima.Core.Server.Compression; + +public static class Deflate +{ + [ThreadStatic] + private static LibDeflateBinding _standard; + + public static LibDeflateBinding Standard => _standard ??= new LibDeflateBinding(); +} diff --git a/src/Prima.Core.Server/Prima.Core.Server.csproj b/src/Prima.Core.Server/Prima.Core.Server.csproj index 0b81d46..1bed8e7 100644 --- a/src/Prima.Core.Server/Prima.Core.Server.csproj +++ b/src/Prima.Core.Server/Prima.Core.Server.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Prima.UOData/Data/Geometry/Point2D.cs b/src/Prima.UOData/Data/Geometry/Point2D.cs index 169196b..14928e8 100644 --- a/src/Prima.UOData/Data/Geometry/Point2D.cs +++ b/src/Prima.UOData/Data/Geometry/Point2D.cs @@ -13,6 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ +using System.ComponentModel; using System.Runtime.CompilerServices; using Orion.Foundations.Extensions; using Prima.UOData.Interfaces.Geometry; @@ -22,25 +23,15 @@ namespace Prima.UOData.Data.Geometry; public struct Point2D : IPoint2D, IComparable, IComparable, IEquatable, IEquatable, - IEquatable, ISpanFormattable, ISpanParsable + IEquatable, ISpanFormattable, ISpanParsable, INotifyPropertyChanged { - internal int m_X; - internal int m_Y; + public event PropertyChangedEventHandler? PropertyChanged; - public static readonly Point2D Zero = new(0, 0); - - public int X - { - get => m_X; - set => m_X = value; - } + public int X { get; set; } + public int Y { get; set; } + public static readonly Point2D Zero = new(0, 0); - public int Y - { - get => m_Y; - set => m_Y = value; - } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Point2D(IPoint2D p) : this(p.X, p.Y) @@ -59,56 +50,56 @@ public Point2D(Point2D p) : this(p.X, p.Y) public Point2D(int x, int y) { - m_X = x; - m_Y = y; + X = x; + Y = y; } - public bool Equals(Point2D other) => m_X == other.m_X && m_Y == other.m_Y; + public bool Equals(Point2D other) => X == other.X && Y == other.Y; - public bool Equals(IPoint2D other) => m_X == other?.X && m_Y == other.Y; + public bool Equals(IPoint2D other) => X == other?.X && Y == other.Y; public override bool Equals(object obj) => obj is Point2D other && Equals(other); - public override int GetHashCode() => HashCode.Combine(m_X, m_Y); + public override int GetHashCode() => HashCode.Combine(X, Y); - public static bool operator ==(Point2D l, Point2D r) => l.m_X == r.m_X && l.m_Y == r.m_Y; + public static bool operator ==(Point2D l, Point2D r) => l.X == r.X && l.Y == r.Y; - public static bool operator !=(Point2D l, Point2D r) => l.m_X != r.m_X || l.m_Y != r.m_Y; + public static bool operator !=(Point2D l, Point2D r) => l.X != r.X || l.Y != r.Y; - public static bool operator ==(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y; + public static bool operator ==(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.X == r.X && l.Y == r.Y; - public static bool operator !=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y); + public static bool operator !=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && (l.X != r.X || l.Y != r.Y); - public static bool operator >(Point2D l, Point2D r) => l.m_X > r.m_X && l.m_Y > r.m_Y; + public static bool operator >(Point2D l, Point2D r) => l.X > r.X && l.Y > r.Y; - public static bool operator >(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y; + public static bool operator >(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.X > r.X && l.Y > r.Y; - public static bool operator <(Point2D l, Point2D r) => l.m_X < r.m_X && l.m_Y < r.m_Y; + public static bool operator <(Point2D l, Point2D r) => l.X < r.X && l.Y < r.Y; - public static bool operator <(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y; + public static bool operator <(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.X < r.X && l.Y < r.Y; - public static bool operator >=(Point2D l, Point2D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y; + public static bool operator >=(Point2D l, Point2D r) => l.X >= r.X && l.Y >= r.Y; - public static bool operator >=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y; + public static bool operator >=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.X >= r.X && l.Y >= r.Y; - public static bool operator <=(Point2D l, Point2D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y; + public static bool operator <=(Point2D l, Point2D r) => l.X <= r.X && l.Y <= r.Y; - public static bool operator <=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y; + public static bool operator <=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.X <= r.X && l.Y <= r.Y; public int CompareTo(Point2D other) { - var xComparison = m_X.CompareTo(other.m_X); - return xComparison != 0 ? xComparison : m_Y.CompareTo(other.m_Y); + var xComparison = X.CompareTo(other.X); + return xComparison != 0 ? xComparison : Y.CompareTo(other.Y); } public int CompareTo(IPoint2D other) { - var xComparison = m_X.CompareTo(other.X); - return xComparison != 0 ? xComparison : m_Y.CompareTo(other.Y); + var xComparison = X.CompareTo(other.X); + return xComparison != 0 ? xComparison : Y.CompareTo(other.Y); } public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) - => destination.TryWrite(provider, $"({m_X}, {m_Y})", out charsWritten); + => destination.TryWrite(provider, $"({X}, {Y})", out charsWritten); public override string ToString() { diff --git a/src/Prima.UOData/Data/Geometry/Point3D.cs b/src/Prima.UOData/Data/Geometry/Point3D.cs index 2eef8fa..f40ab13 100644 --- a/src/Prima.UOData/Data/Geometry/Point3D.cs +++ b/src/Prima.UOData/Data/Geometry/Point3D.cs @@ -22,45 +22,22 @@ namespace Prima.UOData.Data.Geometry; - public struct Point3D : IPoint3D, IComparable, IComparable, IEquatable, IEquatable, IEquatable, ISpanFormattable, ISpanParsable, INotifyPropertyChanged { - +#pragma warning disable 67 public event PropertyChangedEventHandler? PropertyChanged; +#pragma warning restore 67 - internal int m_X; - internal int m_Y; - internal int m_Z; + public int X { get; set; } + public int Y { get; set; } + public int Z { get; set; } public static readonly Point3D Zero = new(0, 0, 0); - - public int X - { - get => m_X; - set => m_X = value; - } - - - - public int Y - { - get => m_Y; - set => m_Y = value; - } - - - - public int Z - { - get => m_Z; - set => m_Z = value; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Point3D(IPoint3D p) : this(p.X, p.Y, p.Z) { @@ -78,86 +55,86 @@ public Point3D(Point2D p, int z) : this(p.X, p.Y, z) public Point3D(int x, int y, int z) { - m_X = x; - m_Y = y; - m_Z = z; + X = x; + Y = y; + Z = z; } - public bool Equals(Point3D other) => m_X == other.m_X && m_Y == other.m_Y && m_Z == other.m_Z; + public bool Equals(Point3D other) => X == other.X && Y == other.Y && Z == other.Z; public bool Equals(IPoint3D other) => - m_X == other?.X && m_Y == other.Y && m_Z == other.Z; + X == other?.X && Y == other.Y && Z == other.Z; public override bool Equals(object obj) => obj is Point3D other && Equals(other); - public override int GetHashCode() => HashCode.Combine(m_X, m_Y, m_Z); + public override int GetHashCode() => HashCode.Combine(X, Y, Z); - public static bool operator ==(Point3D l, Point3D r) => l.m_X == r.m_X && l.m_Y == r.m_Y && l.m_Z == r.m_Z; + public static bool operator ==(Point3D l, Point3D r) => l.X == r.X && l.Y == r.Y && l.Z == r.Z; public static bool operator ==(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y && l.m_Z == r.Z; + !ReferenceEquals(r, null) && l.X == r.X && l.Y == r.Y && l.Z == r.Z; - public static bool operator !=(Point3D l, Point3D r) => l.m_X != r.m_X || l.m_Y != r.m_Y || l.m_Z != r.m_Z; + public static bool operator !=(Point3D l, Point3D r) => l.X != r.X || l.Y != r.Y || l.Z != r.Z; public static bool operator !=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y || l.m_Z != r.Z); + !ReferenceEquals(r, null) && (l.X != r.X || l.Y != r.Y || l.Z != r.Z); - public static bool operator >(Point3D l, Point3D r) => l.m_X > r.m_X && l.m_Y > r.m_Y && l.m_Z > r.m_Z; + public static bool operator >(Point3D l, Point3D r) => l.X > r.X && l.Y > r.Y && l.Z > r.Z; public static bool operator >(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y && l.m_Z > r.Z; + !ReferenceEquals(r, null) && l.X > r.X && l.Y > r.Y && l.Z > r.Z; - public static bool operator <(Point3D l, Point3D r) => l.m_X < r.m_X && l.m_Y < r.m_Y && l.m_Z > r.m_Z; + public static bool operator <(Point3D l, Point3D r) => l.X < r.X && l.Y < r.Y && l.Z > r.Z; public static bool operator <(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y && l.m_Z > r.Z; + !ReferenceEquals(r, null) && l.X < r.X && l.Y < r.Y && l.Z > r.Z; - public static bool operator >=(Point3D l, Point3D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y && l.m_Z > r.m_Z; + public static bool operator >=(Point3D l, Point3D r) => l.X >= r.X && l.Y >= r.Y && l.Z > r.Z; public static bool operator >=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y && l.m_Z > r.Z; + !ReferenceEquals(r, null) && l.X >= r.X && l.Y >= r.Y && l.Z > r.Z; - public static bool operator <=(Point3D l, Point3D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y && l.m_Z > r.m_Z; + public static bool operator <=(Point3D l, Point3D r) => l.X <= r.X && l.Y <= r.Y && l.Z > r.Z; public static bool operator <=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y && l.m_Z > r.Z; + !ReferenceEquals(r, null) && l.X <= r.X && l.Y <= r.Y && l.Z > r.Z; public int CompareTo(Point3D other) { - var xComparison = m_X.CompareTo(other.m_X); + var xComparison = X.CompareTo(other.X); if (xComparison != 0) { return xComparison; } - var yComparison = m_Y.CompareTo(other.m_Y); + var yComparison = Y.CompareTo(other.Y); if (yComparison != 0) { return yComparison; } - return m_Z.CompareTo(other.m_Z); + return Z.CompareTo(other.Z); } public int CompareTo(IPoint3D other) { - var xComparison = m_X.CompareTo(other.X); + var xComparison = X.CompareTo(other.X); if (xComparison != 0) { return xComparison; } - var yComparison = m_Y.CompareTo(other.Y); + var yComparison = Y.CompareTo(other.Y); if (yComparison != 0) { return yComparison; } - return m_Z.CompareTo(other.Z); + return Z.CompareTo(other.Z); } public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) - => destination.TryWrite(provider, $"({m_X}, {m_Y}, {m_Z})", out charsWritten); + => destination.TryWrite(provider, $"({X}, {Y}, {Z})", out charsWritten); public override string ToString() { @@ -287,7 +264,4 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider provider, out result = new Point3D(x, y, z); return true; } - - - } diff --git a/src/Prima.UOData/Data/Geometry/Point3DList.cs b/src/Prima.UOData/Data/Geometry/Point3DList.cs index 1658670..f64f3c5 100644 --- a/src/Prima.UOData/Data/Geometry/Point3DList.cs +++ b/src/Prima.UOData/Data/Geometry/Point3DList.cs @@ -35,9 +35,9 @@ public void Add(int x, int y, int z) } } - m_List[Count].m_X = x; - m_List[Count].m_Y = y; - m_List[Count].m_Z = z; + m_List[Count].X = x; + m_List[Count].Y = y; + m_List[Count].Z = z; ++Count; } @@ -54,9 +54,9 @@ public void Add(Point3D p) } } - m_List[Count].m_X = p.m_X; - m_List[Count].m_Y = p.m_Y; - m_List[Count].m_Z = p.m_Z; + m_List[Count].X = p.X; + m_List[Count].Y = p.Y; + m_List[Count].Z = p.Z; ++Count; } diff --git a/src/Prima.UOData/Data/Geometry/Rectangle2D.cs b/src/Prima.UOData/Data/Geometry/Rectangle2D.cs index 2a679a8..7452abe 100644 --- a/src/Prima.UOData/Data/Geometry/Rectangle2D.cs +++ b/src/Prima.UOData/Data/Geometry/Rectangle2D.cs @@ -61,51 +61,51 @@ public Point2D End public int X { - get => _start.m_X; - set => _start.m_X = value; + get => _start.X; + set => _start.X = value; } public int Y { - get => _start.m_Y; - set => _start.m_Y = value; + get => _start.Y; + set => _start.Y = value; } public int Width { - get => _end.m_X - _start.m_X; - set => _end.m_X = _start.m_X + value; + get => _end.X - _start.X; + set => _end.X = _start.X + value; } public int Height { - get => _end.m_Y - _start.m_Y; - set => _end.m_Y = _start.m_Y + value; + get => _end.Y - _start.Y; + set => _end.Y = _start.Y + value; } public void MakeHold(Rectangle2D r) { - if (r._start.m_X < _start.m_X) + if (r._start.X < _start.X) { - _start.m_X = r._start.m_X; + _start.X = r._start.X; } - if (r._start.m_Y < _start.m_Y) + if (r._start.Y < _start.Y) { - _start.m_Y = r._start.m_Y; + _start.Y = r._start.Y; } - if (r._end.m_X > _end.m_X) + if (r._end.X > _end.X) { - _end.m_X = r._end.m_X; + _end.X = r._end.X; } - if (r._end.m_Y > _end.m_Y) + if (r._end.Y > _end.Y) { - _end.m_Y = r._end.m_Y; + _end.Y = r._end.Y; } } @@ -120,13 +120,13 @@ public void MakeHold(Rectangle2D r) public static bool operator !=(Rectangle2D l, Rectangle2D r) => l._start != r._start || l._end != r._end; public bool Contains(Point3D p) => - _start.m_X <= p.m_X && _start.m_Y <= p.m_Y && _end.m_X > p.m_X && _end.m_Y > p.m_Y; + _start.X <= p.X && _start.Y <= p.Y && _end.X > p.X && _end.Y > p.Y; public bool Contains(Point2D p) => - _start.m_X <= p.m_X && _start.m_Y <= p.m_Y && _end.m_X > p.m_X && _end.m_Y > p.m_Y; + _start.X <= p.X && _start.Y <= p.Y && _end.X > p.X && _end.Y > p.Y; public bool Contains(int x, int y) => - _start.m_X <= x && _start.m_Y <= y && _end.m_X > x && _end.m_Y > y; + _start.X <= x && _start.Y <= y && _end.X > x && _end.Y > y; public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) => destination.TryWrite(provider, $"({X}, {Y})+({Width}, {Height})", out charsWritten); diff --git a/src/Prima.UOData/Data/Geometry/Rectangle3D.cs b/src/Prima.UOData/Data/Geometry/Rectangle3D.cs index c126b2b..505b193 100644 --- a/src/Prima.UOData/Data/Geometry/Rectangle3D.cs +++ b/src/Prima.UOData/Data/Geometry/Rectangle3D.cs @@ -54,22 +54,22 @@ public Point3D End public int X { - get => _start.m_X; - set => _start.m_X = value; + get => _start.X; + set => _start.X = value; } public int Y { - get => _start.m_Y; - set => _start.m_Y = value; + get => _start.Y; + set => _start.Y = value; } public int Z { - get => _start.m_Z; - set => _start.m_Z = value; + get => _start.Z; + set => _start.Z = value; } @@ -93,64 +93,64 @@ public int Z public void MakeHold(Rectangle3D r) { - if (r._start.m_X < _start.m_X) + if (r._start.X < _start.X) { - _start.m_X = r._start.m_X; + _start.X = r._start.X; } - if (r._start.m_Y < _start.m_Y) + if (r._start.Y < _start.Y) { - _start.m_Y = r._start.m_Y; + _start.Y = r._start.Y; } - if (r._start.m_Z < _start.m_Z) + if (r._start.Z < _start.Z) { - _start.m_Z = r._start.m_Z; + _start.Z = r._start.Z; } - if (r._end.m_X > _end.m_X) + if (r._end.X > _end.X) { - _end.m_X = r._end.m_X; + _end.X = r._end.X; } - if (r._end.m_Y > _end.m_Y) + if (r._end.Y > _end.Y) { - _end.m_Y = r._end.m_Y; + _end.Y = r._end.Y; } - if (r._end.m_Z < _end.m_Z) + if (r._end.Z < _end.Z) { - _end.m_Z = r._end.m_Z; + _end.Z = r._end.Z; } } public bool Contains(Point3D p) => - p.m_X >= _start.m_X - && p.m_X < _end.m_X - && p.m_Y >= _start.m_Y - && p.m_Y < _end.m_Y - && p.m_Z >= _start.m_Z - && p.m_Z < _end.m_Z; + p.X >= _start.X + && p.X < _end.X + && p.Y >= _start.Y + && p.Y < _end.Y + && p.Z >= _start.Z + && p.Z < _end.Z; public bool Contains(Point2D p) => - p.m_X >= _start.m_X - && p.m_X < _end.m_X - && p.m_Y >= _start.m_Y - && p.m_Y < _end.m_Y; + p.X >= _start.X + && p.X < _end.X + && p.Y >= _start.Y + && p.Y < _end.Y; public bool Contains(IPoint2D p) => - p.X >= _start.m_X - && p.X < _end.m_X - && p.Y >= _start.m_Y - && p.Y < _end.m_Y; + p.X >= _start.X + && p.X < _end.X + && p.Y >= _start.Y + && p.Y < _end.Y; public bool Contains(IPoint3D p) => - p.X >= _start.m_X - && p.X < _end.m_X - && p.Y >= _start.m_Y - && p.Y < _end.m_Y - && p.Z >= _start.m_Z - && p.Z < _end.m_Z; + p.X >= _start.X + && p.X < _end.X + && p.Y >= _start.Y + && p.Y < _end.Y + && p.Z >= _start.Z + && p.Z < _end.Z; public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) => destination.TryWrite(provider, $"({X}, {Y}, {Z})+({Width}, {Height}, {Depth})", out charsWritten); diff --git a/src/Prima.UOData/Data/Tiles/MultiData.cs b/src/Prima.UOData/Data/Tiles/MultiData.cs new file mode 100644 index 0000000..04b772c --- /dev/null +++ b/src/Prima.UOData/Data/Tiles/MultiData.cs @@ -0,0 +1,717 @@ +using System.IO.Compression; +using Orion.Foundations.Buffers; +using Orion.Foundations.Spans; +using Prima.Core.Server.Compression; +using Prima.UOData.Data.Geometry; +using Prima.UOData.Mul; + +namespace Prima.UOData.Data.Tiles; + +public static class MultiData +{ + public static void Configure() + { + var multiUOPPath = UoFiles.GetFilePath("MultiCollection.uop"); + + if (File.Exists(multiUOPPath)) + { + LoadUOP(multiUOPPath); + return; + } + + // OSI Client 7.0.9.0+ uses 64bit tiledata flags + var postHSMulFormat = true; + + LoadMul(postHSMulFormat); + } + + private static readonly Dictionary _components = new(); + + + public static int Count => _components.Count; + + public static MultiComponentList GetComponents(int multiID) => + _components.TryGetValue(multiID & 0x3FFF, out var mcl) ? mcl : MultiComponentList.Empty; + + private static void LoadUOP(string path) + { + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + + // TODO: Find out if housing.bin is needed and read that. + var uopEntries = UOPFiles.ReadUOPIndexes(stream, ".bin", 0x10000, 4, 6); + + byte[] compressionBuffer = STArrayPool.Shared.Rent(0x10000); + var buffer = STArrayPool.Shared.Rent(0x10000); + + foreach (var (i, entry) in uopEntries) + { + stream.Seek(entry.Offset, SeekOrigin.Begin); + + Span data; + + if (entry.Compressed) + { + if (stream.Read(buffer.AsSpan( 0, entry.CompressedSize)) != entry.CompressedSize) + { + throw new FileLoadException($"Error loading file {stream.Name}."); + } + + var decompressedSize = entry.Size; + if (Deflate.Standard.Unpack(compressionBuffer, buffer, out var bytesDecompressed) != LibDeflateResult.Success + || decompressedSize != bytesDecompressed) + { + throw new FileLoadException($"Error loading file {stream.Name}. Failed to unpack entry {i}."); + } + + data = compressionBuffer.AsSpan(0, decompressedSize); + } + else + { + data = buffer.AsSpan(0, entry.Size); + } + + var tileList = new List(); + + var reader = new SpanReader(data); + + reader.Seek(4, SeekOrigin.Begin); // Skip the first 4 bytes + var count = reader.ReadUInt32LE(); + + for (uint t = 0; t < count; t++) + { + 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[i] = new MultiComponentList(tileList); + } + + STArrayPool.Shared.Return(buffer); + STArrayPool.Shared.Return(compressionBuffer); + } + + private static void LoadMul(bool postHSMulFormat) + { + var idxPath = UoFiles.GetFilePath("multi.idx"); + var mulPath = UoFiles.GetFilePath("multi.mul"); + + using var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var idxReader = new BinaryReader(idx); + + using var stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var bin = new BinaryReader(stream); + + 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 + + if (lookup < 0 || length <= 0) + { + continue; + } + + bin.BaseStream.Seek(lookup, SeekOrigin.Begin); + _components[i] = new MultiComponentList(bin, length, postHSMulFormat); + } + } +} + +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 sealed class MultiComponentList +{ + public static readonly MultiComponentList Empty = new(); + + private Point2D m_Min, m_Max; + + public MultiComponentList(MultiComponentList toCopy) + { + m_Min = toCopy.m_Min; + m_Max = toCopy.m_Max; + + Center = toCopy.Center; + + Width = toCopy.Width; + Height = toCopy.Height; + + Tiles = new StaticTile[Width][][]; + + for (var x = 0; x < Width; ++x) + { + Tiles[x] = new StaticTile[Height][]; + + for (var y = 0; y < Height; ++y) + { + 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]; + } + } + } + + List = new MultiTileEntry[toCopy.List.Length]; + + for (var i = 0; i < List.Length; ++i) + { + List[i] = toCopy.List[i]; + } + } + + // 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(); + // + // var length = reader.ReadInt(); + // + // var allTiles = List = new MultiTileEntry[length]; + // + // if (version == 0) + // { + // for (var i = 0; i < length; ++i) + // { + // 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(); + // } + // } + // else + // { + // 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(); + // } + // } + // + // 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) + // { + // tiles[x][y] = new TileList(); + // } + // } + // + // for (var i = 0; i < allTiles.Length; ++i) + // { + // if (i == 0 || allTiles[i].Flags != 0) + // { + // var xOffset = allTiles[i].OffsetX + Center.X; + // var yOffset = allTiles[i].OffsetY + Center.Y; + // + // tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); + // } + // } + // + // for (var x = 0; x < Width; ++x) + // { + // for (var y = 0; y < Height; ++y) + // { + // Tiles[x][y] = tiles[x][y].ToArray(); + // } + // } + // } + + 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 = postHSFormat ? (TileFlag)reader.ReadUInt64() : (TileFlag)reader.ReadUInt32(); + + var e = allTiles[i]; + + if (i == 0 || e.Flags != 0) + { + if (e.OffsetX < m_Min.X) + { + m_Min.X = e.OffsetX; + } + + if (e.OffsetY < m_Min.Y) + { + m_Min.Y = e.OffsetY; + } + + if (e.OffsetX > m_Max.X) + { + m_Max.X = e.OffsetX; + } + + if (e.OffsetY > m_Max.Y) + { + m_Max.Y = e.OffsetY; + } + } + } + + Center = new Point2D(-m_Min.X, -m_Min.Y); + Width = m_Max.X - m_Min.X + 1; + Height = m_Max.Y - m_Min.Y + 1; + + var tiles = new TileList[Width][]; + Tiles = new StaticTile[Width][][]; + + for (var i = 0; i < allTiles.Length; ++i) + { + if (i == 0 || allTiles[i].Flags != 0) + { + var xOffset = allTiles[i].OffsetX + Center.X; + var yOffset = allTiles[i].OffsetY + Center.Y; + + tiles[xOffset] ??= new TileList[Height]; + Tiles[xOffset] ??= new StaticTile[Height][]; + + tiles[xOffset][yOffset] ??= new TileList(); + tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); + } + } + + for (var x = 0; x < Width; ++x) + { + Tiles[x] ??= new StaticTile[Height][]; + for (var y = 0; y < Height; ++y) + { + var tileList = tiles[x]?[y]; + Tiles[x][y] = tileList?.ToArray() ?? Array.Empty(); + } + } + } + + public MultiComponentList(List list) + { + var allTiles = List = new MultiTileEntry[list.Count]; + + 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; + + allTiles[i].Flags = list[i].Flags; + + var e = allTiles[i]; + + if (i == 0 || e.Flags != 0) + { + if (e.OffsetX < m_Min.X) + { + m_Min.X = e.OffsetX; + } + + if (e.OffsetY < m_Min.Y) + { + m_Min.Y = e.OffsetY; + } + + if (e.OffsetX > m_Max.X) + { + m_Max.X = e.OffsetX; + } + + if (e.OffsetY > m_Max.Y) + { + m_Max.Y = e.OffsetY; + } + } + } + + Center = new Point2D(-m_Min.X, -m_Min.Y); + Width = m_Max.X - m_Min.X + 1; + Height = m_Max.Y - m_Min.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) + { + tiles[x][y] = new TileList(); + } + } + + for (var i = 0; i < allTiles.Length; ++i) + { + if (i == 0 || allTiles[i].Flags != 0) + { + var xOffset = allTiles[i].OffsetX + Center.X; + var yOffset = allTiles[i].OffsetY + Center.Y; + var itemID = (allTiles[i].ItemId & TileData.MaxItemValue) | 0x10000; + + tiles[xOffset][yOffset].Add((ushort)itemID, (sbyte)allTiles[i].OffsetZ); + } + } + + for (var x = 0; x < Width; ++x) + { + for (var y = 0; y < Height; ++y) + { + Tiles[x][y] = tiles[x][y].ToArray(); + } + } + } + + private MultiComponentList() + { + Tiles = Array.Empty(); + List = Array.Empty(); + } + + public Point2D Min => m_Min; + public Point2D Max => m_Max; + + public Point2D Center { get; } + + public int Width { get; private set; } + + public int Height { get; private set; } + + public StaticTile[][][] Tiles { get; private set; } + + public MultiTileEntry[] List { get; private set; } + + public void Add(int itemID, int x, int y, int z) + { + var vx = x + Center.X; + var vy = y + Center.Y; + + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + { + var oldTiles = Tiles[vx][vy]; + + for (var i = oldTiles.Length - 1; i >= 0; --i) + { + var data = TileData.ItemTable[itemID & TileData.MaxItemValue]; + + if (oldTiles[i].Z == z && oldTiles[i].Height > 0 == data.Height > 0) + { + 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); + } + } + } + + oldTiles = Tiles[vx][vy]; + + var newTiles = new StaticTile[oldTiles.Length + 1]; + Array.Copy(oldTiles, newTiles, oldTiles.Length); + + newTiles[^1] = new StaticTile((ushort)itemID, (sbyte)z); + + Tiles[vx][vy] = newTiles; + + var oldList = List; + var newList = new MultiTileEntry[oldList.Length + 1]; + + 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 + ); + + List = newList; + + if (x < m_Min.X) + { + m_Min.X = x; + } + + if (y < m_Min.Y) + { + m_Min.Y = y; + } + + if (x > m_Max.X) + { + m_Max.X = x; + } + + if (y > m_Max.Y) + { + m_Max.Y = y; + } + } + } + + public void RemoveXYZH(int x, int y, int z, int minHeight) + { + var vx = x + Center.X; + var vy = y + Center.Y; + + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + { + var oldTiles = Tiles[vx][vy]; + + for (var i = 0; i < oldTiles.Length; ++i) + { + var tile = oldTiles[i]; + + if (tile.Z == z && tile.Height >= minHeight) + { + var newTiles = new StaticTile[oldTiles.Length - 1]; + Array.Copy(oldTiles, newTiles, i); + Array.Copy(oldTiles, i + 1, newTiles, i, oldTiles.Length - i - 1); + + Tiles[vx][vy] = newTiles; + + break; + } + } + + var oldList = List; + + 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 newList = new MultiTileEntry[oldList.Length - 1]; + Array.Copy(oldList, newList, i); + Array.Copy(oldList, i + 1, newList, i, oldList.Length - i - 1); + + List = newList; + + break; + } + } + } + } + + public void Remove(int itemID, int x, int y, int z) + { + var vx = x + Center.X; + var vy = y + Center.Y; + + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + { + var oldTiles = Tiles[vx][vy]; + + for (var i = 0; i < oldTiles.Length; ++i) + { + var tile = oldTiles[i]; + + if (tile.ID == itemID && tile.Z == z) + { + var newTiles = new StaticTile[oldTiles.Length - 1]; + Array.Copy(oldTiles, newTiles, i); + Array.Copy(oldTiles, i + 1, newTiles, i, oldTiles.Length - i - 1); + + Tiles[vx][vy] = newTiles; + + break; + } + } + + var oldList = List; + + for (var i = 0; i < oldList.Length; ++i) + { + var tile = oldList[i]; + + if (tile.ItemId == itemID && tile.OffsetX == (short)x && tile.OffsetY == (short)y && + tile.OffsetZ == (short)z) + { + var newList = new MultiTileEntry[oldList.Length - 1]; + Array.Copy(oldList, newList, i); + Array.Copy(oldList, i + 1, newList, i, oldList.Length - i - 1); + + List = newList; + + break; + } + } + } + } + + public void Resize(int newWidth, int newHeight) + { + int oldWidth = Width, oldHeight = Height; + var oldTiles = Tiles; + + var totalLength = 0; + + var newTiles = new StaticTile[newWidth][][]; + + for (var x = 0; x < newWidth; ++x) + { + newTiles[x] = new StaticTile[newHeight][]; + + for (var y = 0; y < newHeight; ++y) + { + if (x < oldWidth && y < oldHeight) + { + 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; + + m_Min = Point2D.Zero; + m_Max = Point2D.Zero; + + var index = 0; + + for (var x = 0; x < newWidth; ++x) + { + for (var y = 0; y < newHeight; ++y) + { + var tiles = newTiles[x][y]; + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + + var vx = x - Center.X; + var vy = y - Center.Y; + + if (vx < m_Min.X) + { + m_Min.X = vx; + } + + if (vy < m_Min.Y) + { + m_Min.Y = vy; + } + + if (vx > m_Max.X) + { + m_Max.X = vx; + } + + if (vy > m_Max.Y) + { + m_Max.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; + // + // writer.Write(m_Min); + // writer.Write(m_Max); + // writer.Write(Center); + // + // writer.Write(Width); + // writer.Write(Height); + // + // writer.Write(List.Length); + // + // 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); + // } + // } +} diff --git a/src/Prima.UOData/Entities/Base/BaseWorldEntity.cs b/src/Prima.UOData/Entities/Base/BaseWorldEntity.cs index a85c141..ef2b432 100644 --- a/src/Prima.UOData/Entities/Base/BaseWorldEntity.cs +++ b/src/Prima.UOData/Entities/Base/BaseWorldEntity.cs @@ -10,10 +10,12 @@ namespace Prima.UOData.Entities.Base; public class BaseWorldEntity : IHaveSerial, IEntity, ISerializableEntity, INotifyPropertyChanged { - +#pragma warning disable 67 public event PropertyChangedEventHandler? PropertyChanged; - public Serial Id { get; set; } +#pragma warning restore 67 + + public Serial Id { get; set; } public Point3D Location { get; set; } public int MapIndex { get; set; } diff --git a/src/Prima.UOData/Services/ClientConfigurationService.cs b/src/Prima.UOData/Services/ClientConfigurationService.cs index 1498a24..e487dc4 100644 --- a/src/Prima.UOData/Services/ClientConfigurationService.cs +++ b/src/Prima.UOData/Services/ClientConfigurationService.cs @@ -1,4 +1,5 @@ using System.Buffers.Binary; +using System.Diagnostics; using Microsoft.Extensions.Logging; using Orion.Core.Server.Data.Directories; using Orion.Core.Server.Events.Server; @@ -13,6 +14,7 @@ using Prima.Core.Server.Types.Uo; using Prima.UOData.Context; using Prima.UOData.Data; +using Prima.UOData.Data.Tiles; using Prima.UOData.Interfaces.Services; using Prima.UOData.Mul; using Prima.UOData.Types; @@ -49,6 +51,7 @@ DirectoriesConfig directoriesConfig private async Task LoadData() { + var startTime = Stopwatch.GetTimestamp(); UoFiles.ScanForFiles(_primaServerConfig.Shard.UoDirectory); await GetClientVersionAsync(); @@ -64,11 +67,16 @@ private async Task LoadData() _logger.LogInformation("Found {Count} LandTiles", TileData.LandTable.Length); RaceDefinitions.Configure(); + + MultiData.Configure(); + + _logger.LogInformation("Found {Count} MultiDefs", MultiData.Count); + + _logger.LogInformation("Loading took {Elapsed}ms", Stopwatch.GetElapsedTime(startTime)); } public async Task HandleAsync(ServerStartedEvent @event, CancellationToken cancellationToken) { - } private async Task GetExpansionAsync() diff --git a/src/Prima.UOData/Utils/Utility.cs b/src/Prima.UOData/Utils/Utility.cs index f3e6d76..bd87d7e 100644 --- a/src/Prima.UOData/Utils/Utility.cs +++ b/src/Prima.UOData/Utils/Utility.cs @@ -263,19 +263,19 @@ public static object GetArrayCap(Array array, int index, object emptyValue = nul public static void FixPoints(ref Point3D top, ref Point3D bottom) { - if (bottom.m_X < top.m_X) + if (bottom.X < top.X) { - (top.m_X, bottom.m_X) = (bottom.m_X, top.m_X); + (top.X, bottom.X) = (bottom.X, top.X); } - if (bottom.m_Y < top.m_Y) + if (bottom.Y < top.Y) { - (top.m_Y, bottom.m_Y) = (bottom.m_Y, top.m_Y); + (top.Y, bottom.Y) = (bottom.Y, top.Y); } - if (bottom.m_Z < top.m_Z) + if (bottom.Z < top.Z) { - (top.m_Z, bottom.m_Z) = (bottom.m_Z, top.m_Z); + (top.Z, bottom.Z) = (bottom.Z, top.Z); } }