fix(network): Converts House Design Detailed Packet (#538)

- [X] Converts design state detailed packet
- [X] Removes `Packet` class
- [X] Removes `Send(Packet)` function signatures
- [X] Removes `PacketWriter` class
- [X] Updates dependencies.
This commit is contained in:
Kamron Batman 2021-03-03 02:09:15 -08:00 • committed by GitHub
parent 0a435644eb
commit ec1cc10821
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 538 additions and 463 deletions

View file

@ -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<byte>.Shared.Rent(0x400);
}
var stairBuffers = new byte[6][];
for (var i = 0; i < stairBuffers.Length; ++i)
{
stairBuffers[i] = ArrayPool<byte>.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<byte>.Shared.Rent(0x2000);
for (var i = 0; i < planeBuffers.Length; ++i)
{
if (!m_PlaneUsed[i])
{
ArrayPool<byte>.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<byte>.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<byte>.Shared.Return(stairBuffers[i]);
}
ArrayPool<byte>.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);
}
}
}

View file

@ -13,15 +13,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<bool> 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<byte>(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<byte> 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);
}
}
}