diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs index 15436c5e5..7385fd9c1 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs @@ -12,20 +12,25 @@ namespace Server.Tests.Network { internal class ContextMenuItem : Item { - public ContextMenuItem(Serial serial) : base(serial) - { - } + private bool _requiresNewPacket; + public ContextMenuItem(Serial serial, bool requiresNewPacket) : base(serial) => + _requiresNewPacket = requiresNewPacket; public override void GetContextMenuEntries(Mobile from, List list) { base.GetContextMenuEntries(from, list); - list.Add(new ContextMenuEntry(500000)); - list.Add(new ContextMenuEntry(500001)); - list.Add(new ContextMenuEntry(500002)); + list.Add(new ContextMenuEntry(3000001)); + list.Add(new ContextMenuEntry(3000002)); + list.Add(new ContextMenuEntry(3000003)); + if (_requiresNewPacket) + { + list.Add(new ContextMenuEntry(500000)); + } } } + [Collection("Sequential Tests")] public class MenuPacketTests : IClassFixture { [Fact] @@ -41,45 +46,13 @@ namespace Server.Tests.Network } ); - var data = new DisplayItemListMenu(menu).Compile(); + var expected = new DisplayItemListMenu(menu).Compile(); - var question = menu.Question; - var questionLength = Math.Min(255, question.Length); - var entriesCount = 0; - var length = 11 + questionLength; + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendDisplayItemListMenu(menu); - foreach (var entry in menu.Entries) - { - length += 5 + entry.Name.Length; - if (entriesCount == 255) - { - break; - } - - entriesCount++; - } - - Span expectedData = stackalloc byte[length]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x7C); // Packet ID - expectedData.Write(ref pos, (ushort)length); - expectedData.Write(ref pos, menu.Serial); - expectedData.Write(ref pos, (ushort)0x00); - expectedData.Write(ref pos, (byte)questionLength); - expectedData.WriteAscii(ref pos, question, 255); - expectedData.Write(ref pos, (byte)entriesCount); - for (var i = 0; i < entriesCount; i++) - { - var entry = menu.Entries[i]; - expectedData.Write(ref pos, (ushort)entry.ItemID); - expectedData.Write(ref pos, (ushort)entry.Hue); - var name = entry.Name?.Trim() ?? ""; - expectedData.Write(ref pos, (byte)Math.Min(255, name.Length)); - expectedData.WriteAscii(ref pos, name, 255); - } - - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } [Fact] @@ -95,162 +68,40 @@ namespace Server.Tests.Network } ); - var data = new DisplayQuestionMenu(menu).Compile(); + var expected = new DisplayQuestionMenu(menu).Compile(); + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendDisplayQuestionMenu(menu); - var question = menu.Question; - var questionLength = Math.Min(255, question.Length); - var answersCount = 0; - var length = 11 + questionLength; - - foreach (var answer in menu.Answers) - { - length += 5 + answer.Length; - if (answersCount == 255) - { - break; - } - - answersCount++; - } - - Span expectedData = stackalloc byte[length]; - - var pos = 0; - - expectedData.Write(ref pos, (byte)0x7C); // Packet ID - expectedData.Write(ref pos, (ushort)length); - expectedData.Write(ref pos, menu.Serial); - expectedData.Write(ref pos, (ushort)0x00); - expectedData.Write(ref pos, (byte)question.Length); - expectedData.WriteAscii(ref pos, question, 255); - expectedData.Write(ref pos, (byte)answersCount); - for (var i = 0; i < answersCount; i++) - { - var answer = menu.Answers[i]; -#if NO_LOCAL_INIT - expectedData.Write(ref pos, 0); -#else - pos += 4; -#endif - expectedData.Write(ref pos, (byte)Math.Min(255, answer.Length)); - expectedData.WriteAscii(ref pos, answer, 255); - } - - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } - [Fact] - public void TestDisplayContextMenu() + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void TestDisplayContextMenu(bool newHaven, bool newPacket) { var m = new Mobile(0x1); m.DefaultMobileInit(); - var item = new ContextMenuItem(World.NewItem); + var item = new ContextMenuItem(World.NewItem, newPacket); var menu = new ContextMenu(m, item); - var data = new DisplayContextMenu(menu).Compile(); + var packet = newHaven && newPacket ? (Packet)new DisplayContextMenu(menu) : new DisplayContextMenuOld(menu); + var expected = packet.Compile(); - var length = 12 + menu.Entries.Length * 8; - - Span expectedData = stackalloc byte[length]; - - var pos = 0; - expectedData.Write(ref pos, (byte)0xBF); // Packet ID - expectedData.Write(ref pos, (ushort)length); // Length - expectedData.Write(ref pos, (ushort)0x14); // Command - expectedData.Write(ref pos, (ushort)0x02); // Subcommand - expectedData.Write(ref pos, menu.Target.Serial); - var entries = menu.Entries; - - expectedData.Write(ref pos, (byte)entries.Length); - - for (var i = 0; i < entries.Length; i++) + using var ns = PacketTestUtilities.CreateTestNetState(); + if (newHaven) { - var entry = entries[i]; - expectedData.Write(ref pos, entry.Number); - expectedData.Write(ref pos, (ushort)i); - - var flags = entry.Flags; - - var range = entry.Range; - - if (range == -1) - { - range = 18; - } - - if (!(entry.Enabled && menu.From.InRange(item.GetWorldLocation(), range))) - { - flags |= CMEFlags.Disabled; - } - - expectedData.Write(ref pos, (ushort)flags); + ns.ProtocolChanges |= ProtocolChanges.NewHaven; } - AssertThat.Equal(data, expectedData); - } + ns.SendDisplayContextMenu(menu); - [Fact] - public void TestDisplayContextMenuOld() - { - var m = new Mobile(0x1); - m.DefaultMobileInit(); - - var item = new ContextMenuItem(World.NewItem); - var menu = new ContextMenu(m, item); - - var data = new DisplayContextMenuOld(menu).Compile(); - - var length = 12 + menu.Entries.Sum(entry => 6 + ((entry.Color & 0xFFFF) != 0xFFFF ? 2 : 0)); - - Span expectedData = stackalloc byte[length]; - - var pos = 0; - expectedData.Write(ref pos, (byte)0xBF); // Packet ID - expectedData.Write(ref pos, (ushort)length); // Length - expectedData.Write(ref pos, (ushort)0x14); // Command - expectedData.Write(ref pos, (ushort)0x01); // Subcommand - expectedData.Write(ref pos, menu.Target.Serial); - var entries = menu.Entries; - - expectedData.Write(ref pos, (byte)entries.Length); - - for (var i = 0; i < entries.Length; i++) - { - var entry = entries[i]; - expectedData.Write(ref pos, (ushort)i); - expectedData.Write(ref pos, (ushort)(entry.Number - 3000000)); - - var flags = entry.Flags; - - var color = entry.Color & 0xFFFF; - - if (color != 0xFFFF) - { - flags |= CMEFlags.Colored; - } - - var range = entry.Range; - - if (range == -1) - { - range = 18; - } - - if (!(entry.Enabled && menu.From.InRange(item.GetWorldLocation(), range))) - { - flags |= CMEFlags.Disabled; - } - - expectedData.Write(ref pos, (ushort)flags); - - if ((flags & CMEFlags.Colored) != 0) - { - expectedData.Write(ref pos, (ushort)color); - } - } - - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } } } diff --git a/Projects/Server/Network/Packets/MenuPackets.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPackets.cs similarity index 81% rename from Projects/Server/Network/Packets/MenuPackets.cs rename to Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPackets.cs index 1a602494a..59c4ae8b8 100644 --- a/Projects/Server/Network/Packets/MenuPackets.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPackets.cs @@ -1,19 +1,3 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MenuPackets.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 Server.ContextMenus; using Server.Menus; using Server.Menus.ItemLists; @@ -21,16 +5,6 @@ using Server.Menus.Questions; namespace Server.Network { - [Flags] - public enum CMEFlags - { - None = 0x00, - Disabled = 0x01, - Arrow = 0x02, - Highlighted = 0x04, - Colored = 0x20 - } - public sealed class DisplayItemListMenu : Packet { public DisplayItemListMenu(ItemListMenu menu) : base(0x7C) @@ -167,7 +141,7 @@ namespace Server.Network if (range == -1) { - range = 18; + range = Core.GlobalUpdateRange; } var flags = e.Flags; @@ -218,7 +192,7 @@ namespace Server.Network if (range == -1) { - range = 18; + range = Core.GlobalUpdateRange; } var flags = e.Flags; diff --git a/Projects/Server/Menus/ItemListMenu.cs b/Projects/Server/Menus/ItemListMenu.cs index ed111c304..2b9983c65 100644 --- a/Projects/Server/Menus/ItemListMenu.cs +++ b/Projects/Server/Menus/ItemListMenu.cs @@ -55,7 +55,7 @@ namespace Server.Menus.ItemLists public void SendTo(NetState state) { state.AddMenu(this); - state.Send(new DisplayItemListMenu(this)); + state.SendDisplayItemListMenu(this); } } } diff --git a/Projects/Server/Menus/QuestionMenu.cs b/Projects/Server/Menus/QuestionMenu.cs index 992ae2b4f..aa6beb7aa 100644 --- a/Projects/Server/Menus/QuestionMenu.cs +++ b/Projects/Server/Menus/QuestionMenu.cs @@ -37,7 +37,7 @@ namespace Server.Menus.Questions public void SendTo(NetState state) { state.AddMenu(this); - state.Send(new DisplayQuestionMenu(this)); + state.SendDisplayQuestionMenu(this); } } } diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 3f9194dfe..51f453389 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -1075,19 +1075,7 @@ namespace Server set { m_ContextMenu = value; - - if (m_ContextMenu != null && m_NetState != null) - { - // Old packet is preferred until assistants catch up - if (m_NetState.NewHaven && m_ContextMenu.RequiresNewPacket) - { - Send(new DisplayContextMenu(m_ContextMenu)); - } - else - { - Send(new DisplayContextMenuOld(m_ContextMenu)); - } - } + m_NetState.SendDisplayContextMenu(m_ContextMenu); } } diff --git a/Projects/Server/Network/Packets/OutgoingMenuPackets.cs b/Projects/Server/Network/Packets/OutgoingMenuPackets.cs new file mode 100644 index 000000000..0b02f44c8 --- /dev/null +++ b/Projects/Server/Network/Packets/OutgoingMenuPackets.cs @@ -0,0 +1,219 @@ +using System; +using System.Buffers; +using System.IO; +using Server.ContextMenus; +using Server.Menus.ItemLists; +using Server.Menus.Questions; + +namespace Server.Network +{ + [Flags] + public enum CMEFlags + { + None = 0x00, + Disabled = 0x01, + Arrow = 0x02, + Highlighted = 0x04, + Colored = 0x20 + } + + public static class OutgoingMenuPackets + { + public static void SendDisplayItemListMenu(this NetState ns, ItemListMenu menu) + { + if (ns == null || menu == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0x7C); // Packet ID + writer.Seek(2, SeekOrigin.Current); // Length + writer.Write(menu.Serial); + writer.Write((ushort)0); + + var question = menu.Question?.Trim(); + + if (question == null) + { + writer.Write((byte)0); + } + else + { + var questionLength = question.Length; + writer.Write((byte)questionLength); + writer.WriteAscii(question); + } + + var entries = menu.Entries; + + int entriesLength = (byte)entries.Length; + + writer.Write((byte)entriesLength); + + for (var i = 0; i < entriesLength; ++i) + { + var e = entries[i]; + + writer.Write((ushort)e.ItemID); + writer.Write((short)e.Hue); + + var name = e.Name?.Trim(); + + if (name == null) + { + writer.Write((byte)0); + } + else + { + var nameLength = name.Length; + writer.Write((byte)nameLength); + writer.WriteAscii(name); + } + } + + var length = writer.Position; + writer.Seek(1, SeekOrigin.Begin); + writer.Write((ushort)length); + + ns.Send(ref buffer, length); + } + + public static void SendDisplayQuestionMenu(this NetState ns, QuestionMenu menu) + { + if (ns == null || menu == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0x7C); // Packet ID + writer.Seek(2, SeekOrigin.Current); // Length + writer.Write(menu.Serial); + writer.Write((ushort)0); + + var question = menu.Question?.Trim(); + + if (question == null) + { + writer.Write((byte)0); + } + else + { + var questionLength = question.Length; + writer.Write((byte)questionLength); + writer.WriteAscii(question); + } + + var answers = menu.Answers; + + int answersLength = (byte)answers.Length; + + writer.Write((byte)answersLength); + + for (var i = 0; i < answersLength; ++i) + { + writer.Write(0); + + var answer = answers[i]?.Trim(); + + if (answer == null) + { + writer.Write((byte)0); + } + else + { + var nameLength = answer.Length; + writer.Write((byte)nameLength); + writer.WriteAscii(answer); + } + } + + var length = writer.Position; + writer.Seek(1, SeekOrigin.Begin); + writer.Write((ushort)length); + + ns.Send(ref buffer, length); + } + + public static void SendDisplayContextMenu(this NetState ns, ContextMenu menu) + { + if (ns == null || menu == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var newCommand = ns.NewHaven && menu.RequiresNewPacket; + + var entries = menu.Entries; + var entriesLength = (byte)entries.Length; + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0xBF); // Packet ID + writer.Seek(2, SeekOrigin.Current); // Length + writer.Write((short)0x14); // Subpacket + writer.Write((short)(newCommand ? 0x02 : 0x01)); // Command + + var target = menu.Target; + writer.Write(target.Serial); + writer.Write(entriesLength); + + var p = target switch + { + Mobile _ => target.Location, + Item item => item.GetWorldLocation(), + _ => Point3D.Zero + }; + + for (var i = 0; i < entriesLength; ++i) + { + var e = entries[i]; + + var range = e.Range; + + if (range == -1) + { + range = Core.GlobalUpdateRange; + } + + var flags = e.Flags; + if (!(e.Enabled && menu.From.InRange(p, range))) + { + flags |= CMEFlags.Disabled; + } + + if (newCommand) + { + writer.Write(e.Number); + writer.Write((short)i); + writer.Write((short)flags); + } + else + { + writer.Write((short)i); + writer.Write((ushort)(e.Number - 3000000)); + + var color = e.Color & 0xFFFF; + + if (color != 0xFFFF) + { + flags |= CMEFlags.Colored; + } + + writer.Write((short)flags); + + if ((flags & CMEFlags.Colored) != 0) + { + writer.Write((short)color); + } + } + } + + var length = writer.Position; + writer.Seek(1, SeekOrigin.Begin); + writer.Write((ushort)length); + + ns.Send(ref buffer, length); + } + } +}