From 14b63ca48e392ad5cfd2c3ca0a5cc21084e80f59 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Tue, 22 Mar 2022 20:07:32 -0700 Subject: [PATCH] fix: Updates ArrayPool to STArrayPool for performance. (#968) --- .../Server.Tests/Tests/Network/PipeTests.cs | 10 +- .../{Collections => Buffers}/STArrayPool.cs | 0 Projects/Server/Buffers/SpanWriter.cs | 752 ++--- Projects/Server/Buffers/ValueStringBuilder.cs | 916 +++--- .../Collections/PooledOrderedHashSet.cs | 943 +++--- Projects/Server/Maps/Map.cs | 2807 ++++++++--------- .../Network/Packets/OutgoingGumpPackets.cs | 7 +- .../Network/Packets/PacketContainerBuilder.cs | 10 +- Projects/Server/Text/StringHelpers.cs | 465 ++- .../Monsters/Misc/Melee/BladeSpirits.cs | 199 +- .../Monsters/Misc/Melee/EnergyVortex.cs | 219 +- 11 files changed, 3163 insertions(+), 3165 deletions(-) rename Projects/Server/{Collections => Buffers}/STArrayPool.cs (100%) diff --git a/Projects/Server.Tests/Tests/Network/PipeTests.cs b/Projects/Server.Tests/Tests/Network/PipeTests.cs index ab4556dc6..31b9baaa3 100644 --- a/Projects/Server.Tests/Tests/Network/PipeTests.cs +++ b/Projects/Server.Tests/Tests/Network/PipeTests.cs @@ -11,7 +11,7 @@ namespace Server.Tests.Network { private async void DelayedExecute(Action action) { - await Task.Delay(5); + await Task.Delay(1); action(); } @@ -130,8 +130,12 @@ namespace Server.Tests.Network continue; } - result.CopyFrom(new[] { expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, - expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value }); + result.CopyFrom(new[] { + expected_value, expected_value, expected_value, expected_value, + expected_value, expected_value, expected_value, expected_value, + expected_value, expected_value, expected_value, expected_value, + expected_value, expected_value, expected_value, expected_value + }); writer.Advance(16); count += 16; diff --git a/Projects/Server/Collections/STArrayPool.cs b/Projects/Server/Buffers/STArrayPool.cs similarity index 100% rename from Projects/Server/Collections/STArrayPool.cs rename to Projects/Server/Buffers/STArrayPool.cs diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs index 607e226a2..89981cc5e 100644 --- a/Projects/Server/Buffers/SpanWriter.cs +++ b/Projects/Server/Buffers/SpanWriter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SpanWriter.cs * * * @@ -22,40 +22,41 @@ using System.Runtime.InteropServices; using System.Text; using Microsoft.Toolkit.HighPerformance; using Server; +using Server.Buffers; using Server.Text; -namespace System.Buffers +namespace System.Buffers; + +public ref struct SpanWriter { - public ref struct SpanWriter + private readonly bool _resize; + private byte[] _arrayToReturnToPool; + private Span _buffer; + private int _position; + + public int BytesWritten { get; private set; } + + public int Position { - private readonly bool _resize; - private byte[] _arrayToReturnToPool; - private Span _buffer; - private int _position; - - public int BytesWritten { get; private set; } - - public int Position + get => _position; + private set { - get => _position; - private set - { - _position = value; + _position = value; - if (value > BytesWritten) - { - BytesWritten = value; - } + if (value > BytesWritten) + { + BytesWritten = value; } } + } - public int Capacity => _buffer.Length; + public int Capacity => _buffer.Length; - public ReadOnlySpan Span => _buffer[..Position]; + public ReadOnlySpan Span => _buffer[..Position]; - public Span RawBuffer => _buffer; + public Span RawBuffer => _buffer; - /** + /** * Converts the writer to a Span using a SpanOwner. * If the buffer was stackalloc, it will be copied to a rented buffer. * Otherwise the existing rented buffer is used. @@ -64,395 +65,394 @@ namespace System.Buffers * Do not use the SpanWriter after calling this method. * This method will effectively dispose of the SpanWriter and is therefore considered terminal. */ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SpanOwner ToSpan() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SpanOwner ToSpan() + { + var toReturn = _arrayToReturnToPool; + + SpanOwner apo; + if (_position == 0) { - var toReturn = _arrayToReturnToPool; - - SpanOwner apo; - if (_position == 0) - { - apo = new SpanOwner(_position, Array.Empty()); - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } - } - else if (toReturn != null) - { - apo = new SpanOwner(_position, toReturn); - } - else - { - var buffer = ArrayPool.Shared.Rent(_position); - _buffer.CopyTo(buffer); - apo = new SpanOwner(_position, buffer); - } - - this = default; // Don't allow two references to the same buffer - return apo; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SpanWriter(Span initialBuffer, bool resize = false) - { - _resize = resize; - _buffer = initialBuffer; - _position = 0; - BytesWritten = 0; - _arrayToReturnToPool = null; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SpanWriter(int initialCapacity, bool resize = false) - { - _resize = resize; - _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); - _buffer = _arrayToReturnToPool; - _position = 0; - BytesWritten = 0; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void Grow(int additionalCapacity) - { - var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2); - byte[] poolArray = ArrayPool.Shared.Rent(newSize); - - _buffer[..BytesWritten].CopyTo(poolArray); - - byte[] toReturn = _arrayToReturnToPool; - _buffer = _arrayToReturnToPool = poolArray; + apo = new SpanOwner(_position, Array.Empty()); if (toReturn != null) { - ArrayPool.Shared.Return(toReturn); + STArrayPool.Shared.Return(toReturn); } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void GrowIfNeeded(int count) + else if (toReturn != null) { - if (_position + count > _buffer.Length) + apo = new SpanOwner(_position, toReturn); + } + else + { + var buffer = STArrayPool.Shared.Rent(_position); + _buffer.CopyTo(buffer); + apo = new SpanOwner(_position, buffer); + } + + this = default; // Don't allow two references to the same buffer + return apo; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SpanWriter(Span initialBuffer, bool resize = false) + { + _resize = resize; + _buffer = initialBuffer; + _position = 0; + BytesWritten = 0; + _arrayToReturnToPool = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SpanWriter(int initialCapacity, bool resize = false) + { + _resize = resize; + _arrayToReturnToPool = STArrayPool.Shared.Rent(initialCapacity); + _buffer = _arrayToReturnToPool; + _position = 0; + BytesWritten = 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacity) + { + var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2); + byte[] poolArray = STArrayPool.Shared.Rent(newSize); + + _buffer[..BytesWritten].CopyTo(poolArray); + + byte[] toReturn = _arrayToReturnToPool; + _buffer = _arrayToReturnToPool = poolArray; + if (toReturn != null) + { + STArrayPool.Shared.Return(toReturn); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GrowIfNeeded(int count) + { + if (_position + count > _buffer.Length) + { + if (!_resize) { - if (!_resize) - { - throw new OutOfMemoryException(); - } - - Grow(count); - } - } - - public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(_buffer); - - public void EnsureCapacity(int capacity) - { - if (capacity > _buffer.Length) - { - if (!_resize) - { - throw new OutOfMemoryException(); - } - - Grow(capacity - BytesWritten); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void Write(bool value) - { - GrowIfNeeded(1); - _buffer[Position++] = *(byte*)&value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(byte value) - { - GrowIfNeeded(1); - _buffer[Position++] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(sbyte value) - { - GrowIfNeeded(1); - _buffer[Position++] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(short value) - { - GrowIfNeeded(2); - BinaryPrimitives.WriteInt16BigEndian(_buffer[_position..], value); - Position += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLE(short value) - { - GrowIfNeeded(2); - BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value); - Position += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ushort value) - { - GrowIfNeeded(2); - BinaryPrimitives.WriteUInt16BigEndian(_buffer[_position..], value); - Position += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLE(ushort value) - { - GrowIfNeeded(2); - BinaryPrimitives.WriteUInt16LittleEndian(_buffer[_position..], value); - Position += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(int value) - { - GrowIfNeeded(4); - BinaryPrimitives.WriteInt32BigEndian(_buffer[_position..], value); - Position += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLE(int value) - { - GrowIfNeeded(4); - BinaryPrimitives.WriteInt32LittleEndian(_buffer[_position..], value); - Position += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(uint value) - { - GrowIfNeeded(4); - BinaryPrimitives.WriteUInt32BigEndian(_buffer[_position..], value); - Position += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(Serial serial) => Write(serial.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLE(uint value) - { - GrowIfNeeded(4); - BinaryPrimitives.WriteUInt32LittleEndian(_buffer[_position..], value); - Position += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(long value) - { - GrowIfNeeded(8); - BinaryPrimitives.WriteInt64BigEndian(_buffer[_position..], value); - Position += 8; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ulong value) - { - GrowIfNeeded(8); - BinaryPrimitives.WriteUInt64BigEndian(_buffer[_position..], value); - Position += 8; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ReadOnlySpan buffer) - { - var count = buffer.Length; - GrowIfNeeded(count); - buffer.CopyTo(_buffer[_position..]); - Position += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteAscii(char chr) => Write((byte)chr); - - public void WriteString(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable - { - int sizeT = Unsafe.SizeOf(); - - if (sizeT > 2) - { - throw new InvalidConstraintException("WriteString only accepts byte, sbyte, char, short, and ushort as a constraint"); + throw new OutOfMemoryException(); } - value ??= string.Empty; + Grow(count); + } + } - var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length); - var src = value.AsSpan(0, charLength); + public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(_buffer); - var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value); - if (byteCount == 0) + public void EnsureCapacity(int capacity) + { + if (capacity > _buffer.Length) + { + if (!_resize) { - return; + throw new OutOfMemoryException(); } - GrowIfNeeded(byteCount); + Grow(capacity - BytesWritten); + } + } - var bytesWritten = encoding.GetBytes(src, _buffer[_position..]); - Position += bytesWritten; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Write(bool value) + { + GrowIfNeeded(1); + _buffer[Position++] = *(byte*)&value; + } - if (fixedLength > -1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(byte value) + { + GrowIfNeeded(1); + _buffer[Position++] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(sbyte value) + { + GrowIfNeeded(1); + _buffer[Position++] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(short value) + { + GrowIfNeeded(2); + BinaryPrimitives.WriteInt16BigEndian(_buffer[_position..], value); + Position += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(short value) + { + GrowIfNeeded(2); + BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value); + Position += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ushort value) + { + GrowIfNeeded(2); + BinaryPrimitives.WriteUInt16BigEndian(_buffer[_position..], value); + Position += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(ushort value) + { + GrowIfNeeded(2); + BinaryPrimitives.WriteUInt16LittleEndian(_buffer[_position..], value); + Position += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int value) + { + GrowIfNeeded(4); + BinaryPrimitives.WriteInt32BigEndian(_buffer[_position..], value); + Position += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(int value) + { + GrowIfNeeded(4); + BinaryPrimitives.WriteInt32LittleEndian(_buffer[_position..], value); + Position += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(uint value) + { + GrowIfNeeded(4); + BinaryPrimitives.WriteUInt32BigEndian(_buffer[_position..], value); + Position += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(Serial serial) => Write(serial.Value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(uint value) + { + GrowIfNeeded(4); + BinaryPrimitives.WriteUInt32LittleEndian(_buffer[_position..], value); + Position += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(long value) + { + GrowIfNeeded(8); + BinaryPrimitives.WriteInt64BigEndian(_buffer[_position..], value); + Position += 8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ulong value) + { + GrowIfNeeded(8); + BinaryPrimitives.WriteUInt64BigEndian(_buffer[_position..], value); + Position += 8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan buffer) + { + var count = buffer.Length; + GrowIfNeeded(count); + buffer.CopyTo(_buffer[_position..]); + Position += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAscii(char chr) => Write((byte)chr); + + public void WriteString(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable + { + int sizeT = Unsafe.SizeOf(); + + if (sizeT > 2) + { + throw new InvalidConstraintException("WriteString only accepts byte, sbyte, char, short, and ushort as a constraint"); + } + + value ??= string.Empty; + + var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length); + var src = value.AsSpan(0, charLength); + + var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value); + if (byteCount == 0) + { + return; + } + + GrowIfNeeded(byteCount); + + var bytesWritten = encoding.GetBytes(src, _buffer[_position..]); + Position += bytesWritten; + + if (fixedLength > -1) + { + var extra = fixedLength * sizeT - bytesWritten; + if (extra > 0) { - var extra = fixedLength * sizeT - bytesWritten; - if (extra > 0) - { - Clear(extra); - } + Clear(extra); } } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLittleUni(string value) => WriteString(value, TextEncoding.UnicodeLE); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUni(string value) => WriteString(value, TextEncoding.UnicodeLE); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLittleUniNull(string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUniNull(string value) + { + WriteString(value, TextEncoding.UnicodeLE); + Write((ushort)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUni(string value, int fixedLength) => WriteString(value, TextEncoding.UnicodeLE, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUni(string value) => WriteString(value, TextEncoding.Unicode); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUniNull(string value) + { + WriteString(value, TextEncoding.Unicode); + Write((ushort)0); // '\0' + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUni(string value, int fixedLength) => WriteString(value, TextEncoding.Unicode, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8(string value) => WriteString(value, TextEncoding.UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8Null(string value) + { + WriteString(value, TextEncoding.UTF8); + Write((byte)0); // '\0' + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAscii(string value) => WriteString(value, Encoding.ASCII); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAsciiNull(string value) + { + WriteString(value, Encoding.ASCII); + Write((byte)0); // '\0' + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAscii(string value, int fixedLength) => WriteString(value, Encoding.ASCII, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int count) + { + GrowIfNeeded(count); + _buffer.Slice(_position, count).Clear(); + Position += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Seek(int offset, SeekOrigin origin) + { + Debug.Assert( + origin != SeekOrigin.End || _resize || offset <= 0, + "Attempting to seek to a position beyond capacity using SeekOrigin.End without resize" + ); + + Debug.Assert( + origin != SeekOrigin.End || offset >= -_buffer.Length, + + "Attempting to seek to a negative position using SeekOrigin.End" + ); + + Debug.Assert( + origin != SeekOrigin.Begin || offset >= 0, + "Attempting to seek to a negative position using SeekOrigin.Begin" + ); + + Debug.Assert( + origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length, + "Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize" + ); + + Debug.Assert( + origin != SeekOrigin.Current || _position + offset >= 0, + "Attempting to seek to a negative position using SeekOrigin.Current" + ); + + Debug.Assert( + origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length, + "Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize" + ); + + var newPosition = Math.Max(0, origin switch { - WriteString(value, TextEncoding.UnicodeLE); - Write((ushort)0); + SeekOrigin.Current => _position + offset, + SeekOrigin.End => BytesWritten + offset, + _ => offset // Begin + }); + + if (newPosition >= _buffer.Length) + { + Grow(newPosition - _buffer.Length + 1); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLittleUni(string value, int fixedLength) => WriteString(value, TextEncoding.UnicodeLE, fixedLength); + return Position = newPosition; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteBigUni(string value) => WriteString(value, TextEncoding.Unicode); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteBigUniNull(string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + byte[] toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) { - WriteString(value, TextEncoding.Unicode); - Write((ushort)0); // '\0' + STArrayPool.Shared.Return(toReturn); + } + } + + public struct SpanOwner : IDisposable + { + private readonly int _length; + private readonly byte[] _arrayToReturnToPool; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal SpanOwner(int length, byte[] buffer) + { + _length = length; + _arrayToReturnToPool = buffer; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteBigUni(string value, int fixedLength) => WriteString(value, TextEncoding.Unicode, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUTF8(string value) => WriteString(value, TextEncoding.UTF8); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUTF8Null(string value) + public Span Span { - WriteString(value, TextEncoding.UTF8); - Write((byte)0); // '\0' - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteAscii(string value) => WriteString(value, Encoding.ASCII); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteAsciiNull(string value) - { - WriteString(value, Encoding.ASCII); - Write((byte)0); // '\0' - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteAscii(string value, int fixedLength) => WriteString(value, Encoding.ASCII, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(int count) - { - GrowIfNeeded(count); - _buffer.Slice(_position, count).Clear(); - Position += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Seek(int offset, SeekOrigin origin) - { - Debug.Assert( - origin != SeekOrigin.End || _resize || offset <= 0, - "Attempting to seek to a position beyond capacity using SeekOrigin.End without resize" - ); - - Debug.Assert( - origin != SeekOrigin.End || offset >= -_buffer.Length, - - "Attempting to seek to a negative position using SeekOrigin.End" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Begin" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize" - ); - - Debug.Assert( - origin != SeekOrigin.Current || _position + offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Current" - ); - - Debug.Assert( - origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize" - ); - - var newPosition = Math.Max(0, origin switch - { - SeekOrigin.Current => _position + offset, - SeekOrigin.End => BytesWritten + offset, - _ => offset // Begin - }); - - if (newPosition >= _buffer.Length) - { - Grow(newPosition - _buffer.Length + 1); - } - - return Position = newPosition; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => MemoryMarshal.CreateSpan(ref _arrayToReturnToPool.DangerousGetReference(), _length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { byte[] toReturn = _arrayToReturnToPool; - this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again - if (toReturn != null) + this = default; + if (_length > 0) { - ArrayPool.Shared.Return(toReturn); - } - } - - public struct SpanOwner : IDisposable - { - private readonly int _length; - private readonly byte[] _arrayToReturnToPool; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal SpanOwner(int length, byte[] buffer) - { - _length = length; - _arrayToReturnToPool = buffer; - } - - public Span Span - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => MemoryMarshal.CreateSpan(ref _arrayToReturnToPool.DangerousGetReference(), _length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - byte[] toReturn = _arrayToReturnToPool; - this = default; - if (_length > 0) - { - ArrayPool.Shared.Return(toReturn); - } + STArrayPool.Shared.Return(toReturn); } } } diff --git a/Projects/Server/Buffers/ValueStringBuilder.cs b/Projects/Server/Buffers/ValueStringBuilder.cs index 1beda97f8..316c2960e 100644 --- a/Projects/Server/Buffers/ValueStringBuilder.cs +++ b/Projects/Server/Buffers/ValueStringBuilder.cs @@ -2,473 +2,471 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Buffers; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace Server.Buffers +namespace Server.Buffers; + +public ref struct ValueStringBuilder { - public ref struct ValueStringBuilder + private char[] _arrayToReturnToPool; + private Span _chars; + private int _length; + + // If this ctor is used, you cannot pass in stackalloc ROS for append/replace. + public ValueStringBuilder(ReadOnlySpan initialString) : this(initialString.Length) { - private char[] _arrayToReturnToPool; - private Span _chars; - private int _length; + Append(initialString); + } - // If this ctor is used, you cannot pass in stackalloc ROS for append/replace. - public ValueStringBuilder(ReadOnlySpan initialString) : this(initialString.Length) + public ValueStringBuilder(ReadOnlySpan initialString, Span initialBuffer) : this(initialBuffer) + { + Append(initialString); + } + + public ValueStringBuilder(Span initialBuffer) + { + _arrayToReturnToPool = null; + _chars = initialBuffer; + _length = 0; + } + + // If this ctor is used, you cannot pass in stackalloc ROS for append/replace. + public ValueStringBuilder(int initialCapacity) + { + _arrayToReturnToPool = STArrayPool.Shared.Rent(initialCapacity); + _chars = _arrayToReturnToPool; + _length = 0; + } + + public int Length => _length; + + public int Capacity => _chars.Length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + _length = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EnsureCapacity(int capacity) + { + if (capacity > _chars.Length) { - Append(initialString); - } - - public ValueStringBuilder(ReadOnlySpan initialString, Span initialBuffer) : this(initialBuffer) - { - Append(initialString); - } - - public ValueStringBuilder(Span initialBuffer) - { - _arrayToReturnToPool = null; - _chars = initialBuffer; - _length = 0; - } - - // If this ctor is used, you cannot pass in stackalloc ROS for append/replace. - public ValueStringBuilder(int initialCapacity) - { - _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); - _chars = _arrayToReturnToPool; - _length = 0; - } - - public int Length => _length; - - public int Capacity => _chars.Length; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset() - { - _length = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EnsureCapacity(int capacity) - { - if (capacity > _chars.Length) - { - Grow(capacity - Length); - } - } - - /// - /// Get a pinnable reference to the builder. - /// Does not ensure there is a null char after - /// This overload is pattern matched in the C# 7.3+ compiler so you can omit - /// the explicit method call, and write eg "fixed (char* c = builder)" - /// - public ref char GetPinnableReference() => ref MemoryMarshal.GetReference(_chars); - - /// - /// Get a pinnable reference to the builder. - /// - /// Ensures that the builder has a null char after - public ref char GetPinnableReference(bool terminate) - { - if (terminate) - { - EnsureCapacity(_length + 1); - _chars[_length] = '\0'; - } - return ref MemoryMarshal.GetReference(_chars); - } - - public ref char this[int index] => ref _chars[index]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override string ToString() => _chars[.._length].ToString(); - - /// Returns the underlying storage of the builder. - public Span RawChars => _chars; - - /// - /// Returns a span around the contents of the builder. - /// - /// Ensures that the builder has a null char after - public ReadOnlySpan AsSpan(bool terminate) - { - if (terminate) - { - EnsureCapacity(_length + 1); - _chars[_length] = '\0'; - } - return _chars[.._length]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan AsSpan() => _chars[.._length]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan AsSpan(int start) => _chars[start..]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryCopyTo(Span destination, out int charsWritten) - { - if (_chars[.._length].TryCopyTo(destination)) - { - charsWritten = _length; - return true; - } - - charsWritten = 0; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(int index, char value, int count) - { - if (_length > _chars.Length - count) - { - Grow(count); - } - - int remaining = _length - index; - _chars.Slice(index, remaining).CopyTo(_chars[(index + count)..]); - _chars.Slice(index, count).Fill(value); - _length += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(int index, string s) - { - if (s == null) - { - return; - } - - int count = s.Length; - - if (_length > _chars.Length - count) - { - Grow(count); - } - - int remaining = _length - index; - _chars.Slice(index, remaining).CopyTo(_chars[(index + count)..]); - s.AsSpan().CopyTo(_chars[index..]); - _length += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(char c) - { - int pos = _length; - if ((uint)pos < (uint)_chars.Length) - { - _chars[pos] = c; - _length = pos + 1; - } - else - { - GrowAndAppend(c); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(int value, NumberFormatInfo info = null) - { - if (value >= 0) - { - Append((uint)value); - return; - } - - Append((info ?? NumberFormatInfo.CurrentInfo).NegativeSign); - Append((uint)-value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void Append(uint value) - { - int bufferLength = value.CountDigits(); - - int pos = _length; - if ((uint)pos + (uint)bufferLength >= _chars.Length) - { - Grow(bufferLength); - } - - if (bufferLength == 1) - { - _chars[pos] = (char)(value + '0'); - _length = pos + 1; - return; - } - - fixed (char* buffer = _chars[pos..]) - { - char* p = buffer + bufferLength; - do - { - value = Utility.DivRem(value, 10, out uint remainder); - *--p = (char)(remainder + '0'); - } while (value != 0); - } - - _length = pos + bufferLength; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(string s) - { - if (s == null) - { - return; - } - - int pos = _length; - if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. - { - _chars[pos] = s[0]; - _length = pos + 1; - } - else - { - AppendSlow(s); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AppendLine(string s) - { - if (s == null) - { - return; - } - - // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. - if (s.Length == 1) - { - Append(s[0]); - } - else - { - AppendSlow(s); - } - - Append(Environment.NewLine); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AppendSlow(string s) - { - int pos = _length; - if (pos > _chars.Length - s.Length) - { - Grow(s.Length); - } - - s.AsSpan().CopyTo(_chars[pos..]); - _length += s.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(char c, int count) - { - if (_length > _chars.Length - count) - { - Grow(count); - } - - Span dst = _chars.Slice(_length, count); - for (int i = 0; i < dst.Length; i++) - { - dst[i] = c; - } - _length += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void Append(char* value, int length) - { - int pos = _length; - if (pos > _chars.Length - length) - { - Grow(length); - } - - Span dst = _chars.Slice(_length, length); - for (int i = 0; i < dst.Length; i++) - { - dst[i] = *value++; - } - _length += length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(ReadOnlySpan value) - { - int pos = _length; - if (pos > _chars.Length - value.Length) - { - Grow(value.Length); - } - - value.CopyTo(_chars[_length..]); - _length += value.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AppendSpan(int length) - { - int origPos = _length; - if (origPos > _chars.Length - length) - { - Grow(length); - } - - _length = origPos + length; - return _chars.Slice(origPos, length); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void GrowAndAppend(char c) - { - Grow(1); - Append(c); - } - -#nullable enable - /// - /// Resize the internal buffer either by doubling current buffer size or - /// by adding to - /// whichever is greater. - /// - /// - /// Number of chars requested beyond current position. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private void Grow(int additionalCapacityBeyondPos) - { - char[] poolArray = ArrayPool.Shared.Rent(Math.Max(_length + additionalCapacityBeyondPos, _chars.Length * 2)); - - _chars[.._length].CopyTo(poolArray); - - char[] toReturn = _arrayToReturnToPool; - _chars = _arrayToReturnToPool = poolArray; - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - char[] toReturn = _arrayToReturnToPool; - this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } - } -#nullable restore - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ReplaceAny(ReadOnlySpan oldChars, ReadOnlySpan newChars, int startIndex, int count) - { - int currentLength = _length; - if ((uint)startIndex > (uint)currentLength) - { - throw new ArgumentOutOfRangeException(nameof(startIndex)); - } - - if (count < 0 || startIndex > currentLength - count) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - var slice = _chars; - - while (true) - { - var indexOf = slice.IndexOfAny(oldChars); - if (indexOf == -1) - { - break; - } - - var chr = slice[indexOf]; - - slice[indexOf] = newChars[oldChars.IndexOf(chr)]; - slice = slice[(indexOf + 1)..]; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Replace(char oldChar, char newChar, int startIndex, int count) - { - int currentLength = _length; - if ((uint)startIndex > (uint)currentLength) - { - throw new ArgumentOutOfRangeException(nameof(startIndex)); - } - - if (count < 0 || startIndex > currentLength - count) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - var slice = _chars; - - while (true) - { - var indexOf = slice.IndexOf(oldChar); - if (indexOf == -1) - { - break; - } - - slice[indexOf] = newChar; - slice = slice[(indexOf + 1)..]; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Remove(int startIndex, int length) - { - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - if (startIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(startIndex)); - } - - if (length > _length - startIndex) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - if (startIndex == 0) - { - _chars = _chars[length..]; - } - else if (startIndex + length == _length) - { - _chars = _chars[..startIndex]; - } - else - { - // Somewhere in the middle, this will be slow - _chars[(startIndex + length)..].CopyTo(_chars[startIndex..]); - } - - _length -= length; + Grow(capacity - Length); } } + + /// + /// Get a pinnable reference to the builder. + /// Does not ensure there is a null char after + /// This overload is pattern matched in the C# 7.3+ compiler so you can omit + /// the explicit method call, and write eg "fixed (char* c = builder)" + /// + public ref char GetPinnableReference() => ref MemoryMarshal.GetReference(_chars); + + /// + /// Get a pinnable reference to the builder. + /// + /// Ensures that the builder has a null char after + public ref char GetPinnableReference(bool terminate) + { + if (terminate) + { + EnsureCapacity(_length + 1); + _chars[_length] = '\0'; + } + return ref MemoryMarshal.GetReference(_chars); + } + + public ref char this[int index] => ref _chars[index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() => _chars[.._length].ToString(); + + /// Returns the underlying storage of the builder. + public Span RawChars => _chars; + + /// + /// Returns a span around the contents of the builder. + /// + /// Ensures that the builder has a null char after + public ReadOnlySpan AsSpan(bool terminate) + { + if (terminate) + { + EnsureCapacity(_length + 1); + _chars[_length] = '\0'; + } + return _chars[.._length]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan AsSpan() => _chars[.._length]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan AsSpan(int start) => _chars[start..]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryCopyTo(Span destination, out int charsWritten) + { + if (_chars[.._length].TryCopyTo(destination)) + { + charsWritten = _length; + return true; + } + + charsWritten = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Insert(int index, char value, int count) + { + if (_length > _chars.Length - count) + { + Grow(count); + } + + int remaining = _length - index; + _chars.Slice(index, remaining).CopyTo(_chars[(index + count)..]); + _chars.Slice(index, count).Fill(value); + _length += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Insert(int index, string s) + { + if (s == null) + { + return; + } + + int count = s.Length; + + if (_length > _chars.Length - count) + { + Grow(count); + } + + int remaining = _length - index; + _chars.Slice(index, remaining).CopyTo(_chars[(index + count)..]); + s.AsSpan().CopyTo(_chars[index..]); + _length += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char c) + { + int pos = _length; + if ((uint)pos < (uint)_chars.Length) + { + _chars[pos] = c; + _length = pos + 1; + } + else + { + GrowAndAppend(c); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(int value, NumberFormatInfo info = null) + { + if (value >= 0) + { + Append((uint)value); + return; + } + + Append((info ?? NumberFormatInfo.CurrentInfo).NegativeSign); + Append((uint)-value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Append(uint value) + { + int bufferLength = value.CountDigits(); + + int pos = _length; + if ((uint)pos + (uint)bufferLength >= _chars.Length) + { + Grow(bufferLength); + } + + if (bufferLength == 1) + { + _chars[pos] = (char)(value + '0'); + _length = pos + 1; + return; + } + + fixed (char* buffer = _chars[pos..]) + { + char* p = buffer + bufferLength; + do + { + value = Utility.DivRem(value, 10, out uint remainder); + *--p = (char)(remainder + '0'); + } while (value != 0); + } + + _length = pos + bufferLength; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(string s) + { + if (s == null) + { + return; + } + + int pos = _length; + if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. + { + _chars[pos] = s[0]; + _length = pos + 1; + } + else + { + AppendSlow(s); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AppendLine(string s) + { + if (s == null) + { + return; + } + + // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. + if (s.Length == 1) + { + Append(s[0]); + } + else + { + AppendSlow(s); + } + + Append(Environment.NewLine); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AppendSlow(string s) + { + int pos = _length; + if (pos > _chars.Length - s.Length) + { + Grow(s.Length); + } + + s.AsSpan().CopyTo(_chars[pos..]); + _length += s.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char c, int count) + { + if (_length > _chars.Length - count) + { + Grow(count); + } + + Span dst = _chars.Slice(_length, count); + for (int i = 0; i < dst.Length; i++) + { + dst[i] = c; + } + _length += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Append(char* value, int length) + { + int pos = _length; + if (pos > _chars.Length - length) + { + Grow(length); + } + + Span dst = _chars.Slice(_length, length); + for (int i = 0; i < dst.Length; i++) + { + dst[i] = *value++; + } + _length += length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(ReadOnlySpan value) + { + int pos = _length; + if (pos > _chars.Length - value.Length) + { + Grow(value.Length); + } + + value.CopyTo(_chars[_length..]); + _length += value.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + int origPos = _length; + if (origPos > _chars.Length - length) + { + Grow(length); + } + + _length = origPos + length; + return _chars.Slice(origPos, length); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowAndAppend(char c) + { + Grow(1); + Append(c); + } + +#nullable enable + /// + /// Resize the internal buffer either by doubling current buffer size or + /// by adding to + /// whichever is greater. + /// + /// + /// Number of chars requested beyond current position. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacityBeyondPos) + { + char[] poolArray = STArrayPool.Shared.Rent(Math.Max(_length + additionalCapacityBeyondPos, _chars.Length * 2)); + + _chars[.._length].CopyTo(poolArray); + + char[] toReturn = _arrayToReturnToPool; + _chars = _arrayToReturnToPool = poolArray; + if (toReturn != null) + { + STArrayPool.Shared.Return(toReturn); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + char[] toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) + { + STArrayPool.Shared.Return(toReturn); + } + } +#nullable restore + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReplaceAny(ReadOnlySpan oldChars, ReadOnlySpan newChars, int startIndex, int count) + { + int currentLength = _length; + if ((uint)startIndex > (uint)currentLength) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + + if (count < 0 || startIndex > currentLength - count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + var slice = _chars; + + while (true) + { + var indexOf = slice.IndexOfAny(oldChars); + if (indexOf == -1) + { + break; + } + + var chr = slice[indexOf]; + + slice[indexOf] = newChars[oldChars.IndexOf(chr)]; + slice = slice[(indexOf + 1)..]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Replace(char oldChar, char newChar, int startIndex, int count) + { + int currentLength = _length; + if ((uint)startIndex > (uint)currentLength) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + + if (count < 0 || startIndex > currentLength - count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + var slice = _chars; + + while (true) + { + var indexOf = slice.IndexOf(oldChar); + if (indexOf == -1) + { + break; + } + + slice[indexOf] = newChar; + slice = slice[(indexOf + 1)..]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Remove(int startIndex, int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if (startIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + + if (length > _length - startIndex) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if (startIndex == 0) + { + _chars = _chars[length..]; + } + else if (startIndex + length == _length) + { + _chars = _chars[..startIndex]; + } + else + { + // Somewhere in the middle, this will be slow + _chars[(startIndex + length)..].CopyTo(_chars[startIndex..]); + } + + _length -= length; + } } diff --git a/Projects/Server/Collections/PooledOrderedHashSet.cs b/Projects/Server/Collections/PooledOrderedHashSet.cs index 4a6e51f1b..8853918ba 100644 --- a/Projects/Server/Collections/PooledOrderedHashSet.cs +++ b/Projects/Server/Collections/PooledOrderedHashSet.cs @@ -14,590 +14,589 @@ *************************************************************************/ using System; -using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Collections.Extensions; +using Server.Buffers; -namespace Server.Collections +namespace Server.Collections; + +[DebuggerDisplay("Count = {Count}")] +public class PooledOrderedHashSet : IList, IDisposable { - [DebuggerDisplay("Count = {Count}")] - public class PooledOrderedHashSet : IList, IDisposable + private struct Entry { - private struct Entry - { - public uint HashCode; - public TValue Value; - public int Next; // the index of the next item in the same bucket, -1 if last - } + public uint HashCode; + public TValue Value; + public int Next; // the index of the next item in the same bucket, -1 if last + } - private static readonly Entry[] InitialEntries = new Entry[1]; - private int[] _buckets = HashHelpers.SizeOneIntArray; - private int _bucketsLength = 1; - private Entry[] _entries = InitialEntries; - private int _entriesLength = 1; - private ulong _fastModMultiplier; - private int _count; - private int _version; + private static readonly Entry[] InitialEntries = new Entry[1]; + private int[] _buckets = HashHelpers.SizeOneIntArray; + private int _bucketsLength = 1; + private Entry[] _entries = InitialEntries; + private int _entriesLength = 1; + private ulong _fastModMultiplier; + private int _count; + private int _version; #nullable enable - private readonly IEqualityComparer? _comparer; + private readonly IEqualityComparer? _comparer; #nullable disable - public int Count => _count; + public int Count => _count; #nullable enable - public IEqualityComparer? Comparer => _comparer; + public IEqualityComparer? Comparer => _comparer; #nullable disable - public PooledOrderedHashSet() - : this(0) + public PooledOrderedHashSet() + : this(0) + { + } + + public PooledOrderedHashSet(IEqualityComparer comparer) + : this(0, comparer) + { + } + + public PooledOrderedHashSet(int capacity, IEqualityComparer comparer = null) + { + if (capacity < 0) { + throw new ArgumentOutOfRangeException(nameof(capacity)); } - public PooledOrderedHashSet(IEqualityComparer comparer) - : this(0, comparer) + if (capacity > 0) { + int newSize = HashHelpers.GetPrime(capacity); + _buckets = STArrayPool.Shared.Rent(newSize); + _bucketsLength = newSize; + _entries = STArrayPool.Shared.Rent(newSize); + _entriesLength = newSize; + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); } - public PooledOrderedHashSet(int capacity, IEqualityComparer comparer = null) + if (comparer != EqualityComparer.Default) { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } + _comparer = comparer; + } + } - if (capacity > 0) - { - int newSize = HashHelpers.GetPrime(capacity); - _buckets = ArrayPool.Shared.Rent(newSize); - _bucketsLength = newSize; - _entries = ArrayPool.Shared.Rent(newSize); - _entriesLength = newSize; - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - } - - if (comparer != EqualityComparer.Default) - { - _comparer = comparer; - } + public PooledOrderedHashSet(IEnumerable collection, IEqualityComparer comparer = null) + : this((collection as ICollection)?.Count ?? 0, comparer) + { + if (collection == null) + { + throw new ArgumentNullException(nameof(collection)); } - public PooledOrderedHashSet(IEnumerable collection, IEqualityComparer comparer = null) - : this((collection as ICollection)?.Count ?? 0, comparer) + foreach (TValue value in collection) { - if (collection == null) - { - throw new ArgumentNullException(nameof(collection)); - } - - foreach (TValue value in collection) - { - Add(value); - } + Add(value); } + } - public bool Contains(TValue item) => TryGetValue(item, out var value) && EqualityComparer.Default.Equals(value); + public bool Contains(TValue item) => TryGetValue(item, out var value) && EqualityComparer.Default.Equals(value); - public void Clear() + public void Clear() + { + if (_count > 0) { - if (_count > 0) - { - Array.Clear(_buckets, 0, _bucketsLength); - Array.Clear(_entries, 0, _count); - _count = 0; - ++_version; - } - } - - public Enumerator GetEnumerator() => new(this); - - void ICollection.Add(TValue item) => TryAdd(item); - - public bool Add(TValue item) => TryAdd(item); - - public int GetOrAdd(TValue value) => TryInsert(null, value); - - public int IndexOf(TValue value) => IndexOf(value, out _); - - public void Insert(int index, TValue value) - { - if ((uint)index > (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - TryInsert(index, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetBucketRef(uint hashCode) - { - int[] buckets = _buckets!; - return ref buckets[HashHelpers.FastMod(hashCode, (uint)_bucketsLength, _fastModMultiplier)]; - } - - public bool Remove(TValue value) - { - int index = IndexOf(value); - if (index >= 0) - { - RemoveAt(index); - return true; - } - - return false; - } - - public void RemoveAt(int index) - { - int count = Count; - if ((uint)index >= (uint)count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - // Remove the entry from the bucket - RemoveEntryFromBucket(index); - - // Decrement the indices > index - Entry[] entries = _entries; - for (int i = index + 1; i < count; ++i) - { - entries[i - 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: -1); - } - --_count; - entries[_count] = default; + Array.Clear(_buckets, 0, _bucketsLength); + Array.Clear(_entries, 0, _count); + _count = 0; ++_version; } + } - public bool TryAdd(TValue value) => TryInsert(null, value) != _count - 1; + public Enumerator GetEnumerator() => new(this); - public bool TryGetValue(TValue value, out TValue actualValue) + void ICollection.Add(TValue item) => TryAdd(item); + + public bool Add(TValue item) => TryAdd(item); + + public int GetOrAdd(TValue value) => TryInsert(null, value); + + public int IndexOf(TValue value) => IndexOf(value, out _); + + public void Insert(int index, TValue value) + { + if ((uint)index > (uint)Count) { - int index = IndexOf(value); - if (index >= 0) - { - actualValue = _entries[index].Value; - return true; - } - - actualValue = default; - return false; + throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); } - public TValue this[int index] + TryInsert(index, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref int GetBucketRef(uint hashCode) + { + int[] buckets = _buckets!; + return ref buckets[HashHelpers.FastMod(hashCode, (uint)_bucketsLength, _fastModMultiplier)]; + } + + public bool Remove(TValue value) + { + int index = IndexOf(value); + if (index >= 0) { - get - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - return _entries[index].Value; - } - set - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - TValue v = value; - int foundIndex = IndexOf(v, out uint hashCode); - if (foundIndex < 0) - { - RemoveEntryFromBucket(index); - Entry entry = new Entry { HashCode = hashCode, Value = value }; - AddEntryToBucket(ref entry, index, _buckets, _bucketsLength); - _entries[index] = entry; - ++_version; - } - else if (foundIndex == index) - { - ref Entry entry = ref _entries[index]; - entry.Value = value; - } - else - { - throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_AddingDuplicate, v.ToString())); - } - } + RemoveAt(index); + return true; } - public bool IsReadOnly => false; + return false; + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void CopyTo(TValue[] array, int arrayIndex) + public void RemoveAt(int index) + { + int count = Count; + if ((uint)index >= (uint)count) { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if ((uint)arrayIndex > (uint)array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex), CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); - } - - int count = Count; - if (array.Length - arrayIndex < count) - { - throw new ArgumentException(CollectionThrowStrings.Arg_ArrayPlusOffTooSmall); - } - - Entry[] entries = _entries; - for (int i = 0; i < count; ++i) - { - Entry entry = entries[i]; - array[i + arrayIndex] = entry.Value; - } + throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Entry[] Resize(int newSize) + // Remove the entry from the bucket + RemoveEntryFromBucket(index); + + // Decrement the indices > index + Entry[] entries = _entries; + for (int i = index + 1; i < count; ++i) { - int[] newBuckets = _buckets.Length < newSize ? ArrayPool.Shared.Rent(newSize) : _buckets; - Entry[] newEntries = _entries.Length < newSize ? ArrayPool.Shared.Rent(newSize) : _entries; + entries[i - 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: -1); + } + --_count; + entries[_count] = default; + ++_version; + } - int count = Count; - Array.Copy(_entries, newEntries, count); + public bool TryAdd(TValue value) => TryInsert(null, value) != _count - 1; - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - - for (int i = 0; i < count; ++i) - { - AddEntryToBucket(ref newEntries[i], i, newBuckets, newSize); - } - - var oldBuckets = _buckets; - var oldEntries = _entries; - - if (oldBuckets.Length > 1 && oldBuckets != newBuckets) - { - ArrayPool.Shared.Return(oldBuckets, true); - } - - if (oldEntries.Length > 1 && oldEntries != newEntries) - { - ArrayPool.Shared.Return(oldEntries, true); - } - - _buckets = newBuckets; - _bucketsLength = newSize; - _entries = newEntries; - _entriesLength = newSize; - return newEntries; + public bool TryGetValue(TValue value, out TValue actualValue) + { + int index = IndexOf(value); + if (index >= 0) + { + actualValue = _entries[index].Value; + return true; } -#nullable enable - private int IndexOf(TValue value, out uint hashCode) + actualValue = default; + return false; + } + + public TValue this[int index] + { + get { - ref int bucket = ref Unsafe.NullRef(); - int i; - - IEqualityComparer? comparer = _comparer; - if (comparer == null) + if ((uint)index >= (uint)Count) { - hashCode = (uint)value.GetHashCode(); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; + throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); + } - if (i >= 0) - { - if (typeof(TValue).IsValueType) - { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && EqualityComparer.Default.Equals(entry.Value, value)) - { - break; - } + return _entries[index].Value; + } + set + { + if ((uint)index >= (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); + } - i = entry.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - else - { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), - // so cache in a local rather than get EqualityComparer per loop iteration. - var defaultComparer = EqualityComparer.Default; - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && defaultComparer.Equals(entry.Value, value)) - { - break; - } - - i = entry.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - } + TValue v = value; + int foundIndex = IndexOf(v, out uint hashCode); + if (foundIndex < 0) + { + RemoveEntryFromBucket(index); + Entry entry = new Entry { HashCode = hashCode, Value = value }; + AddEntryToBucket(ref entry, index, _buckets, _bucketsLength); + _entries[index] = entry; + ++_version; + } + else if (foundIndex == index) + { + ref Entry entry = ref _entries[index]; + entry.Value = value; } else { - hashCode = (uint)comparer.GetHashCode(value); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; - if (i >= 0) + throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_AddingDuplicate, v.ToString())); + } + } + } + + public bool IsReadOnly => false; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void CopyTo(TValue[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if ((uint)arrayIndex > (uint)array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); + } + + int count = Count; + if (array.Length - arrayIndex < count) + { + throw new ArgumentException(CollectionThrowStrings.Arg_ArrayPlusOffTooSmall); + } + + Entry[] entries = _entries; + for (int i = 0; i < count; ++i) + { + Entry entry = entries[i]; + array[i + arrayIndex] = entry.Value; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Entry[] Resize(int newSize) + { + int[] newBuckets = _buckets.Length < newSize ? STArrayPool.Shared.Rent(newSize) : _buckets; + Entry[] newEntries = _entries.Length < newSize ? STArrayPool.Shared.Rent(newSize) : _entries; + + int count = Count; + Array.Copy(_entries, newEntries, count); + + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); + + for (int i = 0; i < count; ++i) + { + AddEntryToBucket(ref newEntries[i], i, newBuckets, newSize); + } + + var oldBuckets = _buckets; + var oldEntries = _entries; + + if (oldBuckets.Length > 1 && oldBuckets != newBuckets) + { + STArrayPool.Shared.Return(oldBuckets, true); + } + + if (oldEntries.Length > 1 && oldEntries != newEntries) + { + STArrayPool.Shared.Return(oldEntries, true); + } + + _buckets = newBuckets; + _bucketsLength = newSize; + _entries = newEntries; + _entriesLength = newSize; + return newEntries; + } + +#nullable enable + private int IndexOf(TValue value, out uint hashCode) + { + ref int bucket = ref Unsafe.NullRef(); + int i; + + IEqualityComparer? comparer = _comparer; + if (comparer == null) + { + hashCode = (uint)value.GetHashCode(); + bucket = ref GetBucketRef(hashCode); + i = bucket - 1; + + if (i >= 0) + { + if (typeof(TValue).IsValueType) { + // ValueType: Devirtualize with EqualityComparer.Default intrinsic Entry[] entries = _entries; int collisionCount = 0; do { Entry entry = entries[i]; - if (entry.HashCode == hashCode && comparer.Equals(entry.Value, value)) + if (entry.HashCode == hashCode && EqualityComparer.Default.Equals(entry.Value, value)) { break; } + i = entry.Next; if (collisionCount >= _entriesLength) { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); + throw new InvalidOperationException( + CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported + ); } + + ++collisionCount; + } while (i >= 0); + } + else + { + // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), + // so cache in a local rather than get EqualityComparer per loop iteration. + var defaultComparer = EqualityComparer.Default; + Entry[] entries = _entries; + int collisionCount = 0; + do + { + Entry entry = entries[i]; + if (entry.HashCode == hashCode && defaultComparer.Equals(entry.Value, value)) + { + break; + } + + i = entry.Next; + if (collisionCount >= _entriesLength) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException( + CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported + ); + } + ++collisionCount; } while (i >= 0); } } - - return i; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int TryInsert(int? index, TValue value) + else { - int i = IndexOf(value, out uint hashCode); - return i >= 0 ? i : AddInternal(index, value, hashCode); + hashCode = (uint)comparer.GetHashCode(value); + bucket = ref GetBucketRef(hashCode); + i = bucket - 1; + if (i >= 0) + { + Entry[] entries = _entries; + int collisionCount = 0; + do + { + Entry entry = entries[i]; + if (entry.HashCode == hashCode && comparer.Equals(entry.Value, value)) + { + break; + } + i = entry.Next; + if (collisionCount >= _entriesLength) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; + } while (i >= 0); + } } - private int AddInternal(int? index, TValue value, uint hashCode) + return i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int TryInsert(int? index, TValue value) + { + int i = IndexOf(value, out uint hashCode); + return i >= 0 ? i : AddInternal(index, value, hashCode); + } + + private int AddInternal(int? index, TValue value, uint hashCode) + { + Entry[] entries = _entries; + // Check if resize is needed + int count = Count; + if (_entriesLength == count || entries.Length == 1) { - Entry[] entries = _entries; - // Check if resize is needed - int count = Count; - if (_entriesLength == count || entries.Length == 1) - { - entries = Resize(HashHelpers.ExpandPrime(_entriesLength)); - } - - // Increment indices >= index; - int actualIndex = index ?? count; - for (int i = count - 1; i >= actualIndex; --i) - { - entries[i + 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: 1); - } - - ref Entry entry = ref entries[actualIndex]; - entry.HashCode = hashCode; - entry.Value = value; - AddEntryToBucket(ref entry, actualIndex, _buckets, _bucketsLength); - ++_count; - ++_version; - return actualIndex; + entries = Resize(HashHelpers.ExpandPrime(_entriesLength)); } + + // Increment indices >= index; + int actualIndex = index ?? count; + for (int i = count - 1; i >= actualIndex; --i) + { + entries[i + 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: 1); + } + + ref Entry entry = ref entries[actualIndex]; + entry.HashCode = hashCode; + entry.Value = value; + AddEntryToBucket(ref entry, actualIndex, _buckets, _bucketsLength); + ++_count; + ++_version; + return actualIndex; + } #nullable restore - // Returns the index of the next entry in the bucket - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets, int bucketsLength) - { - ref int b = ref buckets[(int)(entry.HashCode % (uint)bucketsLength)]; - entry.Next = b - 1; - b = entryIndex + 1; - } + // Returns the index of the next entry in the bucket + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets, int bucketsLength) + { + ref int b = ref buckets[(int)(entry.HashCode % (uint)bucketsLength)]; + entry.Next = b - 1; + b = entryIndex + 1; + } - private void RemoveEntryFromBucket(int entryIndex) + private void RemoveEntryFromBucket(int entryIndex) + { + Entry[] entries = _entries; + Entry entry = entries[entryIndex]; + ref int bucket = ref GetBucketRef(entry.HashCode); + // Bucket was pointing to removed entry. Update it to point to the next in the chain + if (bucket == entryIndex + 1) { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to removed entry. Update it to point to the next in the chain - if (bucket == entryIndex + 1) + bucket = entry.Next + 1; + } + else + { + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain + int i = bucket - 1; + int collisionCount = 0; + while (true) { - bucket = entry.Next + 1; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain - int i = bucket - 1; - int collisionCount = 0; - while (true) + ref Entry e = ref entries[i]; + if (e.Next == entryIndex) { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next = entry.Next; - return; - } - i = e.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; + e.Next = entry.Next; + return; } + i = e.Next; + if (collisionCount >= _entriesLength) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; } } + } - private void UpdateBucketIndex(int entryIndex, int incrementAmount) + private void UpdateBucketIndex(int entryIndex, int incrementAmount) + { + Entry[] entries = _entries; + Entry entry = entries[entryIndex]; + ref int bucket = ref GetBucketRef(entry.HashCode); + // Bucket was pointing to entry. Increment the index by incrementAmount. + if (bucket == entryIndex + 1) { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to entry. Increment the index by incrementAmount. - if (bucket == entryIndex + 1) + bucket += incrementAmount; + } + else + { + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. + int i = bucket - 1; + int collisionCount = 0; + while (true) { - bucket += incrementAmount; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. - int i = bucket - 1; - int collisionCount = 0; - while (true) + ref Entry e = ref entries[i]; + if (e.Next == entryIndex) { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next += incrementAmount; - return; - } - i = e.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; + e.Next += incrementAmount; + return; } + i = e.Next; + if (collisionCount >= _entriesLength) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; } } + } - public struct Enumerator : IEnumerator + public struct Enumerator : IEnumerator + { + private readonly PooledOrderedHashSet _PooledOrderedHashSet; + private readonly int _version; + private int _index; + private TValue _current; + + public TValue Current => _current; + + object IEnumerator.Current => _current; + + internal Enumerator(PooledOrderedHashSet PooledOrderedHashSet) { - private readonly PooledOrderedHashSet _PooledOrderedHashSet; - private readonly int _version; - private int _index; - private TValue _current; - - public TValue Current => _current; - - object IEnumerator.Current => _current; - - internal Enumerator(PooledOrderedHashSet PooledOrderedHashSet) - { - _PooledOrderedHashSet = PooledOrderedHashSet; - _version = PooledOrderedHashSet._version; - _index = 0; - _current = default; - } - - public void Dispose() - { - } - - public bool MoveNext() - { - if (_version != _PooledOrderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - if (_index < _PooledOrderedHashSet.Count) - { - Entry entry = _PooledOrderedHashSet._entries[_index]; - _current = entry.Value; - ++_index; - return true; - } - _current = default; - return false; - } - - void IEnumerator.Reset() - { - if (_version != _PooledOrderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - _index = 0; - _current = default; - } + _PooledOrderedHashSet = PooledOrderedHashSet; + _version = PooledOrderedHashSet._version; + _index = 0; + _current = default; } public void Dispose() { - if (_buckets.Length > 1) - { - ArrayPool.Shared.Return(_buckets, true); - } - - if (_entries.Length > 1) - { - ArrayPool.Shared.Return(_entries, true); - } - - _buckets = HashHelpers.SizeOneIntArray; - _entries = InitialEntries; - _count = 0; - - GC.SuppressFinalize(this); } - ~PooledOrderedHashSet() + public bool MoveNext() { - if (_buckets.Length > 1) + if (_version != _PooledOrderedHashSet._version) { - ArrayPool.Shared.Return(_buckets, true); + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); } - if (_entries.Length > 1) + if (_index < _PooledOrderedHashSet.Count) { - ArrayPool.Shared.Return(_entries, true); + Entry entry = _PooledOrderedHashSet._entries[_index]; + _current = entry.Value; + ++_index; + return true; + } + _current = default; + return false; + } + + void IEnumerator.Reset() + { + if (_version != _PooledOrderedHashSet._version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); } - _buckets = HashHelpers.SizeOneIntArray; - _entries = InitialEntries; - _count = 0; + _index = 0; + _current = default; } } + + public void Dispose() + { + if (_buckets.Length > 1) + { + STArrayPool.Shared.Return(_buckets, true); + } + + if (_entries.Length > 1) + { + STArrayPool.Shared.Return(_entries, true); + } + + _buckets = HashHelpers.SizeOneIntArray; + _entries = InitialEntries; + _count = 0; + + GC.SuppressFinalize(this); + } + + ~PooledOrderedHashSet() + { + if (_buckets.Length > 1) + { + STArrayPool.Shared.Return(_buckets, true); + } + + if (_entries.Length > 1) + { + STArrayPool.Shared.Return(_entries, true); + } + + _buckets = HashHelpers.SizeOneIntArray; + _entries = InitialEntries; + _count = 0; + } } diff --git a/Projects/Server/Maps/Map.cs b/Projects/Server/Maps/Map.cs index 9c93f772d..e11ae4bd1 100644 --- a/Projects/Server/Maps/Map.cs +++ b/Projects/Server/Maps/Map.cs @@ -1,751 +1,774 @@ using System; -using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using Server.Buffers; using Server.Items; using Server.Logging; using Server.Network; using Server.Targeting; -namespace Server +namespace Server; + +[Flags] +public enum MapRules { - [Flags] - public enum MapRules + None = 0x0000, + Internal = 0x0001, // Internal map (used for dragging, commodity deeds, etc) + FreeMovement = 0x0002, // Anyone can move over anyone else without taking stamina loss + BeneficialRestrictions = 0x0004, // Disallow performing beneficial actions on criminals/murderers + HarmfulRestrictions = 0x0008, // Disallow performing harmful actions on innocents + TrammelRules = FreeMovement | BeneficialRestrictions | HarmfulRestrictions, + FeluccaRules = None +} + +public interface IPooledEnumerable : IEnumerable +{ + void Free(); +} + +public interface IPooledEnumerable : IPooledEnumerable, IEnumerable +{ +} + +public static class PooledEnumeration +{ + public delegate IEnumerable Selector(Sector sector, Rectangle2D bounds); + + static PooledEnumeration() { - None = 0x0000, - Internal = 0x0001, // Internal map (used for dragging, commodity deeds, etc) - FreeMovement = 0x0002, // Anyone can move over anyone else without taking stamina loss - BeneficialRestrictions = 0x0004, // Disallow performing beneficial actions on criminals/murderers - HarmfulRestrictions = 0x0008, // Disallow performing harmful actions on innocents - TrammelRules = FreeMovement | BeneficialRestrictions | HarmfulRestrictions, - FeluccaRules = None + ClientSelector = SelectClients; + EntitySelector = SelectEntities; + MobileSelector = SelectMobiles; + ItemSelector = SelectItems; + MultiSelector = SelectMultis; + MultiTileSelector = SelectMultiTiles; } - public interface IPooledEnumerable : IEnumerable + public static Selector ClientSelector { get; set; } + public static Selector EntitySelector { get; set; } + public static Selector MobileSelector { get; set; } + public static Selector ItemSelector { get; set; } + public static Selector MultiSelector { get; set; } + public static Selector MultiTileSelector { get; set; } + + public static IEnumerable SelectClients(Sector s, Rectangle2D bounds) { - void Free(); + var clients = new List(s.Clients.Count); + foreach (var client in s.Clients) + { + var m = client.Mobile; + + if (m?.Deleted == false && bounds.Contains(m.Location)) + { + clients.Add(client); + } + } + + return clients; } - public interface IPooledEnumerable : IPooledEnumerable, IEnumerable + public static IEnumerable SelectEntities(Sector s, Rectangle2D bounds) { - } - - public static class PooledEnumeration - { - public delegate IEnumerable Selector(Sector sector, Rectangle2D bounds); - - static PooledEnumeration() + var entities = new List(s.Mobiles.Count + s.Items.Count); + for (int i = s.Mobiles.Count - 1, j = s.Items.Count - 1; i >= 0 || j >= 0; --i, --j) { - ClientSelector = SelectClients; - EntitySelector = SelectEntities; - MobileSelector = SelectMobiles; - ItemSelector = SelectItems; - MultiSelector = SelectMultis; - MultiTileSelector = SelectMultiTiles; - } - - public static Selector ClientSelector { get; set; } - public static Selector EntitySelector { get; set; } - public static Selector MobileSelector { get; set; } - public static Selector ItemSelector { get; set; } - public static Selector MultiSelector { get; set; } - public static Selector MultiTileSelector { get; set; } - - public static IEnumerable SelectClients(Sector s, Rectangle2D bounds) - { - var clients = new List(s.Clients.Count); - foreach (var client in s.Clients) + if (j >= 0) { - var m = client.Mobile; - - if (m?.Deleted == false && bounds.Contains(m.Location)) - { - clients.Add(client); - } - } - - return clients; - } - - public static IEnumerable SelectEntities(Sector s, Rectangle2D bounds) - { - var entities = new List(s.Mobiles.Count + s.Items.Count); - for (int i = s.Mobiles.Count - 1, j = s.Items.Count - 1; i >= 0 || j >= 0; --i, --j) - { - if (j >= 0) - { - Item item = s.Items[j]; - if (item is { Deleted: false, Parent: null } && bounds.Contains(item.Location)) - { - entities.Add(item); - } - } - - if (i >= 0) - { - Mobile mob = s.Mobiles[i]; - if (mob is { Deleted: false } && bounds.Contains(mob.Location)) - { - entities.Add(mob); - } - } - } - return entities; - } - - public static IEnumerable SelectMobiles(Sector s, Rectangle2D bounds) where T : Mobile - { - var entities = new List(s.Mobiles.Count); - for (int i = s.Mobiles.Count - 1; i >= 0; --i) - { - if (s.Mobiles[i] is T { Deleted: false } mob && bounds.Contains(mob.Location)) - { - entities.Add(mob); - } - } - return entities; - } - - public static IEnumerable SelectItems(Sector s, Rectangle2D bounds) where T : Item - { - var entities = new List(s.Items.Count); - for (int i = s.Items.Count - 1; i >= 0; --i) - { - if (s.Items[i] is T { Deleted: false, Parent: null } item && bounds.Contains(item.Location)) + Item item = s.Items[j]; + if (item is { Deleted: false, Parent: null } && bounds.Contains(item.Location)) { entities.Add(item); } } - return entities; - } - public static IEnumerable SelectMultis(Sector s, Rectangle2D bounds) - { - var entities = new List(s.Multis.Count); - for (int i = s.Multis.Count - 1; i >= 0; --i) + if (i >= 0) { - BaseMulti multi = s.Multis[i]; - if (multi is { Deleted: false } && bounds.Contains(multi.Location)) + Mobile mob = s.Mobiles[i]; + if (mob is { Deleted: false } && bounds.Contains(mob.Location)) { - entities.Add(multi); + entities.Add(mob); } } - return entities; } + return entities; + } - public static IEnumerable SelectMultiTiles(Sector s, Rectangle2D bounds) + public static IEnumerable SelectMobiles(Sector s, Rectangle2D bounds) where T : Mobile + { + var entities = new List(s.Mobiles.Count); + for (int i = s.Mobiles.Count - 1; i >= 0; --i) { - for (int l = s.Multis.Count - 1; l >= 0; --l) + if (s.Mobiles[i] is T { Deleted: false } mob && bounds.Contains(mob.Location)) { - BaseMulti o = s.Multis[l]; - if (o?.Deleted != false) + entities.Add(mob); + } + } + return entities; + } + + public static IEnumerable SelectItems(Sector s, Rectangle2D bounds) where T : Item + { + var entities = new List(s.Items.Count); + for (int i = s.Items.Count - 1; i >= 0; --i) + { + if (s.Items[i] is T { Deleted: false, Parent: null } item && bounds.Contains(item.Location)) + { + entities.Add(item); + } + } + return entities; + } + + public static IEnumerable SelectMultis(Sector s, Rectangle2D bounds) + { + var entities = new List(s.Multis.Count); + for (int i = s.Multis.Count - 1; i >= 0; --i) + { + BaseMulti multi = s.Multis[i]; + if (multi is { Deleted: false } && bounds.Contains(multi.Location)) + { + entities.Add(multi); + } + } + return entities; + } + + public static IEnumerable SelectMultiTiles(Sector s, Rectangle2D bounds) + { + for (int l = s.Multis.Count - 1; l >= 0; --l) + { + BaseMulti o = s.Multis[l]; + if (o?.Deleted != false) + { + continue; + } + + MultiComponentList c = o.Components; + + int x, y, xo, yo; + StaticTile[] t, r; + + for (x = bounds.Start.X; x < bounds.End.X; x++) + { + xo = x - (o.X + c.Min.X); + + if (xo < 0 || xo >= c.Width) { continue; } - MultiComponentList c = o.Components; - - int x, y, xo, yo; - StaticTile[] t, r; - - for (x = bounds.Start.X; x < bounds.End.X; x++) + for (y = bounds.Start.Y; y < bounds.End.Y; y++) { - xo = x - (o.X + c.Min.X); + yo = y - (o.Y + c.Min.Y); - if (xo < 0 || xo >= c.Width) + if (yo < 0 || yo >= c.Height) { continue; } - for (y = bounds.Start.Y; y < bounds.End.Y; y++) + t = c.Tiles[xo][yo]; + + if (t.Length <= 0) { - yo = y - (o.Y + c.Min.Y); - - if (yo < 0 || yo >= c.Height) - { - continue; - } - - t = c.Tiles[xo][yo]; - - if (t.Length <= 0) - { - continue; - } - - r = new StaticTile[t.Length]; - - for (var i = 0; i < t.Length; i++) - { - r[i] = t[i]; - r[i].Z += o.Z; - } - - yield return r; + continue; } + + r = new StaticTile[t.Length]; + + for (var i = 0; i < t.Length; i++) + { + r[i] = t[i]; + r[i].Z += o.Z; + } + + yield return r; } } } - - public static Map.PooledEnumerable GetClients(Map map, Rectangle2D bounds) => - Map.PooledEnumerable.Instantiate(map, bounds, ClientSelector ?? SelectClients); - - public static Map.PooledEnumerable GetEntities(Map map, Rectangle2D bounds) => - Map.PooledEnumerable.Instantiate(map, bounds, EntitySelector ?? SelectEntities); - - public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) => - GetMobiles(map, bounds); - - public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) where T : Mobile => - Map.PooledEnumerable.Instantiate(map, bounds, SelectMobiles); - - public static Map.PooledEnumerable GetItems(Map map, Rectangle2D bounds) where T : Item => - Map.PooledEnumerable.Instantiate(map, bounds, SelectItems); - - public static Map.PooledEnumerable GetMultis(Map map, Rectangle2D bounds) => - Map.PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); - - public static Map.PooledEnumerable GetMultiTiles(Map map, Rectangle2D bounds) => - Map.PooledEnumerable.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles); - - public static IEnumerable EnumerateSectors(Map map, Rectangle2D bounds) - { - if (map == null || map == Map.Internal) - { - yield break; - } - - var x1 = bounds.Start.X; - var y1 = bounds.Start.Y; - var x2 = bounds.End.X; - var y2 = bounds.End.Y; - - if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out var xSector, out var ySector)) - { - yield break; - } - - var index = 0; - - while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out var s)) - { - yield return s; - } - } - - public static bool Bound( - Map map, - ref int x1, - ref int y1, - ref int x2, - ref int y2, - out int xSector, - out int ySector - ) - { - if (map == null || map == Map.Internal) - { - xSector = ySector = 0; - return false; - } - - map.Bound(x1, y1, out x1, out y1); - map.Bound(x2 - 1, y2 - 1, out x2, out y2); - - x1 >>= Map.SectorShift; - y1 >>= Map.SectorShift; - x2 >>= Map.SectorShift; - y2 >>= Map.SectorShift; - - xSector = x1; - ySector = y1; - - return true; - } - - private static bool NextSector( - Map map, - int x1, - int y1, - int x2, - int y2, - ref int index, - ref int xSector, - ref int ySector, - out Sector s - ) - { - if (map == null) - { - s = null; - xSector = ySector = 0; - return false; - } - - if (map == Map.Internal) - { - s = map.InvalidSector; - xSector = ySector = 0; - return false; - } - - if (index++ > 0) - { - if (++ySector > y2) - { - ySector = y1; - - if (++xSector > x2) - { - xSector = x1; - - s = map.InvalidSector; - return false; - } - } - } - - s = map.GetRealSector(xSector, ySector); - return true; - } } - [Parsable] - public sealed class Map : IComparable + public static Map.PooledEnumerable GetClients(Map map, Rectangle2D bounds) => + Map.PooledEnumerable.Instantiate(map, bounds, ClientSelector ?? SelectClients); + + public static Map.PooledEnumerable GetEntities(Map map, Rectangle2D bounds) => + Map.PooledEnumerable.Instantiate(map, bounds, EntitySelector ?? SelectEntities); + + public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) => + GetMobiles(map, bounds); + + public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) where T : Mobile => + Map.PooledEnumerable.Instantiate(map, bounds, SelectMobiles); + + public static Map.PooledEnumerable GetItems(Map map, Rectangle2D bounds) where T : Item => + Map.PooledEnumerable.Instantiate(map, bounds, SelectItems); + + public static Map.PooledEnumerable GetMultis(Map map, Rectangle2D bounds) => + Map.PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); + + public static Map.PooledEnumerable GetMultiTiles(Map map, Rectangle2D bounds) => + Map.PooledEnumerable.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles); + + public static IEnumerable EnumerateSectors(Map map, Rectangle2D bounds) { - public const int SectorSize = 16; - public const int SectorShift = 4; - public const int SectorActiveRange = 2; - - private static ILogger _logger; - private static ILogger Logger => _logger ??= LogFactory.GetLogger(typeof(Map)); - - private readonly int m_FileIndex; - private readonly Sector[][] m_Sectors; - private readonly int m_SectorsHeight; - - private readonly int m_SectorsWidth; - - private readonly object tileLock = new(); - private Region m_DefaultRegion; - - private string m_Name; - - private TileMatrix m_Tiles; - - public Map(int mapID, int mapIndex, int fileIndex, int width, int height, int season, string name, MapRules rules) + if (map == null || map == Map.Internal) { - MapID = mapID; - MapIndex = mapIndex; - m_FileIndex = fileIndex; - Width = width; - Height = height; - Season = season; - m_Name = name; - Rules = rules; - Regions = new Dictionary(StringComparer.OrdinalIgnoreCase); - InvalidSector = new Sector(0, 0, this); - m_SectorsWidth = width >> SectorShift; - m_SectorsHeight = height >> SectorShift; - m_Sectors = new Sector[m_SectorsWidth][]; + yield break; } - public static Map[] Maps { get; } = new Map[0x100]; + var x1 = bounds.Start.X; + var y1 = bounds.Start.Y; + var x2 = bounds.End.X; + var y2 = bounds.End.Y; - public static Map Felucca => Maps[0]; - public static Map Trammel => Maps[1]; - public static Map Ilshenar => Maps[2]; - public static Map Malas => Maps[3]; - public static Map Tokuno => Maps[4]; - public static Map TerMur => Maps[5]; - public static Map Internal => Maps[0x7F]; - - public static List AllMaps { get; } = new(); - - public int Season { get; set; } - - public TileMatrix Tiles + if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out var xSector, out var ySector)) { - get - { - if (m_Tiles == null) - { - lock (tileLock) - { - m_Tiles = new TileMatrix(this, m_FileIndex, MapID, Width, Height); - } - } + yield break; + } - return m_Tiles; + var index = 0; + + while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out var s)) + { + yield return s; + } + } + + public static bool Bound( + Map map, + ref int x1, + ref int y1, + ref int x2, + ref int y2, + out int xSector, + out int ySector + ) + { + if (map == null || map == Map.Internal) + { + xSector = ySector = 0; + return false; + } + + map.Bound(x1, y1, out x1, out y1); + map.Bound(x2 - 1, y2 - 1, out x2, out y2); + + x1 >>= Map.SectorShift; + y1 >>= Map.SectorShift; + x2 >>= Map.SectorShift; + y2 >>= Map.SectorShift; + + xSector = x1; + ySector = y1; + + return true; + } + + private static bool NextSector( + Map map, + int x1, + int y1, + int x2, + int y2, + ref int index, + ref int xSector, + ref int ySector, + out Sector s + ) + { + if (map == null) + { + s = null; + xSector = ySector = 0; + return false; + } + + if (map == Map.Internal) + { + s = map.InvalidSector; + xSector = ySector = 0; + return false; + } + + if (index++ > 0) + { + if (++ySector > y2) + { + ySector = y1; + + if (++xSector > x2) + { + xSector = x1; + + s = map.InvalidSector; + return false; + } } } - public int MapID { get; } + s = map.GetRealSector(xSector, ySector); + return true; + } +} - public int MapIndex { get; } +[Parsable] +public sealed class Map : IComparable +{ + public const int SectorSize = 16; + public const int SectorShift = 4; + public const int SectorActiveRange = 2; - public int Width { get; } + private static ILogger _logger; + private static ILogger Logger => _logger ??= LogFactory.GetLogger(typeof(Map)); - public int Height { get; } + private readonly int m_FileIndex; + private readonly Sector[][] m_Sectors; + private readonly int m_SectorsHeight; - public Dictionary Regions { get; } + private readonly int m_SectorsWidth; - public Region DefaultRegion + private readonly object tileLock = new(); + private Region m_DefaultRegion; + + private string m_Name; + + private TileMatrix m_Tiles; + + public Map(int mapID, int mapIndex, int fileIndex, int width, int height, int season, string name, MapRules rules) + { + MapID = mapID; + MapIndex = mapIndex; + m_FileIndex = fileIndex; + Width = width; + Height = height; + Season = season; + m_Name = name; + Rules = rules; + Regions = new Dictionary(StringComparer.OrdinalIgnoreCase); + InvalidSector = new Sector(0, 0, this); + m_SectorsWidth = width >> SectorShift; + m_SectorsHeight = height >> SectorShift; + m_Sectors = new Sector[m_SectorsWidth][]; + } + + public static Map[] Maps { get; } = new Map[0x100]; + + public static Map Felucca => Maps[0]; + public static Map Trammel => Maps[1]; + public static Map Ilshenar => Maps[2]; + public static Map Malas => Maps[3]; + public static Map Tokuno => Maps[4]; + public static Map TerMur => Maps[5]; + public static Map Internal => Maps[0x7F]; + + public static List AllMaps { get; } = new(); + + public int Season { get; set; } + + public TileMatrix Tiles + { + get { - get => m_DefaultRegion ??= new Region(null, this, 0, Array.Empty()); - set => m_DefaultRegion = value; - } - - public MapRules Rules { get; set; } - - public Sector InvalidSector { get; } - - public string Name - { - get + if (m_Tiles == null) { - if (this == Internal && m_Name != "Internal") + lock (tileLock) { - Logger.Warning($"Internal map name was '{m_Name}'\n{new StackTrace()}"); - m_Name = "Internal"; + m_Tiles = new TileMatrix(this, m_FileIndex, MapID, Width, Height); } - - return m_Name; } - set + + return m_Tiles; + } + } + + public int MapID { get; } + + public int MapIndex { get; } + + public int Width { get; } + + public int Height { get; } + + public Dictionary Regions { get; } + + public Region DefaultRegion + { + get => m_DefaultRegion ??= new Region(null, this, 0, Array.Empty()); + set => m_DefaultRegion = value; + } + + public MapRules Rules { get; set; } + + public Sector InvalidSector { get; } + + public string Name + { + get + { + if (this == Internal && m_Name != "Internal") { - if (this == Internal && value != "Internal") - { - Logger.Warning($"Attempted to set internal map name to '{value}'\n{new StackTrace()}"); + Logger.Warning($"Internal map name was '{m_Name}'\n{new StackTrace()}"); + m_Name = "Internal"; + } - value = "Internal"; - } + return m_Name; + } + set + { + if (this == Internal && value != "Internal") + { + Logger.Warning($"Attempted to set internal map name to '{value}'\n{new StackTrace()}"); - m_Name = value; + value = "Internal"; + } + + m_Name = value; + } + } + + public static int[] InvalidLandTiles { get; set; } = { 0x244 }; + + public static int MaxLOSDistance { get; set; } = 25; + + public int CompareTo(Map other) => other == null ? -1 : MapID.CompareTo(other.MapID); + + public static string[] GetMapNames() + { + var mapCount = 0; + for (var i = 0; i < Maps.Length; i++) + { + var map = Maps[i]; + if (map != null) + { + mapCount++; } } - public static int[] InvalidLandTiles { get; set; } = { 0x244 }; - - public static int MaxLOSDistance { get; set; } = 25; - - public int CompareTo(Map other) => other == null ? -1 : MapID.CompareTo(other.MapID); - - public static string[] GetMapNames() + var mapNames = new string[mapCount]; + for (int i = 0, mIndex = 0; i < Maps.Length; i++) { - var mapCount = 0; - for (var i = 0; i < Maps.Length; i++) + var map = Maps[i]; + if (map != null) { - var map = Maps[i]; - if (map != null) - { - mapCount++; - } + mapNames[mIndex++] = map.Name; } - - var mapNames = new string[mapCount]; - for (int i = 0, mIndex = 0; i < Maps.Length; i++) - { - var map = Maps[i]; - if (map != null) - { - mapNames[mIndex++] = map.Name; - } - } - - return mapNames; } - public static Map[] GetMapValues() + return mapNames; + } + + public static Map[] GetMapValues() + { + var mapCount = 0; + for (var i = 0; i < Maps.Length; i++) { - var mapCount = 0; - for (var i = 0; i < Maps.Length; i++) + var map = Maps[i]; + if (map != null) { - var map = Maps[i]; - if (map != null) - { - mapCount++; - } + mapCount++; } - - var mapValues = new Map[mapCount]; - for (int i = 0, mIndex = 0; i < Maps.Length; i++) - { - var map = Maps[i]; - if (map != null) - { - mapValues[mIndex++] = map; - } - } - - return mapValues; } - public static Map Parse(string value) + var mapValues = new Map[mapCount]; + for (int i = 0, mIndex = 0; i < Maps.Length; i++) { - if (string.IsNullOrWhiteSpace(value)) + var map = Maps[i]; + if (map != null) { - return null; + mapValues[mIndex++] = map; } + } - if (value.InsensitiveEquals("Internal")) - { - return Internal; - } - - if (!int.TryParse(value, out var index)) - { - index = -1; - } - else if (index == 127) - { - return Internal; - } - - for (int i = 0; i < Maps.Length; i++) - { - var map = Maps[i]; - if (map == null) - { - continue; - } - - if (index >= 0 && map.MapIndex == index || map.Name.InsensitiveEquals(value)) - { - return map; - } - } + return mapValues; + } + public static Map Parse(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { return null; } - public override string ToString() => Name; - - public int GetAverageZ(int x, int y) + if (value.InsensitiveEquals("Internal")) { - GetAverageZ(x, y, out _, out var avg, out _); - return avg; + return Internal; } - public void GetAverageZ(int x, int y, out int z, out int avg, out int top) + if (!int.TryParse(value, out var index)) { - var zTop = Tiles.GetLandTile(x, y).Z; - var zLeft = Tiles.GetLandTile(x, y + 1).Z; - var zRight = Tiles.GetLandTile(x + 1, y).Z; - var zBottom = Tiles.GetLandTile(x + 1, y + 1).Z; - - z = zTop; - if (zLeft < z) - { - z = zLeft; - } - - if (zRight < z) - { - z = zRight; - } - - if (zBottom < z) - { - z = zBottom; - } - - top = zTop; - if (zLeft > top) - { - top = zLeft; - } - - if (zRight > top) - { - top = zRight; - } - - if (zBottom > top) - { - top = zBottom; - } - - avg = (zTop - zBottom).Abs() > (zLeft - zRight).Abs() - ? FloorAverage(zLeft, zRight) - : FloorAverage(zTop, zBottom); + index = -1; + } + else if (index == 127) + { + return Internal; } - private static int FloorAverage(int a, int b) + for (int i = 0; i < Maps.Length; i++) { - var v = a + b; - - if (v < 0) + var map = Maps[i]; + if (map == null) { - --v; + continue; } - return v / 2; + if (index >= 0 && map.MapIndex == index || map.Name.InsensitiveEquals(value)) + { + return map; + } } - public IPooledEnumerable GetMultiTilesAt(int x, int y) => - PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); + return null; + } - private static void AcquireFixItems(Map map, int x, int y, Item[] pool, out int length) + public override string ToString() => Name; + + public int GetAverageZ(int x, int y) + { + GetAverageZ(x, y, out _, out var avg, out _); + return avg; + } + + public void GetAverageZ(int x, int y, out int z, out int avg, out int top) + { + var zTop = Tiles.GetLandTile(x, y).Z; + var zLeft = Tiles.GetLandTile(x, y + 1).Z; + var zRight = Tiles.GetLandTile(x + 1, y).Z; + var zBottom = Tiles.GetLandTile(x + 1, y + 1).Z; + + z = zTop; + if (zLeft < z) { - length = 0; - if (map == null || map == Internal || x < 0 || x > map.Width || y < 0 || y > map.Height) - { - return; - } + z = zLeft; + } - var eable = map.GetItemsInRange(new Point3D(x, y, 0), 0); - foreach (var item in eable) + if (zRight < z) + { + z = zRight; + } + + if (zBottom < z) + { + z = zBottom; + } + + top = zTop; + if (zLeft > top) + { + top = zLeft; + } + + if (zRight > top) + { + top = zRight; + } + + if (zBottom > top) + { + top = zBottom; + } + + avg = (zTop - zBottom).Abs() > (zLeft - zRight).Abs() + ? FloorAverage(zLeft, zRight) + : FloorAverage(zTop, zBottom); + } + + private static int FloorAverage(int a, int b) + { + var v = a + b; + + if (v < 0) + { + --v; + } + + return v / 2; + } + + public IPooledEnumerable GetMultiTilesAt(int x, int y) => + PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); + + private static void AcquireFixItems(Map map, int x, int y, Item[] pool, out int length) + { + length = 0; + if (map == null || map == Internal || x < 0 || x > map.Width || y < 0 || y > map.Height) + { + return; + } + + var eable = map.GetItemsInRange(new Point3D(x, y, 0), 0); + foreach (var item in eable) + { + if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue) { - if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue) + if (length == 128) { - if (length == 128) - { - break; - } + break; + } - pool[length++] = item; + pool[length++] = item; + } + } + + eable.Free(); + + Array.Sort(pool, 0, length, ZComparer.Default); + } + + public void FixColumn(int x, int y) + { + var landTile = Tiles.GetLandTile(x, y); + var tiles = Tiles.GetStaticTiles(x, y, true); + + GetAverageZ(x, y, out _, out var landAvg, out _); + + var items = STArrayPool.Shared.Rent(128); + AcquireFixItems(this, x, y, items, out var length); + + for (var i = 0; i < length; i++) + { + var toFix = items[i]; + + if (!toFix.Movable) + { + continue; + } + + var z = int.MinValue; + var currentZ = toFix.Z; + + if (!landTile.Ignored && landAvg <= currentZ) + { + z = landAvg; + } + + foreach (var tile in tiles) + { + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + var checkZ = tile.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + { + ++checkTop; + } + + if (checkTop > z && checkTop <= currentZ) + { + z = checkTop; } } - eable.Free(); - - Array.Sort(pool, 0, length, ZComparer.Default); - } - - public void FixColumn(int x, int y) - { - var landTile = Tiles.GetLandTile(x, y); - var tiles = Tiles.GetStaticTiles(x, y, true); - - GetAverageZ(x, y, out _, out var landAvg, out _); - - var items = ArrayPool.Shared.Rent(128); - AcquireFixItems(this, x, y, items, out var length); - - for (var i = 0; i < length; i++) + for (var j = 0; j < length; ++j) { - var toFix = items[i]; - - if (!toFix.Movable) + if (j == i) { continue; } - var z = int.MinValue; - var currentZ = toFix.Z; + var item = items[j]; + var id = item.ItemData; - if (!landTile.Ignored && landAvg <= currentZ) + var checkZ = item.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) { - z = landAvg; + ++checkTop; } - foreach (var tile in tiles) + if (checkTop > z && checkTop <= currentZ) { - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - var checkZ = tile.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - { - ++checkTop; - } - - if (checkTop > z && checkTop <= currentZ) - { - z = checkTop; - } - } - - for (var j = 0; j < length; ++j) - { - if (j == i) - { - continue; - } - - var item = items[j]; - var id = item.ItemData; - - var checkZ = item.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - { - ++checkTop; - } - - if (checkTop > z && checkTop <= currentZ) - { - z = checkTop; - } - } - - if (z != int.MinValue) - { - toFix.Location = new Point3D(toFix.X, toFix.Y, z); + z = checkTop; } } - ArrayPool.Shared.Return(items, true); - } - - /* This could probably be re-implemented if necessary (perhaps via an ITile interface?). - public List GetTilesAt( Point2D p, bool items, bool land, bool statics ) - { - List list = new List(); - - if (this == Internal) - return list; - - if (land) - list.Add( Tiles.GetLandTile( p.m_X, p.m_Y ) ); - - if (statics) - list.AddRange( Tiles.GetStaticTiles( p.m_X, p.m_Y, true ) ); - - if (items) - { - Sector sector = GetSector( p ); - - foreach ( Item item in sector.Items ) - if (item.AtWorldPoint( p.m_X, p.m_Y )) - list.Add( new StaticTile( (ushort)item.ItemID, (sbyte) item.Z ) ); - } - - return list; - } - */ - - /// - /// Gets the highest surface that is lower than . - /// - /// The reference point. - /// A surface or . - public object GetTopSurface(Point3D p) - { - if (this == Internal) + if (z != int.MinValue) { - return null; + toFix.Location = new Point3D(toFix.X, toFix.Y, z); } + } - object surface = null; - var surfaceZ = int.MinValue; + STArrayPool.Shared.Return(items, true); + } - var lt = Tiles.GetLandTile(p.X, p.Y); + /* This could probably be re-implemented if necessary (perhaps via an ITile interface?). + public List GetTilesAt( Point2D p, bool items, bool land, bool statics ) + { + List list = new List(); - if (!lt.Ignored) + if (this == Internal) + return list; + + if (land) + list.Add( Tiles.GetLandTile( p.m_X, p.m_Y ) ); + + if (statics) + list.AddRange( Tiles.GetStaticTiles( p.m_X, p.m_Y, true ) ); + + if (items) + { + Sector sector = GetSector( p ); + + foreach ( Item item in sector.Items ) + if (item.AtWorldPoint( p.m_X, p.m_Y )) + list.Add( new StaticTile( (ushort)item.ItemID, (sbyte) item.Z ) ); + } + + return list; + } + */ + + /// + /// Gets the highest surface that is lower than . + /// + /// The reference point. + /// A surface or . + public object GetTopSurface(Point3D p) + { + if (this == Internal) + { + return null; + } + + object surface = null; + var surfaceZ = int.MinValue; + + var lt = Tiles.GetLandTile(p.X, p.Y); + + if (!lt.Ignored) + { + var avgZ = GetAverageZ(p.X, p.Y); + + if (avgZ <= p.Z) { - var avgZ = GetAverageZ(p.X, p.Y); + surface = lt; + surfaceZ = avgZ; - if (avgZ <= p.Z) + if (surfaceZ == p.Z) { - surface = lt; - surfaceZ = avgZ; + return surface; + } + } + } + + var staticTiles = Tiles.GetStaticTiles(p.X, p.Y, true); + + for (var i = 0; i < staticTiles.Length; i++) + { + var tile = staticTiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + if (id.Surface || id.Wet) + { + var tileZ = tile.Z + id.CalcHeight; + + if (tileZ > surfaceZ && tileZ <= p.Z) + { + surface = tile; + surfaceZ = tileZ; if (surfaceZ == p.Z) { @@ -753,22 +776,27 @@ namespace Server } } } + } - var staticTiles = Tiles.GetStaticTiles(p.X, p.Y, true); + var sector = GetSector(p.X, p.Y); - for (var i = 0; i < staticTiles.Length; i++) + for (var i = 0; i < sector.Items.Count; i++) + { + var item = sector.Items[i]; + + if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(p.X, p.Y) && + !item.Movable) { - var tile = staticTiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + var id = item.ItemData; if (id.Surface || id.Wet) { - var tileZ = tile.Z + id.CalcHeight; + var itemZ = item.Z + id.CalcHeight; - if (tileZ > surfaceZ && tileZ <= p.Z) + if (itemZ > surfaceZ && itemZ <= p.Z) { - surface = tile; - surfaceZ = tileZ; + surface = item; + surfaceZ = itemZ; if (surfaceZ == p.Z) { @@ -777,913 +805,884 @@ namespace Server } } } + } - var sector = GetSector(p.X, p.Y); + return surface; + } - for (var i = 0; i < sector.Items.Count; i++) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Bound(int x, int y, out int newX, out int newY) + { + newX = Math.Clamp(x, 0, Width - 1); + newY = Math.Clamp(y, 0, Height - 1); + } + + public Point2D Bound(Point3D p) + { + Bound(p.m_X, p.m_Y, out var x, out var y); + return new Point2D(x, y); + } + + public Point2D Bound(Point2D p) + { + Bound(p.m_X, p.m_Y, out var x, out var y); + return new Point2D(x, y); + } + + public void ActivateSectors(int cx, int cy) + { + for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) + { + for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) { - var item = sector.Items[i]; - - if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(p.X, p.Y) && - !item.Movable) + var sect = GetRealSector(x, y); + if (sect != InvalidSector) { - var id = item.ItemData; - - if (id.Surface || id.Wet) - { - var itemZ = item.Z + id.CalcHeight; - - if (itemZ > surfaceZ && itemZ <= p.Z) - { - surface = item; - surfaceZ = itemZ; - - if (surfaceZ == p.Z) - { - return surface; - } - } - } + sect.Activate(); } } - - return surface; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Bound(int x, int y, out int newX, out int newY) + public void DeactivateSectors(int cx, int cy) + { + for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) { - newX = Math.Clamp(x, 0, Width - 1); - newY = Math.Clamp(y, 0, Height - 1); - } - - public Point2D Bound(Point3D p) - { - Bound(p.m_X, p.m_Y, out var x, out var y); - return new Point2D(x, y); - } - - public Point2D Bound(Point2D p) - { - Bound(p.m_X, p.m_Y, out var x, out var y); - return new Point2D(x, y); - } - - public void ActivateSectors(int cx, int cy) - { - for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) + for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) { - for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) + var sect = GetRealSector(x, y); + if (sect != InvalidSector && !PlayersInRange(sect, SectorActiveRange)) { - var sect = GetRealSector(x, y); - if (sect != InvalidSector) - { - sect.Activate(); - } + sect.Deactivate(); + } + } + } + } + + private bool PlayersInRange(Sector sect, int range) + { + for (var x = sect.X - range; x <= sect.X + range; ++x) + { + for (var y = sect.Y - range; y <= sect.Y + range; ++y) + { + var check = GetRealSector(x, y); + if (check != InvalidSector && check.Clients.Count > 0) + { + return true; } } } - public void DeactivateSectors(int cx, int cy) + return false; + } + + public void OnClientChange(NetState oldState, NetState newState, Mobile m) + { + if (this != Internal) { - for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) - { - for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) - { - var sect = GetRealSector(x, y); - if (sect != InvalidSector && !PlayersInRange(sect, SectorActiveRange)) - { - sect.Deactivate(); - } - } - } + GetSector(m.Location).OnClientChange(oldState, newState); + } + } + + public void OnEnter(Mobile m) + { + if (this != Internal) + { + GetSector(m.Location).OnEnter(m); + } + } + + public void OnEnter(Item item) + { + if (this == Internal) + { + return; } - private bool PlayersInRange(Sector sect, int range) - { - for (var x = sect.X - range; x <= sect.X + range; ++x) - { - for (var y = sect.Y - range; y <= sect.Y + range; ++y) - { - var check = GetRealSector(x, y); - if (check != InvalidSector && check.Clients.Count > 0) - { - return true; - } - } - } + GetSector(item.Location).OnEnter(item); - return false; + if (item is BaseMulti m) + { + var mcl = m.Components; + + var start = GetMultiMinSector(m.Location, mcl); + var end = GetMultiMaxSector(m.Location, mcl); + + AddMulti(m, start, end); + } + } + + public void OnLeave(Mobile m) + { + if (this != Internal) + { + GetSector(m.Location).OnLeave(m); + } + } + + public void OnLeave(Item item) + { + if (this == Internal) + { + return; } - public void OnClientChange(NetState oldState, NetState newState, Mobile m) + GetSector(item.Location).OnLeave(item); + + if (item is BaseMulti m) { - if (this != Internal) - { - GetSector(m.Location).OnClientChange(oldState, newState); - } + var mcl = m.Components; + + var start = GetMultiMinSector(m.Location, mcl); + var end = GetMultiMaxSector(m.Location, mcl); + + RemoveMulti(m, start, end); + } + } + + public void RemoveMulti(BaseMulti m, Sector start, Sector end) + { + if (this == Internal) + { + return; } - public void OnEnter(Mobile m) + for (var x = start.X; x <= end.X; ++x) { - if (this != Internal) + for (var y = start.Y; y <= end.Y; ++y) { - GetSector(m.Location).OnEnter(m); + InternalGetSector(x, y).OnMultiLeave(m); } } + } - public void OnEnter(Item item) + public void AddMulti(BaseMulti m, Sector start, Sector end) + { + if (this == Internal) { - if (this == Internal) + return; + } + + for (var x = start.X; x <= end.X; ++x) + { + for (var y = start.Y; y <= end.Y; ++y) { - return; + InternalGetSector(x, y).OnMultiEnter(m); } + } + } - GetSector(item.Location).OnEnter(item); + public Sector GetMultiMinSector(Point3D loc, MultiComponentList mcl) => + GetSector(Bound(new Point2D(loc.m_X + mcl.Min.m_X, loc.m_Y + mcl.Min.m_Y))); - if (item is BaseMulti m) + public Sector GetMultiMaxSector(Point3D loc, MultiComponentList mcl) => + GetSector(Bound(new Point2D(loc.m_X + mcl.Max.m_X, loc.m_Y + mcl.Max.m_Y))); + + public void OnMove(Point3D oldLocation, Mobile m) + { + if (this == Internal) + { + return; + } + + var oldSector = GetSector(oldLocation); + var newSector = GetSector(m.Location); + + if (oldSector != newSector) + { + oldSector.OnLeave(m); + newSector.OnEnter(m); + } + } + + public void OnMove(Point3D oldLocation, Item item) + { + if (this == Internal) + { + return; + } + + var oldSector = GetSector(oldLocation); + var newSector = GetSector(item.Location); + + if (oldSector != newSector) + { + oldSector.OnLeave(item); + newSector.OnEnter(item); + } + + if (item is BaseMulti m) + { + var mcl = m.Components; + + var start = GetMultiMinSector(m.Location, mcl); + var end = GetMultiMaxSector(m.Location, mcl); + + var oldStart = GetMultiMinSector(oldLocation, mcl); + var oldEnd = GetMultiMaxSector(oldLocation, mcl); + + if (oldStart != start || oldEnd != end) { - var mcl = m.Components; - - var start = GetMultiMinSector(m.Location, mcl); - var end = GetMultiMaxSector(m.Location, mcl); - + RemoveMulti(m, oldStart, oldEnd); AddMulti(m, start, end); } } + } - public void OnLeave(Mobile m) + public void RegisterRegion(Region reg) + { + var regName = reg.Name; + + if (regName == null) { - if (this != Internal) - { - GetSector(m.Location).OnLeave(m); - } + return; } - public void OnLeave(Item item) + if (Regions.ContainsKey(regName)) { - if (this == Internal) - { - return; - } + Logger.Warning($"Duplicate region name '{regName}' for map '{Name}'"); + } + else + { + Regions[regName] = reg; + } + } - GetSector(item.Location).OnLeave(item); + public void UnregisterRegion(Region reg) + { + var regName = reg.Name; - if (item is BaseMulti m) - { - var mcl = m.Components; + if (regName != null) + { + Regions.Remove(regName); + } + } - var start = GetMultiMinSector(m.Location, mcl); - var end = GetMultiMaxSector(m.Location, mcl); + public Point3D GetPoint(object o, bool eye) + { + Point3D p; - RemoveMulti(m, start, end); - } + if (o is Mobile mobile) + { + p = mobile.Location; + p.Z += 14; // eye ? 15 : 10; + } + else if (o is Item item) + { + p = item.GetWorldLocation(); + p.Z += item.ItemData.Height / 2 + 1; + } + else if (o is Point3D point3D) + { + p = point3D; + } + else if (o is LandTarget target) + { + p = target.Location; + + GetAverageZ(p.X, p.Y, out _, out _, out var top); + + p.Z = top + 1; + } + else if (o is StaticTarget st) + { + var id = TileData.ItemTable[st.ItemID & TileData.MaxItemValue]; + + p = new Point3D(st.X, st.Y, st.Z - id.CalcHeight + id.Height / 2 + 1); + } + else if (o is IPoint3D d) + { + p = new Point3D(d.X, d.Y, d.Z); + } + else + { + Logger.Warning($"Warning: Invalid object ({o}) in line of sight"); + p = Point3D.Zero; } - public void RemoveMulti(BaseMulti m, Sector start, Sector end) - { - if (this == Internal) - { - return; - } + return p; + } - for (var x = start.X; x <= end.X; ++x) - { - for (var y = start.Y; y <= end.Y; ++y) - { - InternalGetSector(x, y).OnMultiLeave(m); - } - } + public IPooledEnumerable GetObjectsInRange(Point3D p) => GetObjectsInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetObjectsInRange(Point3D p, int range) => + GetObjectsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public IPooledEnumerable GetObjectsInBounds(Rectangle2D bounds) => + PooledEnumeration.GetEntities(this, bounds); + + public IPooledEnumerable GetClientsInRange(Point3D p) => GetClientsInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetClientsInRange(Point3D p, int range) => + GetClientsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) => + PooledEnumeration.GetClients(this, bounds); + + public IPooledEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); + + public IPooledEnumerable GetItemsInRange(Point3D p, int range) where T : Item => + GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); + + public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) where T : Item => + PooledEnumeration.GetItems(this, bounds); + + public IPooledEnumerable GetMobilesInRange(Point3D p) => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetMobilesInRange(Point3D p, int range) => GetMobilesInRange(p, range); + + public IPooledEnumerable GetMobilesInRange(Point3D p, int range) where T : Mobile => + GetMobilesInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) => GetMobilesInBounds(bounds); + + public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) where T : Mobile => + PooledEnumeration.GetMobiles(this, bounds); + + public bool CanFit( + Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true, + bool requireSurface = true + ) => + CanFit(p.m_X, p.m_Y, p.m_Z, height, checkBlocksFit, checkMobiles, requireSurface); + + public bool CanFit( + Point2D p, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, + bool requireSurface = true + ) => + CanFit(p.m_X, p.m_Y, z, height, checkBlocksFit, checkMobiles, requireSurface); + + public bool CanFit( + int x, int y, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, + bool requireSurface = true + ) + { + if (this == Internal) + { + return false; } - public void AddMulti(BaseMulti m, Sector start, Sector end) + if (x < 0 || y < 0 || x >= Width || y >= Height) { - if (this == Internal) - { - return; - } - - for (var x = start.X; x <= end.X; ++x) - { - for (var y = start.Y; y <= end.Y; ++y) - { - InternalGetSector(x, y).OnMultiEnter(m); - } - } + return false; } - public Sector GetMultiMinSector(Point3D loc, MultiComponentList mcl) => - GetSector(Bound(new Point2D(loc.m_X + mcl.Min.m_X, loc.m_Y + mcl.Min.m_Y))); + var hasSurface = false; - public Sector GetMultiMaxSector(Point3D loc, MultiComponentList mcl) => - GetSector(Bound(new Point2D(loc.m_X + mcl.Max.m_X, loc.m_Y + mcl.Max.m_Y))); + var lt = Tiles.GetLandTile(x, y); + GetAverageZ(x, y, out var lowZ, out var avgZ, out _); + var landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags; - public void OnMove(Point3D oldLocation, Mobile m) + if ((landFlags & TileFlag.Impassable) != 0 && avgZ > z && z + height > lowZ) { - if (this == Internal) - { - return; - } - - var oldSector = GetSector(oldLocation); - var newSector = GetSector(m.Location); - - if (oldSector != newSector) - { - oldSector.OnLeave(m); - newSector.OnEnter(m); - } + return false; } - public void OnMove(Point3D oldLocation, Item item) + if ((landFlags & TileFlag.Impassable) == 0 && z == avgZ && !lt.Ignored) { - if (this == Internal) - { - return; - } - - var oldSector = GetSector(oldLocation); - var newSector = GetSector(item.Location); - - if (oldSector != newSector) - { - oldSector.OnLeave(item); - newSector.OnEnter(item); - } - - if (item is BaseMulti m) - { - var mcl = m.Components; - - var start = GetMultiMinSector(m.Location, mcl); - var end = GetMultiMaxSector(m.Location, mcl); - - var oldStart = GetMultiMinSector(oldLocation, mcl); - var oldEnd = GetMultiMaxSector(oldLocation, mcl); - - if (oldStart != start || oldEnd != end) - { - RemoveMulti(m, oldStart, oldEnd); - AddMulti(m, start, end); - } - } + hasSurface = true; } - public void RegisterRegion(Region reg) + var staticTiles = Tiles.GetStaticTiles(x, y, true); + + bool surface, impassable; + + for (var i = 0; i < staticTiles.Length; ++i) { - var regName = reg.Name; + var id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue]; + surface = id.Surface; + impassable = id.Impassable; - if (regName == null) - { - return; - } - - if (Regions.ContainsKey(regName)) - { - Logger.Warning($"Duplicate region name '{regName}' for map '{Name}'"); - } - else - { - Regions[regName] = reg; - } - } - - public void UnregisterRegion(Region reg) - { - var regName = reg.Name; - - if (regName != null) - { - Regions.Remove(regName); - } - } - - public Point3D GetPoint(object o, bool eye) - { - Point3D p; - - if (o is Mobile mobile) - { - p = mobile.Location; - p.Z += 14; // eye ? 15 : 10; - } - else if (o is Item item) - { - p = item.GetWorldLocation(); - p.Z += item.ItemData.Height / 2 + 1; - } - else if (o is Point3D point3D) - { - p = point3D; - } - else if (o is LandTarget target) - { - p = target.Location; - - GetAverageZ(p.X, p.Y, out _, out _, out var top); - - p.Z = top + 1; - } - else if (o is StaticTarget st) - { - var id = TileData.ItemTable[st.ItemID & TileData.MaxItemValue]; - - p = new Point3D(st.X, st.Y, st.Z - id.CalcHeight + id.Height / 2 + 1); - } - else if (o is IPoint3D d) - { - p = new Point3D(d.X, d.Y, d.Z); - } - else - { - Logger.Warning($"Warning: Invalid object ({o}) in line of sight"); - p = Point3D.Zero; - } - - return p; - } - - public IPooledEnumerable GetObjectsInRange(Point3D p) => GetObjectsInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetObjectsInRange(Point3D p, int range) => - GetObjectsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetObjectsInBounds(Rectangle2D bounds) => - PooledEnumeration.GetEntities(this, bounds); - - public IPooledEnumerable GetClientsInRange(Point3D p) => GetClientsInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetClientsInRange(Point3D p, int range) => - GetClientsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) => - PooledEnumeration.GetClients(this, bounds); - - public IPooledEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); - - public IPooledEnumerable GetItemsInRange(Point3D p, int range) where T : Item => - GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); - - public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) where T : Item => - PooledEnumeration.GetItems(this, bounds); - - public IPooledEnumerable GetMobilesInRange(Point3D p) => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetMobilesInRange(Point3D p, int range) => GetMobilesInRange(p, range); - - public IPooledEnumerable GetMobilesInRange(Point3D p, int range) where T : Mobile => - GetMobilesInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) => GetMobilesInBounds(bounds); - - public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) where T : Mobile => - PooledEnumeration.GetMobiles(this, bounds); - - public bool CanFit( - Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true, - bool requireSurface = true - ) => - CanFit(p.m_X, p.m_Y, p.m_Z, height, checkBlocksFit, checkMobiles, requireSurface); - - public bool CanFit( - Point2D p, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, - bool requireSurface = true - ) => - CanFit(p.m_X, p.m_Y, z, height, checkBlocksFit, checkMobiles, requireSurface); - - public bool CanFit( - int x, int y, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, - bool requireSurface = true - ) - { - if (this == Internal) + if ((surface || impassable) && staticTiles[i].Z + id.CalcHeight > z && z + height > staticTiles[i].Z) { return false; } - if (x < 0 || y < 0 || x >= Width || y >= Height) - { - return false; - } - - var hasSurface = false; - - var lt = Tiles.GetLandTile(x, y); - GetAverageZ(x, y, out var lowZ, out var avgZ, out _); - var landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags; - - if ((landFlags & TileFlag.Impassable) != 0 && avgZ > z && z + height > lowZ) - { - return false; - } - - if ((landFlags & TileFlag.Impassable) == 0 && z == avgZ && !lt.Ignored) + if (surface && !impassable && z == staticTiles[i].Z + id.CalcHeight) { hasSurface = true; } + } - var staticTiles = Tiles.GetStaticTiles(x, y, true); + var sector = GetSector(x, y); + var items = sector.Items; + var mobs = sector.Mobiles; - bool surface, impassable; + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; - for (var i = 0; i < staticTiles.Length; ++i) + if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y)) { - var id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue]; + var id = item.ItemData; surface = id.Surface; impassable = id.Impassable; - if ((surface || impassable) && staticTiles[i].Z + id.CalcHeight > z && z + height > staticTiles[i].Z) + if ((surface || impassable || checkBlocksFit && item.BlocksFit) && item.Z + id.CalcHeight > z && + z + height > item.Z) { return false; } - if (surface && !impassable && z == staticTiles[i].Z + id.CalcHeight) + if (surface && !impassable && !item.Movable && z == item.Z + id.CalcHeight) { hasSurface = true; } } - - var sector = GetSector(x, y); - var items = sector.Items; - var mobs = sector.Mobiles; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y)) - { - var id = item.ItemData; - surface = id.Surface; - impassable = id.Impassable; - - if ((surface || impassable || checkBlocksFit && item.BlocksFit) && item.Z + id.CalcHeight > z && - z + height > item.Z) - { - return false; - } - - if (surface && !impassable && !item.Movable && z == item.Z + id.CalcHeight) - { - hasSurface = true; - } - } - } - - if (checkMobiles) - { - for (var i = 0; i < mobs.Count; ++i) - { - var m = mobs[i]; - - if (m.Location.m_X == x && m.Location.m_Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden) && - m.Z + 16 > z && z + height > m.Z) - { - return false; - } - } - } - - return !requireSurface || hasSurface; } - public bool CanSpawnMobile(Point3D p) => CanSpawnMobile(p.m_X, p.m_Y, p.m_Z); - - public bool CanSpawnMobile(Point2D p, int z) => CanSpawnMobile(p.m_X, p.m_Y, z); - - public bool CanSpawnMobile(int x, int y, int z) => - Region.Find(new Point3D(x, y, z), this).AllowSpawn() && CanFit(x, y, z, 16); - - private class ZComparer : IComparer + if (checkMobiles) { - public static readonly ZComparer Default = new(); - - public int Compare(Item x, Item y) => x!.Z.CompareTo(y!.Z); - } - - public Sector GetSector(Point3D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); - - public Sector GetSector(Point2D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); - - // public Sector GetSector(IPoint2D p) => InternalGetSector(p.X >> SectorShift, p.Y >> SectorShift); - - public Sector GetSector(int x, int y) => InternalGetSector(x >> SectorShift, y >> SectorShift); - - public Sector GetRealSector(int x, int y) => InternalGetSector(x, y); - - private Sector InternalGetSector(int x, int y) - { - if (x >= 0 && x < m_SectorsWidth && y >= 0 && y < m_SectorsHeight) + for (var i = 0; i < mobs.Count; ++i) { - var xSectors = m_Sectors[x]; + var m = mobs[i]; - if (xSectors == null) - { - m_Sectors[x] = xSectors = new Sector[m_SectorsHeight]; - } - - var sec = xSectors[y]; - - if (sec == null) - { - xSectors[y] = sec = new Sector(x, y, this); - } - - return sec; - } - - return InvalidSector; - } - - public bool LineOfSight(Point3D org, Point3D dest) - { - if (this == Internal) - { - return false; - } - - if (!Utility.InRange(org, dest, MaxLOSDistance)) - { - return false; - } - - var end = dest; - - if (org.X > dest.X || org.X == dest.X && org.Y > dest.Y || org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z) - { - (org, dest) = (dest, org); - } - - int height; - Point3D p; - var path = new Point3DList(); - TileFlag flags; - - if (org == dest) - { - return true; - } - - if (path.Count > 0) - { - path.Clear(); - } - - var xd = dest.m_X - org.m_X; - var yd = dest.m_Y - org.m_Y; - var zd = dest.m_Z - org.m_Z; - var zslp = Math.Sqrt(xd * xd + yd * yd); - var sq3d = zd != 0 ? Math.Sqrt(zslp * zslp + zd * zd) : zslp; - - var rise = yd / sq3d; - var run = xd / sq3d; - zslp = zd / sq3d; - - double y = org.m_Y; - double z = org.m_Z; - double x = org.m_X; - while (Utility.NumberBetween(x, dest.m_X, org.m_X, 0.5) && Utility.NumberBetween(y, dest.m_Y, org.m_Y, 0.5) && - Utility.NumberBetween(z, dest.m_Z, org.m_Z, 0.5)) - { - var ix = (int)Math.Round(x); - var iy = (int)Math.Round(y); - var iz = (int)Math.Round(z); - if (path.Count > 0) - { - p = path.Last; - - if (p.m_X != ix || p.m_Y != iy || p.m_Z != iz) - { - path.Add(ix, iy, iz); - } - } - else - { - path.Add(ix, iy, iz); - } - - x += run; - y += rise; - z += zslp; - } - - if (path.Count == 0) - { - return true; // <--should never happen, but to be safe. - } - - p = path.Last; - - if (p != dest) - { - path.Add(dest); - } - - Point3D pTop = org, pBottom = dest; - Utility.FixPoints(ref pTop, ref pBottom); - - var pathCount = path.Count; - var endTop = end.m_Z + 1; - - for (var i = 0; i < pathCount; ++i) - { - var point = path[i]; - var pointTop = point.m_Z + 1; - - var landTile = Tiles.GetLandTile(point.X, point.Y); - GetAverageZ(point.m_X, point.m_Y, out var landZ, out _, out var landTop); - - if (landZ <= pointTop && landTop >= point.m_Z && - (point.m_X != end.m_X || point.m_Y != end.m_Y || landZ > endTop || landTop < end.m_Z) && - !landTile.Ignored) + if (m.Location.m_X == x && m.Location.m_Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden) && + m.Z + 16 > z && z + height > m.Z) { return false; } - - /* --Do land tiles need to be checked? There is never land between two people, always statics.-- - LandTile landTile = Tiles.GetLandTile( point.X, point.Y ); - if (landTile.Z-1 >= point.Z && landTile.Z+1 <= point.Z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Impassable) != 0) - return false; - */ - - var statics = Tiles.GetStaticTiles(point.m_X, point.m_Y, true); - - var contains = false; - var ltID = landTile.ID; - - for (var j = 0; !contains && j < InvalidLandTiles.Length; ++j) - { - contains = ltID == InvalidLandTiles[j]; - } - - if (contains && statics.Length == 0) - { - var eable = GetItemsInRange(point, 0); - - foreach (Item item in eable) - { - if (item.Visible) - { - contains = false; - break; - } - } - - eable.Free(); - - if (contains) - { - return false; - } - } - - for (var j = 0; j < statics.Length; ++j) - { - var t = statics[j]; - - var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; - - flags = id.Flags; - height = id.CalcHeight; - - if (t.Z <= pointTop && t.Z + height >= point.Z && (flags & (TileFlag.Window | TileFlag.NoShoot)) != 0) - { - if (point.m_X == end.m_X && point.m_Y == end.m_Y && t.Z <= endTop && t.Z + height >= end.m_Z) - { - continue; - } - - return false; - } - } } + } - var rect = new Rectangle2D(pTop.m_X, pTop.m_Y, pBottom.m_X - pTop.m_X + 1, pBottom.m_Y - pTop.m_Y + 1); + return !requireSurface || hasSurface; + } - var area = GetItemsInBounds(rect); + public bool CanSpawnMobile(Point3D p) => CanSpawnMobile(p.m_X, p.m_Y, p.m_Z); - foreach (var i in area) + public bool CanSpawnMobile(Point2D p, int z) => CanSpawnMobile(p.m_X, p.m_Y, z); + + public bool CanSpawnMobile(int x, int y, int z) => + Region.Find(new Point3D(x, y, z), this).AllowSpawn() && CanFit(x, y, z, 16); + + private class ZComparer : IComparer + { + public static readonly ZComparer Default = new(); + + public int Compare(Item x, Item y) => x!.Z.CompareTo(y!.Z); + } + + public Sector GetSector(Point3D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); + + public Sector GetSector(Point2D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); + + // public Sector GetSector(IPoint2D p) => InternalGetSector(p.X >> SectorShift, p.Y >> SectorShift); + + public Sector GetSector(int x, int y) => InternalGetSector(x >> SectorShift, y >> SectorShift); + + public Sector GetRealSector(int x, int y) => InternalGetSector(x, y); + + private Sector InternalGetSector(int x, int y) + { + if (x >= 0 && x < m_SectorsWidth && y >= 0 && y < m_SectorsHeight) + { + var xSectors = m_Sectors[x]; + + if (xSectors == null) { - if (!i.Visible) - { - continue; - } - - if (i is BaseMulti || i.ItemID > TileData.MaxItemValue) - { - continue; - } - - var id = i.ItemData; - flags = id.Flags; - - if ((flags & (TileFlag.Window | TileFlag.NoShoot)) == 0) - { - continue; - } - - height = id.CalcHeight; - - var found = false; - - var count = path.Count; - - for (var j = 0; j < count; ++j) - { - var point = path[j]; - var pointTop = point.m_Z + 1; - var loc = i.Location; - - // if (t.Z <= point.Z && t.Z+height >= point.Z && ( height != 0 || ( t.Z == dest.Z && zd != 0 ) )) - if (loc.m_X == point.m_X && loc.m_Y == point.m_Y && loc.m_Z <= pointTop && loc.m_Z + height >= point.m_Z) - { - if (loc.m_X != end.m_X || loc.m_Y != end.m_Y || loc.m_Z > endTop || loc.m_Z + height < end.m_Z) - { - found = true; - break; - } - } - } - - if (!found) - { - continue; - } - - area.Free(); - return false; + m_Sectors[x] = xSectors = new Sector[m_SectorsHeight]; } - area.Free(); + var sec = xSectors[y]; + + if (sec == null) + { + xSectors[y] = sec = new Sector(x, y, this); + } + + return sec; + } + + return InvalidSector; + } + + public bool LineOfSight(Point3D org, Point3D dest) + { + if (this == Internal) + { + return false; + } + + if (!Utility.InRange(org, dest, MaxLOSDistance)) + { + return false; + } + + var end = dest; + + if (org.X > dest.X || org.X == dest.X && org.Y > dest.Y || org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z) + { + (org, dest) = (dest, org); + } + + int height; + Point3D p; + var path = new Point3DList(); + TileFlag flags; + + if (org == dest) + { return true; } - public bool LineOfSight(object from, object dest) => - from == dest || (from as Mobile)?.AccessLevel > AccessLevel.Player || - (dest as Item)?.RootParent == from || LineOfSight(GetPoint(from, true), GetPoint(dest, false)); - - public bool LineOfSight(Mobile from, Point3D target) + if (path.Count > 0) { - if (from.AccessLevel > AccessLevel.Player) - { - return true; - } - - var eye = from.Location; - - eye.Z += 14; - - return LineOfSight(eye, target); + path.Clear(); } - public bool LineOfSight(Mobile from, Mobile to) + var xd = dest.m_X - org.m_X; + var yd = dest.m_Y - org.m_Y; + var zd = dest.m_Z - org.m_Z; + var zslp = Math.Sqrt(xd * xd + yd * yd); + var sq3d = zd != 0 ? Math.Sqrt(zslp * zslp + zd * zd) : zslp; + + var rise = yd / sq3d; + var run = xd / sq3d; + zslp = zd / sq3d; + + double y = org.m_Y; + double z = org.m_Z; + double x = org.m_X; + while (Utility.NumberBetween(x, dest.m_X, org.m_X, 0.5) && Utility.NumberBetween(y, dest.m_Y, org.m_Y, 0.5) && + Utility.NumberBetween(z, dest.m_Z, org.m_Z, 0.5)) { - if (from == to || from.AccessLevel > AccessLevel.Player) + var ix = (int)Math.Round(x); + var iy = (int)Math.Round(y); + var iz = (int)Math.Round(z); + if (path.Count > 0) { - return true; + p = path.Last; + + if (p.m_X != ix || p.m_Y != iy || p.m_Z != iz) + { + path.Add(ix, iy, iz); + } + } + else + { + path.Add(ix, iy, iz); } - var eye = from.Location; - var target = to.Location; - - eye.Z += 14; - target.Z += 14; // 10; - - return LineOfSight(eye, target); + x += run; + y += rise; + z += zslp; } - public Point3D GetRandomNearbyLocation( - Point3D loc, int maxRange = 2, int minRange = 0, int retryCount = 10, - int height = 16, bool checkBlocksFit = false, - bool checkMobiles = false + if (path.Count == 0) + { + return true; // <--should never happen, but to be safe. + } + + p = path.Last; + + if (p != dest) + { + path.Add(dest); + } + + Point3D pTop = org, pBottom = dest; + Utility.FixPoints(ref pTop, ref pBottom); + + var pathCount = path.Count; + var endTop = end.m_Z + 1; + + for (var i = 0; i < pathCount; ++i) + { + var point = path[i]; + var pointTop = point.m_Z + 1; + + var landTile = Tiles.GetLandTile(point.X, point.Y); + GetAverageZ(point.m_X, point.m_Y, out var landZ, out _, out var landTop); + + if (landZ <= pointTop && landTop >= point.m_Z && + (point.m_X != end.m_X || point.m_Y != end.m_Y || landZ > endTop || landTop < end.m_Z) && + !landTile.Ignored) + { + return false; + } + + /* --Do land tiles need to be checked? There is never land between two people, always statics.-- + LandTile landTile = Tiles.GetLandTile( point.X, point.Y ); + if (landTile.Z-1 >= point.Z && landTile.Z+1 <= point.Z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Impassable) != 0) + return false; + */ + + var statics = Tiles.GetStaticTiles(point.m_X, point.m_Y, true); + + var contains = false; + var ltID = landTile.ID; + + for (var j = 0; !contains && j < InvalidLandTiles.Length; ++j) + { + contains = ltID == InvalidLandTiles[j]; + } + + if (contains && statics.Length == 0) + { + var eable = GetItemsInRange(point, 0); + + foreach (Item item in eable) + { + if (item.Visible) + { + contains = false; + break; + } + } + + eable.Free(); + + if (contains) + { + return false; + } + } + + for (var j = 0; j < statics.Length; ++j) + { + var t = statics[j]; + + var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; + + flags = id.Flags; + height = id.CalcHeight; + + if (t.Z <= pointTop && t.Z + height >= point.Z && (flags & (TileFlag.Window | TileFlag.NoShoot)) != 0) + { + if (point.m_X == end.m_X && point.m_Y == end.m_Y && t.Z <= endTop && t.Z + height >= end.m_Z) + { + continue; + } + + return false; + } + } + } + + var rect = new Rectangle2D(pTop.m_X, pTop.m_Y, pBottom.m_X - pTop.m_X + 1, pBottom.m_Y - pTop.m_Y + 1); + + var area = GetItemsInBounds(rect); + + foreach (var i in area) + { + if (!i.Visible) + { + continue; + } + + if (i is BaseMulti || i.ItemID > TileData.MaxItemValue) + { + continue; + } + + var id = i.ItemData; + flags = id.Flags; + + if ((flags & (TileFlag.Window | TileFlag.NoShoot)) == 0) + { + continue; + } + + height = id.CalcHeight; + + var found = false; + + var count = path.Count; + + for (var j = 0; j < count; ++j) + { + var point = path[j]; + var pointTop = point.m_Z + 1; + var loc = i.Location; + + // if (t.Z <= point.Z && t.Z+height >= point.Z && ( height != 0 || ( t.Z == dest.Z && zd != 0 ) )) + if (loc.m_X == point.m_X && loc.m_Y == point.m_Y && loc.m_Z <= pointTop && loc.m_Z + height >= point.m_Z) + { + if (loc.m_X != end.m_X || loc.m_Y != end.m_Y || loc.m_Z > endTop || loc.m_Z + height < end.m_Z) + { + found = true; + break; + } + } + } + + if (!found) + { + continue; + } + + area.Free(); + return false; + } + + area.Free(); + return true; + } + + public bool LineOfSight(object from, object dest) => + from == dest || (from as Mobile)?.AccessLevel > AccessLevel.Player || + (dest as Item)?.RootParent == from || LineOfSight(GetPoint(from, true), GetPoint(dest, false)); + + public bool LineOfSight(Mobile from, Point3D target) + { + if (from.AccessLevel > AccessLevel.Player) + { + return true; + } + + var eye = from.Location; + + eye.Z += 14; + + return LineOfSight(eye, target); + } + + public bool LineOfSight(Mobile from, Mobile to) + { + if (from == to || from.AccessLevel > AccessLevel.Player) + { + return true; + } + + var eye = from.Location; + var target = to.Location; + + eye.Z += 14; + target.Z += 14; // 10; + + return LineOfSight(eye, target); + } + + public Point3D GetRandomNearbyLocation( + Point3D loc, int maxRange = 2, int minRange = 0, int retryCount = 10, + int height = 16, bool checkBlocksFit = false, + bool checkMobiles = false + ) + { + var j = 0; + var range = maxRange - minRange; + var locs = range <= 10 ? new bool[range + 1, range + 1] : null; + + do + { + var xRand = Utility.Random(range); + var yRand = Utility.Random(range); + + if (locs?[xRand, yRand] != true) + { + var x = loc.X + xRand + minRange; + var y = loc.Y + yRand + minRange; + + if (CanFit(x, y, loc.Z, height, checkBlocksFit, checkMobiles)) + { + loc = new Point3D(x, y, loc.Z); + break; + } + + var z = GetAverageZ(x, y); + + if (CanFit(x, y, z, height, checkBlocksFit, checkMobiles)) + { + loc = new Point3D(x, y, z); + break; + } + + if (locs != null) + { + locs[xRand, yRand] = true; + } + } + + j++; + } while (j < retryCount); + + return loc; + } + + public class NullEnumerable : IPooledEnumerable + { + public static readonly NullEnumerable Instance = new(); + + private readonly IEnumerable m_Empty = Enumerable.Empty(); + + IEnumerator IEnumerable.GetEnumerator() => m_Empty.GetEnumerator(); + + public IEnumerator GetEnumerator() => m_Empty.GetEnumerator(); + + public void Free() + { + } + } + + public sealed class PooledEnumerable : IPooledEnumerable, IDisposable + { + private static readonly Queue> _Buffer = new(0x400); + + private bool m_IsDisposed; + + private List m_Pool = new(0x40); + + public PooledEnumerable(IEnumerable pool) + { + m_Pool.AddRange(pool); + } + + public void Dispose() + { + m_IsDisposed = true; + + m_Pool.Clear(); + m_Pool.TrimExcess(); + m_Pool = null; + } + + IEnumerator IEnumerable.GetEnumerator() => m_Pool.GetEnumerator(); + + public IEnumerator GetEnumerator() => m_Pool.GetEnumerator(); + + public void Free() + { + if (m_IsDisposed) + { + return; + } + + m_Pool.Clear(); + m_Pool.Capacity = Math.Max(m_Pool.Capacity, 0x100); + + lock (((ICollection)_Buffer).SyncRoot) + { + _Buffer.Enqueue(this); + } + } +#pragma warning disable CA1000 // Do not declare static members on generic types + public static PooledEnumerable Instantiate( + Map map, Rectangle2D bounds, PooledEnumeration.Selector selector ) { - var j = 0; - var range = maxRange - minRange; - var locs = range <= 10 ? new bool[range + 1, range + 1] : null; + PooledEnumerable e = null; - do + lock (((ICollection)_Buffer).SyncRoot) { - var xRand = Utility.Random(range); - var yRand = Utility.Random(range); - - if (locs?[xRand, yRand] != true) + if (_Buffer.Count > 0) { - var x = loc.X + xRand + minRange; - var y = loc.Y + yRand + minRange; - - if (CanFit(x, y, loc.Z, height, checkBlocksFit, checkMobiles)) - { - loc = new Point3D(x, y, loc.Z); - break; - } - - var z = GetAverageZ(x, y); - - if (CanFit(x, y, z, height, checkBlocksFit, checkMobiles)) - { - loc = new Point3D(x, y, z); - break; - } - - if (locs != null) - { - locs[xRand, yRand] = true; - } + e = _Buffer.Dequeue(); } + } - j++; - } while (j < retryCount); + var pool = PooledEnumeration.EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); - return loc; + if (e == null) + { + return new PooledEnumerable(pool); + } + + e.m_Pool.AddRange(pool); + return e; } - - public class NullEnumerable : IPooledEnumerable - { - public static readonly NullEnumerable Instance = new(); - - private readonly IEnumerable m_Empty = Enumerable.Empty(); - - IEnumerator IEnumerable.GetEnumerator() => m_Empty.GetEnumerator(); - - public IEnumerator GetEnumerator() => m_Empty.GetEnumerator(); - - public void Free() - { - } - } - - public sealed class PooledEnumerable : IPooledEnumerable, IDisposable - { - private static readonly Queue> _Buffer = new(0x400); - - private bool m_IsDisposed; - - private List m_Pool = new(0x40); - - public PooledEnumerable(IEnumerable pool) - { - m_Pool.AddRange(pool); - } - - public void Dispose() - { - m_IsDisposed = true; - - m_Pool.Clear(); - m_Pool.TrimExcess(); - m_Pool = null; - } - - IEnumerator IEnumerable.GetEnumerator() => m_Pool.GetEnumerator(); - - public IEnumerator GetEnumerator() => m_Pool.GetEnumerator(); - - public void Free() - { - if (m_IsDisposed) - { - return; - } - - m_Pool.Clear(); - m_Pool.Capacity = Math.Max(m_Pool.Capacity, 0x100); - - lock (((ICollection)_Buffer).SyncRoot) - { - _Buffer.Enqueue(this); - } - } -#pragma warning disable CA1000 // Do not declare static members on generic types - public static PooledEnumerable Instantiate( - Map map, Rectangle2D bounds, PooledEnumeration.Selector selector - ) - { - PooledEnumerable e = null; - - lock (((ICollection)_Buffer).SyncRoot) - { - if (_Buffer.Count > 0) - { - e = _Buffer.Dequeue(); - } - } - - var pool = PooledEnumeration.EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); - - if (e == null) - { - return new PooledEnumerable(pool); - } - - e.m_Pool.AddRange(pool); - return e; - } - } -#pragma warning restore CA1000 // Do not declare static members on generic types } +#pragma warning restore CA1000 // Do not declare static members on generic types } diff --git a/Projects/Server/Network/Packets/OutgoingGumpPackets.cs b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs index 9460f9c8f..f8d3e9b29 100644 --- a/Projects/Server/Network/Packets/OutgoingGumpPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingGumpPackets.cs * * * @@ -18,6 +18,7 @@ using System.Buffers; using System.IO; using System.IO.Compression; using System.Runtime.CompilerServices; +using Server.Buffers; using Server.Collections; using Server.Gumps; using Server.Logging; @@ -68,7 +69,7 @@ public static class OutgoingGumpPackets if (wantLength > packBuffer.Length) { - packBuffer = rentedBuffer = ArrayPool.Shared.Rent(wantLength); + packBuffer = rentedBuffer = STArrayPool.Shared.Rent(wantLength); } var packLength = wantLength; @@ -90,7 +91,7 @@ public static class OutgoingGumpPackets if (rentedBuffer != null) { - ArrayPool.Shared.Return(rentedBuffer); + STArrayPool.Shared.Return(rentedBuffer); } } diff --git a/Projects/Server/Network/Packets/PacketContainerBuilder.cs b/Projects/Server/Network/Packets/PacketContainerBuilder.cs index 55eba01c1..4719004d6 100644 --- a/Projects/Server/Network/Packets/PacketContainerBuilder.cs +++ b/Projects/Server/Network/Packets/PacketContainerBuilder.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PacketContainerBuilder.cs * * * @@ -14,9 +14,9 @@ *************************************************************************/ using System; -using System.Buffers; using System.Buffers.Binary; using System.Runtime.CompilerServices; +using Server.Buffers; namespace Server.Network; @@ -89,7 +89,7 @@ public ref struct PacketContainerBuilder private void Grow(int additionalCapacityBeyondPos) { var newLength = Math.Max(Length + additionalCapacityBeyondPos, _bytes.Length * 2); - byte[] poolArray = ArrayPool.Shared.Rent(newLength); + byte[] poolArray = STArrayPool.Shared.Rent(newLength); _bytes[..Length].CopyTo(poolArray); @@ -97,7 +97,7 @@ public ref struct PacketContainerBuilder _bytes = _arrayToReturnToPool = poolArray; if (toReturn != null) { - ArrayPool.Shared.Return(toReturn); + STArrayPool.Shared.Return(toReturn); } } @@ -108,7 +108,7 @@ public ref struct PacketContainerBuilder this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again if (toReturn != null) { - ArrayPool.Shared.Return(toReturn); + STArrayPool.Shared.Return(toReturn); } } } diff --git a/Projects/Server/Text/StringHelpers.cs b/Projects/Server/Text/StringHelpers.cs index cf97b6a7c..ab87a90bb 100644 --- a/Projects/Server/Text/StringHelpers.cs +++ b/Projects/Server/Text/StringHelpers.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: StringHelpers.cs * * * @@ -14,276 +14,275 @@ *************************************************************************/ using System; -using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Server.Buffers; -namespace Server +namespace Server; + +public static class StringHelpers { - public static class StringHelpers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string DefaultIfNullOrEmpty(this string value, string def) => + string.IsNullOrWhiteSpace(value) ? def : value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Remove( + this ReadOnlySpan a, + ReadOnlySpan b, + StringComparison comparison, + Span buffer, + out int size + ) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string DefaultIfNullOrEmpty(this string value, string def) => - string.IsNullOrWhiteSpace(value) ? def : value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Remove( - this ReadOnlySpan a, - ReadOnlySpan b, - StringComparison comparison, - Span buffer, - out int size - ) + size = 0; + if (a == null || a.Length == 0) { - size = 0; - if (a == null || a.Length == 0) + return; + } + + var sliced = a; + + while (true) + { + var indexOf = sliced.IndexOf(b, comparison); + if (indexOf == -1) { - return; + indexOf = sliced.Length; } - var sliced = a; - - while (true) + if (size + indexOf > buffer.Length) { - var indexOf = sliced.IndexOf(b, comparison); - if (indexOf == -1) - { - indexOf = sliced.Length; - } + throw new OutOfMemoryException(nameof(buffer)); + } - if (size + indexOf > buffer.Length) - { - throw new OutOfMemoryException(nameof(buffer)); - } + sliced[..indexOf].CopyTo(buffer[size..]); + size += indexOf; - sliced[..indexOf].CopyTo(buffer[size..]); - size += indexOf; + if (indexOf == sliced.Length) + { + break; + } - if (indexOf == sliced.Length) + sliced = sliced[(indexOf + 1)..]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Remove(this ReadOnlySpan a, ReadOnlySpan b, StringComparison comparison) + { + if (a == null) + { + return null; + } + + if (a.Length == 0) + { + return ""; + } + + Span span = a.Length < 1024 ? stackalloc char[a.Length] : null; + char[] chrs; + if (span == null) + { + chrs = STArrayPool.Shared.Rent(a.Length); + span = chrs.AsSpan(); + } + else + { + chrs = null; + } + + a.Remove(b, comparison, span, out var size); + + var str = span[..size].ToString(); + + if (chrs != null) + { + STArrayPool.Shared.Return(chrs); + } + + return str; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Capitalize(this string value) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + Span span = value.Length < 1024 ? stackalloc char[value.Length] : null; + char[] chrs; + if (span == null) + { + chrs = STArrayPool.Shared.Rent(value.Length); + span = chrs.AsSpan(); + } + else + { + chrs = null; + } + + var sliced = value.AsSpan(); + // Copy over the previous span + sliced.CopyTo(span); + + var index = 0; + + while (true) + { + // Special case for titles - words that don't get capitalized + if (sliced.InsensitiveStartsWith("the ")) + { + sliced = sliced[4..]; + index += 4; + continue; + } + + var indexOf = sliced.IndexOf(' '); + span[index] = char.ToUpperInvariant(sliced[0]); + + if (indexOf == -1) + { + break; + } + + if (indexOf == sliced.Length - 1) + { + break; + } + + sliced = sliced[(indexOf + 1)..]; + index += indexOf + 1; + } + + var str = span.ToString(); + + if (chrs != null) + { + STArrayPool.Shared.Return(chrs); + } + + return str; + } + + public static string TrimMultiline(this string str, string lineSeparator = "\n") + { + var parts = str.Split(lineSeparator); + for (var i = 0; i < parts.Length; i++) + { + parts[i] = parts[i].Trim(); + } + + return string.Join(lineSeparator, parts); + } + + public static string IndentMultiline(this string str, string indent = "\t", string lineSeparator = "\n") + { + var parts = str.Split(lineSeparator); + for (var i = 0; i < parts.Length; i++) + { + parts[i] = $"{indent}{parts[i]}"; + } + + return string.Join(lineSeparator, parts); + } + + public static List Wrap(this string value, int perLine, int maxLines) + { + if ((value = value?.Trim() ?? "").Length <= 0) + { + return null; + } + + var span = value.AsSpan(); + var list = new List(maxLines); + var lineLength = 0; + + while (span.Length > 0) + { + var spaceIndex = span[lineLength..].IndexOf(' '); + if (spaceIndex == -1) + { + spaceIndex = span.Length - lineLength; // End of the string + } + + var newLineLength = lineLength + spaceIndex; + + // If the previous line is exactly perLine or not too long and we are at the end + if (newLineLength == perLine || newLineLength < perLine && newLineLength == span.Length) + { + list.Add(span[..newLineLength].ToString()); + if (list.Count == maxLines || newLineLength == span.Length) { break; } - sliced = sliced[(indexOf + 1)..]; + span = span[(newLineLength + 1)..]; + lineLength = 0; } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Remove(this ReadOnlySpan a, ReadOnlySpan b, StringComparison comparison) - { - if (a == null) + // We haven't hit perLine and are not sure if we can continue adding more words without going over + else if (newLineLength < perLine) { - return null; + lineLength = newLineLength + 1; } - - if (a.Length == 0) + // We already tried making the line longer, and it was too long, so fall back to the old line + else if (lineLength > 0 && lineLength <= perLine) { - return ""; - } + list.Add(span[..(lineLength - 1)].ToString()); + if (list.Count == maxLines) + { + break; + } - Span span = a.Length < 1024 ? stackalloc char[a.Length] : null; - char[] chrs; - if (span == null) - { - chrs = ArrayPool.Shared.Rent(a.Length); - span = chrs.AsSpan(); + span = span[lineLength..]; + lineLength = 0; } + // We have a really long single word with no spaces and have to forcibly break it up. else { - chrs = null; - } + lineLength = newLineLength; + var index = 0; - a.Remove(b, comparison, span, out var size); - - var str = span[..size].ToString(); - - if (chrs != null) - { - ArrayPool.Shared.Return(chrs); - } - - return str; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Capitalize(this string value) - { - if (string.IsNullOrEmpty(value)) - { - return value; - } - - Span span = value.Length < 1024 ? stackalloc char[value.Length] : null; - char[] chrs; - if (span == null) - { - chrs = ArrayPool.Shared.Rent(value.Length); - span = chrs.AsSpan(); - } - else - { - chrs = null; - } - - var sliced = value.AsSpan(); - // Copy over the previous span - sliced.CopyTo(span); - - var index = 0; - - while (true) - { - // Special case for titles - words that don't get capitalized - if (sliced.InsensitiveStartsWith("the ")) + while (index < lineLength) { - sliced = sliced[4..]; - index += 4; - continue; - } + lineLength -= perLine; - var indexOf = sliced.IndexOf(' '); - span[index] = char.ToUpperInvariant(sliced[0]); - - if (indexOf == -1) - { - break; - } - - if (indexOf == sliced.Length - 1) - { - break; - } - - sliced = sliced[(indexOf + 1)..]; - index += indexOf + 1; - } - - var str = span.ToString(); - - if (chrs != null) - { - ArrayPool.Shared.Return(chrs); - } - - return str; - } - - public static string TrimMultiline(this string str, string lineSeparator = "\n") - { - var parts = str.Split(lineSeparator); - for (var i = 0; i < parts.Length; i++) - { - parts[i] = parts[i].Trim(); - } - - return string.Join(lineSeparator, parts); - } - - public static string IndentMultiline(this string str, string indent = "\t", string lineSeparator = "\n") - { - var parts = str.Split(lineSeparator); - for (var i = 0; i < parts.Length; i++) - { - parts[i] = $"{indent}{parts[i]}"; - } - - return string.Join(lineSeparator, parts); - } - - public static List Wrap(this string value, int perLine, int maxLines) - { - if ((value = value?.Trim() ?? "").Length <= 0) - { - return null; - } - - var span = value.AsSpan(); - var list = new List(maxLines); - var lineLength = 0; - - while (span.Length > 0) - { - var spaceIndex = span[lineLength..].IndexOf(' '); - if (spaceIndex == -1) - { - spaceIndex = span.Length - lineLength; // End of the string - } - - var newLineLength = lineLength + spaceIndex; - - // If the previous line is exactly perLine or not too long and we are at the end - if (newLineLength == perLine || newLineLength < perLine && newLineLength == span.Length) - { - list.Add(span[..newLineLength].ToString()); - if (list.Count == maxLines || newLineLength == span.Length) - { - break; - } - - span = span[(newLineLength + 1)..]; - lineLength = 0; - } - // We haven't hit perLine and are not sure if we can continue adding more words without going over - else if (newLineLength < perLine) - { - lineLength = newLineLength + 1; - } - // We already tried making the line longer, and it was too long, so fall back to the old line - else if (lineLength > 0 && lineLength <= perLine) - { - list.Add(span[..(lineLength - 1)].ToString()); + var length = perLine - (span[index] == ' ' ? 1 : 0); + list.Add(span.Slice(index, length).ToString()); if (list.Count == maxLines) { break; } - span = span[lineLength..]; - lineLength = 0; + index += perLine; } - // We have a really long single word with no spaces and have to forcibly break it up. - else - { - lineLength = newLineLength; - var index = 0; - while (index < lineLength) - { - lineLength -= perLine; - - var length = perLine - (span[index] == ' ' ? 1 : 0); - list.Add(span.Slice(index, length).ToString()); - if (list.Count == maxLines) - { - break; - } - - index += perLine; - } - - span = span[(newLineLength - lineLength)..]; - } + span = span[(newLineLength - lineLength)..]; } - - return list; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfTerminator(this Span buffer, int sizeT) => - sizeT switch - { - 2 => MemoryMarshal.Cast(buffer).IndexOf((char)0) * 2, - 4 => MemoryMarshal.Cast(buffer).IndexOf((uint)0) * 4, - _ => buffer.IndexOf((byte)0) - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfTerminator(this ReadOnlySpan buffer, int sizeT) => - sizeT switch - { - 2 => MemoryMarshal.Cast(buffer).IndexOf((char)0) * 2, - 4 => MemoryMarshal.Cast(buffer).IndexOf((uint)0) * 4, - _ => buffer.IndexOf((byte)0) - }; + return list; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfTerminator(this Span buffer, int sizeT) => + sizeT switch + { + 2 => MemoryMarshal.Cast(buffer).IndexOf((char)0) * 2, + 4 => MemoryMarshal.Cast(buffer).IndexOf((uint)0) * 4, + _ => buffer.IndexOf((byte)0) + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfTerminator(this ReadOnlySpan buffer, int sizeT) => + sizeT switch + { + 2 => MemoryMarshal.Cast(buffer).IndexOf((char)0) * 2, + 4 => MemoryMarshal.Cast(buffer).IndexOf((uint)0) * 4, + _ => buffer.IndexOf((byte)0) + }; } diff --git a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs index 8f326bc5f..d05ffb814 100644 --- a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs +++ b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs @@ -1,118 +1,117 @@ using System; -using System.Buffers; +using Server.Buffers; using Server.Collections; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class BladeSpirits : BaseCreature { - public class BladeSpirits : BaseCreature + [Constructible] + public BladeSpirits() : base(AIType.AI_Melee) { - [Constructible] - public BladeSpirits() : base(AIType.AI_Melee) + Body = 574; + + SetSpeed(0.5, 1.2); + SetStr(150); + SetDex(150); + SetInt(100); + + SetHits(Core.SE ? 160 : 80); + SetStam(250); + SetMana(0); + + SetDamage(10, 14); + + SetDamageType(ResistanceType.Physical, 60); + SetDamageType(ResistanceType.Poison, 20); + SetDamageType(ResistanceType.Energy, 20); + + SetResistance(ResistanceType.Physical, 30, 40); + SetResistance(ResistanceType.Fire, 40, 50); + SetResistance(ResistanceType.Cold, 30, 40); + SetResistance(ResistanceType.Poison, 100); + SetResistance(ResistanceType.Energy, 20, 30); + + SetSkill(SkillName.MagicResist, 70.0); + SetSkill(SkillName.Tactics, 90.0); + SetSkill(SkillName.Wrestling, 90.0); + + Fame = 0; + Karma = 0; + + VirtualArmor = 40; + ControlSlots = Core.SE ? 2 : 1; + } + + public BladeSpirits(Serial serial) : base(serial) + { + } + + public override string CorpseName => "a blade spirit corpse"; + public override bool DeleteCorpseOnDeath => Core.AOS; + public override bool IsHouseSummonable => true; + + public override double DispelDifficulty => 0.0; + public override double DispelFocus => 20.0; + + public override string DefaultName => "a blade spirit"; + + public override bool BleedImmune => true; + public override Poison PoisonImmune => Poison.Lethal; + + public override double GetFightModeRanking(Mobile m, FightMode acqType, bool bPlayerOnly) => + (m.Str + m.Skills.Tactics.Value) / Math.Max(GetDistanceToSqrt(m), 1.0); + + public override int GetAngerSound() => 0x23A; + + public override int GetAttackSound() => 0x3B8; + + public override int GetHurtSound() => 0x23A; + + public override void OnThink() + { + if (Core.SE && Summoned) { - Body = 574; - - SetSpeed(0.5, 1.2); - SetStr(150); - SetDex(150); - SetInt(100); - - SetHits(Core.SE ? 160 : 80); - SetStam(250); - SetMana(0); - - SetDamage(10, 14); - - SetDamageType(ResistanceType.Physical, 60); - SetDamageType(ResistanceType.Poison, 20); - SetDamageType(ResistanceType.Energy, 20); - - SetResistance(ResistanceType.Physical, 30, 40); - SetResistance(ResistanceType.Fire, 40, 50); - SetResistance(ResistanceType.Cold, 30, 40); - SetResistance(ResistanceType.Poison, 100); - SetResistance(ResistanceType.Energy, 20, 30); - - SetSkill(SkillName.MagicResist, 70.0); - SetSkill(SkillName.Tactics, 90.0); - SetSkill(SkillName.Wrestling, 90.0); - - Fame = 0; - Karma = 0; - - VirtualArmor = 40; - ControlSlots = Core.SE ? 2 : 1; - } - - public BladeSpirits(Serial serial) : base(serial) - { - } - - public override string CorpseName => "a blade spirit corpse"; - public override bool DeleteCorpseOnDeath => Core.AOS; - public override bool IsHouseSummonable => true; - - public override double DispelDifficulty => 0.0; - public override double DispelFocus => 20.0; - - public override string DefaultName => "a blade spirit"; - - public override bool BleedImmune => true; - public override Poison PoisonImmune => Poison.Lethal; - - public override double GetFightModeRanking(Mobile m, FightMode acqType, bool bPlayerOnly) => - (m.Str + m.Skills.Tactics.Value) / Math.Max(GetDistanceToSqrt(m), 1.0); - - public override int GetAngerSound() => 0x23A; - - public override int GetAttackSound() => 0x3B8; - - public override int GetHurtSound() => 0x23A; - - public override void OnThink() - { - if (Core.SE && Summoned) + var eable = GetMobilesInRange(5); + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) { - var eable = GetMobilesInRange(5); - using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) { - if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) - { - queue.Enqueue(m); - } - } - eable.Free(); - - var amount = queue.Count - 6; - if (amount > 0) - { - var mobs = queue.ToPooledArray(); - mobs.Shuffle(); - - while (amount > 0) - { - Dispel(mobs[amount--]); - } - - ArrayPool.Shared.Return(mobs, true); + queue.Enqueue(m); } } + eable.Free(); - base.OnThink(); + var amount = queue.Count - 6; + if (amount > 0) + { + var mobs = queue.ToPooledArray(); + mobs.Shuffle(); + + while (amount > 0) + { + Dispel(mobs[amount--]); + } + + STArrayPool.Shared.Return(mobs, true); + } } - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + base.OnThink(); + } - writer.Write(0); // version - } + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); + writer.Write(0); // version + } - var version = reader.ReadInt(); - } + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); } } diff --git a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs index 66f808bcc..ddc689b87 100644 --- a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs +++ b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs @@ -1,129 +1,128 @@ using System; -using System.Buffers; +using Server.Buffers; using Server.Collections; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class EnergyVortex : BaseCreature { - public class EnergyVortex : BaseCreature + [Constructible] + public EnergyVortex() : base(AIType.AI_Melee) { - [Constructible] - public EnergyVortex() : base(AIType.AI_Melee) + if (Core.SE && Utility.Random(500) == 0) // Per OSI FoF, it's a 1/500 chance. { - if (Core.SE && Utility.RandomDouble() < 0.002) // Per OSI FoF, it's a 1/500 chance. - { - // Llama vortex! - Body = 0xDC; - Hue = 0x76; - } - else - { - Body = 164; - } - - SetStr(200); - SetDex(200); - SetInt(100); - - SetHits(Core.SE ? 140 : 70); - SetStam(250); - SetMana(0); - - SetDamage(14, 17); - - SetDamageType(ResistanceType.Physical, 0); - SetDamageType(ResistanceType.Energy, 100); - - SetResistance(ResistanceType.Physical, 60, 70); - SetResistance(ResistanceType.Fire, 40, 50); - SetResistance(ResistanceType.Cold, 40, 50); - SetResistance(ResistanceType.Poison, 40, 50); - SetResistance(ResistanceType.Energy, 90, 100); - - SetSkill(SkillName.MagicResist, 99.9); - SetSkill(SkillName.Tactics, 100.0); - SetSkill(SkillName.Wrestling, 120.0); - - Fame = 0; - Karma = 0; - - VirtualArmor = 40; - ControlSlots = Core.SE ? 2 : 1; + // Llama vortex! + Body = 0xDC; + Hue = 0x76; + } + else + { + Body = 164; } - public EnergyVortex(Serial serial) - : base(serial) + SetStr(200); + SetDex(200); + SetInt(100); + + SetHits(Core.SE ? 140 : 70); + SetStam(250); + SetMana(0); + + SetDamage(14, 17); + + SetDamageType(ResistanceType.Physical, 0); + SetDamageType(ResistanceType.Energy, 100); + + SetResistance(ResistanceType.Physical, 60, 70); + SetResistance(ResistanceType.Fire, 40, 50); + SetResistance(ResistanceType.Cold, 40, 50); + SetResistance(ResistanceType.Poison, 40, 50); + SetResistance(ResistanceType.Energy, 90, 100); + + SetSkill(SkillName.MagicResist, 99.9); + SetSkill(SkillName.Tactics, 100.0); + SetSkill(SkillName.Wrestling, 120.0); + + Fame = 0; + Karma = 0; + + VirtualArmor = 40; + ControlSlots = Core.SE ? 2 : 1; + } + + public EnergyVortex(Serial serial) + : base(serial) + { + } + + public override string CorpseName => "an energy vortex corpse"; + public override bool DeleteCorpseOnDeath => Summoned; + public override bool AlwaysMurderer => true; // Or Llama vortices will appear gray. + + public override double DispelDifficulty => 80.0; + public override double DispelFocus => 20.0; + + public override string DefaultName => "an energy vortex"; + + public override bool BleedImmune => true; + public override Poison PoisonImmune => Poison.Lethal; + + public override double GetFightModeRanking(Mobile m, FightMode acqType, bool bPlayerOnly) => + (m.Int + m.Skills.Magery.Value) / Math.Max(GetDistanceToSqrt(m), 1.0); + + public override int GetAngerSound() => 0x15; + + public override int GetAttackSound() => 0x28; + + public override void OnThink() + { + if (Core.SE && Summoned) { - } - - public override string CorpseName => "an energy vortex corpse"; - public override bool DeleteCorpseOnDeath => Summoned; - public override bool AlwaysMurderer => true; // Or Llama vortices will appear gray. - - public override double DispelDifficulty => 80.0; - public override double DispelFocus => 20.0; - - public override string DefaultName => "an energy vortex"; - - public override bool BleedImmune => true; - public override Poison PoisonImmune => Poison.Lethal; - - public override double GetFightModeRanking(Mobile m, FightMode acqType, bool bPlayerOnly) => - (m.Int + m.Skills.Magery.Value) / Math.Max(GetDistanceToSqrt(m), 1.0); - - public override int GetAngerSound() => 0x15; - - public override int GetAttackSound() => 0x28; - - public override void OnThink() - { - if (Core.SE && Summoned) + var eable = GetMobilesInRange(5); + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) { - var eable = GetMobilesInRange(5); - using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) { - if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) - { - queue.Enqueue(m); - } - } - eable.Free(); - - var amount = queue.Count - 6; - if (amount > 0) - { - var mobs = queue.ToPooledArray(); - mobs.Shuffle(); - - while (amount > 0) - { - Dispel(mobs[amount--]); - } - - ArrayPool.Shared.Return(mobs, true); + queue.Enqueue(m); } } + eable.Free(); - base.OnThink(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - if (BaseSoundID == 263) + var amount = queue.Count - 6; + if (amount > 0) { - BaseSoundID = 0; + var mobs = queue.ToPooledArray(); + mobs.Shuffle(); + + while (amount > 0) + { + Dispel(mobs[amount--]); + } + + STArrayPool.Shared.Return(mobs, true); } } + + base.OnThink(); + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + if (BaseSoundID == 263) + { + BaseSoundID = 0; + } } }