namespace Ananta.Server.Protocol.Client4229938; /// /// 搜打撤的格子占位模型 —— 逐条镜像客户端 LX6/Manager/ExtractionShooterUtils.lua 的 /// OccupyCells / CheckCanAddItemByCell / TryGetFreeCellByOrder。 /// /// 为什么必须镜像:客户端拖拽落地前会**自己**用这套规则校验一次(CheckShiftItemInBag、 /// CheckCellAllowed、TryGetFreeCellIndexByConfigId),服务端如果只判"原点格空不空", /// 就会出现「2x3 的物品和 1 格的物品叠在一起」——因为 2x3 的覆盖区里那些非原点格在服务端看来是空的。 /// /// 三条硬约束(照抄客户端,不要"优化"): /// 1. 坐标是 0 基,合法范围 x∈[0,sizeX-1]、y∈[0,sizeY-1]。 /// 2. 物品占 Volume.x × Volume.y,IsRotated 时两者互换。 /// 3. 找空格是 y 外层、x 内层(先铺满一行再换行),无旋转优先、旋转次之。 /// internal sealed class ExtractionGrid4229938 { private readonly bool[] _cells; internal ExtractionGrid4229938(int sizeX, int sizeY) { SizeX = Math.Max(1, sizeX); SizeY = Math.Max(1, sizeY); _cells = new bool[SizeX * SizeY]; } internal int SizeX { get; } internal int SizeY { get; } /// 把 Volume 按旋转换算成实际占格 (宽, 高)。 internal static (int W, int H) Rotate(int volumeX, int volumeY, bool isRotated) => isRotated ? (Math.Max(1, volumeY), Math.Max(1, volumeX)) : (Math.Max(1, volumeX), Math.Max(1, volumeY)); internal bool InBounds(int x, int y) => x >= 0 && y >= 0 && x < SizeX && y < SizeY; internal bool IsFree(int x, int y) => InBounds(x, y) && !_cells[(y * SizeX) + x]; /// 整块 (x,y,w,h) 是否都在界内且没被占。对应客户端 CheckCanAddItemByCell。 internal bool CanPlace(int x, int y, int w, int h) { if (x < 0 || y < 0 || w <= 0 || h <= 0) return false; if (x + w > SizeX || y + h > SizeY) return false; for (var oy = 0; oy < h; oy++) for (var ox = 0; ox < w; ox++) if (_cells[((y + oy) * SizeX) + x + ox]) return false; return true; } internal void Occupy(int x, int y, int w, int h) { for (var oy = 0; oy < h; oy++) for (var ox = 0; ox < w; ox++) if (InBounds(x + ox, y + oy)) _cells[((y + oy) * SizeX) + x + ox] = true; } internal void Release(int x, int y, int w, int h) { for (var oy = 0; oy < h; oy++) for (var ox = 0; ox < w; ox++) if (InBounds(x + ox, y + oy)) _cells[((y + oy) * SizeX) + x + ox] = false; } /// /// 按客户端的顺序找第一个能放下 (w,h) 的格子:y 外层、x 内层。 /// 与 TryGetFreeCellByOrder 的扫描顺序一致 —— 顺序不一致会导致服务端认为"放得下"、 /// 客户端认为"放不下",玩家看到的就是拖过去弹回。 /// internal bool TryFirstFit(int w, int h, out int x, out int y) { x = 0; y = 0; if (w > SizeX || h > SizeY) return false; for (var cy = 0; cy <= SizeY - h; cy++) for (var cx = 0; cx <= SizeX - w; cx++) if (CanPlace(cx, cy, w, h)) { x = cx; y = cy; return true; } return false; } /// 先试不旋转,再试旋转。对应 TryGetFreeCellByOrderWithRotation。 internal bool TryFirstFitWithRotation(int volumeX, int volumeY, out int x, out int y, out bool rotated) { if (TryFirstFit(volumeX, volumeY, out x, out y)) { rotated = false; return true; } if (TryFirstFit(volumeY, volumeX, out x, out y)) { rotated = true; return true; } rotated = false; return false; } }