/************************************************************************* * ModernUO * * Copyright 2019-2023 - ModernUO Development Team * * Email: hi@modernuo.com * * File: HousePackets.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *************************************************************************/ using System; using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using Server.Compression; using Server.Logging; using Server.Network; namespace Server.Multis; public static class HousePackets { private static readonly ILogger logger = LogFactory.GetLogger(typeof(HousePackets)); public static void SendBeginHouseCustomization(this NetState ns, Serial house) { if (ns.CannotSendPackets()) { return; } var writer = new SpanWriter(stackalloc byte[17]); writer.Write((byte)0xBF); // Packet Id writer.Write((ushort)17); writer.Write((short)0x20); // Sub-packet writer.Write(house); writer.Write((byte)0x04); // command writer.Write((ushort)0x0000); writer.Write((ushort)0xFFFF); writer.Write((ushort)0xFFFF); writer.Write((byte)0xFF); ns.Send(writer.Span); } public static void SendEndHouseCustomization(this NetState ns, Serial house) { if (ns.CannotSendPackets()) { return; } var writer = new SpanWriter(stackalloc byte[17]); writer.Write((byte)0xBF); // Packet Id writer.Write((ushort)17); writer.Write((short)0x20); // Sub-packet writer.Write(house); writer.Write((byte)0x05); // command writer.Write((ushort)0x0000); writer.Write((ushort)0xFFFF); writer.Write((ushort)0xFFFF); writer.Write((byte)0xFF); ns.Send(writer.Span); } public static void SendDesignStateGeneral(this NetState ns, Serial house, int revision) { if (ns.CannotSendPackets()) { return; } var writer = new SpanWriter(stackalloc byte[13]); writer.Write((byte)0xBF); // Packet Id writer.Write((ushort)13); writer.Write((short)0x1D); // Sub-packet writer.Write(house); writer.Write(revision); ns.Send(writer.Span); } private const int planeCount = 9; private const int maxPlaneLength = 0x400; private const int maxPerPlaneOffsetBuffer = 750; public static byte[] CreateHouseDesignStateDetailed(Serial 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 width = xMax - xMin + 1; var height = yMax - yMin + 1; var tiles = components.List; var planeLength = width * height * 2; using var planeOffsetWriter = new SpanWriter(0x500, true); using var planesWriter = new SpanWriter(planeLength * planeCount); Span planesUsed = stackalloc bool[9]; planesUsed.Clear(); int index; var totalPlaneOffsets = 0; var totalPlanes = 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 = 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 < maxPlaneLength) { var planeUsed = planesUsed[plane]; var planeWriterIndex = planeLength * plane; if (!planeUsed) { planesUsed[plane] = true; totalPlanes++; planesWriter.Seek(planeWriterIndex, SeekOrigin.Begin); planesWriter.Clear(planeLength); } planesWriter.Seek(planeWriterIndex + index, SeekOrigin.Begin); planesWriter.Write(mte.ItemId); continue; } } planeOffsetWriter.Write(mte.ItemId); planeOffsetWriter.Write((byte)mte.OffsetX); planeOffsetWriter.Write((byte)mte.OffsetY); planeOffsetWriter.Write((byte)mte.OffsetZ); totalPlaneOffsets++; } var writer = new SpanWriter(0x10000); 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 totalLength = 1; // includes plane count for (var i = 0; i < planeCount; i++) { if (!planesUsed[i]) { continue; } int size = i switch { 0 => planeLength, < 5 => (width - 1) * (height - 2) * 2, _ => width * (height - 1) * 2 }; var planeWriterIndex = planeLength * i; var source = planesWriter.RawBuffer.Slice(planeWriterIndex, size); writer.Write((byte)(0x20 | i)); writer.Write((byte)size); WritePacked(source, ref writer, out int destLength); totalLength += 4 + destLength; } index = 0; while (totalPlaneOffsets > 0) { var count = Math.Min(maxPerPlaneOffsetBuffer, totalPlaneOffsets); totalPlaneOffsets -= count; var size = count * 5; var source = planeOffsetWriter.RawBuffer.Slice(index * 5, size); writer.Write((byte)(9 + index++)); writer.Write((byte)size); 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(); var buffer = writer.Span.ToArray(); writer.Dispose(); 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[(writer.Position + 2)..]; length = Deflate.Standard.Pack(dest, source); if (length == 0) { logger.Warning("House packet compression failed."); } writer.Write((byte)length); writer.Write((byte)(((size >> 4) & 0xF0) | ((length >> 8) & 0xF))); writer.Seek(length, SeekOrigin.Current); } }