diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs index fa1ac8d3c..0fe34ed0f 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs @@ -1,7 +1,4 @@ using System; -using System.Buffers; -using System.Collections.Generic; -using System.Linq; using Server.Gumps; using Server.Network; using Xunit; diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs index 1a675e85d..7e3d9a33e 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers; using Server.Network; using Xunit; diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs index 932b241e3..65d5ba6d8 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs @@ -590,7 +590,7 @@ namespace Server.Tests.Network itemID |= 0x8000; } - Stream.Write(HairInfo.FakeSerial(beheld)); + Stream.Write(HairInfo.FakeSerial(beheld.Serial)); Stream.Write((ushort)itemID); Stream.Write((byte)Layer.Hair); @@ -621,7 +621,7 @@ namespace Server.Tests.Network itemID |= 0x8000; } - Stream.Write(FacialHairInfo.FakeSerial(beheld)); + Stream.Write(FacialHairInfo.FakeSerial(beheld.Serial)); Stream.Write((ushort)itemID); Stream.Write((byte)Layer.FacialHair); diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs index c8f7201de..543c7fe9c 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers; using Server.HuePickers; using Server.Network; using Xunit; diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs index 9786b8604..fa2beb10f 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs @@ -1,7 +1,5 @@ using System; -using System.Buffers; using System.Collections.Generic; -using System.Linq; using Server.Network; using Xunit; diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPacketTests.cs new file mode 100644 index 000000000..76f412dfa --- /dev/null +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPacketTests.cs @@ -0,0 +1,40 @@ +using System; +using Server.Network; +using Server.Tests.Network; +using Xunit; + +namespace Server.Tests +{ + public class VirtualHairPacketTests: IClassFixture + { + [Fact] + public void TestSendVirtualHairUpdate() + { + var m = new Mobile(0x1024u); + m.DefaultMobileInit(); + + var expected = new HairEquipUpdate(m).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendHairEquipUpdatePacket(m, HairInfo.FakeSerial(m.Serial), Layer.Hair); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestSendRemoveVirtualHair() + { + var m = new Mobile(0x1024u); + m.DefaultMobileInit(); + + var expected = new RemoveHair(m).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendRemoveHairPacket(HairInfo.FakeSerial(m.Serial)); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + } +} diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPackets.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPackets.cs new file mode 100644 index 000000000..d4b425f52 --- /dev/null +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPackets.cs @@ -0,0 +1,52 @@ +namespace Server.Network +{ + public sealed class HairEquipUpdate : Packet + { + public HairEquipUpdate(Mobile parent) + : base(0x2E, 15) + { + var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.HairHue; + + Stream.Write(HairInfo.FakeSerial(parent.Serial)); + Stream.Write((short)parent.HairItemID); + Stream.Write((byte)0); + Stream.Write((byte)Layer.Hair); + Stream.Write(parent.Serial); + Stream.Write((short)hue); + } + } + + public sealed class FacialHairEquipUpdate : Packet + { + public FacialHairEquipUpdate(Mobile parent) + : base(0x2E, 15) + { + var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.FacialHairHue; + + Stream.Write(FacialHairInfo.FakeSerial(parent.Serial)); + Stream.Write((short)parent.FacialHairItemID); + Stream.Write((byte)0); + Stream.Write((byte)Layer.FacialHair); + Stream.Write(parent.Serial); + Stream.Write((short)hue); + } + } + + public sealed class RemoveHair : Packet + { + public RemoveHair(Mobile parent) + : base(0x1D, 5) + { + Stream.Write(HairInfo.FakeSerial(parent.Serial)); + } + } + + public sealed class RemoveFacialHair : Packet + { + public RemoveFacialHair(Mobile parent) + : base(0x1D, 5) + { + Stream.Write(FacialHairInfo.FakeSerial(parent.Serial)); + } + } +} diff --git a/Projects/Server/Items/VirtualHair.cs b/Projects/Server/Items/VirtualHair.cs index 66de253eb..56970d27d 100644 --- a/Projects/Server/Items/VirtualHair.cs +++ b/Projects/Server/Items/VirtualHair.cs @@ -1,7 +1,62 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; using Server.Network; namespace Server { + public static class OutgoingVirtualHairPackets + { + public const int EquipUpdatePacketLength = 15; + public const int RemovePacketLength = 5; + + public static void SendHairEquipUpdatePacket(this NetState ns, Mobile m, Serial hairSerial, Layer layer) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[EquipUpdatePacketLength]; + CreateHairEquipUpdatePacket(buffer, m, hairSerial, layer); + ns.Send(buffer); + } + + public static void CreateHairEquipUpdatePacket(Span buffer, Mobile m, Serial hairSerial, Layer layer) + { + var hue = m.SolidHueOverride >= 0 ? m.SolidHueOverride : m.HairHue; + + var writer = new SpanWriter(buffer); + writer.Write((byte)0x2E); // Packet ID + + writer.Write(hairSerial); + writer.Write((short)m.HairItemID); + writer.Write((byte)0); + writer.Write((byte)layer); + writer.Write(m.Serial); + writer.Write((short)hue); + } + + public static void SendRemoveHairPacket(this NetState ns, Serial hairSerial) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[RemovePacketLength]; + CreateRemoveHairPacket(buffer, hairSerial); + ns.Send(buffer); + } + + public static void CreateRemoveHairPacket(Span buffer, Serial hairSerial) + { + var writer = new SpanWriter(buffer); + writer.Write((byte)0x1D); // Packet ID + writer.Write(hairSerial); + } + } + public abstract class BaseHairInfo { protected BaseHairInfo(int itemid, int hue = 0) @@ -57,7 +112,8 @@ namespace Server } // TODO: Can we make this higher for newer clients? - public static uint FakeSerial(Mobile parent) => 0x7FFFFFFF - 0x400 - parent.Serial * 4; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - m * 4; } public class FacialHairInfo : BaseHairInfo @@ -77,57 +133,8 @@ namespace Server { } - // TOOD: Can we make this higher for newer clients? - public static uint FakeSerial(Mobile parent) => 0x7FFFFFFF - 0x400 - 1 - parent.Serial * 4; - } - - public sealed class HairEquipUpdate : Packet - { - public HairEquipUpdate(Mobile parent) - : base(0x2E, 15) - { - var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.HairHue; - - Stream.Write(HairInfo.FakeSerial(parent)); - Stream.Write((short)parent.HairItemID); - Stream.Write((byte)0); - Stream.Write((byte)Layer.Hair); - Stream.Write(parent.Serial); - Stream.Write((short)hue); - } - } - - public sealed class FacialHairEquipUpdate : Packet - { - public FacialHairEquipUpdate(Mobile parent) - : base(0x2E, 15) - { - var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.FacialHairHue; - - Stream.Write(FacialHairInfo.FakeSerial(parent)); - Stream.Write((short)parent.FacialHairItemID); - Stream.Write((byte)0); - Stream.Write((byte)Layer.FacialHair); - Stream.Write(parent.Serial); - Stream.Write((short)hue); - } - } - - public sealed class RemoveHair : Packet - { - public RemoveHair(Mobile parent) - : base(0x1D, 5) - { - Stream.Write(HairInfo.FakeSerial(parent)); - } - } - - public sealed class RemoveFacialHair : Packet - { - public RemoveFacialHair(Mobile parent) - : base(0x1D, 5) - { - Stream.Write(FacialHairInfo.FakeSerial(parent)); - } + // TODO: Can we make this higher for newer clients? + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - 1 - m * 4; } } diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 5b71a73b2..b788c10b6 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -3101,11 +3101,11 @@ namespace Server { if (removeHair) { - ourState.Send(new RemoveHair(this)); + ourState.SendRemoveHairPacket(HairInfo.FakeSerial(Serial)); } else { - ourState.Send(new HairEquipUpdate(this)); + ourState.SendHairEquipUpdatePacket(this, HairInfo.FakeSerial(Serial), Layer.Hair); } } @@ -3113,11 +3113,11 @@ namespace Server { if (removeFacialHair) { - ourState.Send(new RemoveFacialHair(this)); + ourState.SendRemoveHairPacket(FacialHairInfo.FakeSerial(Serial)); } else { - ourState.Send(new FacialHairEquipUpdate(this)); + ourState.SendHairEquipUpdatePacket(this, FacialHairInfo.FakeSerial(Serial), Layer.FacialHair); } } @@ -3144,9 +3144,6 @@ namespace Server return; } - Packet hairPacket = null; - Packet facialhairPacket = null; - var eable = Map.GetClientsInRange(m_Location); Span statBufferTrue = stackalloc byte[OutgoingMobilePackets.MobileStatusCompactLength]; @@ -3170,6 +3167,20 @@ namespace Server Span hitsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributePacketLength]; hitsPacket.InitializePacket(); + var hairLength = removeHair + ? OutgoingVirtualHairPackets.RemovePacketLength + : OutgoingVirtualHairPackets.EquipUpdatePacketLength; + + Span hairPacket = stackalloc byte[hairLength]; + hairPacket.InitializePacket(); + + var facialHairLength = removeFacialHair + ? OutgoingVirtualHairPackets.RemovePacketLength + : OutgoingVirtualHairPackets.EquipUpdatePacketLength; + + Span facialhairPacket = stackalloc byte[facialHairLength]; + facialhairPacket.InitializePacket(); + foreach (var state in eable) { var beholder = state.Mobile; @@ -3265,18 +3276,46 @@ namespace Server if (sendHair) { - hairPacket ??= removeHair - ? Packet.Acquire(new RemoveHair(this)) - : Packet.Acquire(new HairEquipUpdate(this)); + if (hairPacket[0] == 0) + { + var hairSerial = HairInfo.FakeSerial(Serial); + if (removeHair) + { + OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, hairSerial); + } + else + { + OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( + hairPacket, + this, + hairSerial, + Layer.Hair + ); + } + } state.Send(hairPacket); } if (sendFacialHair) { - facialhairPacket ??= removeFacialHair - ? Packet.Acquire(new RemoveFacialHair(this)) - : Packet.Acquire(new FacialHairEquipUpdate(this)); + if (facialhairPacket[0] == 0) + { + var hairSerial = HairInfo.FakeSerial(Serial); + if (removeFacialHair) + { + OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialhairPacket, hairSerial); + } + else + { + OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( + facialhairPacket, + this, + hairSerial, + Layer.FacialHair + ); + } + } state.Send(facialhairPacket); } @@ -3284,9 +3323,6 @@ namespace Server SendOPLPacketTo(state); } - Packet.Release(hairPacket); - Packet.Release(facialhairPacket); - eable.Free(); } diff --git a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs index 4919eb93c..2d2bb27f3 100644 --- a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs @@ -614,7 +614,7 @@ namespace Server.Network itemID |= 0x8000; } - writer.Write(HairInfo.FakeSerial(beheld)); + writer.Write(HairInfo.FakeSerial(beheld.Serial)); writer.Write((ushort)itemID); writer.Write((byte)Layer.Hair); @@ -637,7 +637,7 @@ namespace Server.Network itemID |= 0x8000; } - writer.Write(FacialHairInfo.FakeSerial(beheld)); + writer.Write(FacialHairInfo.FakeSerial(beheld.Serial)); writer.Write((ushort)itemID); writer.Write((byte)Layer.FacialHair); diff --git a/Projects/UOContent/Items/Misc/Corpses/Packets.cs b/Projects/UOContent/Items/Misc/Corpses/Packets.cs index c9f6223ae..35def09c6 100644 --- a/Projects/UOContent/Items/Misc/Corpses/Packets.cs +++ b/Projects/UOContent/Items/Misc/Corpses/Packets.cs @@ -38,13 +38,13 @@ namespace Server.Network if (beheld.Hair?.ItemID > 0) { Stream.Write((byte)(Layer.Hair + 1)); - Stream.Write(HairInfo.FakeSerial(beheld.Owner) - 2); + Stream.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2); } if (beheld.FacialHair?.ItemID > 0) { Stream.Write((byte)(Layer.FacialHair + 1)); - Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner) - 2); + Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2); } Stream.Write((byte)Layer.Invalid); @@ -98,7 +98,7 @@ namespace Server.Network if (beheld.Hair?.ItemID > 0) { - Stream.Write(HairInfo.FakeSerial(beheld.Owner) - 2); + Stream.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2); Stream.Write((ushort)beheld.Hair.ItemID); Stream.Write((byte)0); // signed, itemID offset Stream.Write((ushort)1); @@ -112,7 +112,7 @@ namespace Server.Network if (beheld.FacialHair?.ItemID > 0) { - Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner) - 2); + Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2); Stream.Write((ushort)beheld.FacialHair.ItemID); Stream.Write((byte)0); // signed, itemID offset Stream.Write((ushort)1); @@ -177,7 +177,7 @@ namespace Server.Network if (beheld.Hair?.ItemID > 0) { - Stream.Write(HairInfo.FakeSerial(beheld.Owner) - 2); + Stream.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2); Stream.Write((ushort)beheld.Hair.ItemID); Stream.Write((byte)0); // signed, itemID offset Stream.Write((ushort)1); @@ -192,7 +192,7 @@ namespace Server.Network if (beheld.FacialHair?.ItemID > 0) { - Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner) - 2); + Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2); Stream.Write((ushort)beheld.FacialHair.ItemID); Stream.Write((byte)0); // signed, itemID offset Stream.Write((ushort)1);