diff --git a/Projects/Server/Buffers/RawInterpolatedStringHandler.cs b/Projects/Server/Buffers/RawInterpolatedStringHandler.cs
index 20c996ead..699b61792 100644
--- a/Projects/Server/Buffers/RawInterpolatedStringHandler.cs
+++ b/Projects/Server/Buffers/RawInterpolatedStringHandler.cs
@@ -78,7 +78,7 @@ public ref struct RawInterpolatedStringHandler
/// The number of interpolation expressions in the interpolated string.
[MethodImpl(MethodImplOptions.AggressiveInlining)] // becomes a constant when inputs are constant
internal static int GetDefaultLength(int literalLength, int formattedCount) =>
- Math.Max(MinimumArrayPoolLength, literalLength + (formattedCount * GuessedLengthPerHole));
+ Math.Max(MinimumArrayPoolLength, literalLength + formattedCount * GuessedLengthPerHole);
/// Clears the handler, returning any rented array to the pool.
[MethodImpl(MethodImplOptions.AggressiveInlining)] // used only on a few hot paths
diff --git a/Projects/Server/Buffers/STArrayPool.cs b/Projects/Server/Buffers/STArrayPool.cs
index 8210aa3de..65e8a9d6c 100644
--- a/Projects/Server/Buffers/STArrayPool.cs
+++ b/Projects/Server/Buffers/STArrayPool.cs
@@ -78,7 +78,7 @@ public class STArrayPool : ArrayPool
{
if (array is null)
{
- throw new ArgumentNullException(nameof(array));
+ return;
}
var bucketIndex = SelectBucketIndex(array.Length);
diff --git a/Projects/Server/Items/Container.cs b/Projects/Server/Items/Container.cs
index 1b8375553..1d06752cc 100644
--- a/Projects/Server/Items/Container.cs
+++ b/Projects/Server/Items/Container.cs
@@ -755,7 +755,7 @@ namespace Server.Items
public virtual void SendContentTo(NetState state) => state.SendContainerContent(state.Mobile, this);
- public override void GetProperties(ObjectPropertyList list)
+ public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
@@ -767,21 +767,14 @@ namespace Server.Items
{
list.Add(
1073841, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones
- "{0}\t{1}\t{2}",
- TotalItems,
- MaxItems,
- TotalWeight
+ $"{TotalItems}\t{MaxItems}\t{TotalWeight}"
);
}
else
{
list.Add(
1072241, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~/~4_MAXWEIGHT~ stones
- "{0}\t{1}\t{2}\t{3}",
- TotalItems,
- MaxItems,
- TotalWeight,
- MaxWeight
+ $"{TotalItems}\t{MaxItems}\t{TotalWeight}\t{MaxWeight}"
);
}
@@ -790,7 +783,7 @@ namespace Server.Items
else
{
// ~1_COUNT~ items, ~2_WEIGHT~ stones
- list.Add(1050044, "{0}\t{1}", TotalItems, TotalWeight);
+ list.Add(1050044, $"{TotalItems}\t{TotalWeight}");
}
}
}
diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs
index 188bafc47..80c1ee96b 100644
--- a/Projects/Server/Items/Item.cs
+++ b/Projects/Server/Items/Item.cs
@@ -176,7 +176,7 @@ namespace Server
Spawner = 0x100
}
- public class Item : IHued, IComparable- , ISpawnable, IPropertyListObject
+ public class Item : IHued, IComparable
- , ISpawnable, IObjectPropertyListEntity
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Item));
@@ -769,7 +769,7 @@ namespace Server
/// custom
/// properties.
///
- public virtual void GetProperties(ObjectPropertyList list)
+ public virtual void GetProperties(IPropertyList list)
{
AddNameProperties(list);
}
@@ -1817,7 +1817,7 @@ namespace Server
/// Overridable. Adds the name of this item to the given . This method should be overridden
/// if the item requires a complex naming format.
///
- public virtual void AddNameProperty(ObjectPropertyList list)
+ public virtual void AddNameProperty(IPropertyList list)
{
var name = Name;
@@ -1829,7 +1829,7 @@ namespace Server
}
else
{
- list.Add(1050039, "{0}\t#{1}", m_Amount, LabelNumber); // ~1_NUMBER~ ~2_ITEMNAME~
+ list.Add(1050039, $"{m_Amount}\t#{LabelNumber}"); // ~1_NUMBER~ ~2_ITEMNAME~
}
}
else
@@ -1840,7 +1840,7 @@ namespace Server
}
else
{
- list.Add(1050039, "{0}\t{1}", m_Amount, Name); // ~1_NUMBER~ ~2_ITEMNAME~
+ list.Add(1050039, $"{m_Amount}\t{Name}"); // ~1_NUMBER~ ~2_ITEMNAME~
}
}
}
@@ -1849,7 +1849,7 @@ namespace Server
/// Overridable. Adds the loot type of this item to the given . By default, this will be
/// either 'blessed', 'cursed', or 'insured'.
///
- public virtual void AddLootTypeProperty(ObjectPropertyList list)
+ public virtual void AddLootTypeProperty(IPropertyList list)
{
if (m_LootType == LootType.Blessed)
{
@@ -1868,59 +1868,51 @@ namespace Server
///
/// Overridable. Adds any elemental resistances of this item to the given .
///
- public virtual void AddResistanceProperties(ObjectPropertyList list)
+ public virtual void AddResistanceProperties(IPropertyList list)
{
var v = PhysicalResistance;
if (v != 0)
{
- list.Add(1060448, v.ToString()); // physical resist ~1_val~%
+ list.Add(1060448, $"{v}"); // physical resist ~1_val~%
}
v = FireResistance;
if (v != 0)
{
- list.Add(1060447, v.ToString()); // fire resist ~1_val~%
+ list.Add(1060447, $"{v}"); // fire resist ~1_val~%
}
v = ColdResistance;
if (v != 0)
{
- list.Add(1060445, v.ToString()); // cold resist ~1_val~%
+ list.Add(1060445, $"{v}"); // cold resist ~1_val~%
}
v = PoisonResistance;
if (v != 0)
{
- list.Add(1060449, v.ToString()); // poison resist ~1_val~%
+ list.Add(1060449, $"{v}"); // poison resist ~1_val~%
}
v = EnergyResistance;
if (v != 0)
{
- list.Add(1060446, v.ToString()); // energy resist ~1_val~%
+ list.Add(1060446, $"{v}"); // energy resist ~1_val~%
}
}
///
/// Overridable. Displays cliloc 1072788-1072789.
///
- public virtual void AddWeightProperty(ObjectPropertyList list)
+ public virtual void AddWeightProperty(IPropertyList list)
{
var weight = PileWeight + TotalWeight;
-
- if (weight == 1)
- {
- list.Add(1072788, weight.ToString()); // Weight: ~1_WEIGHT~ stone
- }
- else
- {
- list.Add(1072789, weight.ToString()); // Weight: ~1_WEIGHT~ stones
- }
+ list.Add(weight == 1 ? 1072788 : 1072789, $"{weight}");
}
///
@@ -1928,7 +1920,7 @@ namespace Server
/// (if applicable), and (if
/// ).
///
- public virtual void AddNameProperties(ObjectPropertyList list)
+ public virtual void AddNameProperties(IPropertyList list)
{
AddNameProperty(list);
@@ -1969,7 +1961,7 @@ namespace Server
///
/// Overridable. Adds the "Quest Item" property to the given .
///
- public virtual void AddQuestItemProperty(ObjectPropertyList list)
+ public virtual void AddQuestItemProperty(IPropertyList list)
{
list.Add(1072351); // Quest Item
}
@@ -1977,7 +1969,7 @@ namespace Server
///
/// Overridable. Adds the "Locked Down & Secure" property to the given .
///
- public virtual void AddSecureProperty(ObjectPropertyList list)
+ public virtual void AddSecureProperty(IPropertyList list)
{
list.Add(501644); // locked down & secure
}
@@ -1985,7 +1977,7 @@ namespace Server
///
/// Overridable. Adds the "Locked Down" property to the given .
///
- public virtual void AddLockedDownProperty(ObjectPropertyList list)
+ public virtual void AddLockedDownProperty(IPropertyList list)
{
list.Add(501643); // locked down
}
@@ -1993,9 +1985,9 @@ namespace Server
///
/// Overridable. Adds the "Blessed for ~1_NAME~" property to the given .
///
- public virtual void AddBlessedForProperty(ObjectPropertyList list, Mobile m)
+ public virtual void AddBlessedForProperty(IPropertyList list, Mobile m)
{
- list.Add(1062203, "{0}", m.Name); // Blessed for ~1_NAME~
+ list.Add(1062203, m.Name); // Blessed for ~1_NAME~
}
///
@@ -2003,7 +1995,7 @@ namespace Server
/// Recursively calls Item.GetChildProperties or
/// Mobile.GetChildProperties.
///
- public virtual void GetChildProperties(ObjectPropertyList list, Item item)
+ public virtual void GetChildProperties(IPropertyList list, Item item)
{
if (m_Parent is Item parentItem)
{
@@ -2021,7 +2013,7 @@ namespace Server
/// . Recursively calls Item.GetChildNameProperties or
/// Mobile.GetChildNameProperties.
///
- public virtual void GetChildNameProperties(ObjectPropertyList list, Item item)
+ public virtual void GetChildNameProperties(IPropertyList list, Item item)
{
if (m_Parent is Item parentItem)
{
@@ -2377,7 +2369,7 @@ namespace Server
return bounds;
}
- public virtual void AppendChildProperties(ObjectPropertyList list)
+ public virtual void AppendChildProperties(IPropertyList list)
{
if (m_Parent is Item item)
{
@@ -2389,7 +2381,7 @@ namespace Server
}
}
- public virtual void AppendChildNameProperties(ObjectPropertyList list)
+ public virtual void AppendChildNameProperties(IPropertyList list)
{
if (m_Parent is Item item)
{
diff --git a/Projects/Server/Items/VirtualCheck.cs b/Projects/Server/Items/VirtualCheck.cs
index da952aaef..c72866f20 100644
--- a/Projects/Server/Items/VirtualCheck.cs
+++ b/Projects/Server/Items/VirtualCheck.cs
@@ -120,7 +120,7 @@ namespace Server.Items
LabelTo(from, "Offer: {0:#,0} platinum, {1:#,0} gold", Plat, Gold);
}
- public override void GetProperties(ObjectPropertyList list)
+ public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs
index 8a953447c..92f007906 100644
--- a/Projects/Server/Mobiles/Mobile.cs
+++ b/Projects/Server/Mobiles/Mobile.cs
@@ -403,7 +403,7 @@ namespace Server
///
/// Base class representing players, npcs, and creatures.
///
- public class Mobile : IHued, IComparable, ISpawnable, IPropertyListObject
+ public class Mobile : IHued, IComparable, ISpawnable, IObjectPropertyListEntity
{
// Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds
private const int WarmodeCatchCount = 4;
@@ -2476,7 +2476,7 @@ namespace Server
public virtual int HuedItemID => m_Female ? 0x2107 : 0x2106;
public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this));
- public virtual void GetProperties(ObjectPropertyList list)
+ public virtual void GetProperties(IPropertyList list)
{
AddNameProperties(list);
}
@@ -3445,57 +3445,51 @@ namespace Server
public virtual string ApplyNameSuffix(string suffix) => suffix;
- public virtual void AddNameProperties(ObjectPropertyList list)
+ public virtual void AddNameProperties(IPropertyList list)
{
- var name = Name ?? "";
+ var name = Name ?? " ";
string prefix;
if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000)
{
- prefix = m_Female ? "Lady" : "Lord";
+ prefix = m_Female ? "Lady " : "Lord ";
}
else
{
- prefix = "";
+ prefix = " ";
}
- var suffix = "";
-
- if (PropertyTitle && !string.IsNullOrEmpty(Title))
- {
- suffix = Title;
- }
+ var title = PropertyTitle && !string.IsNullOrEmpty(Title) ? Title : "";
+ string suffix;
var guild = m_Guild;
-
if (guild != null && (m_Player || m_DisplayGuildTitle))
{
- suffix = suffix.Length > 0
- ? $"{suffix} [{Utility.FixHtml(guild.Abbreviation)}]"
+ suffix = title.Length > 0
+ ? $"{title} [{Utility.FixHtml(guild.Abbreviation)}]"
: $"[{Utility.FixHtml(guild.Abbreviation)}]";
}
+ else
+ {
+ suffix = " ";
+ }
- suffix = ApplyNameSuffix(suffix);
-
- list.Add(1050045, "{0} \t{1}\t {2}", prefix, name, suffix); // ~1_PREFIX~~2_NAME~~3_SUFFIX~
+ list.Add(1050045, $"{prefix}\t{name}\t{ApplyNameSuffix(suffix)}"); // ~1_PREFIX~~2_NAME~~3_SUFFIX~
if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular))
{
var type = guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length ? m_GuildTypes[(int)guild.Type] : "";
- var title = GuildTitle?.Trim() ?? "";
+ var guildTitle = GuildTitle?.Trim() ?? "";
- if (title.Length > 0)
+ if (guildTitle.Length > 0)
{
- if (NewGuildDisplay)
- {
- list.Add("{0}, {1}", Utility.FixHtml(title), Utility.FixHtml(guild.Name));
- }
- else
- {
- list.Add("{0}, {1} Guild{2}", Utility.FixHtml(title), Utility.FixHtml(guild.Name), type);
- }
+ list.Add(
+ NewGuildDisplay
+ ? $"{Utility.FixHtml(guildTitle)}, {Utility.FixHtml(guild.Name)}"
+ : $"{Utility.FixHtml(guildTitle)}, {Utility.FixHtml(guild.Name)} Guild{type}"
+ );
}
else
{
@@ -3504,11 +3498,11 @@ namespace Server
}
}
- public virtual void GetChildProperties(ObjectPropertyList list, Item item)
+ public virtual void GetChildProperties(IPropertyList list, Item item)
{
}
- public virtual void GetChildNameProperties(ObjectPropertyList list, Item item)
+ public virtual void GetChildNameProperties(IPropertyList list, Item item)
{
}
diff --git a/Projects/Server/Network/Packets/OutgoingEntityPackets.cs b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs
index cf84157e2..1e546c962 100644
--- a/Projects/Server/Network/Packets/OutgoingEntityPackets.cs
+++ b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs
@@ -41,7 +41,7 @@ public static class OutgoingEntityPackets
writer.Write(hash);
}
- public static void SendOPLInfo(this NetState ns, IPropertyListObject obj) =>
+ public static void SendOPLInfo(this NetState ns, IObjectPropertyListEntity obj) =>
ns.SendOPLInfo(obj.Serial, obj.PropertyList.Hash);
public static void SendOPLInfo(this NetState ns, Serial serial, int hash)
diff --git a/Projects/Server/ObjectPropertyList.cs b/Projects/Server/ObjectPropertyList.cs
deleted file mode 100644
index 369cfd2ef..000000000
--- a/Projects/Server/ObjectPropertyList.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-using System;
-using System.Buffers;
-using System.IO;
-using System.Runtime.CompilerServices;
-using Server.Network;
-
-namespace Server
-{
- public interface IPropertyListObject : IEntity
- {
- ObjectPropertyList PropertyList { get; }
-
- void GetProperties(ObjectPropertyList list);
- }
-
- public sealed class ObjectPropertyList
- {
- // Each of these are localized to "~1_NOTHING~" which allows the string argument to be used
- private static readonly int[] m_StringNumbers =
- {
- 1042971,
- 1070722
- };
-
- private int _hash;
- private int _strings;
- private byte[] _buffer;
- private int _position;
-
- public ObjectPropertyList(IEntity e)
- {
- Entity = e;
- _buffer = GC.AllocateUninitializedArray(64);
-
- var writer = new SpanWriter(_buffer);
- writer.Write((byte)0xD6); // Packet ID
- writer.Seek(2, SeekOrigin.Current);
- writer.Write((ushort)1);
- writer.Write(e.Serial);
- writer.Write((ushort)0);
- _position = writer.Position + 4; // Hash
- }
-
- public IEntity Entity { get; }
-
- public int Hash => 0x40000000 + _hash;
-
- public int Header { get; set; }
-
- public string HeaderArgs { get; set; }
-
- public static bool Enabled { get; set; }
-
- public byte[] Buffer => _buffer;
-
- public void Reset()
- {
- _position = 15;
- _hash = 0;
- _strings = 0;
- Header = 0;
- HeaderArgs = null;
- }
-
- public void Flush()
- {
- Resize(_buffer.Length * 2);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void Resize(int amount)
- {
- var newBuffer = GC.AllocateUninitializedArray(amount);
- _buffer.AsSpan(0, Math.Min(amount, _buffer.Length)).CopyTo(newBuffer);
- _buffer = newBuffer;
- }
-
- public void Terminate()
- {
- int length = _position + 4;
- if (length != _buffer.Length)
- {
- Resize(length);
- }
-
- var writer = new SpanWriter(_buffer);
- writer.Seek(_position, SeekOrigin.Begin);
- writer.Write(0);
-
- writer.Seek(11, SeekOrigin.Begin);
- writer.Write(_hash);
- writer.WritePacketLength();
- }
-
- public void AddHash(int val)
- {
- _hash ^= val & 0x3FFFFFF;
- _hash ^= (val >> 26) & 0x3F;
- }
-
- public void Add(int number, string arguments = null)
- {
- if (number == 0)
- {
- return;
- }
-
- arguments ??= "";
-
- if (Header == 0)
- {
- Header = number;
- HeaderArgs = arguments;
- }
-
- AddHash(number);
- if (arguments.Length > 0)
- {
- AddHash(arguments.GetHashCode(StringComparison.Ordinal));
- }
-
- int strLength = arguments.Length * 2;
- int length = _position + 6 + strLength;
- while (length > _buffer.Length)
- {
- Flush();
- }
-
- var writer = new SpanWriter(_buffer.AsSpan(_position));
- writer.Write(number);
- writer.Write((ushort)strLength);
- writer.WriteLittleUni(arguments);
-
- _position += writer.BytesWritten;
- }
-
- public void Add(int number, string format, object arg0)
- {
- Add(number, string.Format(format, arg0));
- }
-
- public void Add(int number, string format, object arg0, object arg1)
- {
- Add(number, string.Format(format, arg0, arg1));
- }
-
- public void Add(int number, string format, object arg0, object arg1, object arg2)
- {
- Add(number, string.Format(format, arg0, arg1, arg2));
- }
-
- public void Add(int number, string format, params object[] args)
- {
- Add(number, string.Format(format, args));
- }
-
- private int GetStringNumber() => m_StringNumbers[_strings++ % m_StringNumbers.Length];
-
- public void Add(string text)
- {
- Add(GetStringNumber(), text);
- }
-
- public void Add(string format, string arg0)
- {
- Add(GetStringNumber(), string.Format(format, arg0));
- }
-
- public void Add(string format, string arg0, string arg1)
- {
- Add(GetStringNumber(), string.Format(format, arg0, arg1));
- }
-
- public void Add(string format, string arg0, string arg1, string arg2)
- {
- Add(GetStringNumber(), string.Format(format, arg0, arg1, arg2));
- }
-
- public void Add(string format, params object[] args)
- {
- Add(GetStringNumber(), string.Format(format, args));
- }
- }
-}
diff --git a/Projects/Server/PropertyList/IObjectPropertyListEntity.cs b/Projects/Server/PropertyList/IObjectPropertyListEntity.cs
new file mode 100644
index 000000000..4ba8d3735
--- /dev/null
+++ b/Projects/Server/PropertyList/IObjectPropertyListEntity.cs
@@ -0,0 +1,23 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2022 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: IObjectPropertyListEntity.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+namespace Server;
+
+public interface IObjectPropertyListEntity : IEntity
+{
+ ObjectPropertyList PropertyList { get; }
+
+ void GetProperties(IPropertyList list);
+}
diff --git a/Projects/Server/PropertyList/IPropertyList.cs b/Projects/Server/PropertyList/IPropertyList.cs
new file mode 100644
index 000000000..59d9a57d7
--- /dev/null
+++ b/Projects/Server/PropertyList/IPropertyList.cs
@@ -0,0 +1,30 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2022 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: IPropertyList.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System.Runtime.CompilerServices;
+using Server.Text;
+
+namespace Server;
+
+public interface IPropertyList : ISelfInterpolatedStringHandler
+{
+ public void Reset();
+ public void Terminate();
+ public void Add(int number, string argument = null);
+ public void Add(string text);
+
+ // String Interpolation
+ public void Add(int number, [InterpolatedStringHandlerArgument("")] ref InterpolatedStringHandler handler);
+}
diff --git a/Projects/Server/PropertyList/ObjectPropertyList.cs b/Projects/Server/PropertyList/ObjectPropertyList.cs
new file mode 100644
index 000000000..834afffd8
--- /dev/null
+++ b/Projects/Server/PropertyList/ObjectPropertyList.cs
@@ -0,0 +1,508 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2022 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: ObjectPropertyList.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+#nullable enable
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using Server.Buffers;
+using Server.Network;
+using Server.Text;
+
+namespace Server;
+
+public sealed class ObjectPropertyList : IPropertyList, IDisposable
+{
+ // Each of these are localized to "~1_NOTHING~" which allows the string argument to be used
+ private static readonly int[] _stringNumbers =
+ {
+ 1042971,
+ 1070722,
+ 1114057, // ~1_val~
+ 1114778, // ~1_val~
+ 1114779 // ~1_val~
+ };
+
+ private int _hash;
+ private int _stringNumbersIndex;
+ private byte[] _buffer;
+ private int _bufferPos;
+
+ // For string interpolation
+ private int _pos;
+ private char[]? _arrayToReturnToPool;
+
+ public ObjectPropertyList(IEntity? e)
+ {
+ Entity = e;
+ _buffer = GC.AllocateUninitializedArray(64);
+
+ var writer = new SpanWriter(_buffer);
+ writer.Write((byte)0xD6); // Packet ID
+ writer.Seek(2, SeekOrigin.Current);
+ writer.Write((ushort)1);
+ writer.Write(e?.Serial ?? Serial.Zero);
+ writer.Write((ushort)0);
+ _bufferPos = writer.Position + 4; // Hash
+ }
+
+ public IEntity? Entity { get; }
+
+ public int Hash => 0x40000000 + _hash;
+
+ public int Header { get; set; }
+
+ public string HeaderArgs { get; set; }
+
+ public static bool Enabled { get; set; }
+
+ public byte[] Buffer => _buffer;
+
+ public void Reset()
+ {
+ _bufferPos = 15;
+ _hash = 0;
+ _stringNumbersIndex = 0;
+ Header = 0;
+ HeaderArgs = null;
+ STArrayPool.Shared.Return(_arrayToReturnToPool);
+ _pos = 0;
+ }
+
+ private void Flush()
+ {
+ Resize(_buffer.Length * 2);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Resize(int amount)
+ {
+ var newBuffer = GC.AllocateUninitializedArray(amount);
+ _buffer.AsSpan(0, Math.Min(amount, _buffer.Length)).CopyTo(newBuffer);
+ _buffer = newBuffer;
+ }
+
+ public void Terminate()
+ {
+ int length = _bufferPos + 4;
+ if (length != _buffer.Length)
+ {
+ Resize(length);
+ }
+
+ var writer = new SpanWriter(_buffer);
+ writer.Seek(_bufferPos, SeekOrigin.Begin);
+ writer.Write(0);
+
+ writer.Seek(11, SeekOrigin.Begin);
+ writer.Write(_hash);
+ writer.WritePacketLength();
+ }
+
+ private void AddHash(int val)
+ {
+ _hash ^= val & 0x3FFFFFF;
+ _hash ^= (val >> 26) & 0x3F;
+ }
+
+ public void Add(int number, string? arguments = null)
+ {
+ if (number == 0)
+ {
+ return;
+ }
+
+ arguments ??= "";
+
+ if (Header == 0)
+ {
+ Header = number;
+ HeaderArgs = arguments;
+ }
+
+ AddHash(number);
+ if (arguments.Length > 0)
+ {
+ AddHash(arguments.GetHashCode(StringComparison.Ordinal));
+ }
+
+ int strLength = arguments.Length * 2;
+ int length = _bufferPos + 6 + strLength;
+ while (length > _buffer.Length)
+ {
+ Flush();
+ }
+
+ var writer = new SpanWriter(_buffer.AsSpan(_bufferPos));
+ writer.Write(number);
+ writer.Write((ushort)strLength);
+ writer.WriteLittleUni(arguments);
+
+ _bufferPos += writer.BytesWritten;
+ _pos = 0;
+ }
+
+ private int GetStringNumber() => _stringNumbers[_stringNumbersIndex++ % _stringNumbers.Length];
+
+ public void Add(string argument) => Add(GetStringNumber(), argument);
+
+ public void Add(
+ [InterpolatedStringHandlerArgument("")]
+ ref IPropertyList.InterpolatedStringHandler handler
+ ) => Add(GetStringNumber(), ref handler);
+
+ // String Interpolation
+ public void Add(
+ int number,
+ [InterpolatedStringHandlerArgument("")]
+ ref IPropertyList.InterpolatedStringHandler handler)
+ {
+ if (number == 0)
+ {
+ return;
+ }
+
+ var chars = _arrayToReturnToPool.AsSpan(0, _pos);
+
+ if (Header == 0)
+ {
+ Header = number;
+ HeaderArgs = chars.ToString();
+ HeaderArgs.GetHashCode(StringComparison.Ordinal);
+ }
+
+ AddHash(number);
+ if (chars.Length > 0)
+ {
+ AddHash(string.GetHashCode(chars, StringComparison.Ordinal));
+ }
+
+ int strLength = chars.Length * 2;
+ int length = _bufferPos + 6 + strLength;
+ while (length > _buffer.Length)
+ {
+ Flush();
+ }
+
+ var writer = new SpanWriter(_buffer.AsSpan(_bufferPos));
+ writer.Write(number);
+ writer.Write((ushort)strLength);
+ writer.Write(chars, TextEncoding.UnicodeLE);
+
+ _bufferPos += writer.BytesWritten;
+ }
+
+ public void InitializeInterpolation(int literalLength, int formattedCount)
+ {
+ _arrayToReturnToPool ??= STArrayPool.Shared.Rent(GetDefaultLength(literalLength, formattedCount));
+ _pos = 0;
+ }
+
+ // Copied from RawInterpolatedStringHandler
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int GetDefaultLength(int literalLength, int formattedCount) =>
+ Math.Max(256, literalLength + formattedCount * 11);
+
+ public void AppendLiteral(string value)
+ {
+ if (value.Length == 1)
+ {
+ Span chars = _arrayToReturnToPool.AsSpan();
+ int pos = _pos;
+ if ((uint)pos < (uint)chars.Length)
+ {
+ chars[pos] = value[0];
+ _pos = pos + 1;
+ }
+ else
+ {
+ GrowThenCopyString(value);
+ }
+ return;
+ }
+
+ AppendStringDirect(value);
+ }
+
+ private void AppendStringDirect(string value)
+ {
+ if (value.TryCopyTo(_arrayToReturnToPool.AsSpan(_pos..)))
+ {
+ _pos += value.Length;
+ }
+ else
+ {
+ GrowThenCopyString(value);
+ }
+ }
+
+ public void AppendFormatted(T value)
+ {
+
+ string? s;
+ if (value is IFormattable)
+ {
+ if (value is ISpanFormattable)
+ {
+ int charsWritten;
+ while (!((ISpanFormattable)value).TryFormat(_arrayToReturnToPool.AsSpan(_pos..), out charsWritten, default, null))
+ {
+ Grow();
+ }
+
+ _pos += charsWritten;
+ return;
+ }
+
+ s = ((IFormattable)value).ToString(format: null, null);
+ }
+ else
+ {
+ s = value?.ToString();
+ }
+
+ if (s is not null)
+ {
+ AppendStringDirect(s);
+ }
+ }
+
+ public void AppendFormatted(T value, string? format)
+ {
+ string? s;
+ if (value is IFormattable)
+ {
+ if (value is ISpanFormattable)
+ {
+ int charsWritten;
+ while (!((ISpanFormattable)value).TryFormat(_arrayToReturnToPool.AsSpan(_pos..), out charsWritten, format, null))
+ {
+ Grow();
+ }
+
+ _pos += charsWritten;
+ return;
+ }
+
+ s = ((IFormattable)value).ToString(format, null);
+ }
+ else
+ {
+ s = value?.ToString();
+ }
+
+ if (s is not null)
+ {
+ AppendStringDirect(s);
+ }
+ }
+
+ public void AppendFormatted(T value, int alignment)
+ {
+ int startingPos = _pos;
+ AppendFormatted(value);
+ if (alignment != 0)
+ {
+ AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
+ }
+ }
+
+ public void AppendFormatted(T value, int alignment, string? format)
+ {
+ int startingPos = _pos;
+ AppendFormatted(value, format);
+ if (alignment != 0)
+ {
+ AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
+ }
+ }
+
+ public void AppendFormatted(ReadOnlySpan value)
+ {
+ if (value.TryCopyTo(_arrayToReturnToPool.AsSpan(_pos..)))
+ {
+ _pos += value.Length;
+ }
+ else
+ {
+ GrowThenCopySpan(value);
+ }
+ }
+
+ public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null)
+ {
+ bool leftAlign = false;
+ if (alignment < 0)
+ {
+ leftAlign = true;
+ alignment = -alignment;
+ }
+
+ int paddingRequired = alignment - value.Length;
+ if (paddingRequired <= 0)
+ {
+ AppendFormatted(value);
+ return;
+ }
+
+ EnsureCapacityForAdditionalChars(value.Length + paddingRequired);
+ var chars = _arrayToReturnToPool.AsSpan();
+ if (leftAlign)
+ {
+ value.CopyTo(chars[_pos..]);
+ _pos += value.Length;
+ chars.Slice(_pos, paddingRequired).Fill(' ');
+ _pos += paddingRequired;
+ }
+ else
+ {
+ chars.Slice(_pos, paddingRequired).Fill(' ');
+ _pos += paddingRequired;
+ value.CopyTo(chars[_pos..]);
+ _pos += value.Length;
+ }
+ }
+
+ public void AppendFormatted(string? value)
+ {
+ if (value?.TryCopyTo(_arrayToReturnToPool.AsSpan(_pos..)) == true)
+ {
+ _pos += value.Length;
+ }
+ else
+ {
+ AppendFormattedSlow(value);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void AppendFormattedSlow(string? value)
+ {
+ if (value is not null)
+ {
+ EnsureCapacityForAdditionalChars(value.Length);
+ value.CopyTo(_arrayToReturnToPool.AsSpan(_pos..));
+ _pos += value.Length;
+ }
+ }
+
+ public void AppendFormatted(string? value, int alignment = 0, string? format = null) =>
+ AppendFormatted(value, alignment, format);
+
+ public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
+ AppendFormatted