From 77f665502b1e8c1fffc44796c446ec9df5ec81c7 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 29 Nov 2020 03:24:51 -0800 Subject: [PATCH] Converts Item & Entity Packets (#326) - [X] Converts World Item packets - [X] Converter Remove Entity packets - [X] Updates `LOSBlocker` and `Blocker` - [X] Fixes a few bugs - [X] Converts container packets Bumps release version --- .../Packets/BenchmarkPacketBroadcast.cs | 44 +- .../Packets/Outgoing/ContainerPacketTests.cs | 196 +++++++ .../Packets/Outgoing/ContainerPackets.cs} | 210 +------ .../Packets/Outgoing/ItemPacketTests.cs | 524 +----------------- .../Network/Packets/Outgoing/ItemPackets.cs | 208 +++++++ Projects/Server/Items/Container.cs | 32 +- Projects/Server/Items/Containers.cs | 2 +- Projects/Server/Items/Item.cs | 367 +++++------- Projects/Server/Mobiles/Mobile.cs | 157 +++--- Projects/Server/Network/NetState/NetState.cs | 5 + .../Packets/OutgoingContainerPackets.cs | 216 ++++++++ .../Network/Packets/OutgoingEntityPackets.cs | 73 +++ .../Network/Packets/OutgoingItemPackets.cs | 140 +++++ .../Server/Network/StaticPacketHandlers.cs | 138 ----- Projects/Server/ObjectPropertyList.cs | 126 ++--- Projects/UOContent/Commands/VisibilityList.cs | 28 +- .../Engines/ConPVP/Games/TourneyMatch.cs | 2 +- Projects/UOContent/Items/Misc/Blocker.cs | 129 ++--- .../UOContent/Items/Misc/BulletinBoards.cs | 9 +- .../UOContent/Items/Misc/Corpses/Corpse.cs | 40 +- Projects/UOContent/Items/Misc/LOSBlocker.cs | 129 ++--- .../Items/Skill Items/Magical/Spellbook.cs | 54 +- Projects/UOContent/Misc/Paperdoll.cs | 15 +- Projects/UOContent/Mobiles/PlayerMobile.cs | 87 ++- .../UOContent/Mobiles/Vendors/BaseVendor.cs | 2 +- Projects/UOContent/Multis/HouseFoundation.cs | 24 +- 26 files changed, 1351 insertions(+), 1606 deletions(-) create mode 100644 Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs rename Projects/{Server/Network/Packets/ItemPackets.cs => Server.Tests/Tests/Network/Packets/Outgoing/ContainerPackets.cs} (58%) create mode 100644 Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPackets.cs create mode 100644 Projects/Server/Network/Packets/OutgoingContainerPackets.cs create mode 100644 Projects/Server/Network/Packets/OutgoingEntityPackets.cs create mode 100644 Projects/Server/Network/Packets/OutgoingItemPackets.cs delete mode 100644 Projects/Server/Network/StaticPacketHandlers.cs diff --git a/Projects/Benchmarks/Packets/BenchmarkPacketBroadcast.cs b/Projects/Benchmarks/Packets/BenchmarkPacketBroadcast.cs index 933206cf3..d68bad03a 100644 --- a/Projects/Benchmarks/Packets/BenchmarkPacketBroadcast.cs +++ b/Projects/Benchmarks/Packets/BenchmarkPacketBroadcast.cs @@ -1,6 +1,5 @@ using System; using System.Buffers; -using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using Server; @@ -69,16 +68,14 @@ namespace Benchmarks return writer.Position; } - private Pipe[] _pipes = new Pipe[512]; - private IntPtr[] _pointers = new IntPtr[512]; + private Pipe[] _pipes = new Pipe[25000]; [IterationSetup] public void SetUp() { for (var i = 0; i < _pipes.Length; i++) { - _pipes[i] = new Pipe(new byte[8192]); - _pointers[i] = Marshal.AllocHGlobal(8192); + _pipes[i] = new Pipe(new byte[4096]); } } @@ -88,7 +85,6 @@ namespace Benchmarks for (var i = 0; i < _pipes.Length; i++) { _pipes[i] = null; - Marshal.FreeHGlobal(_pointers[i]); } } @@ -151,30 +147,28 @@ namespace Benchmarks return _pipes.Length; } + private static void SendUnicodeMessageWithSpan(Pipe pipe, string text) + { + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)]; + var length = CreateUnicodeMessage( + ref buffer, + Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text + ); + + buffer = buffer.Slice(0, length); + var result = pipe.Writer.TryGetMemory(); + result.CopyFrom(buffer); + pipe.Writer.Advance((uint)buffer.Length); + } + [Benchmark] - public int TestSpanWriterAllocH() + public int TestSpanWriterLooped() { var text = "This is some really long text that we want to handle. It should take a little bit to encode this."; - foreach (var pointer in _pointers) + foreach (var pipe in _pipes) { - Span buffer; - unsafe - { - buffer = new Span(pointer.ToPointer(), 8192); - } - - CreateUnicodeMessage( - ref buffer, - Serial.MinusOne, - -1, - MessageType.Regular, - 0x3B2, - 3, - "ENU", - "System", - text - ); + SendUnicodeMessageWithSpan(pipe, text); } return _pipes.Length; diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs new file mode 100644 index 000000000..ee0ec367e --- /dev/null +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs @@ -0,0 +1,196 @@ +using System; +using Server.Items; +using Server.Network; +using Xunit; + +namespace Server.Tests.Network +{ + [Collection("Sequential Tests")] + public class ContainerPacketTests : IClassFixture + { + + [Fact] + public void TestContainerDisplay() + { + Serial serial = 0x1000; + ushort gumpId = 100; + + var expected = new ContainerDisplay(serial, gumpId).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendDisplayContainer(serial, gumpId); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestContainerDisplayHS() + { + Serial serial = 0x1000; + ushort gumpId = 100; + + var expected = new ContainerDisplayHS(serial, gumpId).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.ContainerGridLines | ProtocolChanges.HighSeas; + ns.SendDisplayContainer(serial, gumpId); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestDisplaySpellbook() + { + Serial serial = 0x1000; + + var expected = new DisplaySpellbook(serial).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendDisplaySpellbook(serial); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestDisplaySpellbookHS() + { + Serial serial = 0x1000; + + var expected = new DisplaySpellbookHS(serial).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.ContainerGridLines | ProtocolChanges.HighSeas; + ns.SendDisplaySpellbook(serial); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestNewSpellbookContent() + { + Serial serial = 0x1000; + ushort graphic = 100; + ushort offset = 10; + ulong content = 0x123456789ABCDEF0; + bool opl = ObjectPropertyList.Enabled; + + var expected = new NewSpellbookContent(serial, graphic, offset, content).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.ContainerGridLines | ProtocolChanges.NewSpellbook; + ObjectPropertyList.Enabled = true; + ns.SendSpellbookContent(serial, graphic, offset, content); + ObjectPropertyList.Enabled = opl; + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestSpellbookContent() + { + Serial serial = 0x1000; + ushort offset = 10; + ushort graphic = 100; + ulong content = 0x123456789ABCDEF0; + + var expected = new SpellbookContent(serial, offset, content).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendSpellbookContent(serial, graphic, offset, content); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestSpellbookContent6017() + { + Serial serial = 0x1000; + ushort offset = 10; + ushort graphic = 100; + ulong content = 0x123456789ABCDEF0; + + var expected = new SpellbookContent6017(serial, offset, content).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges |= ProtocolChanges.ContainerGridLines; + ns.SendSpellbookContent(serial, graphic, offset, content); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestContainerContentUpdate() + { + Serial serial = 0x1; + var item = new Item(serial); + + var expected = new ContainerContentUpdate(item).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendContainerContentUpdate(item); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestContainerContentUpdate6017() + { + Serial serial = 0x1; + var item = new Item(serial); + + var expected = new ContainerContentUpdate6017(item).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges |= ProtocolChanges.ContainerGridLines; + ns.SendContainerContentUpdate(item); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestContainerContent() + { + var cont = new Container(World.NewItem); + cont.AddItem(new Item(World.NewItem)); + + var m = new Mobile(0x1); + m.DefaultMobileInit(); + + var expected = new ContainerContent(m, cont).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendContainerContent(m, cont); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestContainerContent6017() + { + var cont = new Container(World.NewItem); + cont.AddItem(new Item(World.NewItem)); + + var m = new Mobile(0x1); + m.DefaultMobileInit(); + + var expected = new ContainerContent6017(m, cont).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges |= ProtocolChanges.ContainerGridLines; + ns.SendContainerContent(m, cont); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + } +} diff --git a/Projects/Server/Network/Packets/ItemPackets.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPackets.cs similarity index 58% rename from Projects/Server/Network/Packets/ItemPackets.cs rename to Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPackets.cs index 1ef140e91..543533b48 100644 --- a/Projects/Server/Network/Packets/ItemPackets.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPackets.cs @@ -1,210 +1,8 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ItemPackets.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; using System.IO; -using Server.Items; namespace Server.Network { - public sealed class WorldItem : Packet - { - public WorldItem(Item item) : base(0x1A) - { - EnsureCapacity(20); - - // 14 base length - // +2 - Amount - // +2 - Hue - // +1 - Flags - - var serial = item.Serial.Value; - var itemID = item.ItemID & 0x3FFF; - var amount = item.Amount; - var loc = item.Location; - var x = loc.m_X; - var y = loc.m_Y; - var hue = item.Hue; - var flags = item.GetPacketFlags(); - var direction = (int)item.Direction; - - if (amount != 0) - { - serial |= 0x80000000; - } - else - { - serial &= 0x7FFFFFFF; - } - - Stream.Write(serial); - - if (item is BaseMulti) - { - Stream.Write((short)(itemID | 0x4000)); - } - else - { - Stream.Write((short)itemID); - } - - if (amount != 0) - { - Stream.Write((short)amount); - } - - x &= 0x7FFF; - - if (direction != 0) - { - x |= 0x8000; - } - - Stream.Write((short)x); - - y &= 0x3FFF; - - if (hue != 0) - { - y |= 0x8000; - } - - if (flags != 0) - { - y |= 0x4000; - } - - Stream.Write((short)y); - - if (direction != 0) - { - Stream.Write((byte)direction); - } - - Stream.Write((sbyte)loc.m_Z); - - if (hue != 0) - { - Stream.Write((ushort)hue); - } - - if (flags != 0) - { - Stream.Write((byte)flags); - } - } - } - - public sealed class WorldItemSA : Packet - { - public WorldItemSA(Item item) : base(0xF3, 24) - { - Stream.Write((short)0x1); - - var itemID = item.ItemID; - - if (item is BaseMulti) - { - Stream.Write((byte)0x02); - - Stream.Write(item.Serial); - - itemID &= 0x3FFF; - - Stream.Write((short)itemID); - - Stream.Write((byte)0); - } - else - { - Stream.Write((byte)0x00); - - Stream.Write(item.Serial); - - itemID &= 0x7FFF; - - Stream.Write((short)itemID); - - Stream.Write((byte)0); - } - - var amount = item.Amount; - Stream.Write((short)amount); - Stream.Write((short)amount); - - var loc = item.Location; - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write((sbyte)loc.m_Z); - - Stream.Write((byte)item.Light); - Stream.Write((short)item.Hue); - Stream.Write((byte)item.GetPacketFlags()); - } - } - - public sealed class WorldItemHS : Packet - { - public WorldItemHS(Item item) : base(0xF3, 26) - { - Stream.Write((short)0x1); - - var itemID = item.ItemID; - - if (item is BaseMulti) - { - Stream.Write((byte)0x02); - - Stream.Write(item.Serial); - - itemID &= 0x3FFF; - - Stream.Write((ushort)itemID); - - Stream.Write((byte)0); - } - else - { - Stream.Write((byte)0x00); - - Stream.Write(item.Serial); - - itemID &= 0xFFFF; - - Stream.Write((ushort)itemID); - - Stream.Write((byte)0); - } - - var amount = item.Amount; - Stream.Write((short)amount); - Stream.Write((short)amount); - - var loc = item.Location; - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write((sbyte)loc.m_Z); - - Stream.Write((byte)item.Light); - Stream.Write((short)item.Hue); - Stream.Write((byte)item.GetPacketFlags()); - - Stream.Write((short)0x00); // ?? - } - } - public sealed class DisplaySpellbook : Packet { public DisplaySpellbook(Serial book) : base(0x24, 7) @@ -414,8 +212,8 @@ namespace Server.Network Stream.Write((ushort)child.ItemID); Stream.Write((byte)0); // signed, itemID offset Stream.Write((ushort)Math.Min(child.Amount, ushort.MaxValue)); - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); + Stream.Write((short)loc.X); + Stream.Write((short)loc.Y); Stream.Write(beheld.Serial); Stream.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); @@ -455,8 +253,8 @@ namespace Server.Network Stream.Write((ushort)child.ItemID); Stream.Write((byte)0); // signed, itemID offset Stream.Write((ushort)Math.Min(child.Amount, ushort.MaxValue)); - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); + Stream.Write((short)loc.X); + Stream.Write((short)loc.Y); Stream.Write((byte)0); // Grid Location? Stream.Write(beheld.Serial); Stream.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs index 74436f928..7ffdbf57a 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs @@ -34,87 +34,13 @@ namespace Server.Tests.Network Direction = Direction.Left }; - var data = new WorldItem(item).Compile(); + var expected = new WorldItem(item).Compile(); - Span expectedData = stackalloc byte[20]; // Max size - var pos = 0; + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendWorldItem(item); - expectedData.Write(ref pos, (byte)0x1A); - pos += 2; // Length - - if (item.Amount != 0) - { - expectedData.Write(ref pos, serial | 0x80000000); - } - else - { - expectedData.Write(ref pos, serial & 0x7FFFFFFF); - } - - if (item is BaseMulti) - { - expectedData.Write(ref pos, (ushort)(item.ItemID | 0x4000)); - } - else - { - expectedData.Write(ref pos, (ushort)item.ItemID); - } - - if (item.Amount != 0) - { - expectedData.Write(ref pos, (ushort)item.Amount); - } - - var direction = (byte)item.Direction; - var x = (ushort)(item.X & 0x7FFF); - - if (direction != 0) - { - x |= 0x8000; - } - - expectedData.Write(ref pos, x); - - var hue = item.Hue; - var flags = item.GetPacketFlags(); - var y = (ushort)(item.Y & 0x3FFF); - - if (hue != 0) - { - y |= 0x8000; - } - - if (flags != 0) - { - y |= 0x4000; - } - - expectedData.Write(ref pos, y); - - if (direction != 0) - { - expectedData.Write(ref pos, direction); - } - - expectedData.Write(ref pos, (byte)item.Z); - - if (hue != 0) - { - expectedData.Write(ref pos, (ushort)hue); - } - - if (flags != 0) - { - expectedData.Write(ref pos, (byte)flags); - } - - // Length - expectedData.Slice(1, 2).Write((ushort)pos); - - // Slice the data to match in size - data = data.Slice(0, pos); - - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } [Fact] @@ -142,34 +68,14 @@ namespace Server.Tests.Network Location = new Point3D(1000, 100, -10) }; - var loc = item.Location; - var isMulti = item is BaseMulti; + var expected = new WorldItemSA(item).Compile(); - var data = new WorldItemSA(item).Compile(); + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.StygianAbyss; + ns.SendWorldItem(item); - Span expectedData = stackalloc byte[24]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0xF3); // Packet ID - expectedData.Write(ref pos, (ushort)0x1); - expectedData.Write(ref pos, (byte)(isMulti ? 0x2 : 0x00)); // Item Type (Regular, or Multi) - expectedData.Write(ref pos, item.Serial); - expectedData.Write(ref pos, (ushort)(item.ItemID & (isMulti ? 0x3FFF : 0xFFFF))); - -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0) -#else - pos++; -#endif - - expectedData.Write(ref pos, (ushort)item.Amount); // Amount (min?) - expectedData.Write(ref pos, (ushort)item.Amount); // Amount (max?) - expectedData.Write(ref pos, loc); // X, Y, Z - expectedData.Write(ref pos, (byte)item.Light); // Light - expectedData.Write(ref pos, (ushort)item.Hue); // Hue - expectedData.Write(ref pos, (byte)item.GetPacketFlags()); // Flags - - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } [Fact] @@ -197,410 +103,14 @@ namespace Server.Tests.Network Location = new Point3D(1000, 100, -10) }; - var loc = item.Location; - var isMulti = item is BaseMulti; + var expected = new WorldItemHS(item).Compile(); - var data = new WorldItemHS(item).Compile(); + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.StygianAbyss | ProtocolChanges.HighSeas; + ns.SendWorldItem(item); - Span expectedData = stackalloc byte[26]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0xF3); // Packet ID - expectedData.Write(ref pos, (ushort)0x1); - expectedData.Write(ref pos, (byte)(isMulti ? 0x2 : 0x00)); // Item Type (Regular, or Multi) - expectedData.Write(ref pos, item.Serial); - expectedData.Write(ref pos, (ushort)(item.ItemID & (isMulti ? 0x3FFF : 0xFFFF))); - -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); -#else - pos++; -#endif - - expectedData.Write(ref pos, (ushort)item.Amount); // Amount (min?) - expectedData.Write(ref pos, (ushort)item.Amount); // Amount (max?) - expectedData.Write(ref pos, loc); // X, Y, Z - expectedData.Write(ref pos, (byte)item.Light); // Light - expectedData.Write(ref pos, (ushort)item.Hue); // Hue - expectedData.Write(ref pos, (byte)item.GetPacketFlags()); // Flags - -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (ushort)0); // ?? -#endif - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestContainerDisplay() - { - Serial serial = 0x1000; - ushort gumpId = 100; - - var data = new ContainerDisplay(serial, gumpId).Compile(); - - Span expectedData = stackalloc byte[7]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x24); // Packet ID - expectedData.Write(ref pos, serial); - expectedData.Write(ref pos, gumpId); - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestContainerDisplayHS() - { - Serial serial = 0x1000; - ushort gumpId = 100; - - var data = new ContainerDisplayHS(serial, gumpId).Compile(); - - Span expectedData = stackalloc byte[9]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x24); // Packet ID - expectedData.Write(ref pos, serial); - expectedData.Write(ref pos, gumpId); - expectedData.Write(ref pos, (ushort)0x7D); // Max Items? - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestDisplaySpellbook() - { - Serial serial = 0x1000; - - var data = new DisplaySpellbook(serial).Compile(); - - Span expectedData = stackalloc byte[7]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x24); // Packet ID - expectedData.Write(ref pos, serial); - expectedData.Write(ref pos, (ushort)0xFFFF); // Gump ID - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestDisplaySpellbookHS() - { - Serial serial = 0x1000; - - var data = new DisplaySpellbookHS(serial).Compile(); - - Span expectedData = stackalloc byte[9]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x24); // Packet ID - expectedData.Write(ref pos, serial); - expectedData.Write(ref pos, (ushort)0xFFFF); // Gump ID - expectedData.Write(ref pos, (ushort)0x7D); // Max Items? - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestNewSpellbookContent() - { - Serial serial = 0x1000; - ushort graphic = 100; - ushort offset = 10; - ulong content = 0x123456789ABCDEF0; - - var data = new NewSpellbookContent(serial, graphic, offset, content).Compile(); - - Span expectedData = stackalloc byte[23]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0xBF); // Packet ID - expectedData.Write(ref pos, (ushort)0x17); // Length - expectedData.Write(ref pos, (ushort)0x1B); // Sub-packet - expectedData.Write(ref pos, (ushort)0x1); // Command - expectedData.Write(ref pos, serial); - expectedData.Write(ref pos, graphic); - expectedData.Write(ref pos, offset); - expectedData.WriteLE(ref pos, content); - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestSpellbookContent() - { - Serial serial = 0x1000; - ushort offset = 10; - ulong content = 0x123456789ABCDEF0; - - var data = new SpellbookContent(serial, offset, content).Compile(); - - Span expectedData = stackalloc byte[5 + 64 * 19]; // Max size - var pos = 0; - - expectedData.Write(ref pos, (byte)0x3C); // Packet ID - pos += 4; // Length + spell count - - ushort count = 0; - - for (var i = 0; i < 64; i++) - { - if ((content & (1ul << i)) != 0) - { - expectedData.Write(ref pos, 0x7FFFFFFF - i); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (ushort)0); - expectedData.Write(ref pos, (byte)0); -#else - pos += 3; -#endif - expectedData.Write(ref pos, (ushort)(i + offset)); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, 0); // X. Y -#else - pos += 4; -#endif - expectedData.Write(ref pos, serial); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (ushort)0); -#else - pos += 2; -#endif - count++; - } - } - - expectedData.Slice(1, 2).Write((ushort)pos); // Length - expectedData.Slice(3, 2).Write(count); // Count - - expectedData = expectedData.Slice(0, pos); - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestSpellbookContent6017() - { - Serial serial = 0x1000; - ushort offset = 10; - ulong content = 0x123456789ABCDEF0; - - var data = new SpellbookContent6017(serial, offset, content).Compile(); - - Span expectedData = stackalloc byte[5 + 64 * 20]; // Max size - var pos = 0; - - expectedData.Write(ref pos, (byte)0x3C); // Packet ID - pos += 4; // Length + spell count - - ushort count = 0; - - for (var i = 0; i < 64; i++) - { - if ((content & (1ul << i)) != 0) - { - expectedData.Write(ref pos, 0x7FFFFFFF - i); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (ushort)0); - expectedData.Write(ref pos, (byte)0); -#else - pos += 3; -#endif - expectedData.Write(ref pos, (ushort)(i + offset)); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, 0); // X. Y - expectedData.Write(ref pos, (byte)0); // Grid Location -#else - pos += 5; -#endif - expectedData.Write(ref pos, serial); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (ushort)0); -#else - pos += 2; -#endif - count++; - } - } - - expectedData.Slice(1, 2).Write((ushort)pos); // Length - expectedData.Slice(3, 2).Write(count); // Count - - expectedData = expectedData.Slice(0, pos); - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestContainerContentUpdate() - { - Serial serial = 0x1; - var item = new Item(serial); - - var data = new ContainerContentUpdate(item).Compile(); - - Span expectedData = stackalloc byte[20]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x25); // Packet ID - expectedData.Write(ref pos, item.Serial); - expectedData.Write(ref pos, (ushort)item.ItemID); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); // ItemID offset -#else - pos++; -#endif - expectedData.Write(ref pos, (ushort)Math.Min(item.Amount, ushort.MaxValue)); - expectedData.Write(ref pos, (ushort)item.X); - expectedData.Write(ref pos, (ushort)item.Y); - expectedData.Write(ref pos, item.Parent?.Serial ?? Serial.Zero); - expectedData.Write(ref pos, (ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue)); - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestContainerContentUpdate6017() - { - Serial serial = 0x1; - var item = new Item(serial); - - var data = new ContainerContentUpdate6017(item).Compile(); - - Span expectedData = stackalloc byte[21]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x25); // Packet ID - expectedData.Write(ref pos, item.Serial); - expectedData.Write(ref pos, (ushort)item.ItemID); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); // ItemID offset -#else - pos++; -#endif - expectedData.Write(ref pos, (ushort)Math.Min(item.Amount, ushort.MaxValue)); - expectedData.Write(ref pos, (ushort)item.X); - expectedData.Write(ref pos, (ushort)item.Y); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); // Grid Location? -#else - pos++; -#endif - expectedData.Write(ref pos, item.Parent?.Serial ?? Serial.Zero); - expectedData.Write(ref pos, (ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue)); - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestContainerContent() - { - var cont = new Container(World.NewItem); - cont.AddItem(new Item(World.NewItem)); - - var m = new Mobile(0x1); - m.DefaultMobileInit(); - - var data = new ContainerContent(m, cont).Compile(); - - Span expectedData = stackalloc byte[5 + cont.Items.Count * 19]; // Max Size - var pos = 0; - - expectedData.Write(ref pos, (byte)0x3C); // Packet ID - pos += 4; // Length + Count - - ushort count = 0; - - var itemCount = cont.Items.Count; - for (var i = 0; i < itemCount; i++) - { - var child = cont.Items[i]; - if (child.Deleted || !m.CanSee(child)) - { - continue; - } - - expectedData.Write(ref pos, child.Serial); - expectedData.Write(ref pos, (ushort)child.ItemID); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); // ItemID offset -#else - pos++; -#endif - expectedData.Write(ref pos, (ushort)Math.Min(child.Amount, ushort.MaxValue)); - expectedData.Write(ref pos, (ushort)child.X); - expectedData.Write(ref pos, (ushort)child.Y); - expectedData.Write(ref pos, cont.Serial); - expectedData.Write(ref pos, (ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); - - count++; - } - - expectedData.Slice(1, 2).Write((ushort)pos); // Length - expectedData.Slice(3, 2).Write(count); // Count - - expectedData = expectedData.Slice(0, pos); - - AssertThat.Equal(data, expectedData); - } - - [Fact] - public void TestContainerContent6017() - { - var cont = new Container(World.NewItem); - cont.AddItem(new Item(World.NewItem)); - - var m = new Mobile(0x1); - m.DefaultMobileInit(); - - var data = new ContainerContent6017(m, cont).Compile(); - - Span expectedData = stackalloc byte[5 + cont.Items.Count * 20]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x3C); // Packet ID - pos += 4; // Length + Count - - ushort count = 0; - - var itemCount = cont.Items.Count; - for (var i = 0; i < itemCount; i++) - { - var child = cont.Items[i]; - if (child.Deleted || !m.CanSee(child)) - { - continue; - } - - expectedData.Write(ref pos, child.Serial); - expectedData.Write(ref pos, (ushort)child.ItemID); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); // ItemID offset -#else - pos++; -#endif - expectedData.Write(ref pos, (ushort)Math.Min(child.Amount, ushort.MaxValue)); - expectedData.Write(ref pos, (ushort)child.X); - expectedData.Write(ref pos, (ushort)child.Y); -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); // Grid Location? -#else - pos++; -#endif - expectedData.Write(ref pos, cont.Serial); - expectedData.Write(ref pos, (ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); - - count++; - } - - expectedData.Slice(1, 2).Write((ushort)pos); // Length - expectedData.Slice(3, 2).Write(count); // Count - - expectedData = expectedData.Slice(0, pos); - - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPackets.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPackets.cs new file mode 100644 index 000000000..732fa6556 --- /dev/null +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPackets.cs @@ -0,0 +1,208 @@ +using Server.Items; + +namespace Server.Network +{ + public sealed class WorldItem : Packet + { + public WorldItem(Item item) : base(0x1A) + { + EnsureCapacity(20); + + // 14 base length + // +2 - Amount + // +2 - Hue + // +1 - Flags + + var serial = item.Serial.Value; + var itemID = item.ItemID & 0x3FFF; + var amount = item.Amount; + var loc = item.Location; + var x = loc.X; + var y = loc.Y; + var hue = item.Hue; + var flags = item.GetPacketFlags(); + var direction = (int)item.Direction; + + if (amount != 0) + { + serial |= 0x80000000; + } + else + { + serial &= 0x7FFFFFFF; + } + + Stream.Write(serial); + + if (item is BaseMulti) + { + Stream.Write((short)(itemID | 0x4000)); + } + else + { + Stream.Write((short)itemID); + } + + if (amount != 0) + { + Stream.Write((short)amount); + } + + x &= 0x7FFF; + + if (direction != 0) + { + x |= 0x8000; + } + + Stream.Write((short)x); + + y &= 0x3FFF; + + if (hue != 0) + { + y |= 0x8000; + } + + if (flags != 0) + { + y |= 0x4000; + } + + Stream.Write((short)y); + + if (direction != 0) + { + Stream.Write((byte)direction); + } + + Stream.Write((sbyte)loc.Z); + + if (hue != 0) + { + Stream.Write((ushort)hue); + } + + if (flags != 0) + { + Stream.Write((byte)flags); + } + } + } + + public sealed class WorldItemSA : Packet + { + public WorldItemSA(Item item) : base(0xF3, 24) + { + Stream.Write((short)0x1); + + var itemID = item.ItemID; + + if (item is BaseMulti) + { + Stream.Write((byte)0x02); + + Stream.Write(item.Serial); + + itemID &= 0x3FFF; + + Stream.Write((short)itemID); + + Stream.Write((byte)0); + } + else + { + Stream.Write((byte)0x00); + + Stream.Write(item.Serial); + + itemID &= 0x7FFF; + + Stream.Write((short)itemID); + + Stream.Write((byte)0); + } + + var amount = item.Amount; + Stream.Write((short)amount); + Stream.Write((short)amount); + + var loc = item.Location; + Stream.Write((short)loc.X); + Stream.Write((short)loc.Y); + Stream.Write((sbyte)loc.Z); + + Stream.Write((byte)item.Light); + Stream.Write((short)item.Hue); + Stream.Write((byte)item.GetPacketFlags()); + } + } + + public sealed class WorldItemHS : Packet + { + public WorldItemHS(Item item) : base(0xF3, 26) + { + Stream.Write((short)0x1); + + var itemID = item.ItemID; + + if (item is BaseMulti) + { + Stream.Write((byte)0x02); + + Stream.Write(item.Serial); + + itemID &= 0x3FFF; + + Stream.Write((ushort)itemID); + + Stream.Write((byte)0); + } + else + { + Stream.Write((byte)0x00); + + Stream.Write(item.Serial); + + itemID &= 0xFFFF; + + Stream.Write((ushort)itemID); + + Stream.Write((byte)0); + } + + var amount = item.Amount; + Stream.Write((short)amount); + Stream.Write((short)amount); + + var loc = item.Location; + Stream.Write((short)loc.X); + Stream.Write((short)loc.Y); + Stream.Write((sbyte)loc.Z); + + Stream.Write((byte)item.Light); + Stream.Write((short)item.Hue); + Stream.Write((byte)item.GetPacketFlags()); + + Stream.Write((short)0x00); // ?? + } + } + + public sealed class OPLInfo : Packet + { + /*public OPLInfo( ObjectPropertyList list ) : base( 0xBF ) + { + EnsureCapacity( 13 ); + + m_Stream.Write( (short) 0x10 ); + m_Stream.Write( (int) list.Entity.Serial ); + m_Stream.Write( (int) list.Hash ); + }*/ + + public OPLInfo(Serial serial, int hash) : base(0xDC, 9) + { + Stream.Write(serial); + Stream.Write(hash); + } + } +} diff --git a/Projects/Server/Items/Container.cs b/Projects/Server/Items/Container.cs index d74afa9c3..bd8ebdc08 100644 --- a/Projects/Server/Items/Container.cs +++ b/Projects/Server/Items/Container.cs @@ -695,24 +695,15 @@ namespace Server.Items if (ns != null) { - if (ns.HighSeas) - { - to.Send(new ContainerDisplayHS(Serial, GumpID)); - } - else - { - to.Send(new ContainerDisplay(Serial, GumpID)); - } + ns.SendDisplayContainer(Serial, GumpID); SendContentTo(ns); if (ObjectPropertyList.Enabled) { - var items = Items; - - for (var i = 0; i < items.Count; ++i) + for (var i = 0; i < Items.Count; ++i) { - to.Send(items[i].OPLPacket); + Items[i].SendOPLPacketTo(to.NetState, true); } } } @@ -764,22 +755,7 @@ namespace Server.Items } } - public virtual void SendContentTo(NetState state) - { - if (state == null) - { - return; - } - - if (state.ContainerGridLines) - { - state.Send(new ContainerContent6017(state.Mobile, this)); - } - else - { - state.Send(new ContainerContent(state.Mobile, this)); - } - } + public virtual void SendContentTo(NetState state) => state.SendContainerContent(state.Mobile, this); public override void GetProperties(ObjectPropertyList list) { diff --git a/Projects/Server/Items/Containers.cs b/Projects/Server/Items/Containers.cs index 7dcea24e8..0217d1973 100644 --- a/Projects/Server/Items/Containers.cs +++ b/Projects/Server/Items/Containers.cs @@ -89,7 +89,7 @@ namespace Server.Items if (SendDeleteOnClose) { - Owner?.Send(RemovePacket); + Owner?.NetState.SendRemoveEntity(Serial); } } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 05240b2f6..5f2a4b72c 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -353,19 +353,6 @@ namespace Server set => SetFlag(ImplFlag.Stackable, value); } - public Packet RemovePacket => StaticPacketHandlers.GetRemoveEntityPacket(this); - - // World packets need to be invalidated when any of the following changes: - // - ItemID - // - Amount - // - Location - // - Hue - // - Packet Flags - // - Direction - public Packet WorldPacket => StaticPacketHandlers.GetWorldItemPacket(this); - public Packet WorldPacketSA => StaticPacketHandlers.GetWorldItemSAPacket(this); - public Packet WorldPacketHS => StaticPacketHandlers.GetWorldItemHSPacket(this); - [CommandProperty(AccessLevel.GameMaster)] public bool Visible { @@ -375,7 +362,6 @@ namespace Server if (GetFlag(ImplFlag.Visible) != value) { SetFlag(ImplFlag.Visible, value); - ReleaseWorldPackets(); if (m_Map != null) { @@ -383,13 +369,16 @@ namespace Server var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { var m = state.Mobile; if (!m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) { - state.Send(RemovePacket); + state.Send(removeEntity); } } @@ -410,7 +399,7 @@ namespace Server if (GetFlag(ImplFlag.Movable) != value) { SetFlag(ImplFlag.Movable, value); - ReleaseWorldPackets(); + Delta(ItemDelta.Update); } } @@ -533,7 +522,6 @@ namespace Server if (m_Hue != value) { m_Hue = value; - ReleaseWorldPackets(); Delta(ItemDelta.Update); } @@ -599,7 +587,6 @@ namespace Server var oldPileWeight = PileWeight; m_ItemID = value; - ReleaseWorldPackets(); var newPileWeight = PileWeight; @@ -673,8 +660,6 @@ namespace Server if ((LightType)m_Direction != value) { m_Direction = (Direction)value; - ReleaseWorldPackets(); - Delta(ItemDelta.Update); } } @@ -689,8 +674,6 @@ namespace Server if (m_Direction != value) { m_Direction = value; - ReleaseWorldPackets(); - Delta(ItemDelta.Update); } } @@ -709,7 +692,6 @@ namespace Server var oldPileWeight = PileWeight; m_Amount = value; - ReleaseWorldPackets(); var newPileWeight = PileWeight; @@ -757,11 +739,7 @@ namespace Server set { SetFlag(ImplFlag.QuestItem, value); - InvalidateProperties(); - - ReleaseWorldPackets(); - Delta(ItemDelta.Update); } } @@ -803,8 +781,7 @@ namespace Server public int CompareTo(Item other) => other == null ? -1 : Serial.CompareTo(other.Serial); public virtual int HuedItemID => m_ItemID; - public OPLInfo OPLPacket => StaticPacketHandlers.GetOPLInfoPacket(this); - public ObjectPropertyList PropertyList => m_PropertyList ??= NewObjectPropertyList(); + public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this)); /// /// Overridable. Fills an with everything applicable. By default, this invokes @@ -923,7 +900,7 @@ namespace Server flags |= SaveFlag.Parent; } - if (items?.Count > 0) + if (items.Count > 0) { flags |= SaveFlag.Items; } @@ -1151,35 +1128,18 @@ namespace Server if (oldLocation.m_X != 0) { - var eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.InRange(oldLocation, GetUpdateRange(m))) - { - state.Send(RemovePacket); - } - } - - eable.Free(); + SendRemovePacket(oldLocation); } } m_Location = location; OnLocationChange(oldRealLocation); - ReleaseWorldPackets(); - var items = LookupItems(); - if (items != null) + for (var i = 0; i < items.Count; ++i) { - for (var i = 0; i < items.Count; ++i) - { - items[i].Map = map; - } + items[i].Map = map; } m_Map = map; @@ -1189,6 +1149,24 @@ namespace Server if (m_Map != null) { + Span oldWorldItem = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength]; + var length = OutgoingItemPackets.CreateWorldItem(ref oldWorldItem, this); + oldWorldItem = oldWorldItem.Slice(0, length); + + Span saWorldItem = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength]; + length = OutgoingItemPackets.CreateWorldItemNew(ref saWorldItem, this, false); + saWorldItem = saWorldItem.Slice(0, length); + + Span hsWorldItem = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength]; + length = OutgoingItemPackets.CreateWorldItemNew(ref hsWorldItem, this, true); + hsWorldItem = hsWorldItem.Slice(0, length); + + Span opl = ObjectPropertyList.Enabled ? stackalloc byte[OutgoingEntityPackets.OPLPacketLength] : null; + if (opl != null) + { + OutgoingEntityPackets.CreateOPLInfo(ref opl, this); + } + var eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); foreach (var state in eable) @@ -1197,7 +1175,18 @@ namespace Server if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) { - SendInfoTo(state); + if (state.HighSeas) + { + SendInfoTo(state, hsWorldItem, opl); + } + else if (state.StygianAbyss) + { + SendInfoTo(state, saWorldItem, opl); + } + else + { + SendInfoTo(state, oldWorldItem, opl); + } } } @@ -1219,13 +1208,16 @@ namespace Server { eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { var m = state.Mobile; if (!m.InRange(location, GetUpdateRange(m))) { - state.Send(RemovePacket); + state.Send(removeEntity); } } @@ -1237,8 +1229,6 @@ namespace Server m_Location = location; OnLocationChange(oldRealLocation); - ReleaseWorldPackets(); - eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); foreach (var state in eable) @@ -1287,12 +1277,9 @@ namespace Server var items = LookupItems(); - if (items != null) + for (var i = 0; i < items.Count; ++i) { - for (var i = 0; i < items.Count; ++i) - { - items[i].Map = value; - } + items[i].Map = value; } m_Map = value; @@ -1342,19 +1329,8 @@ namespace Server if (ns != null && rootParent.CanSee(this) && rootParent.InRange(worldLoc, GetUpdateRange(rootParent))) { - if (ns.ContainerGridLines) - { - ns.Send(new ContainerContentUpdate6017(this)); - } - else - { - ns.Send(new ContainerContentUpdate(this)); - } - - if (ObjectPropertyList.Enabled) - { - ns.Send(OPLPacket); - } + ns.SendContainerContentUpdate(this); + SendOPLPacketTo(ns); } } @@ -1380,19 +1356,8 @@ namespace Server if (ns != null && tradeRecip.CanSee(this) && tradeRecip.InRange(worldLoc, GetUpdateRange(tradeRecip))) { - if (ns.ContainerGridLines) - { - ns.Send(new ContainerContentUpdate6017(this)); - } - else - { - ns.Send(new ContainerContentUpdate(this)); - } - - if (ObjectPropertyList.Enabled) - { - ns.Send(OPLPacket); - } + ns.SendContainerContentUpdate(this); + SendOPLPacketTo(ns); } } @@ -1423,19 +1388,8 @@ namespace Server if (ns != null && mob.CanSee(this)) { - if (ns.ContainerGridLines) - { - ns.Send(new ContainerContentUpdate6017(this)); - } - else - { - ns.Send(new ContainerContentUpdate(this)); - } - - if (ObjectPropertyList.Enabled) - { - ns.Send(OPLPacket); - } + ns.SendContainerContentUpdate(this); + SendOPLPacketTo(ns); } } } @@ -1465,20 +1419,14 @@ namespace Server { if (m_Parent == null) { - SendInfoTo(state, ObjectPropertyList.Enabled); + SendInfoTo(state); } else { if (m_Parent is Item) { - if (state.ContainerGridLines) - { - state.Send(new ContainerContentUpdate6017(this)); - } - else - { - state.Send(new ContainerContentUpdate(this)); - } + // TODO: Optimize by writing once? + state.SendContainerContentUpdate(this); } else if (m_Parent is Mobile) { @@ -1486,24 +1434,17 @@ namespace Server state.SendEquipUpdate(this); } - if (ObjectPropertyList.Enabled) - { - state.Send(OPLPacket); - } + SendOPLPacketTo(state); } } else if ((flags & ItemDelta.EquipOnly) != 0 && m_Parent is Mobile) { state.SendEquipUpdate(this); - - if (ObjectPropertyList.Enabled) - { - state.Send(OPLPacket); - } + SendOPLPacketTo(state); } else if (ObjectPropertyList.Enabled && (flags & ItemDelta.Properties) != 0) { - state.Send(OPLPacket); + SendOPLPacketTo(state); } } @@ -1521,14 +1462,11 @@ namespace Server var items = LookupItems(); - if (items != null) + for (var i = items.Count - 1; i >= 0; --i) { - for (var i = items.Count - 1; i >= 0; --i) + if (i < items.Count) { - if (i < items.Count) - { - items[i].OnParentDeleted(this); - } + items[i].OnParentDeleted(this); } } @@ -1561,7 +1499,7 @@ namespace Server OnAfterDelete(); - FreeCache(); + m_PropertyList = null; } public ISpawner Spawner @@ -1611,13 +1549,16 @@ namespace Server { eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { var m = state.Mobile; if (!m.InRange(value, GetUpdateRange(m))) { - state.Send(RemovePacket); + state.Send(removeEntity); } } @@ -1626,7 +1567,6 @@ namespace Server var oldLoc = m_Location; m_Location = value; - ReleaseWorldPackets(); SetLastMoved(); @@ -1648,17 +1588,13 @@ namespace Server RemDelta(ItemDelta.Update); } - else if (m_Parent is Item) - { - m_Location = value; - ReleaseWorldPackets(); - - Delta(ItemDelta.Update); - } else { m_Location = value; - ReleaseWorldPackets(); + if (m_Parent is Item) + { + Delta(ItemDelta.Update); + } } if (m_Parent == null) @@ -1669,7 +1605,6 @@ namespace Server else { m_Location = value; - ReleaseWorldPackets(); } OnLocationChange(oldLocation); @@ -1715,17 +1650,6 @@ namespace Server && p.Y >= Location.m_Y - range && p.Y <= Location.m_Y + range; - public void ReleaseOPLPacket() - { - if (m_PropertyList == null) - { - return; - } - - Packet.Release(m_PropertyList); - m_PropertyList = null; - } - public ExpandFlag GetExpandFlags() { var info = LookupCompactInfo(); @@ -1817,15 +1741,7 @@ namespace Server } } - public List LookupItems() - { - if (this is Container container) - { - return container.m_Items; - } - - return LookupCompactInfo()?.m_Items; - } + public List LookupItems() => (this is Container container ? container.m_Items : LookupCompactInfo()?.m_Items) ?? EmptyItems; public List AcquireItems() { @@ -1937,7 +1853,7 @@ namespace Server /// public virtual void SendPropertiesTo(Mobile from) { - from.Send(PropertyList); + from.NetState?.Send(PropertyList.Buffer); } /// @@ -2164,8 +2080,7 @@ namespace Server public void Bounce(Mobile from) { - m_Parent.RemoveItem(this); - + m_Parent?.RemoveItem(this); m_Parent = null; var bounce = GetBounce(); @@ -2491,22 +2406,17 @@ namespace Server } } - public ObjectPropertyList NewObjectPropertyList() + private ObjectPropertyList InitializePropertyList(ObjectPropertyList list) { - var list = new ObjectPropertyList(this); - GetProperties(list); AppendChildProperties(list); - list.Terminate(); - list.SetStatic(); return list; } public void ClearProperties() { - ReleaseOPLPacket(); - StaticPacketHandlers.FreeOPLInfoPacket(this); + m_PropertyList = null; } public void InvalidateProperties() @@ -2518,12 +2428,23 @@ namespace Server if (m_Map != null && m_Map != Map.Internal && !World.Loading) { - var oldList = m_PropertyList; - m_PropertyList = null; - - if (oldList != null && oldList.Hash != PropertyList.Hash) + int? oldHash; + int newHash; + if (m_PropertyList != null) + { + oldHash = m_PropertyList.Hash; + m_PropertyList.Reset(); + InitializePropertyList(m_PropertyList); + newHash = m_PropertyList.Hash; + } + else + { + oldHash = null; + newHash = PropertyList.Hash; + } + + if (oldHash != newHash) { - StaticPacketHandlers.FreeOPLInfoPacket(this); Delta(ItemDelta.Properties); } } @@ -2533,11 +2454,6 @@ namespace Server } } - public void ReleaseWorldPackets() - { - StaticPacketHandlers.FreeWorldItemPackets(this); - } - public virtual int GetPacketFlags() { var flags = 0; @@ -3163,36 +3079,39 @@ namespace Server public virtual int GetUpdateRange(Mobile m) => 18; - public void SendInfoTo(NetState state) + public virtual void SendInfoTo(NetState ns, ReadOnlySpan world, ReadOnlySpan opl = default) { - SendInfoTo(state, ObjectPropertyList.Enabled); + SendWorldPacketTo(ns, world); + SendOPLPacketTo(ns, opl); } - public virtual void SendInfoTo(NetState state, bool sendOplPacket) - { - state.Send(GetWorldPacketFor(state)); + public void SendInfoTo(NetState ns) => SendInfoTo(ns, ObjectPropertyList.Enabled); + public virtual void SendInfoTo(NetState ns, bool sendOplPacket) + { + SendWorldPacketTo(ns); + SendOPLPacketTo(ns, sendOplPacket); + } + + public void SendOPLPacketTo(NetState ns) => SendOPLPacketTo(ns, ObjectPropertyList.Enabled); + + public virtual void SendOPLPacketTo(NetState ns, bool sendOplPacket) + { if (sendOplPacket) { - state.Send(OPLPacket); + ns.SendOPLInfo(this); } } - protected virtual Packet GetWorldPacketFor(NetState state) + public virtual void SendOPLPacketTo(NetState ns, ReadOnlySpan opl) { - if (state.HighSeas) - { - return WorldPacketHS; - } - - if (state.StygianAbyss) - { - return WorldPacketSA; - } - - return WorldPacket; + ns?.Send(opl); } + protected virtual void SendWorldPacketTo(NetState ns) => ns.SendWorldItem(this); + + public virtual void SendWorldPacketTo(NetState ns, ReadOnlySpan world) => ns?.Send(world); + public virtual int GetTotal(TotalType type) => 0; public virtual void UpdateTotal(Item sender, TotalType type, int delta) @@ -3408,14 +3327,6 @@ namespace Server Delete(); } - public virtual void FreeCache() - { - ReleaseWorldPackets(); - StaticPacketHandlers.FreeRemoveItemPacket(this); - StaticPacketHandlers.FreeOPLInfoPacket(this); - ReleaseOPLPacket(); - } - public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text) { if (m_Map == null) @@ -3489,12 +3400,10 @@ namespace Server { var items = LookupItems(); - if (items?.Contains(item) == true) + if (items.Remove(item)) { item.SendRemovePacket(); - items.Remove(item); - if (!item.IsVirtualItem) { UpdateTotal(item, TotalType.Gold, -item.TotalGold); @@ -3712,28 +3621,27 @@ namespace Server var eable = map.GetItemsInRange(p, 0); var items = eable.Where( - item => + item => + { + if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) { - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) - { - return false; - } - - var id = item.ItemData; - - if (id.Surface) - { - var top = item.Z + id.CalcHeight; - if (top <= maxZ && top >= z) - { - z = top; - } - } - - return true; + return false; } - ) - .ToList(); + + var id = item.ItemData; + + if (id.Surface) + { + var top = item.Z + id.CalcHeight; + if (top <= maxZ && top >= z) + { + z = top; + } + } + + return true; + } + ).ToList(); eable.Free(); @@ -3915,24 +3823,27 @@ namespace Server return true; } - public void SendRemovePacket() + public void SendRemovePacket() => SendRemovePacket(GetWorldLocation()); + + public void SendRemovePacket(Point3D worldLoc) { if (Deleted || m_Map == null) { return; } - var worldLoc = GetWorldLocation(); - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { var m = state.Mobile; if (m.InRange(worldLoc, GetUpdateRange(m))) { - state.Send(RemovePacket); + state.Send(removeEntity); } } diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 22d9c4ab4..0009fc53f 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -1787,8 +1787,6 @@ namespace Server public Region Region => m_Region ?? (Map == null ? Map.Internal.DefaultRegion : Map.DefaultRegion); - public Packet RemovePacket => StaticPacketHandlers.GetRemoveEntityPacket(this); - [CommandProperty(AccessLevel.GameMaster)] public int SolidHueOverride { @@ -2566,8 +2564,7 @@ namespace Server public int CompareTo(Mobile other) => other == null ? -1 : Serial.CompareTo(other.Serial); public virtual int HuedItemID => m_Female ? 0x2107 : 0x2106; - public OPLInfo OPLPacket => StaticPacketHandlers.GetOPLInfoPacket(this); - public ObjectPropertyList PropertyList => m_PropertyList ??= NewObjectPropertyList(); + public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this)); public virtual void GetProperties(ObjectPropertyList list) { @@ -2783,7 +2780,7 @@ namespace Server OnAfterDelete(); - FreeCache(); + m_PropertyList = null; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] @@ -3305,7 +3302,7 @@ namespace Server if (sendOPLUpdate) { - ourState.Send(OPLPacket); + SendOPLPacketTo(ourState); } } @@ -3332,6 +3329,9 @@ namespace Server var eable = m.Map.GetClientsInRange(m.m_Location); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { beholder = state.Mobile; @@ -3340,7 +3340,7 @@ namespace Server { if (sendRemove) { - state.Send(RemovePacket); + state.Send(removeEntity); } if (sendIncoming) @@ -3440,10 +3440,7 @@ namespace Server state.Send(facialhairPacket); } - if (sendOPLUpdate) - { - state.Send(OPLPacket); - } + SendOPLPacketTo(state); } } @@ -3498,17 +3495,6 @@ namespace Server && p.Y >= Location.m_Y - range && p.Y <= Location.m_Y + range; - public void ReleaseOPLPacket() - { - if (m_PropertyList == null) - { - return; - } - - Packet.Release(m_PropertyList); - m_PropertyList = null; - } - protected virtual void OnRaceChange(Race oldRace) { } @@ -3667,7 +3653,7 @@ namespace Server public virtual void SendPropertiesTo(Mobile from) { - from.Send(PropertyList); + from.NetState?.Send(PropertyList.Buffer); } public virtual void OnAosSingleClick(Mobile from) @@ -5230,6 +5216,9 @@ namespace Server var eable = m_Map.GetClientsInRange(m_Location); var corpseSerial = c?.Serial ?? Serial.Zero; + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { if (state != m_NetState) @@ -5240,7 +5229,7 @@ namespace Server if (!state.Mobile.CanSee(this)) { - state.Send(RemovePacket); + state.Send(removeEntity); } } } @@ -5587,14 +5576,7 @@ namespace Server if (item.Parent is Item) { - if (state.ContainerGridLines) - { - state.Send(new ContainerContentUpdate6017(item)); - } - else - { - state.Send(new ContainerContentUpdate(item)); - } + state.SendContainerContentUpdate(item); } else if (item.Parent is Mobile) { @@ -5605,9 +5587,9 @@ namespace Server item.SendInfoTo(state); } - if (ObjectPropertyList.Enabled && item.Parent != null) + if (item.Parent != null) { - state.Send(item.OPLPacket); + item.SendOPLPacketTo(state); } } } @@ -7062,14 +7044,10 @@ namespace Server return; } - if (Items.Contains(item)) + if (Items.Remove(item)) { item.SendRemovePacket(); - // int oldCount = m_Items.Count; - - Items.Remove(item); - if (!item.IsVirtualItem) { UpdateTotal(item, TotalType.Gold, -item.TotalGold); @@ -7230,6 +7208,21 @@ namespace Server eable.Free(); } + public void SendOPLPacketTo(NetState state) => SendOPLPacketTo(state, ObjectPropertyList.Enabled); + + protected virtual void SendOPLPacketTo(NetState ns, bool sendOplPacket) + { + if (sendOplPacket) + { + ns.SendOPLInfo(this); + } + } + + public virtual void SendOPLPacketTo(NetState ns, ReadOnlySpan opl) + { + ns?.Send(opl); + } + public virtual void OnAccessLevelChanged(AccessLevel oldLevel) { } @@ -7262,11 +7255,14 @@ namespace Server var eable = m_Map.GetClientsInRange(m_Location); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { if (state != m_NetState && (everyone || !state.Mobile.CanSee(this))) { - state.Send(RemovePacket); + state.Send(removeEntity); } } @@ -7288,14 +7284,14 @@ namespace Server { if (m != this && Utility.InUpdateRange(m_Location, m.m_Location)) { - m_NetState.Send(m.RemovePacket); + m_NetState.SendRemoveEntity(m.Serial); } } else if (o is Item item) { if (InRange(item.Location, item.GetUpdateRange(this))) { - m_NetState.Send(item.RemovePacket); + m_NetState.SendRemoveEntity(item.Serial); } } } @@ -7399,10 +7395,7 @@ namespace Server ns.SendBondedStatus(m.Serial, true); } - if (ObjectPropertyList.Enabled) - { - ns.Send(m.OPLPacket); - } + m.SendOPLPacketTo(ns); } } } @@ -7544,11 +7537,14 @@ namespace Server var eable = m_Map.GetClientsInRange(m_Location); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var state in eable) { if (!state.Mobile.CanSee(this)) { - state.Send(RemovePacket); + state.Send(removeEntity); } else { @@ -7559,10 +7555,7 @@ namespace Server state.SendBondedStatus(Serial, true); } - if (ObjectPropertyList.Enabled) - { - state.Send(OPLPacket); - } + SendOPLPacketTo(state); } } @@ -7685,28 +7678,16 @@ namespace Server return delta != 0 ? body : 0; } - public virtual void FreeCache() + private ObjectPropertyList InitializePropertyList(ObjectPropertyList list) { - StaticPacketHandlers.FreeRemoveItemPacket(this); - StaticPacketHandlers.FreeOPLInfoPacket(this); - ReleaseOPLPacket(); - } - - public ObjectPropertyList NewObjectPropertyList() - { - var list = new ObjectPropertyList(this); - GetProperties(list); - list.Terminate(); - list.SetStatic(); return list; } public void ClearProperties() { - ReleaseOPLPacket(); - StaticPacketHandlers.FreeOPLInfoPacket(this); + m_PropertyList = null; } public void InvalidateProperties() @@ -7718,12 +7699,23 @@ namespace Server if (m_Map != null && m_Map != Map.Internal && !World.Loading) { - var oldList = m_PropertyList; - m_PropertyList = null; - - if (oldList != null && oldList.Hash != PropertyList.Hash) + int? oldHash; + int newHash; + if (m_PropertyList != null) + { + oldHash = m_PropertyList.Hash; + m_PropertyList.Reset(); + InitializePropertyList(m_PropertyList); + newHash = m_PropertyList.Hash; + } + else + { + oldHash = null; + newHash = PropertyList.Hash; + } + + if (oldHash != newHash) { - StaticPacketHandlers.FreeOPLInfoPacket(this); Delta(MobileDelta.Properties); } } @@ -7783,11 +7775,14 @@ namespace Server var eable = map.GetClientsInRange(oldLocation); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, Serial); + foreach (var ns in eable) { if (ns != m_NetState && !Utility.InUpdateRange(newLocation, ns.Mobile.Location)) { - ns.Send(RemovePacket); + ns.Send(removeEntity); } } @@ -7843,10 +7838,7 @@ namespace Server m.m_NetState.SendBondedStatus(Serial, true); } - if (ObjectPropertyList.Enabled) - { - m.m_NetState.Send(OPLPacket); - } + SendOPLPacketTo(m.m_NetState); } if (inOldRange || !CanSee(m)) @@ -7870,10 +7862,7 @@ namespace Server ourState.SendBondedStatus(m.Serial, true); } - if (ObjectPropertyList.Enabled) - { - ourState.Send(m.OPLPacket); - } + m.SendOPLPacketTo(ourState); } } @@ -7905,10 +7894,7 @@ namespace Server ns.SendBondedStatus(Serial, true); } - if (ObjectPropertyList.Enabled) - { - ns.Send(OPLPacket); - } + SendOPLPacketTo(ns); } } @@ -7993,10 +7979,7 @@ namespace Server state.SendBondedStatus(Serial, true); } - if (ObjectPropertyList.Enabled) - { - state.Send(OPLPacket); - } + SendOPLPacketTo(state); } } diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index 59e5989a1..40e92e478 100644 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -379,6 +379,11 @@ namespace Server.Network public void Send(ReadOnlySpan span) { + if (span == null) + { + return; + } + var length = span.Length; if (Connection == null || BlockAllPackets || length <= 0 || !GetSendBuffer(out var buffer)) { diff --git a/Projects/Server/Network/Packets/OutgoingContainerPackets.cs b/Projects/Server/Network/Packets/OutgoingContainerPackets.cs new file mode 100644 index 000000000..b01e937e4 --- /dev/null +++ b/Projects/Server/Network/Packets/OutgoingContainerPackets.cs @@ -0,0 +1,216 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: OutgoingContainerPackets.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; +using System.Buffers; +using System.IO; + +namespace Server.Network +{ + public static class OutgoingContainerPackets + { + public static void SendDisplaySpellbook(this NetState ns, Serial book) => ns.SendDisplayContainer(book, -1); + + public static void SendSpellbookContent(this NetState ns, Serial book, int graphic, int offset, ulong content) + { + if (ns == null) + { + return; + } + + if (ObjectPropertyList.Enabled && ns.NewSpellbook) + { + ns.SendNewSpellbookContent(book, graphic, offset, content); + } + else + { + ns.SendOldSpellbookContent(book, offset, content); + } + } + + public static void SendNewSpellbookContent(this NetState ns, Serial book, int graphic, int offset, ulong content) + { + if (ns == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0xBF); // Packet ID + writer.Write((ushort)23); // Length + writer.Write((short)0x1B); // Subpacket + writer.Write((short)0x01); // Command + + writer.Write(book); + writer.Write((short)graphic); + writer.Write((short)offset); + + for (var i = 0; i < 8; ++i) + { + writer.Write((byte)(content >> (i * 8))); + } + + ns.Send(ref buffer, writer.Position); + } + + public static void SendOldSpellbookContent(this NetState ns, Serial book, int offset, ulong content) + { + if (ns == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0x3C); // Packet ID + writer.Seek(4, SeekOrigin.Current); // Length & written count + + var written = 0; + + ulong mask = 1; + + for (var i = 0; i < 64; ++i, mask <<= 1) + { + if ((content & mask) != 0) + { + writer.Write(0x7FFFFFFF - i); + writer.Write((ushort)0); // child ItemID + writer.Write((byte)0); // ItemID offset + writer.Write((ushort)(i + offset)); // Amount + writer.Write(0); // X, Y + if (ns.ContainerGridLines) + { + writer.Write((byte)0); // Grid Location + } + writer.Write(book); + writer.Write((short)0); // Quest Hue + + ++written; + } + } + + var length = writer.Position; + writer.Seek(1, SeekOrigin.Begin); + writer.Write((ushort)length); + writer.Write((ushort)written); + + ns.Send(ref buffer, length); + } + + public static void SendDisplayContainer(this NetState ns, Serial cont, int gumpId) + { + if (ns == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0x24); // Packet ID + writer.Write(cont); + writer.Write((ushort)gumpId); + if (ns.HighSeas) + { + writer.Write((short)0x7D); + } + + ns.Send(ref buffer, writer.Position); + } + + public static void SendContainerContentUpdate(this NetState ns, Item item) + { + if (ns == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + Serial parentSerial; + + if (item.Parent is Item parentItem) + { + parentSerial = parentItem.Serial; + } + else + { + Console.WriteLine("Warning: ContainerContentUpdate on item with !(parent is Item)"); + parentSerial = Serial.Zero; + } + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0x25); // Packet ID + writer.Write(item.Serial); + writer.Write((ushort)item.ItemID); + writer.Write((byte)0); // signed, itemID offset + writer.Write((ushort)Math.Min(item.Amount, ushort.MaxValue)); + writer.Write((short)item.X); + writer.Write((short)item.Y); + if (ns.ContainerGridLines) + { + writer.Write((byte)0); // Grid Location? + } + writer.Write(parentSerial); + writer.Write((ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue)); + + ns.Send(ref buffer, writer.Position); + } + + public static void SendContainerContent(this NetState ns, Mobile beholder, Item beheld) + { + if (ns == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var items = beheld.Items; + var count = items.Count; + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0x3C); // Packet ID + writer.Seek(4, SeekOrigin.Current); // Length & writter count + + var written = 0; + + for (var i = 0; i < count; ++i) + { + var child = items[i]; + + if (!child.Deleted && beholder.CanSee(child)) + { + var loc = child.Location; + + writer.Write(child.Serial); + writer.Write((ushort)child.ItemID); + writer.Write((byte)0); // signed, itemID offset + writer.Write((ushort)Math.Min(child.Amount, ushort.MaxValue)); + writer.Write((short)loc.X); + writer.Write((short)loc.Y); + if (ns.ContainerGridLines) + { + writer.Write((byte)0); // Grid Location? + } + writer.Write(beheld.Serial); + writer.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); + + ++written; + } + } + + var length = writer.Position; + writer.Seek(1, SeekOrigin.Begin); + writer.Write((ushort)length); + writer.Write((ushort)written); + + ns.Send(ref buffer, length); + } + } +} diff --git a/Projects/Server/Network/Packets/OutgoingEntityPackets.cs b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs new file mode 100644 index 000000000..279e8aa68 --- /dev/null +++ b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs @@ -0,0 +1,73 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: OutgoingEntityPackets.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; +using System.Buffers; + +namespace Server.Network +{ + public static class OutgoingEntityPackets + { + public const int OPLPacketLength = 9; + public const int RemoveEntityLength = 5; + + public static void CreateOPLInfo(ref Span buffer, Item item) => + CreateOPLInfo(ref buffer, item.Serial, item.PropertyList.Hash); + + public static void CreateOPLInfo(ref Span buffer, Serial serial, int hash) + { + var writer = new SpanWriter(buffer); + writer.Write((byte)0xDC); // Packet ID + writer.Write(serial); + writer.Write(hash); + } + + public static void SendOPLInfo(this NetState ns, IPropertyListObject obj) => + ns.SendOPLInfo(obj.Serial, obj.PropertyList.Hash); + + public static void SendOPLInfo(this NetState ns, Serial serial, int hash) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[OPLPacketLength]; + CreateOPLInfo(ref buffer, serial, hash); + + ns.Send(buffer); + } + + public static void CreateRemoveEntity(ref Span buffer, Serial serial) + { + var writer = new SpanWriter(buffer); + writer.Write((byte)0x1D); // Packet ID + writer.Write(serial); + } + + public static void SendRemoveEntity(this NetState ns, Serial serial) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[RemoveEntityLength]; + CreateRemoveEntity(ref buffer, serial); + + ns.Send(buffer); + } + } +} diff --git a/Projects/Server/Network/Packets/OutgoingItemPackets.cs b/Projects/Server/Network/Packets/OutgoingItemPackets.cs new file mode 100644 index 000000000..692c08bb9 --- /dev/null +++ b/Projects/Server/Network/Packets/OutgoingItemPackets.cs @@ -0,0 +1,140 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: OutgoingItemPackets.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; +using System.Buffers; +using Server.Items; + +namespace Server.Network +{ + public static class OutgoingItemPackets + { + public const int MaxWorldItemPacketLength = 26; + + public static int CreateWorldItem(ref Span buffer, Item item) + { + var itemID = item is BaseMulti ? item.ItemID | 0x4000 : item.ItemID & 0x3FFF; + var hasAmount = item.Amount != 0; + var amount = item.Amount; + var serial = hasAmount ? item.Serial | 0x80000000 : item.Serial & 0x7FFFFFFF; + var loc = item.Location; + var hue = item.Hue; + var flags = item.GetPacketFlags(); + var direction = (int)item.Direction; + var hasDirection = direction != 0; + var hasHue = hue != 0; + var hasFlags = flags != 0; + var x = hasDirection ? loc.X | 0x8000 : loc.X & 0x7FFF; + var y = (loc.Y & 0x3FFF) | (hasHue ? 0x8000 : 0) | (hasFlags ? 0x4000 : 0); + var length = 14 + (hasAmount ? 2 : 0) + + (hasDirection ? 1 : 0) + + (hasHue ? 2 : 0) + + (hasFlags ? 1 : 0); + + var writer = new SpanWriter(buffer); + writer.Write((byte)0x1A); // Packet ID + writer.Write((ushort)length); + writer.Write(serial); + writer.Write((ushort)itemID); + + if (amount != 0) + { + writer.Write((ushort)amount); + } + + writer.Write((ushort)x); + writer.Write((ushort)y); + + if (direction != 0) + { + writer.Write((byte)direction); + } + + writer.Write((sbyte)loc.Z); + + if (hue != 0) + { + writer.Write((ushort)hue); + } + + if (flags != 0) + { + writer.Write((byte)flags); + } + + return writer.Position; + } + + public static void SendWorldItem(this NetState ns, Item item) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[MaxWorldItemPacketLength]; + + var length = ns.StygianAbyss ? + CreateWorldItemNew(ref buffer, item, ns.HighSeas) : + CreateWorldItem(ref buffer, item); + + ns.Send(buffer.Slice(0, length)); + } + + public static int CreateWorldItemNew(ref Span buffer, Item item, bool isHS) + { + var writer = new SpanWriter(buffer); + writer.Write((byte)0xF3); // Packet ID + writer.Write((short)0x1); // command + + var itemID = item.ItemID; + + if (item is BaseMulti) + { + writer.Write((byte)2); + writer.Write(item.Serial); + writer.Write((short)(itemID & 0x3FFF)); + writer.Write((byte)0); + } + else + { + writer.Write((byte)0); + writer.Write(item.Serial); + writer.Write((short)(itemID & (isHS ? 0xFFFF : 0x7FFF))); + writer.Write((byte)0); + } + + var amount = item.Amount; + writer.Write((short)amount); // Min + writer.Write((short)amount); // Max + + var loc = item.Location; + writer.Write((short)loc.X); + writer.Write((short)loc.Y); + writer.Write((sbyte)loc.Z); + + writer.Write((byte)item.Light); + writer.Write((short)item.Hue); + writer.Write((byte)item.GetPacketFlags()); + + if (isHS) + { + writer.Write((short)0); + } + + return writer.Position; + } + } +} diff --git a/Projects/Server/Network/StaticPacketHandlers.cs b/Projects/Server/Network/StaticPacketHandlers.cs deleted file mode 100644 index 09b2302c1..000000000 --- a/Projects/Server/Network/StaticPacketHandlers.cs +++ /dev/null @@ -1,138 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: StaticPacketHandlers.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.Collections.Concurrent; - -namespace Server.Network -{ - public static class StaticPacketHandlers - { - private static readonly ConcurrentDictionary OPLInfoPackets = - new(); - - private static readonly ConcurrentDictionary RemoveEntityPackets = - new(); - - private static readonly ConcurrentDictionary WorldItemPackets = - new(); - - private static readonly ConcurrentDictionary WorldItemSAPackets = - new(); - - private static readonly ConcurrentDictionary WorldItemHSPackets = - new(); - - public static OPLInfo GetOPLInfoPacket(IPropertyListObject obj) - { - return OPLInfoPackets.GetOrAdd( - obj, - value => - { - var packet = new OPLInfo(value.PropertyList.Entity.Serial, value.PropertyList.Hash); - packet.SetStatic(); - return packet; - } - ); - } - - public static OPLInfo FreeOPLInfoPacket(IPropertyListObject obj) - { - if (OPLInfoPackets.TryRemove(obj, out var p)) - { - Packet.Release(p); - } - - return p; - } - - public static RemoveEntity GetRemoveEntityPacket(IEntity entity) - { - return RemoveEntityPackets.GetOrAdd( - entity, - value => - { - var packet = new RemoveEntity(value.Serial); - packet.SetStatic(); - return packet; - } - ); - } - - public static void FreeRemoveItemPacket(IEntity entity) - { - if (RemoveEntityPackets.TryRemove(entity, out var p)) - { - Packet.Release(p); - } - } - - public static WorldItem GetWorldItemPacket(Item item) - { - return WorldItemPackets.GetOrAdd( - item, - value => - { - var packet = new WorldItem(value); - packet.SetStatic(); - return packet; - } - ); - } - - public static WorldItemSA GetWorldItemSAPacket(Item item) - { - return WorldItemSAPackets.GetOrAdd( - item, - value => - { - var packet = new WorldItemSA(value); - packet.SetStatic(); - return packet; - } - ); - } - - public static WorldItemHS GetWorldItemHSPacket(Item item) - { - return WorldItemHSPackets.GetOrAdd( - item, - value => - { - var packet = new WorldItemHS(value); - packet.SetStatic(); - return packet; - } - ); - } - - public static void FreeWorldItemPackets(Item item) - { - if (WorldItemPackets.TryRemove(item, out var wi)) - { - Packet.Release(wi); - } - - if (WorldItemSAPackets.TryRemove(item, out var wisa)) - { - Packet.Release(wisa); - } - - if (WorldItemHSPackets.TryRemove(item, out var wihs)) - { - Packet.Release(wihs); - } - } - } -} diff --git a/Projects/Server/ObjectPropertyList.cs b/Projects/Server/ObjectPropertyList.cs index f36f2684b..aaf410ee2 100644 --- a/Projects/Server/ObjectPropertyList.cs +++ b/Projects/Server/ObjectPropertyList.cs @@ -1,21 +1,18 @@ using System; -using System.IO; +using System.Buffers; using System.Text; -using Server.Network; namespace Server { public interface IPropertyListObject : IEntity { ObjectPropertyList PropertyList { get; } - OPLInfo OPLPacket { get; } void GetProperties(ObjectPropertyList list); } - public sealed class ObjectPropertyList : Packet + public sealed class ObjectPropertyList { - private static byte[] m_Buffer = new byte[1024]; private static readonly Encoding m_Encoding = Encoding.Unicode; // Each of these are localized to "~1_NOTHING~" which allows the string argument to be used @@ -25,25 +22,26 @@ namespace Server 1070722 }; - private int m_Hash; - private int m_Strings; + private int _hash; + private int _strings; + private byte[] _buffer = new byte[64]; + private int _position; - public ObjectPropertyList(IEntity e) : base(0xD6) + public ObjectPropertyList(IEntity e) { - EnsureCapacity(128); - Entity = e; - - Stream.Write((short)1); - Stream.Write(e.Serial); - Stream.Write((byte)0); - Stream.Write((byte)0); - Stream.Write(e.Serial); + Span buffer = _buffer; + buffer.Write(ref _position, (byte)0xD6); // Packet ID + _position += 2; // Length + buffer.Write(ref _position, (ushort)1); + buffer.Write(ref _position, e.Serial); + buffer.Write(ref _position, (ushort)0); + _position += 4; // Hash } public IEntity Entity { get; } - public int Hash => 0x40000000 + m_Hash; + public int Hash => 0x40000000 + _hash; public int Header { get; set; } @@ -51,40 +49,46 @@ namespace Server public static bool Enabled { get; set; } - public void Add(int number) + public byte[] Buffer => _buffer; + + public void Reset() { - if (number == 0) - { - return; - } + _position = 15; + _hash = 0; + _strings = 0; + } - AddHash(number); + public void Flush() + { + Resize(_buffer.Length * 2); + } - if (Header == 0) - { - Header = number; - HeaderArgs = ""; - } - - Stream.Write(number); - Stream.Write((short)0); + private void Resize(int amount) + { + Array.Resize(ref _buffer, amount); } public void Terminate() { - Stream.Write(0); + int length = _position + 4; + if (length != _buffer.Length) + { + Resize(length); + } - Stream.Seek(11, SeekOrigin.Begin); - Stream.Write(m_Hash); + Span buffer = _buffer; + buffer.Write(ref _position, 0); + buffer.Slice(11, 4).Write(_hash); + buffer.Slice(1, 2).Write((ushort)_position); } public void AddHash(int val) { - m_Hash ^= val & 0x3FFFFFF; - m_Hash ^= (val >> 26) & 0x3F; + _hash ^= val & 0x3FFFFFF; + _hash ^= (val >> 26) & 0x3F; } - public void Add(int number, string arguments) + public void Add(int number, string arguments = null) { if (number == 0) { @@ -100,21 +104,27 @@ namespace Server } AddHash(number); - AddHash(arguments.GetHashCode(StringComparison.Ordinal)); - - Stream.Write(number); - - var byteCount = m_Encoding.GetByteCount(arguments); - - if (byteCount > m_Buffer.Length) + if (arguments.Length > 0) { - m_Buffer = new byte[byteCount]; + AddHash(arguments.GetHashCode(StringComparison.Ordinal)); } - byteCount = m_Encoding.GetBytes(arguments, 0, arguments.Length, m_Buffer, 0); + int strLength = m_Encoding.GetByteCount(arguments); - Stream.Write((short)byteCount); - Stream.Write(m_Buffer, 0, byteCount); + int length = _position + 6 + strLength; + if (length > _buffer.Length) + { + Flush(); + } + + Span buffer = _buffer; + buffer.Write(ref _position, number); + buffer.Write(ref _position, (ushort)strLength); + if (strLength > 0) + { + m_Encoding.GetBytes(arguments, buffer.Slice(_position)); + _position += strLength; + } } public void Add(int number, string format, object arg0) @@ -137,7 +147,7 @@ namespace Server Add(number, string.Format(format, args)); } - private int GetStringNumber() => m_StringNumbers[m_Strings++ % m_StringNumbers.Length]; + private int GetStringNumber() => m_StringNumbers[_strings++ % m_StringNumbers.Length]; public void Add(string text) { @@ -164,22 +174,4 @@ namespace Server Add(GetStringNumber(), string.Format(format, args)); } } - - public sealed class OPLInfo : Packet - { - /*public OPLInfo( ObjectPropertyList list ) : base( 0xBF ) - { - EnsureCapacity( 13 ); - - m_Stream.Write( (short) 0x10 ); - m_Stream.Write( (int) list.Entity.Serial ); - m_Stream.Write( (int) list.Hash ); - }*/ - - public OPLInfo(Serial serial, int hash) : base(0xDC, 9) - { - Stream.Write(serial); - Stream.Write(hash); - } - } } diff --git a/Projects/UOContent/Commands/VisibilityList.cs b/Projects/UOContent/Commands/VisibilityList.cs index e72493080..9e3e0be0d 100644 --- a/Projects/UOContent/Commands/VisibilityList.cs +++ b/Projects/UOContent/Commands/VisibilityList.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Server.Mobiles; using Server.Network; @@ -66,13 +67,19 @@ namespace Server.Commands pm.VisibilityList.Clear(); pm.SendMessage("Your visibility list has been cleared."); - for (var i = 0; i < list.Count; ++i) + if (list.Count > 0) { - var m = list[i]; + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + OutgoingEntityPackets.CreateRemoveEntity(ref removeEntity, pm.Serial); - if (!m.CanSee(pm) && Utility.InUpdateRange(m, pm)) + for (var i = 0; i < list.Count; ++i) { - m.Send(pm.RemovePacket); + var m = list[i]; + + if (!m.CanSee(pm) && Utility.InUpdateRange(m, pm)) + { + m.NetState?.Send(removeEntity); + } } } } @@ -113,19 +120,16 @@ namespace Server.Commands { ns.Send(MobileIncoming.Create(ns, targ, pm)); - if (ObjectPropertyList.Enabled) - { - ns.Send(pm.OPLPacket); + pm.SendOPLPacketTo(ns); - foreach (var item in pm.Items) - { - ns.Send(item.OPLPacket); - } + foreach (var item in pm.Items) + { + item.SendOPLPacketTo(ns); } } else { - ns.Send(pm.RemovePacket); + ns.SendRemoveEntity(pm.Serial); } } } diff --git a/Projects/UOContent/Engines/ConPVP/Games/TourneyMatch.cs b/Projects/UOContent/Engines/ConPVP/Games/TourneyMatch.cs index b2540b6f4..2925e28a3 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/TourneyMatch.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/TourneyMatch.cs @@ -126,7 +126,7 @@ namespace Server.Engines.ConPVP { if (!mob.CanSee(view)) { - mob.Send(view.RemovePacket); + mob.NetState.SendRemoveEntity(view.Serial); } } diff --git a/Projects/UOContent/Items/Misc/Blocker.cs b/Projects/UOContent/Items/Misc/Blocker.cs index 828ec12e7..c9cdf83f3 100644 --- a/Projects/UOContent/Items/Misc/Blocker.cs +++ b/Projects/UOContent/Items/Misc/Blocker.cs @@ -1,9 +1,13 @@ +using System; +using System.Buffers.Binary; using Server.Network; namespace Server.Items { public class Blocker : Item { + private const ushort GMItemId = 0x1183; + [Constructible] public Blocker() : base(0x21A4) => Movable = false; @@ -13,16 +17,49 @@ namespace Server.Items public override int LabelNumber => 503057; // Impassable! - protected override Packet GetWorldPacketFor(NetState state) + public override void SendWorldPacketTo(NetState ns, ReadOnlySpan world) { - var mob = state.Mobile; - - if (mob?.AccessLevel >= AccessLevel.GameMaster) + var mob = ns.Mobile; + if (AccessLevel.GameMaster >= mob?.AccessLevel) { - return new GMItemPacket(this); + base.SendWorldPacketTo(ns, world); + return; } - return base.GetWorldPacketFor(state); + SendGMItem(ns); + } + + protected override void SendWorldPacketTo(NetState ns) + { + var mob = ns.Mobile; + if (AccessLevel.GameMaster >= mob?.AccessLevel) + { + base.SendWorldPacketTo(ns); + return; + } + + SendGMItem(ns); + } + + private void SendGMItem(NetState ns) + { + // GM Packet + Span buffer = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength]; + + int length; + + if (ns.StygianAbyss) + { + length = OutgoingItemPackets.CreateWorldItemNew(ref buffer, this, ns.HighSeas); + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(8, 2), GMItemId); + } + else + { + length = OutgoingItemPackets.CreateWorldItem(ref buffer, this); + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(7, 2), GMItemId); + } + + ns.Send(buffer.Slice(0, length)); } public override void Serialize(IGenericWriter writer) @@ -38,85 +75,5 @@ namespace Server.Items var version = reader.ReadInt(); } - - public sealed class GMItemPacket : Packet - { - public GMItemPacket(Item item) : base(0x1A) - { - EnsureCapacity(20); - - // 14 base length - // +2 - Amount - // +2 - Hue - // +1 - Flags - - var serial = item.Serial.Value; - var itemID = 0x1183; - var amount = item.Amount; - var loc = item.Location; - var x = loc.X; - var y = loc.Y; - var hue = item.Hue; - var flags = item.GetPacketFlags(); - var direction = (int)item.Direction; - - if (amount != 0) - { - serial |= 0x80000000; - } - else - { - serial &= 0x7FFFFFFF; - } - - Stream.Write(serial); - Stream.Write((short)(itemID & 0x7FFF)); - - if (amount != 0) - { - Stream.Write((short)amount); - } - - x &= 0x7FFF; - - if (direction != 0) - { - x |= 0x8000; - } - - Stream.Write((short)x); - - y &= 0x3FFF; - - if (hue != 0) - { - y |= 0x8000; - } - - if (flags != 0) - { - y |= 0x4000; - } - - Stream.Write((short)y); - - if (direction != 0) - { - Stream.Write((byte)direction); - } - - Stream.Write((sbyte)loc.Z); - - if (hue != 0) - { - Stream.Write((ushort)hue); - } - - if (flags != 0) - { - Stream.Write((byte)flags); - } - } - } } } diff --git a/Projects/UOContent/Items/Misc/BulletinBoards.cs b/Projects/UOContent/Items/Misc/BulletinBoards.cs index 52cabaa43..82d4dde5c 100644 --- a/Projects/UOContent/Items/Misc/BulletinBoards.cs +++ b/Projects/UOContent/Items/Misc/BulletinBoards.cs @@ -167,14 +167,7 @@ namespace Server.Items var state = from.NetState; state.Send(new BBDisplayBoard(this)); - if (state.ContainerGridLines) - { - state.Send(new ContainerContent6017(from, this)); - } - else - { - state.Send(new ContainerContent(from, this)); - } + state.SendContainerContent(from, this); } else { diff --git a/Projects/UOContent/Items/Misc/Corpses/Corpse.cs b/Projects/UOContent/Items/Misc/Corpses/Corpse.cs index fbac6fe2b..d379028a9 100644 --- a/Projects/UOContent/Items/Misc/Corpses/Corpse.cs +++ b/Projects/UOContent/Items/Misc/Corpses/Corpse.cs @@ -1,5 +1,7 @@ using System; +using System.Buffers; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Server.ContextMenus; using Server.Engines.PartySystem; using Server.Engines.Quests.Doom; @@ -799,25 +801,39 @@ namespace Server.Items return m_Devourer.Devour(this); // Devour the corpse if it hasn't } - public override void SendInfoTo(NetState state, bool sendOplPacket) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SendCorpseContent(NetState ns) { - base.SendInfoTo(state, sendOplPacket); - - if (!(((Body)Amount).IsHuman && ItemID == 0x2006)) + if (ns.ContainerGridLines) { - return; - } - - if (state.ContainerGridLines) - { - state.Send(new CorpseContent6017(state.Mobile, this)); + ns.Send(new CorpseContent6017(ns.Mobile, this)); } else { - state.Send(new CorpseContent(state.Mobile, this)); + ns.Send(new CorpseContent(ns.Mobile, this)); } - state.Send(new CorpseEquip(state.Mobile, this)); + ns.Send(new CorpseEquip(ns.Mobile, this)); + } + + public override void SendInfoTo(NetState ns, bool sendOplPacket) + { + base.SendInfoTo(ns, sendOplPacket); + + if (((Body)Amount).IsHuman && ItemID == 0x2006) + { + SendCorpseContent(ns); + } + } + + public override void SendInfoTo(NetState ns, ReadOnlySpan world, ReadOnlySpan opl = default) + { + base.SendInfoTo(ns, world, opl); + + if (((Body)Amount).IsHuman && ItemID == 0x2006) + { + SendCorpseContent(ns); + } } public bool IsCriminalAction(Mobile from) diff --git a/Projects/UOContent/Items/Misc/LOSBlocker.cs b/Projects/UOContent/Items/Misc/LOSBlocker.cs index 2a5a755c4..aedacc00e 100644 --- a/Projects/UOContent/Items/Misc/LOSBlocker.cs +++ b/Projects/UOContent/Items/Misc/LOSBlocker.cs @@ -1,9 +1,13 @@ +using System; +using System.Buffers.Binary; using Server.Network; namespace Server.Items { public class LOSBlocker : Item { + private const ushort GMItemId = 0x36FF; + [Constructible] public LOSBlocker() : base(0x21A2) => Movable = false; @@ -19,16 +23,49 @@ namespace Server.Items TileData.ItemTable[0x21A2].Height = 20; } - protected override Packet GetWorldPacketFor(NetState state) + public override void SendWorldPacketTo(NetState ns, ReadOnlySpan world) { - var mob = state.Mobile; - - if (mob?.AccessLevel >= AccessLevel.GameMaster) + var mob = ns.Mobile; + if (AccessLevel.GameMaster >= mob?.AccessLevel) { - return new GMItemPacket(this); + base.SendWorldPacketTo(ns, world); + return; } - return base.GetWorldPacketFor(state); + SendGMItem(ns); + } + + protected override void SendWorldPacketTo(NetState ns) + { + var mob = ns.Mobile; + if (AccessLevel.GameMaster >= mob?.AccessLevel) + { + base.SendWorldPacketTo(ns); + return; + } + + SendGMItem(ns); + } + + private void SendGMItem(NetState ns) + { + // GM Packet + Span buffer = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength]; + + int length; + + if (ns.StygianAbyss) + { + length = OutgoingItemPackets.CreateWorldItemNew(ref buffer, this, ns.HighSeas); + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(8, 2), GMItemId); + } + else + { + length = OutgoingItemPackets.CreateWorldItem(ref buffer, this); + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(7, 2), GMItemId); + } + + ns.Send(buffer.Slice(0, length)); } public override void Serialize(IGenericWriter writer) @@ -49,85 +86,5 @@ namespace Server.Items ItemID = 0x21A2; } } - - public sealed class GMItemPacket : Packet - { - public GMItemPacket(Item item) : base(0x1A) - { - EnsureCapacity(20); - - // 14 base length - // +2 - Amount - // +2 - Hue - // +1 - Flags - - var serial = item.Serial.Value; - var itemID = 0x36FF; - var amount = item.Amount; - var loc = item.Location; - var x = loc.X; - var y = loc.Y; - var hue = item.Hue; - var flags = item.GetPacketFlags(); - var direction = (int)item.Direction; - - if (amount != 0) - { - serial |= 0x80000000; - } - else - { - serial &= 0x7FFFFFFF; - } - - Stream.Write(serial); - Stream.Write((short)(itemID & 0x7FFF)); - - if (amount != 0) - { - Stream.Write((short)amount); - } - - x &= 0x7FFF; - - if (direction != 0) - { - x |= 0x8000; - } - - Stream.Write((short)x); - - y &= 0x3FFF; - - if (hue != 0) - { - y |= 0x8000; - } - - if (flags != 0) - { - y |= 0x4000; - } - - Stream.Write((short)y); - - if (direction != 0) - { - Stream.Write((byte)direction); - } - - Stream.Write((sbyte)loc.Z); - - if (hue != 0) - { - Stream.Write((ushort)hue); - } - - if (flags != 0) - { - Stream.Write((byte)flags); - } - } - } } } diff --git a/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs b/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs index 82c552b0a..fce7f0651 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs @@ -669,7 +669,6 @@ namespace Server.Items public void DisplayTo(Mobile to) { // The client must know about the spellbook or it will crash! - var ns = to.NetState; if (ns == null) @@ -679,64 +678,19 @@ namespace Server.Items if (Parent == null) { - to.Send(WorldPacket); + SendWorldPacketTo(to.NetState); } else if (Parent is Item) { - // What will happen if the client doesn't know about our parent? - if (ns.ContainerGridLines) - { - to.Send(new ContainerContentUpdate6017(this)); - } - else - { - to.Send(new ContainerContentUpdate(this)); - } + to.NetState.SendContainerContentUpdate(this); } else if (Parent is Mobile) { - // What will happen if the client doesn't know about our parent? to.NetState.SendEquipUpdate(this); } - if (ns.HighSeas) - { - to.Send(new DisplaySpellbookHS(Serial)); - } - else - { - to.Send(new DisplaySpellbook(Serial)); - } - - if (ObjectPropertyList.Enabled) - { - if (ns.NewSpellbook) - { - to.Send(new NewSpellbookContent(Serial, ItemID, BookOffset + 1, m_Content)); - } - else - { - if (ns.ContainerGridLines) - { - to.Send(new SpellbookContent6017(Serial, BookOffset + 1, m_Content)); - } - else - { - to.Send(new SpellbookContent(Serial, BookOffset + 1, m_Content)); - } - } - } - else - { - if (ns.ContainerGridLines) - { - to.Send(new SpellbookContent6017(Serial, BookOffset + 1, m_Content)); - } - else - { - to.Send(new SpellbookContent(Serial, BookOffset + 1, m_Content)); - } - } + to.NetState.SendDisplaySpellbook(Serial); + to.NetState.SendSpellbookContent(Serial, ItemID, BookOffset + 1, m_Content); } public override void GetProperties(ObjectPropertyList list) diff --git a/Projects/UOContent/Misc/Paperdoll.cs b/Projects/UOContent/Misc/Paperdoll.cs index a30f50f81..c147997a9 100644 --- a/Projects/UOContent/Misc/Paperdoll.cs +++ b/Projects/UOContent/Misc/Paperdoll.cs @@ -20,18 +20,13 @@ namespace Server.Misc ) ); - if (ObjectPropertyList.Enabled) + for (var i = 0; i < beheld.Items.Count; ++i) { - var items = beheld.Items; - - for (var i = 0; i < items.Count; ++i) - { - beholder.Send(items[i].OPLPacket); - } - - // NOTE: OSI sends MobileUpdate when opening your own paperdoll. - // It has a very bad rubber-banding affect. What positive affects does it have? + beheld.Items[i].SendOPLPacketTo(beholder.NetState); } + + // NOTE: OSI sends MobileUpdate when opening your own paperdoll. + // It has a very bad rubber-banding affect. What positive affects does it have? } } } diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 87f992a25..9880755d1 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -916,16 +916,13 @@ namespace Server.Mobiles { var mobiles = Map.GetMobilesInRange(location, 0); - var found = mobiles.Any( - m => - m.Z >= location.Z && m.Z < location.Z + 16 && (!m.Hidden || m.AccessLevel == AccessLevel.Player) - ); - - mobiles.Free(); - - if (found) + foreach (Mobile m in mobiles) { - return false; + if (m.Z >= location.Z && m.Z < location.Z + 16 && (!m.Hidden || m.AccessLevel == AccessLevel.Player)) + { + mobiles.Free(); + return false; + } } mobiles.Free(); @@ -933,51 +930,51 @@ namespace Server.Mobiles var bi = item.GetBounce(); - if (bi != null) + if (bi == null) { - var type = item.GetType(); + return true; + } - if (type.IsDefined(typeof(FurnitureAttribute), true) || - type.IsDefined(typeof(DynamicFlipingAttribute), true)) + var type = item.GetType(); + + if (type.IsDefined(typeof(FurnitureAttribute), true) || + type.IsDefined(typeof(DynamicFlipingAttribute), true)) + { + var objs = type.GetCustomAttributes(typeof(FlippableAttribute), true); + + if (objs.Length > 0) { - var objs = type.GetCustomAttributes(typeof(FlippableAttribute), true); - - if (objs.Length > 0) + if (objs[0] is FlippableAttribute fp) { - if (objs[0] is FlippableAttribute fp) + var itemIDs = fp.ItemIDs; + + var oldWorldLoc = bi.WorldLoc; + var newWorldLoc = location; + + if (oldWorldLoc.X != newWorldLoc.X || oldWorldLoc.Y != newWorldLoc.Y) { - var itemIDs = fp.ItemIDs; + var dir = GetDirection4(oldWorldLoc, newWorldLoc); - var oldWorldLoc = bi.WorldLoc; - var newWorldLoc = location; - - if (oldWorldLoc.X != newWorldLoc.X || oldWorldLoc.Y != newWorldLoc.Y) + item.ItemID = itemIDs.Length switch { - var dir = GetDirection4(oldWorldLoc, newWorldLoc); - - if (itemIDs.Length == 2) + 2 => dir switch { - item.ItemID = dir switch - { - Direction.North => itemIDs[0], - Direction.South => itemIDs[0], - Direction.East => itemIDs[1], - Direction.West => itemIDs[1], - _ => item.ItemID - }; - } - else if (itemIDs.Length == 4) + Direction.North => itemIDs[0], + Direction.South => itemIDs[0], + Direction.East => itemIDs[1], + Direction.West => itemIDs[1], + _ => item.ItemID + }, + 4 => dir switch { - item.ItemID = dir switch - { - Direction.South => itemIDs[0], - Direction.East => itemIDs[1], - Direction.North => itemIDs[2], - Direction.West => itemIDs[3], - _ => item.ItemID - }; - } - } + Direction.South => itemIDs[0], + Direction.East => itemIDs[1], + Direction.North => itemIDs[2], + Direction.West => itemIDs[3], + _ => item.ItemID + }, + _ => item.ItemID + }; } } } diff --git a/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs b/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs index 4a42dbbab..d5459ed68 100644 --- a/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs +++ b/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs @@ -1005,7 +1005,7 @@ namespace Server.Mobiles for (var i = 0; i < opls.Count; ++i) { - from.Send(opls[i]); + from.NetState?.Send(opls[i].Buffer); } SayTo(from, 500186); // Greetings. Have a look around. diff --git a/Projects/UOContent/Multis/HouseFoundation.cs b/Projects/UOContent/Multis/HouseFoundation.cs index 512126f55..6c0763556 100644 --- a/Projects/UOContent/Multis/HouseFoundation.cs +++ b/Projects/UOContent/Multis/HouseFoundation.cs @@ -880,12 +880,20 @@ namespace Server.Multis DesignState.SendDetailedInfoTo(ns); } - public override void SendInfoTo(NetState state, bool sendOplPacket) + public override void SendInfoTo(NetState ns, bool sendOplPacket) { - base.SendInfoTo(state, sendOplPacket); + base.SendInfoTo(ns, sendOplPacket); - var stateToSend = DesignContext.Find(state.Mobile)?.Foundation == this ? DesignState : CurrentState; - stateToSend.SendGeneralInfoTo(state); + var stateToSend = DesignContext.Find(ns.Mobile)?.Foundation == this ? DesignState : CurrentState; + stateToSend.SendGeneralInfoTo(ns); + } + + public override void SendInfoTo(NetState ns, ReadOnlySpan world, ReadOnlySpan opl = default) + { + base.SendInfoTo(ns, world, opl); + + var stateToSend = DesignContext.Find(ns.Mobile)?.Foundation == this ? DesignState : CurrentState; + stateToSend.SendGeneralInfoTo(ns); } public override void Serialize(IGenericWriter writer) @@ -2321,22 +2329,22 @@ namespace Server.Multis { var item = fixtures[i]; - state.Send(item.RemovePacket); + state.SendRemoveEntity(item.Serial); } if (foundation.Signpost != null) { - state.Send(foundation.Signpost.RemovePacket); + state.SendRemoveEntity(foundation.Signpost.Serial); } if (foundation.SignHanger != null) { - state.Send(foundation.SignHanger.RemovePacket); + state.SendRemoveEntity(foundation.SignHanger.Serial); } if (foundation.Sign != null) { - state.Send(foundation.Sign.RemovePacket); + state.SendRemoveEntity(foundation.Sign.Serial); } }