From 08f05afd01c9477a9188aa5f312877d2deb5f94a Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 18 Jan 2021 08:38:32 -0800 Subject: [PATCH] fix(core): Converts boat packets (#416) - [X] Converts boat packets - [X] Makes a packet container builder for packet 0xF7 - [X] Generalizes world item packet so it works for mobiles too Notes: - This PR doesn't address proper smooth movement for boats. --- .../Utilities => Helpers}/GumpUtilities.cs | 0 .../PacketTestUtilities.cs | 6 + .../Packets/Outgoing/ItemPacketTests.cs | 4 +- .../Server/Collections/CollectionHelpers.cs | 30 ++++ Projects/Server/Items/Item.cs | 16 +- .../Network/Packets/OutgoingEntityPackets.cs | 68 +++++++ .../Network/Packets/OutgoingItemPackets.cs | 55 +----- .../Network/Packets/PacketContainerBuilder.cs | 115 ++++++++++++ .../Tests/Multis/Boats/BoatPacketTests.cs | 84 +++++++++ .../Tests/Multis/Boats/Packets.cs | 145 +++++++++++++++ Projects/UOContent/Items/Misc/Blocker.cs | 4 +- Projects/UOContent/Items/Misc/LOSBlocker.cs | 4 +- Projects/UOContent/Multis/Boats/BaseBoat.cs | 166 ++---------------- .../UOContent/Multis/Boats/BoatPackets.cs | 88 ++++++++++ Projects/UOContent/Network/EntityPackets.cs | 54 ++++++ 15 files changed, 619 insertions(+), 220 deletions(-) rename Projects/Server.Tests/{Tests/Network/Packets/Utilities => Helpers}/GumpUtilities.cs (100%) rename Projects/Server.Tests/{Tests/Network/Packets/Utilities => Helpers}/PacketTestUtilities.cs (85%) create mode 100644 Projects/Server/Collections/CollectionHelpers.cs create mode 100644 Projects/Server/Network/Packets/PacketContainerBuilder.cs create mode 100644 Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs create mode 100644 Projects/UOContent.Tests/Tests/Multis/Boats/Packets.cs create mode 100644 Projects/UOContent/Multis/Boats/BoatPackets.cs create mode 100644 Projects/UOContent/Network/EntityPackets.cs diff --git a/Projects/Server.Tests/Tests/Network/Packets/Utilities/GumpUtilities.cs b/Projects/Server.Tests/Helpers/GumpUtilities.cs similarity index 100% rename from Projects/Server.Tests/Tests/Network/Packets/Utilities/GumpUtilities.cs rename to Projects/Server.Tests/Helpers/GumpUtilities.cs diff --git a/Projects/Server.Tests/Tests/Network/Packets/Utilities/PacketTestUtilities.cs b/Projects/Server.Tests/Helpers/PacketTestUtilities.cs similarity index 85% rename from Projects/Server.Tests/Tests/Network/Packets/Utilities/PacketTestUtilities.cs rename to Projects/Server.Tests/Helpers/PacketTestUtilities.cs index b90c457e9..ee2aca82a 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Utilities/PacketTestUtilities.cs +++ b/Projects/Server.Tests/Helpers/PacketTestUtilities.cs @@ -24,5 +24,11 @@ namespace Server.Tests.Network return new NetState(socket.Object); } + + public static Map CreateTestMap() + { + var map = new Mock(); + return map.Object; + } } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs index 0dc7a4245..96eab7477 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs @@ -69,7 +69,7 @@ namespace Server.Tests.Network var expected = new WorldItemSA(item).Compile(); using var ns = PacketTestUtilities.CreateTestNetState(); - ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.StygianAbyss; + ns.ProtocolChanges = ProtocolChanges.StygianAbyss; ns.SendWorldItem(item); var result = ns.SendPipe.Reader.TryRead(); @@ -104,7 +104,7 @@ namespace Server.Tests.Network var expected = new WorldItemHS(item).Compile(); using var ns = PacketTestUtilities.CreateTestNetState(); - ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.StygianAbyss | ProtocolChanges.HighSeas; + ns.ProtocolChanges = ProtocolChanges.StygianAbyss | ProtocolChanges.HighSeas; ns.SendWorldItem(item); var result = ns.SendPipe.Reader.TryRead(); diff --git a/Projects/Server/Collections/CollectionHelpers.cs b/Projects/Server/Collections/CollectionHelpers.cs new file mode 100644 index 000000000..15e1dd26f --- /dev/null +++ b/Projects/Server/Collections/CollectionHelpers.cs @@ -0,0 +1,30 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CollectionHelpers.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.Generic; + +namespace Server.Collections +{ + public static class CollectionHelpers + { + public static void AddNotNull(this ICollection coll, T t) where T : class + { + if (t != default) + { + coll.Add(t); + } + } + } +} diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index d7791863f..af3311335 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -1136,9 +1136,9 @@ namespace Server if (m_Map != null) { - Span oldWorldItem = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength].InitializePacket(); - Span saWorldItem = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength].InitializePacket(); - Span hsWorldItem = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength].InitializePacket(); + Span oldWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); + Span saWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); + Span hsWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); Span opl = ObjectPropertyList.Enabled ? stackalloc byte[OutgoingEntityPackets.OPLPacketLength].InitializePacket() : null; var eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); @@ -1151,7 +1151,7 @@ namespace Server { if (state.HighSeas) { - var length = OutgoingItemPackets.CreateWorldItemNew(hsWorldItem, this, true); + var length = OutgoingEntityPackets.CreateWorldEntity(hsWorldItem, this, true, true); if (length != hsWorldItem.Length) { hsWorldItem = hsWorldItem.SliceToLength(length); @@ -1161,7 +1161,7 @@ namespace Server } else if (state.StygianAbyss) { - var length = OutgoingItemPackets.CreateWorldItemNew(saWorldItem, this, false); + var length = OutgoingEntityPackets.CreateWorldEntity(saWorldItem, this, true, false); if (length != saWorldItem.Length) { saWorldItem = saWorldItem.SliceToLength(length); @@ -1606,21 +1606,21 @@ namespace Server [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public int X { - get => m_Location.m_X; + get => Location.m_X; set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public int Y { - get => m_Location.m_Y; + get => Location.m_Y; set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public int Z { - get => m_Location.m_Z; + get => Location.m_Z; set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); } diff --git a/Projects/Server/Network/Packets/OutgoingEntityPackets.cs b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs index 93d4132e8..37c902bbe 100644 --- a/Projects/Server/Network/Packets/OutgoingEntityPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs @@ -15,6 +15,7 @@ using System; using System.Buffers; +using Server.Items; namespace Server.Network { @@ -22,6 +23,7 @@ namespace Server.Network { public const int OPLPacketLength = 9; public const int RemoveEntityLength = 5; + public const int MaxWorldEntityPacketLength = 26; public static void CreateOPLInfo(Span buffer, Item item) => CreateOPLInfo(buffer, item.Serial, item.PropertyList.Hash); @@ -79,5 +81,71 @@ namespace Server.Network ns.Send(buffer); } + + public static int CreateWorldEntity(Span buffer, IEntity entity, bool isSA, bool isHS) + { + if (buffer[0] != 0) + { + return buffer.Length; + } + + var writer = new SpanWriter(buffer); + writer.Write((byte)0xF3); // Packet ID + writer.Write((short)0x1); // command + + int type = 0; + int gfx = 0; + int amount = 1; + int hue = 0; + byte light = 0; + int flags = 0; + + if (entity is BaseMulti multi) + { + type = 2; + gfx = multi.ItemID & (isHS ? 0xFFFF : 0x7FFF); + hue = multi.Hue; + amount = multi.Amount; + } + else if (entity is Item item) + { + // type = 3 if is damageable + gfx = item.ItemID & (isHS ? 0xFFFF : 0x7FFF); + hue = item.Hue; + amount = item.Amount; + light = (byte)item.Light; + flags = item.GetPacketFlags(); + } + else if (entity is Mobile mobile) + { + type = 1; + gfx = mobile.BodyValue; + hue = mobile.Hue; + flags = mobile.GetPacketFlags(isSA); + } + + writer.Write((byte)type); + writer.Write(entity.Serial); + writer.Write((ushort)gfx); + writer.Write((byte)0); + + writer.Write((short)amount); // Min + writer.Write((short)amount); // Max + + writer.Write((short)(entity.X & 0x7FFF)); + writer.Write((short)(entity.Y & 0x3FFF)); + writer.Write((sbyte)entity.Z); + + writer.Write((byte)light); + writer.Write((short)hue); + writer.Write((byte)flags); + + if (isHS) + { + writer.Write((short)0); + } + + return writer.Position; + } } } diff --git a/Projects/Server/Network/Packets/OutgoingItemPackets.cs b/Projects/Server/Network/Packets/OutgoingItemPackets.cs index c438439aa..47777823e 100644 --- a/Projects/Server/Network/Packets/OutgoingItemPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingItemPackets.cs @@ -21,8 +21,6 @@ namespace Server.Network { public static class OutgoingItemPackets { - public const int MaxWorldItemPacketLength = 26; - public static int CreateWorldItem(Span buffer, Item item) { if (buffer[0] != 0) @@ -89,62 +87,13 @@ namespace Server.Network return; } - Span buffer = stackalloc byte[MaxWorldItemPacketLength]; + Span buffer = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength]; var length = ns.StygianAbyss ? - CreateWorldItemNew(buffer, item, ns.HighSeas) : + OutgoingEntityPackets.CreateWorldEntity(buffer, item, ns.StygianAbyss, ns.HighSeas) : CreateWorldItem(buffer, item); ns.Send(buffer.SliceToLength(length)); } - - public static int CreateWorldItemNew(Span buffer, Item item, bool isHS) - { - if (buffer[0] != 0) - { - return buffer.Length; - } - - 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/Packets/PacketContainerBuilder.cs b/Projects/Server/Network/Packets/PacketContainerBuilder.cs new file mode 100644 index 000000000..5d13cb231 --- /dev/null +++ b/Projects/Server/Network/Packets/PacketContainerBuilder.cs @@ -0,0 +1,115 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: PacketContainerBuilder.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.Buffers.Binary; +using System.Runtime.CompilerServices; + +namespace Server.Network +{ + public ref struct PacketContainerBuilder + { + public const int MinPacketLength = 5; + + private bool _finished; + private int _count; + + private byte[]? _arrayToReturnToPool; + private Span _bytes; + + public PacketContainerBuilder(Span initialBuffer) + { + _arrayToReturnToPool = null; + _finished = false; + _count = 0; + + _bytes = initialBuffer; + _bytes[0] = 0xF7; // Packet ID + Length = MinPacketLength; // Length + Count + } + + public int Length { get; set; } + + public int Capacity => _bytes.Length; + + [MethodImpl(MethodImplOptions.NoInlining)] + public ReadOnlySpan Finalize() + { + if (!_finished) + { + BinaryPrimitives.WriteUInt16BigEndian(_bytes.Slice(1, 2), (ushort)Length); + BinaryPrimitives.WriteUInt16BigEndian(_bytes.Slice(3, 2), (ushort)_count); + } + + return _bytes.SliceToLength(Length); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public Span GetSpan(int bytesNeeded) + { + if (_finished) + { + throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize"); + } + + if (Length > _bytes.Length - bytesNeeded) + { + Grow(bytesNeeded); + } + + return _bytes.Slice(Length); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Advance(int bytesWritten) + { + if (_finished) + { + throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize"); + } + + _count++; + Length += bytesWritten; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacityBeyondPos) + { + var newLength = Math.Max(Length + additionalCapacityBeyondPos, _bytes.Length * 2); + byte[] poolArray = ArrayPool.Shared.Rent(newLength); + + _bytes.SliceToLength(Length).CopyTo(poolArray); + + byte[]? toReturn = _arrayToReturnToPool; + _bytes = _arrayToReturnToPool = poolArray; + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + byte[]? toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + } +} diff --git a/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs b/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs new file mode 100644 index 000000000..01b88dde5 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using Moq; +using Server; +using Server.Items; +using Server.Multis; +using Server.Multis.Boats; +using Server.Network; +using Server.Tests; +using Server.Tests.Network; +using Xunit; + +namespace UOContent.Tests +{ + public class BoatPacketTests : IClassFixture + { + [Theory] + [InlineData(Direction.West, 10, 100, 200)] + public void TestMoveBoatHS(Direction d, int speed, int xOffset, int yOffset) + { + var boat = new Mock((Serial)0x2); + boat.Object.Location = new Point3D(10, 20, 15); + boat.Object.Facing = Direction.Right; + + // Item on the boat + var item1 = new Mock((Serial)0x2); + item1.Setup(m => m.ItemID).Returns(0x13B9); + item1.Object.Location = new Point3D(10, 20, 15); + + // Item not on the boat + var item2 = new Mock((Serial)0x2); + item2.Setup(m => m.ItemID).Returns(0x13B9); + item2.Object.Location = new Point3D(100, 200, 15); + + var beholder = new Mock((Serial)0x1024u); + beholder.Object.Location = new Point3D(10, 20, 15); + beholder.Setup(m => m.CanSee(It.Is(e => e == boat.Object))).Returns(true); + beholder.Setup(m => m.CanSee(It.Is(e => e == item1.Object))).Returns(true); + beholder.Setup(m => m.CanSee(It.Is(e => e == item2.Object))).Returns(false); + + var list = new List(4) { item1.Object, item2.Object, boat.Object, beholder.Object }; + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = ProtocolChanges.HighSeas; + var expected = new MoveBoatHS(beholder.Object, boat.Object, d, speed, list, xOffset, yOffset).Compile(); + + ns.SendMoveBoatHS(beholder.Object, boat.Object, d, speed, list, xOffset, yOffset); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestDisplayBoatHS() + { + var item1 = new Item((Serial)0x1000) { ItemID = 0x13B9, Location = new Point3D(11, 21, 16), Map = Map.Felucca }; + var item2 = new Item((Serial)0x2000) { ItemID = 0x13B9, Location = new Point3D(100, 200, 16), Map = Map.Felucca }; + + var beholder = new Mock((Serial)0x100); + beholder.Object.DefaultMobileInit(); + beholder.Object.Location = new Point3D(10, 20, 15); + beholder.Setup(m => m.CanSee(It.Is(e => e == beholder.Object))).Returns(true); + beholder.Setup(m => m.CanSee(It.Is(e => e == item1))).Returns(true); + beholder.Setup(m => m.CanSee(It.Is(e => e == item2))).Returns(false); + + var boat = new Mock((Serial)0x3000); + boat.Setup(b => b.GetMovingEntities()).Returns(() => new List{ item1, beholder.Object }); + boat.Setup(b => b.Location).Returns(new Point3D(10, 20, 15)); + boat.Object.Facing = Direction.Right; + + beholder.Setup(m => m.CanSee(It.Is(e => e == boat.Object))).Returns(true); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = ProtocolChanges.HighSeas; + + var expected = new DisplayBoatHS(beholder.Object, boat.Object).Compile(); + + ns.SendDisplayBoatHS(beholder.Object, boat.Object); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + } +} diff --git a/Projects/UOContent.Tests/Tests/Multis/Boats/Packets.cs b/Projects/UOContent.Tests/Tests/Multis/Boats/Packets.cs new file mode 100644 index 000000000..812f183c0 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Multis/Boats/Packets.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using System.IO; +using Server; +using Server.Collections; +using Server.Items; +using Server.Multis; +using Server.Network; + +namespace UOContent.Tests +{ + public sealed class MoveBoatHS : Packet + { + public MoveBoatHS( + Mobile beholder, BaseBoat boat, Direction d, + int speed, List ents, int xOffset, + int yOffset + ) : base(0xF6) + { + EnsureCapacity(3 + 15 + ents.Count * 10); + + Stream.Write(boat.Serial); + Stream.Write((byte)speed); + Stream.Write((byte)d); + Stream.Write((byte)boat.Facing); + Stream.Write((short)(boat.X + xOffset)); + Stream.Write((short)(boat.Y + yOffset)); + Stream.Write((short)boat.Z); + Stream.Write((short)0); // count placeholder + + var count = 0; + + foreach (var ent in ents) + { + if (!beholder.CanSee(ent)) + { + continue; + } + + Stream.Write(ent.Serial); + Stream.Write((short)(ent.X + xOffset)); + Stream.Write((short)(ent.Y + yOffset)); + Stream.Write((short)ent.Z); + ++count; + } + + Stream.Seek(16, SeekOrigin.Begin); + Stream.Write((short)count); + } + } + + public sealed class DisplayBoatHS : Packet + { + public DisplayBoatHS(Mobile beholder, BaseBoat boat) : base(0xF7) + { + var ents = boat.GetMovingEntities(); + + ents.AddNotNull(boat.TillerMan); + ents.AddNotNull(boat.Hold); + ents.AddNotNull(boat.PPlank); + ents.AddNotNull(boat.SPlank); + + ents.Add(boat); + + EnsureCapacity(3 + 2 + ents.Count * 26); + + Stream.Write((short)0); // count placeholder + + var count = 0; + + foreach (var ent in ents) + { + if (!beholder.CanSee(ent)) + { + continue; + } + + // Embedded WorldItemHS packets + Stream.Write((byte)0xF3); + Stream.Write((short)0x1); + + if (ent is BaseMulti bm) + { + Stream.Write((byte)0x02); + Stream.Write(bm.Serial); + // TODO: Mask no longer needed, merge with Item case? + Stream.Write((ushort)(bm.ItemID & 0x3FFF)); + Stream.Write((byte)0); + + Stream.Write((short)bm.Amount); + Stream.Write((short)bm.Amount); + + Stream.Write((short)(bm.X & 0x7FFF)); + Stream.Write((short)(bm.Y & 0x3FFF)); + Stream.Write((sbyte)bm.Z); + + Stream.Write((byte)bm.Light); + Stream.Write((short)bm.Hue); + Stream.Write((byte)bm.GetPacketFlags()); + } + else if (ent is Mobile m) + { + Stream.Write((byte)0x01); + Stream.Write(m.Serial); + Stream.Write((short)m.Body); + Stream.Write((byte)0); + + Stream.Write((short)1); + Stream.Write((short)1); + + Stream.Write((short)(m.X & 0x7FFF)); + Stream.Write((short)(m.Y & 0x3FFF)); + Stream.Write((sbyte)m.Z); + + Stream.Write((byte)m.Direction); + Stream.Write((short)m.Hue); + Stream.Write((byte)m.GetPacketFlags(true)); + } + else if (ent is Item item) + { + Stream.Write((byte)0x00); + Stream.Write(item.Serial); + Stream.Write((ushort)(item.ItemID & 0xFFFF)); + Stream.Write((byte)0); + + Stream.Write((short)item.Amount); + Stream.Write((short)item.Amount); + + Stream.Write((short)(item.X & 0x7FFF)); + Stream.Write((short)(item.Y & 0x3FFF)); + Stream.Write((sbyte)item.Z); + + Stream.Write((byte)item.Light); + Stream.Write((short)item.Hue); + Stream.Write((byte)item.GetPacketFlags()); + } + + Stream.Write((short)0x00); + ++count; + } + + Stream.Seek(3, SeekOrigin.Begin); + Stream.Write((short)count); + } + } +} diff --git a/Projects/UOContent/Items/Misc/Blocker.cs b/Projects/UOContent/Items/Misc/Blocker.cs index 1ee970946..8a60e7119 100644 --- a/Projects/UOContent/Items/Misc/Blocker.cs +++ b/Projects/UOContent/Items/Misc/Blocker.cs @@ -32,13 +32,13 @@ namespace Server.Items private void SendGMItem(NetState ns) { // GM Packet - Span buffer = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength]; + Span buffer = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength]; int length; if (ns.StygianAbyss) { - length = OutgoingItemPackets.CreateWorldItemNew(buffer, this, ns.HighSeas); + length = OutgoingEntityPackets.CreateWorldEntity(buffer, this, ns.StygianAbyss, ns.HighSeas); BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(8, 2), GMItemId); } else diff --git a/Projects/UOContent/Items/Misc/LOSBlocker.cs b/Projects/UOContent/Items/Misc/LOSBlocker.cs index 96d99fbb9..48d5952b8 100644 --- a/Projects/UOContent/Items/Misc/LOSBlocker.cs +++ b/Projects/UOContent/Items/Misc/LOSBlocker.cs @@ -38,13 +38,13 @@ namespace Server.Items private void SendGMItem(NetState ns) { // GM Packet - Span buffer = stackalloc byte[OutgoingItemPackets.MaxWorldItemPacketLength]; + Span buffer = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength]; int length; if (ns.StygianAbyss) { - length = OutgoingItemPackets.CreateWorldItemNew(buffer, this, ns.HighSeas); + length = OutgoingEntityPackets.CreateWorldEntity(buffer, this, ns.StygianAbyss, ns.HighSeas); BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(8, 2), GMItemId); } else diff --git a/Projects/UOContent/Multis/Boats/BaseBoat.cs b/Projects/UOContent/Multis/Boats/BaseBoat.cs index 066d0c5ee..79a135b3a 100644 --- a/Projects/UOContent/Multis/Boats/BaseBoat.cs +++ b/Projects/UOContent/Multis/Boats/BaseBoat.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; +using Server.Collections; using Server.Items; +using Server.Multis.Boats; using Server.Network; namespace Server.Multis @@ -1737,11 +1738,10 @@ namespace Server.Multis else { var toMove = GetMovingEntities(); - - SafeAdd(TillerMan, toMove); - SafeAdd(Hold, toMove); - SafeAdd(PPlank, toMove); - SafeAdd(SPlank, toMove); + toMove.AddNotNull(TillerMan); + toMove.AddNotNull(Hold); + toMove.AddNotNull(PPlank); + toMove.AddNotNull(SPlank); // Packet must be sent before actual locations are changed foreach (var ns in Map.GetClientsInRange(Location, GetMaxUpdateRange())) @@ -1750,7 +1750,7 @@ namespace Server.Multis if (ns.HighSeas && m.CanSee(this) && m.InRange(Location, GetUpdateRange(m))) { - ns.Send(new MoveBoatHS(m, this, d, clientSpeed, toMove, xOffset, yOffset)); + ns.SendMoveBoatHS(m, this, d, clientSpeed, toMove, xOffset, yOffset); } } @@ -1793,14 +1793,6 @@ namespace Server.Multis return true; } - private static void SafeAdd(Item item, List toMove) - { - if (item != null) - { - toMove.Add(item); - } - } - public void Teleport(int xOffset, int yOffset, int zOffset) { var toMove = GetMovingEntities(); @@ -1822,7 +1814,7 @@ namespace Server.Multis Location = new Point3D(X + xOffset, Y + yOffset, Z + zOffset); } - public List GetMovingEntities() + public virtual List GetMovingEntities() { var list = new List(); @@ -1835,7 +1827,9 @@ namespace Server.Multis var mcl = Components; - foreach (var o in map.GetObjectsInBounds(new Rectangle2D(X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height))) + var eable = map.GetObjectsInBounds(new Rectangle2D(X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height)); + + foreach (var o in eable) { if (o == this || o is TillerMan || o is Hold || o is Plank) { @@ -1858,6 +1852,8 @@ namespace Server.Multis } } + eable.Free(); + return list; } @@ -2089,141 +2085,5 @@ namespace Server.Multis return base.GetWorldPacketFor( state ); } */ - - public sealed class MoveBoatHS : Packet - { - public MoveBoatHS( - Mobile beholder, BaseBoat boat, Direction d, int speed, List ents, int xOffset, - int yOffset - ) - : base(0xF6) - { - EnsureCapacity(3 + 15 + ents.Count * 10); - - Stream.Write(boat.Serial); - Stream.Write((byte)speed); - Stream.Write((byte)d); - Stream.Write((byte)boat.Facing); - Stream.Write((short)(boat.X + xOffset)); - Stream.Write((short)(boat.Y + yOffset)); - Stream.Write((short)boat.Z); - Stream.Write((short)0); // count placeholder - - var count = 0; - - foreach (var ent in ents) - { - if (!beholder.CanSee(ent)) - { - continue; - } - - Stream.Write(ent.Serial); - Stream.Write((short)(ent.X + xOffset)); - Stream.Write((short)(ent.Y + yOffset)); - Stream.Write((short)ent.Z); - ++count; - } - - Stream.Seek(16, SeekOrigin.Begin); - Stream.Write((short)count); - } - } - - public sealed class DisplayBoatHS : Packet - { - public DisplayBoatHS(Mobile beholder, BaseBoat boat) - : base(0xF7) - { - var ents = boat.GetMovingEntities(); - - SafeAdd(boat.TillerMan, ents); - SafeAdd(boat.Hold, ents); - SafeAdd(boat.PPlank, ents); - SafeAdd(boat.SPlank, ents); - - ents.Add(boat); - - EnsureCapacity(3 + 2 + ents.Count * 26); - - Stream.Write((short)0); // count placeholder - - var count = 0; - - foreach (var ent in ents) - { - if (!beholder.CanSee(ent)) - { - continue; - } - - // Embedded WorldItemHS packets - Stream.Write((byte)0xF3); - Stream.Write((short)0x1); - - if (ent is BaseMulti bm) - { - Stream.Write((byte)0x02); - Stream.Write(bm.Serial); - // TODO: Mask no longer needed, merge with Item case? - Stream.Write((ushort)(bm.ItemID & 0x3FFF)); - Stream.Write((byte)0); - - Stream.Write((short)bm.Amount); - Stream.Write((short)bm.Amount); - - Stream.Write((short)(bm.X & 0x7FFF)); - Stream.Write((short)(bm.Y & 0x3FFF)); - Stream.Write((sbyte)bm.Z); - - Stream.Write((byte)bm.Light); - Stream.Write((short)bm.Hue); - Stream.Write((byte)bm.GetPacketFlags()); - } - else if (ent is Mobile m) - { - Stream.Write((byte)0x01); - Stream.Write(m.Serial); - Stream.Write((short)m.Body); - Stream.Write((byte)0); - - Stream.Write((short)1); - Stream.Write((short)1); - - Stream.Write((short)(m.X & 0x7FFF)); - Stream.Write((short)(m.Y & 0x3FFF)); - Stream.Write((sbyte)m.Z); - - Stream.Write((byte)m.Direction); - Stream.Write((short)m.Hue); - Stream.Write((byte)m.GetPacketFlags(true)); - } - else if (ent is Item item) - { - Stream.Write((byte)0x00); - Stream.Write(item.Serial); - Stream.Write((ushort)(item.ItemID & 0xFFFF)); - Stream.Write((byte)0); - - Stream.Write((short)item.Amount); - Stream.Write((short)item.Amount); - - Stream.Write((short)(item.X & 0x7FFF)); - Stream.Write((short)(item.Y & 0x3FFF)); - Stream.Write((sbyte)item.Z); - - Stream.Write((byte)item.Light); - Stream.Write((short)item.Hue); - Stream.Write((byte)item.GetPacketFlags()); - } - - Stream.Write((short)0x00); - ++count; - } - - Stream.Seek(3, SeekOrigin.Begin); - Stream.Write((short)count); - } - } } } diff --git a/Projects/UOContent/Multis/Boats/BoatPackets.cs b/Projects/UOContent/Multis/Boats/BoatPackets.cs new file mode 100644 index 000000000..2c0d3a1b4 --- /dev/null +++ b/Projects/UOContent/Multis/Boats/BoatPackets.cs @@ -0,0 +1,88 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: BoatPackets.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.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Server.Collections; +using Server.Network; + +namespace Server.Multis.Boats +{ + public static class BoatPackets + { + public static void SendMoveBoatHS(this NetState ns, Mobile beholder, BaseBoat boat, + Direction d, int speed, List ents, int xOffset, int yOffset) + { + if (ns?.HighSeas != true) + { + return; + } + + var maxLength = 18 + ents.Count * 10; + var writer = new SpanWriter(stackalloc byte[maxLength]); + writer.Write((byte)0xF6); // Packet ID + writer.Seek(2, SeekOrigin.Current); + + writer.Write(boat.Serial); + writer.Write((byte)speed); + writer.Write((byte)d); + writer.Write((byte)boat.Facing); + writer.Write((short)(boat.X + xOffset)); + writer.Write((short)(boat.Y + yOffset)); + writer.Write((short)boat.Z); + writer.Seek(2, SeekOrigin.Current); // count + + var count = 0; + + foreach (var ent in ents) + { + if (!beholder.CanSee(ent)) + { + continue; + } + + writer.Write(ent.Serial); + writer.Write((short)(ent.X + xOffset)); + writer.Write((short)(ent.Y + yOffset)); + writer.Write((short)ent.Z); + ++count; + } + + writer.Seek(16, SeekOrigin.Begin); + writer.Write((short)count); + writer.WritePacketLength(); + + ns.Send(writer.Span); + } + + public static void SendDisplayBoatHS(this NetState ns, Mobile beholder, BaseBoat boat) + { + var ents = boat.GetMovingEntities(); + + ents.AddNotNull(boat.TillerMan); + ents.AddNotNull(boat.Hold); + ents.AddNotNull(boat.PPlank); + ents.AddNotNull(boat.SPlank); + + ents.Add(boat); + + var eable = ents.Where(beholder.CanSee); + + ns.SendBatchEntities(eable, ents.Count); + } + } +} diff --git a/Projects/UOContent/Network/EntityPackets.cs b/Projects/UOContent/Network/EntityPackets.cs new file mode 100644 index 000000000..be32718bb --- /dev/null +++ b/Projects/UOContent/Network/EntityPackets.cs @@ -0,0 +1,54 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EntityPackets.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.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Server.Network +{ + public static class EntityPackets + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SendBatchEntities(this NetState ns, IReadOnlyCollection entities) => + ns.SendBatchEntities(entities, entities.Count); + + public static void SendBatchEntities(this NetState ns, IEnumerable entities, int estimatedCount) + { + if (ns?.HighSeas != true) + { + return; + } + + bool isSA = ns.StygianAbyss; + bool isHS = ns.HighSeas; + + var minLength = PacketContainerBuilder.MinPacketLength + + OutgoingEntityPackets.MaxWorldEntityPacketLength + * estimatedCount; + + using var builder = new PacketContainerBuilder(stackalloc byte[minLength]); + + foreach (var entity in entities) + { + Span buffer = builder.GetSpan(OutgoingEntityPackets.MaxWorldEntityPacketLength).InitializePacket(); + var bytesWritten = OutgoingEntityPackets.CreateWorldEntity(buffer, entity, isSA, isHS); + builder.Advance(bytesWritten); + } + + ns.Send(builder.Finalize()); + } + } +}