diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 66f65a3a0..414a85a2d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: - name: Setup .NET 5 uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.102 + dotnet-version: 5.0.200 - name: Build run: ./publish.cmd - name: Test diff --git a/Directory.Build.props b/Directory.Build.props index 109556f2c..27af9da47 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -29,16 +29,16 @@ MUO $(SolutionDir) - + true false true true - + win-x64 - + osx-x64 diff --git a/Projects/Server/Network/Packet.cs b/Projects/Server.Tests/Helpers/Packet.cs similarity index 100% rename from Projects/Server/Network/Packet.cs rename to Projects/Server.Tests/Helpers/Packet.cs diff --git a/Projects/Server/Network/PacketWriter.cs b/Projects/Server.Tests/Helpers/PacketWriter.cs similarity index 100% rename from Projects/Server/Network/PacketWriter.cs rename to Projects/Server.Tests/Helpers/PacketWriter.cs diff --git a/Projects/Server.Tests/Server.Tests.csproj b/Projects/Server.Tests/Server.Tests.csproj index af438a866..7fe046cda 100644 --- a/Projects/Server.Tests/Server.Tests.csproj +++ b/Projects/Server.Tests/Server.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs index 8200c4179..feaab3d50 100644 --- a/Projects/Server/Buffers/SpanWriter.cs +++ b/Projects/Server/Buffers/SpanWriter.cs @@ -20,7 +20,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance; using Server.Network; using Server.Text; diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index a1f1cfa5e..af401a00b 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.Serialization; -using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance; using Server.Accounting; using Server.Buffers; using Server.ContextMenus; @@ -7047,24 +7047,6 @@ namespace Server eable.Free(); } - public bool Send(Packet p) => Send(p, false); - - public bool Send(Packet p, bool throwOnOffline) - { - if (m_NetState != null) - { - m_NetState.Send(p); - return true; - } - - if (throwOnOffline) - { - throw new MobileNotConnectedException(this, "Packet could not be sent."); - } - - return false; - } - /// /// Overridable. Event invoked before the Mobile says something. /// diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index cc5925552..c1559bd2b 100644 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -511,78 +511,6 @@ namespace Server.Network } } - public virtual void Send(Packet p) - { - if (Connection == null || BlockAllPackets) - { - p.OnSend(); - return; - } - - var writer = SendPipe.Writer; - - try - { - byte[] buffer = p.Compile(CompressionEnabled, out var length); - - if (buffer == null) - { - WriteConsole("null buffer send, disconnecting...", this); - using (StreamWriter op = new StreamWriter("null_send.log", true)) - { - op.WriteLine("{0} Client: {1}: null buffer send, disconnecting...", DateTime.UtcNow, this); - op.WriteLine(new System.Diagnostics.StackTrace()); - } - - Disconnect("Attempted to send null packet buffer."); - return; - } - - if (buffer.Length <= 0 || length <= 0) - { - p.OnSend(); - return; - } - - PacketSendProfile prof = null; - - if (Core.Profiling) - { - prof = PacketSendProfile.Acquire(p.PacketID); - prof.Start(); - } - - var result = writer.TryGetMemory(); - - if (result.Length >= length) - { - result.CopyFrom(buffer.AsSpan(0, length)); - writer.Advance((uint)length); - - if (!_flushQueued) - { - FlushPending.Enqueue(this); - _flushQueued = true; - } - } - else - { - WriteConsole("Too much data pending, disconnecting..."); - Disconnect("Too much data pending."); - } - - prof?.Finish(length); - } - catch (Exception ex) - { -#if DEBUG - Console.WriteLine(ex); -#endif - TraceException(ex); - Disconnect("Exception while sending."); - } - } - internal void Start() { if (Interlocked.CompareExchange(ref _running, 1, 0) == 1 || Connection == null) diff --git a/Projects/Server/Network/PacketUtilities.cs b/Projects/Server/Network/PacketUtilities.cs index 2edbda4db..33a19ab2e 100644 --- a/Projects/Server/Network/PacketUtilities.cs +++ b/Projects/Server/Network/PacketUtilities.cs @@ -17,7 +17,7 @@ using System; using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.Toolkit.HighPerformance; namespace Server.Network { diff --git a/Projects/Server/Network/Packets/IncomingPlayerPackets.cs b/Projects/Server/Network/Packets/IncomingPlayerPackets.cs index 50f664788..6e5def9df 100644 --- a/Projects/Server/Network/Packets/IncomingPlayerPackets.cs +++ b/Projects/Server/Network/Packets/IncomingPlayerPackets.cs @@ -14,7 +14,7 @@ *************************************************************************/ using System.Diagnostics; -using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance; using Server.Diagnostics; using Server.Exceptions; using Server.Gumps; diff --git a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs index 4cddfe7d2..b62ff6c63 100644 --- a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs @@ -17,7 +17,7 @@ using System; using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.Toolkit.HighPerformance; namespace Server.Network { diff --git a/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs b/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs index 97a8e81a0..59a59919d 100644 --- a/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs @@ -61,7 +61,7 @@ namespace Server.Network [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SendDeathStatus(this NetState ns, bool dead) => - ns?.Send(stackalloc byte[] { 0x2C, dead ? 0 : 2 }); + ns?.Send(stackalloc byte[] { 0x2C, dead ? (byte)0 : (byte)2 }); public static void SendToggleSpecialAbility(this NetState ns, int abilityId, bool active) { diff --git a/Projects/Server/Server.csproj b/Projects/Server/Server.csproj index ed620140d..226e8eb82 100644 --- a/Projects/Server/Server.csproj +++ b/Projects/Server/Server.csproj @@ -28,7 +28,7 @@ - + diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 7186aba67..30e02fe4a 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -8,7 +8,7 @@ using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Text; using System.Xml; -using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance; using Server.Buffers; using Server.Network; using Server.Random; diff --git a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs index ef2c7b080..87b308814 100644 --- a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using Server; using Server.Multis; using Server.Network; using Server.Tests; @@ -48,5 +50,32 @@ namespace UOContent.Tests var result = ns.SendPipe.Reader.TryRead(); AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } + + [Fact] + public void TestHouseDesignStateDetailed() + { + Serial serial = 0x40000001; + var revision = 10; + var tiles = new MultiTileEntry[250]; + for (var i = 0; i < tiles.Length; i++) + { + tiles[i] = new MultiTileEntry( + (ushort)i, + (byte)i, + (byte)i, + (byte)(i / 50), + TileFlag.None + ); + } + var mcl = new MultiComponentList(tiles.ToList()); + + var expected = new DesignStateDetailed( + serial, revision, mcl.Min.X, mcl.Min.Y, mcl.Max.X, mcl.Max.Y, tiles + ).Compile(); + + var actual = HousePackets.CreateHouseDesignStateDetailed(serial, revision, mcl); + + AssertThat.Equal(actual, expected); + } } } diff --git a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePackets.cs b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePackets.cs index 66b4076d1..fe94744d3 100644 --- a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePackets.cs +++ b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePackets.cs @@ -1,3 +1,9 @@ +using System; +using System.Buffers; +using System.IO; +using System.IO.Compression; +using Server.Multis; + namespace Server.Network { public class BeginHouseCustomization : Packet @@ -43,4 +49,287 @@ namespace Server.Network Stream.Write(revision); } } + + public sealed class DesignStateDetailed : Packet + { + public const int MaxItemsPerStairBuffer = 750; + + private readonly bool[] m_PlaneUsed = new bool[9]; + private readonly byte[] m_PrimBuffer = new byte[4]; + + public DesignStateDetailed(uint serial, int revision, int xMin, int yMin, int xMax, int yMax, MultiTileEntry[] tiles) + : base(0xD8) + { + EnsureCapacity(17 + tiles.Length * 5); + + Write((byte)0x03); // Compression Type + Write((byte)0x00); // Unknown + Write(serial); + Write(revision); + Write((short)tiles.Length); + Write((short)0); // Buffer length : reserved + Write((byte)0); // Plane count : reserved + + var totalLength = 1; // includes plane count + + var width = xMax - xMin + 1; + var height = yMax - yMin + 1; + + var planeBuffers = new byte[9][]; + + for (var i = 0; i < planeBuffers.Length; ++i) + { + planeBuffers[i] = ArrayPool.Shared.Rent(0x400); + } + + var stairBuffers = new byte[6][]; + + for (var i = 0; i < stairBuffers.Length; ++i) + { + stairBuffers[i] = ArrayPool.Shared.Rent(MaxItemsPerStairBuffer * 5); + } + + Clear(planeBuffers[0], width * height * 2); + + for (var i = 0; i < 4; ++i) + { + Clear(planeBuffers[1 + i], (width - 1) * (height - 2) * 2); + Clear(planeBuffers[5 + i], width * (height - 1) * 2); + } + + var totalStairsUsed = 0; + + for (var i = 0; i < tiles.Length; ++i) + { + var mte = tiles[i]; + var x = mte.OffsetX - xMin; + var y = mte.OffsetY - yMin; + int z = mte.OffsetZ; + var floor = TileData.ItemTable[mte.ItemId & TileData.MaxItemValue].Height <= 0; + int plane, size; + + switch (z) + { + case 0: + plane = 0; + break; + case 7: + plane = 1; + break; + case 27: + plane = 2; + break; + case 47: + plane = 3; + break; + case 67: + plane = 4; + break; + default: + { + var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer; + var stairBuffer = stairBuffers[stairBufferIndex]; + + var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5; + + stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8); + stairBuffer[byteIndex++] = (byte)mte.ItemId; + + stairBuffer[byteIndex++] = (byte)mte.OffsetX; + stairBuffer[byteIndex++] = (byte)mte.OffsetY; + stairBuffer[byteIndex] = (byte)mte.OffsetZ; + + ++totalStairsUsed; + + continue; + } + } + + if (plane == 0) + { + size = height; + } + else if (floor) + { + size = height - 2; + x -= 1; + y -= 1; + } + else + { + size = height - 1; + plane += 4; + } + + var index = (x * size + y) * 2; + + if (x < 0 || y < 0 || y >= size || index + 1 >= 0x400) + { + var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer; + var stairBuffer = stairBuffers[stairBufferIndex]; + + var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5; + + stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8); + stairBuffer[byteIndex++] = (byte)mte.ItemId; + + stairBuffer[byteIndex++] = (byte)mte.OffsetX; + stairBuffer[byteIndex++] = (byte)mte.OffsetY; + stairBuffer[byteIndex] = (byte)mte.OffsetZ; + + ++totalStairsUsed; + } + else + { + m_PlaneUsed[plane] = true; + planeBuffers[plane][index] = (byte)(mte.ItemId >> 8); + planeBuffers[plane][index + 1] = (byte)mte.ItemId; + } + } + + var planeCount = 0; + + var deflatedBuffer = ArrayPool.Shared.Rent(0x2000); + + for (var i = 0; i < planeBuffers.Length; ++i) + { + if (!m_PlaneUsed[i]) + { + ArrayPool.Shared.Return(planeBuffers[i]); + continue; + } + + ++planeCount; + + int size = i switch + { + 0 => width * height * 2, + < 5 => (width - 1) * (height - 2) * 2, + _ => width * (height - 1) * 2 + }; + + var inflatedBuffer = planeBuffers[i]; + + var deflatedLength = deflatedBuffer.Length; + var ce = Zlib.Pack( + deflatedBuffer, + ref deflatedLength, + inflatedBuffer, + size, + ZlibQuality.Default + ); + + if (ce != ZlibError.Okay) + { + Console.WriteLine("ZLib error: {0} (#{1})", ce, (int)ce); + deflatedLength = 0; + size = 0; + } + + Write((byte)(0x20 | i)); + Write((byte)size); + Write((byte)deflatedLength); + Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF))); + Write(deflatedBuffer, 0, deflatedLength); + + totalLength += 4 + deflatedLength; + ArrayPool.Shared.Return(inflatedBuffer); + } + + var totalStairBuffersUsed = (totalStairsUsed + (MaxItemsPerStairBuffer - 1)) / MaxItemsPerStairBuffer; + + for (var i = 0; i < totalStairBuffersUsed; ++i) + { + ++planeCount; + + var count = Math.Min(MaxItemsPerStairBuffer, totalStairsUsed - i * MaxItemsPerStairBuffer); + + var size = count * 5; + + var inflatedBuffer = stairBuffers[i]; + + var deflatedLength = deflatedBuffer.Length; + var ce = Zlib.Pack( + deflatedBuffer, + ref deflatedLength, + inflatedBuffer, + size, + ZlibQuality.Default + ); + + if (ce != ZlibError.Okay) + { + Console.WriteLine("ZLib error: {0} (#{1})", ce, (int)ce); + deflatedLength = 0; + size = 0; + } + + Write((byte)(9 + i)); + Write((byte)size); + Write((byte)deflatedLength); + Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF))); + Write(deflatedBuffer, 0, deflatedLength); + + totalLength += 4 + deflatedLength; + } + + for (var i = 0; i < stairBuffers.Length; ++i) + { + ArrayPool.Shared.Return(stairBuffers[i]); + } + + ArrayPool.Shared.Return(deflatedBuffer); + + Stream.Seek(15, SeekOrigin.Begin); + + Write((short)totalLength); // Buffer length + Write((byte)planeCount); // Plane count + } + + public void Write(int value) + { + m_PrimBuffer[0] = (byte)(value >> 24); + m_PrimBuffer[1] = (byte)(value >> 16); + m_PrimBuffer[2] = (byte)(value >> 8); + m_PrimBuffer[3] = (byte)value; + + Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4); + } + + public void Write(uint value) + { + m_PrimBuffer[0] = (byte)(value >> 24); + m_PrimBuffer[1] = (byte)(value >> 16); + m_PrimBuffer[2] = (byte)(value >> 8); + m_PrimBuffer[3] = (byte)value; + + Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4); + } + + public void Write(short value) + { + m_PrimBuffer[0] = (byte)(value >> 8); + m_PrimBuffer[1] = (byte)value; + + Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 2); + } + + public void Write(byte value) + { + Stream.UnderlyingStream.WriteByte(value); + } + + public void Write(byte[] buffer, int offset, int size) + { + Stream.UnderlyingStream.Write(buffer, offset, size); + } + + public static void Clear(byte[] buffer, int size) + { + for (var i = 0; i < size; ++i) + { + buffer[i] = 0; + } + } + } } diff --git a/Projects/UOContent.Tests/UOContent.Tests.csproj b/Projects/UOContent.Tests/UOContent.Tests.csproj index 05672ef81..c7c1c1f59 100644 --- a/Projects/UOContent.Tests/UOContent.Tests.csproj +++ b/Projects/UOContent.Tests/UOContent.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/Projects/UOContent/Accounting/Firewall.cs b/Projects/UOContent/Accounting/Firewall.cs index b00b74cf5..d1deaf097 100644 --- a/Projects/UOContent/Accounting/Firewall.cs +++ b/Projects/UOContent/Accounting/Firewall.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Runtime.CompilerServices; -using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance; namespace Server { diff --git a/Projects/UOContent/Engines/Spawners/SpawnPropsGumpCommand.cs b/Projects/UOContent/Engines/Spawners/SpawnPropsCommand.cs similarity index 92% rename from Projects/UOContent/Engines/Spawners/SpawnPropsGumpCommand.cs rename to Projects/UOContent/Engines/Spawners/SpawnPropsCommand.cs index 6681067c3..1f3c0200a 100644 --- a/Projects/UOContent/Engines/Spawners/SpawnPropsGumpCommand.cs +++ b/Projects/UOContent/Engines/Spawners/SpawnPropsCommand.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright (C) 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: SpawnPropsGump.cs * + * File: SpawnPropsCommand.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 * @@ -21,14 +21,14 @@ using Server.Targeting; namespace Server.Engines.Spawners { - public class SpawnPropsGumpCommand : BaseCommand + public class SpawnPropsCommand : BaseCommand { public static void Initialize() { - TargetCommands.Register(new SpawnPropsGumpCommand()); + TargetCommands.Register(new SpawnPropsCommand()); } - public SpawnPropsGumpCommand() + public SpawnPropsCommand() { AccessLevel = AccessLevel.GameMaster; Supports = CommandSupport.Complex | CommandSupport.Simple; diff --git a/Projects/UOContent/Multis/Houses/HouseFoundation.cs b/Projects/UOContent/Multis/Houses/HouseFoundation.cs index 4da353d76..e9ecefdd2 100644 --- a/Projects/UOContent/Multis/Houses/HouseFoundation.cs +++ b/Projects/UOContent/Multis/Houses/HouseFoundation.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Runtime.CompilerServices; using Server.Gumps; using Server.Items; using Server.Mobiles; @@ -1872,8 +1873,6 @@ namespace Server.Multis public class DesignState { - private Packet m_PacketCache; - public DesignState(HouseFoundation foundation, MultiComponentList components) { Foundation = foundation; @@ -1926,21 +1925,7 @@ namespace Server.Multis } } - public Packet PacketCache - { - get => m_PacketCache; - set - { - if (m_PacketCache == value) - { - return; - } - - m_PacketCache?.Release(); - - m_PacketCache = value; - } - } + public byte[] PacketCache { get; set; } public HouseFoundation Foundation { get; } @@ -1975,31 +1960,18 @@ namespace Server.Multis public void OnRevised() { Revision = ++Foundation.LastRevision; - m_PacketCache?.Release(); - m_PacketCache = null; + PacketCache = null; } - public void SendGeneralInfoTo(NetState state) - { - state.SendDesignStateGeneral(Foundation.Serial, Revision); - } + public void SendGeneralInfoTo(NetState state) => state.SendDesignStateGeneral(Foundation.Serial, Revision); + public void SendDetailedInfoTo(NetState state) => state?.Send(PacketCache ??= SendDetails(Foundation.Serial)); - public void SendDetailedInfoTo(NetState state) - { - if (state == null) - { - return; - } - - if (m_PacketCache == null) - { - DesignStateDetailed.SendDetails(state, Foundation, this); - } - else - { - state.Send(m_PacketCache); - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte[] SendDetails(Serial house) => HousePackets.CreateHouseDesignStateDetailed( + house, + Revision, + Components + ); public void FreezeFixtures() { @@ -2375,317 +2347,8 @@ namespace Server.Multis } context.Foundation.Signpost?.SendInfoTo(state); - context.Foundation.SignHanger?.SendInfoTo(state); - context.Foundation.Sign?.SendInfoTo(state); } } - - public sealed class DesignStateDetailed : Packet - { - public const int MaxItemsPerStairBuffer = 750; - - private readonly bool[] m_PlaneUsed = new bool[9]; - private readonly byte[] m_PrimBuffer = new byte[4]; - - public DesignStateDetailed(uint serial, int revision, int xMin, int yMin, int xMax, int yMax, MultiTileEntry[] tiles) - : base(0xD8) - { - EnsureCapacity(17 + tiles.Length * 5); - - Write((byte)0x03); // Compression Type - Write((byte)0x00); // Unknown - Write(serial); - Write(revision); - Write((short)tiles.Length); - Write((short)0); // Buffer length : reserved - Write((byte)0); // Plane count : reserved - - var totalLength = 1; // includes plane count - - var width = xMax - xMin + 1; - var height = yMax - yMin + 1; - - var planeBuffers = new byte[9][]; - - for (var i = 0; i < planeBuffers.Length; ++i) - { - planeBuffers[i] = ArrayPool.Shared.Rent(0x400); - } - - var stairBuffers = new byte[6][]; - - for (var i = 0; i < stairBuffers.Length; ++i) - { - stairBuffers[i] = ArrayPool.Shared.Rent(MaxItemsPerStairBuffer * 5); - } - - Clear(planeBuffers[0], width * height * 2); - - for (var i = 0; i < 4; ++i) - { - Clear(planeBuffers[1 + i], (width - 1) * (height - 2) * 2); - Clear(planeBuffers[5 + i], width * (height - 1) * 2); - } - - var totalStairsUsed = 0; - - for (var i = 0; i < tiles.Length; ++i) - { - var mte = tiles[i]; - var x = mte.OffsetX - xMin; - var y = mte.OffsetY - yMin; - int z = mte.OffsetZ; - var floor = TileData.ItemTable[mte.ItemId & TileData.MaxItemValue].Height <= 0; - int plane, size; - - switch (z) - { - case 0: - plane = 0; - break; - case 7: - plane = 1; - break; - case 27: - plane = 2; - break; - case 47: - plane = 3; - break; - case 67: - plane = 4; - break; - default: - { - var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer; - var stairBuffer = stairBuffers[stairBufferIndex]; - - var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5; - - stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8); - stairBuffer[byteIndex++] = (byte)mte.ItemId; - - stairBuffer[byteIndex++] = (byte)mte.OffsetX; - stairBuffer[byteIndex++] = (byte)mte.OffsetY; - stairBuffer[byteIndex] = (byte)mte.OffsetZ; - - ++totalStairsUsed; - - continue; - } - } - - if (plane == 0) - { - size = height; - } - else if (floor) - { - size = height - 2; - x -= 1; - y -= 1; - } - else - { - size = height - 1; - plane += 4; - } - - var index = (x * size + y) * 2; - - if (x < 0 || y < 0 || y >= size || index + 1 >= 0x400) - { - var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer; - var stairBuffer = stairBuffers[stairBufferIndex]; - - var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5; - - stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8); - stairBuffer[byteIndex++] = (byte)mte.ItemId; - - stairBuffer[byteIndex++] = (byte)mte.OffsetX; - stairBuffer[byteIndex++] = (byte)mte.OffsetY; - stairBuffer[byteIndex] = (byte)mte.OffsetZ; - - ++totalStairsUsed; - } - else - { - m_PlaneUsed[plane] = true; - planeBuffers[plane][index] = (byte)(mte.ItemId >> 8); - planeBuffers[plane][index + 1] = (byte)mte.ItemId; - } - } - - var planeCount = 0; - - var deflatedBuffer = ArrayPool.Shared.Rent(0x2000); - - for (var i = 0; i < planeBuffers.Length; ++i) - { - if (!m_PlaneUsed[i]) - { - ArrayPool.Shared.Return(planeBuffers[i]); - continue; - } - - ++planeCount; - - int size = i switch - { - 0 => width * height * 2, - < 5 => (width - 1) * (height - 2) * 2, - _ => width * (height - 1) * 2 - }; - - var inflatedBuffer = planeBuffers[i]; - - var deflatedLength = deflatedBuffer.Length; - var ce = Zlib.Pack( - deflatedBuffer, - ref deflatedLength, - inflatedBuffer, - size, - ZlibQuality.Default - ); - - if (ce != ZlibError.Okay) - { - Console.WriteLine("ZLib error: {0} (#{1})", ce, (int)ce); - deflatedLength = 0; - size = 0; - } - - Write((byte)(0x20 | i)); - Write((byte)size); - Write((byte)deflatedLength); - Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF))); - Write(deflatedBuffer, 0, deflatedLength); - - totalLength += 4 + deflatedLength; - ArrayPool.Shared.Return(inflatedBuffer); - } - - var totalStairBuffersUsed = (totalStairsUsed + (MaxItemsPerStairBuffer - 1)) / MaxItemsPerStairBuffer; - - for (var i = 0; i < totalStairBuffersUsed; ++i) - { - ++planeCount; - - var count = Math.Min(MaxItemsPerStairBuffer, totalStairsUsed - i * MaxItemsPerStairBuffer); - - var size = count * 5; - - var inflatedBuffer = stairBuffers[i]; - - var deflatedLength = deflatedBuffer.Length; - var ce = Zlib.Pack( - deflatedBuffer, - ref deflatedLength, - inflatedBuffer, - size, - ZlibQuality.Default - ); - - if (ce != ZlibError.Okay) - { - Console.WriteLine("ZLib error: {0} (#{1})", ce, (int)ce); - deflatedLength = 0; - size = 0; - } - - Write((byte)(9 + i)); - Write((byte)size); - Write((byte)deflatedLength); - Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF))); - Write(deflatedBuffer, 0, deflatedLength); - - totalLength += 4 + deflatedLength; - } - - for (var i = 0; i < stairBuffers.Length; ++i) - { - ArrayPool.Shared.Return(stairBuffers[i]); - } - - ArrayPool.Shared.Return(deflatedBuffer); - - Stream.Seek(15, SeekOrigin.Begin); - - Write((short)totalLength); // Buffer length - Write((byte)planeCount); // Plane count - } - - public void Write(int value) - { - m_PrimBuffer[0] = (byte)(value >> 24); - m_PrimBuffer[1] = (byte)(value >> 16); - m_PrimBuffer[2] = (byte)(value >> 8); - m_PrimBuffer[3] = (byte)value; - - Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4); - } - - public void Write(uint value) - { - m_PrimBuffer[0] = (byte)(value >> 24); - m_PrimBuffer[1] = (byte)(value >> 16); - m_PrimBuffer[2] = (byte)(value >> 8); - m_PrimBuffer[3] = (byte)value; - - Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4); - } - - public void Write(short value) - { - m_PrimBuffer[0] = (byte)(value >> 8); - m_PrimBuffer[1] = (byte)value; - - Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 2); - } - - public void Write(byte value) - { - Stream.UnderlyingStream.WriteByte(value); - } - - public void Write(byte[] buffer, int offset, int size) - { - Stream.UnderlyingStream.Write(buffer, offset, size); - } - - public static void Clear(byte[] buffer, int size) - { - for (var i = 0; i < size; ++i) - { - buffer[i] = 0; - } - } - - public static void SendDetails(NetState ns, HouseFoundation house, DesignState state) - { - var p = state.PacketCache; - - if (p == null) - { - var mcl = state.Components; - p = new DesignStateDetailed( - house.Serial, - state.Revision, - mcl.Min.X, - mcl.Min.Y, - mcl.Max.X, - mcl.Max.Y, - mcl.List - ); - - p.SetStatic(); - state.PacketCache = p; - } - - ns.Send(p); - } - } } diff --git a/Projects/UOContent/Multis/Houses/HousePackets.cs b/Projects/UOContent/Multis/Houses/HousePackets.cs index 0b7bcf17d..e94aa1fbf 100644 --- a/Projects/UOContent/Multis/Houses/HousePackets.cs +++ b/Projects/UOContent/Multis/Houses/HousePackets.cs @@ -13,15 +13,17 @@ * along with this program. If not, see . * *************************************************************************/ +using System; using System.Buffers; +using System.IO; +using System.IO.Compression; +using System.Runtime.CompilerServices; using Server.Network; namespace Server.Multis { public static class HousePackets { - private const int MaxItemsPerStairBuffer = 750; - public static void SendBeginHouseCustomization(this NetState ns, Serial house) { if (ns == null) @@ -80,5 +82,187 @@ namespace Server.Multis ns.Send(writer.Span); } + + private const int planeCount = 9; + private const int planeLength = 0x400; // ItemID (max 1024 items per plane within the design grid) + private const int stairsCount = 6; + private const int stairsLength = 5; // ItemID, X, Y, Z (max 4500 items) + private const int stairsPerBuffer = 750; + + // Maximum size of the packed packet (31988 bytes) + private static readonly int maxPacketLength = + 18 + + (Zlib.MaxPackSize(planeLength) + 4) * planeCount + // 9369 + (Zlib.MaxPackSize(stairsPerBuffer * stairsLength) + 4) * stairsCount; // 22602 + + public static byte[] CreateHouseDesignStateDetailed(uint serial, int revision, MultiComponentList components) + { + var xMin = components.Min.X; + var yMin = components.Min.Y; + var xMax = components.Max.X; + var yMax = components.Max.Y; + var tiles = components.List; + + const int totalPlaneLength = planeLength * planeCount; + const int stairsBufferLength = stairsPerBuffer * stairsLength; + const int maxUnpackedSize = totalPlaneLength + stairsBufferLength * stairsCount; + using var inflatedWriter = new SpanWriter(maxUnpackedSize); + + Span planesUsed = stackalloc bool[9]; + var index = 0; + var stairsIndex = totalPlaneLength; + var totalStairsUsed = 0; + var width = xMax - xMin + 1; + var height = yMax - yMin + 1; + + for (var i = 0; i < tiles.Length; ++i) + { + var mte = tiles[i]; + var x = mte.OffsetX - xMin; + var y = mte.OffsetY - yMin; + int z = mte.OffsetZ; + var floor = TileData.ItemTable[mte.ItemId & TileData.MaxItemValue].Height <= 0; + + int plane = z switch + { + 0 => 0, + 7 => 1, + 27 => 2, + 47 => 3, + 67 => 4, + _ => -1 + }; + + if (plane > -1) + { + int size; + if (plane == 0) + { + size = height; + } + else if (floor) + { + size = height - 2; + x -= 1; + y -= 1; + } + else + { + size = height - 1; + plane += 4; + } + + index = (x * size + y) * 2; + if (x >= 0 && y >= 0 && y < size && index + 1 < 0x400) + { + var planeUsed = planesUsed[plane]; + if (!planeUsed) + { + inflatedWriter.Seek(plane * planeLength, SeekOrigin.Begin); + inflatedWriter.Clear(planeLength); + planesUsed[plane] = true; + } + + inflatedWriter.Seek(index, SeekOrigin.Begin); + inflatedWriter.Write(mte.ItemId); + continue; + } + } + + inflatedWriter.Seek(stairsIndex, SeekOrigin.Begin); + inflatedWriter.Write(mte.ItemId); + inflatedWriter.Write((byte)mte.OffsetX); + inflatedWriter.Write((byte)mte.OffsetY); + inflatedWriter.Write((byte)mte.OffsetZ); + stairsIndex = inflatedWriter.Position; + totalStairsUsed++; + } + + var buffer = GC.AllocateUninitializedArray(maxPacketLength); + var writer = new SpanWriter(buffer); + writer.Write((byte)0xD8); // Packet ID + writer.Seek(2, SeekOrigin.Current); // Length + + writer.Write((byte)0x03); // Compression Type + writer.Write((byte)0x00); // Unknown + writer.Write(serial); + writer.Write(revision); + writer.Write((short)tiles.Length); + writer.Seek(3, SeekOrigin.Current); // Buffer Length, Plane Count + + var totalPlanes = 0; + var totalLength = 1; // includes plane count + + for (var i = 0; i < planeCount; i++) + { + if (!planesUsed[i]) + { + inflatedWriter.Seek(planeCount, SeekOrigin.Current); + continue; + } + + int size = i switch + { + 0 => width * height * 2, + < 5 => (width - 1) * (height - 2) * 2, + _ => width * (height - 1) * 2 + }; + + var source = inflatedWriter.RawBuffer.Slice(i * planeLength, size); + + writer.Write((byte)(0x20 | i)); + WritePacked(source, ref writer, out int destLength); + + totalLength += 4 + destLength; + totalPlanes++; + } + + index = 0; + while (totalStairsUsed > 0) + { + var count = Math.Min(stairsPerBuffer, totalStairsUsed); + totalStairsUsed -= count; + + var start = totalPlaneLength + index * stairsLength; + var size = count * 5; + var source = inflatedWriter.RawBuffer.Slice(start, size); + + writer.Write((byte)(9 + index++)); + WritePacked(source, ref writer, out int destLength); + totalLength += 4 + destLength; + totalPlanes++; + } + + writer.Seek(15, SeekOrigin.Begin); + writer.Write((short)totalLength); + writer.Write((byte)totalPlanes); + writer.WritePacketLength(); + + // TODO: Avoid this somehow. + Array.Resize(ref buffer, writer.BytesWritten); + return buffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WritePacked(ReadOnlySpan source, ref SpanWriter writer, out int length) + { + var size = source.Length; + var dest = writer.RawBuffer.Slice(writer.Position + 3); + length = dest.Length; + + var ce = Zlib.Pack(dest, ref length, source, ZlibQuality.Default); + + if (ce != ZlibError.Okay) + { + Console.WriteLine("ZLib error: {0} (#{1})", ce, (int)ce); + length = 0; + size = 0; + } + + writer.Write((byte)size); + writer.Write((byte)length); + writer.Write((byte)(((size >> 4) & 0xF0) | ((length >> 8) & 0xF))); + writer.Seek(length, SeekOrigin.Current); + } } } diff --git a/Projects/UOContent/Special Systems/Engines/TestCenter.cs b/Projects/UOContent/Special Systems/Engines/TestCenter.cs index 30d3d52cb..25355bea2 100644 --- a/Projects/UOContent/Special Systems/Engines/TestCenter.cs +++ b/Projects/UOContent/Special Systems/Engines/TestCenter.cs @@ -1,6 +1,6 @@ using System; using System.Text; -using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance; using Server.Commands; using Server.Gumps; using Server.Network; diff --git a/Projects/UOContent/UOContent.csproj b/Projects/UOContent/UOContent.csproj index c3d844ff4..c0c5b034f 100644 --- a/Projects/UOContent/UOContent.csproj +++ b/Projects/UOContent/UOContent.csproj @@ -29,7 +29,7 @@ false - + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1c5a4ba79..d361ac282 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,7 @@ jobs: displayName: 'Install .NET 5' inputs: packageType: sdk - version: '5.0.102' + version: 5.0.200 - task: NuGetAuthenticate@0 - script: ./publish.cmd Release win displayName: 'Build' @@ -56,7 +56,7 @@ jobs: displayName: 'Install .NET 5' inputs: packageType: sdk - version: '5.0.102' + version: 5.0.200 - task: NuGetAuthenticate@0 - script: ./publish.cmd Release $(os) displayName: 'Build'