This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/Ananta.Server/Ananta.Core/Protocol/Client4229938/ExtractionGrid4229938.cs
T

114 lines
4.0 KiB
C#
Raw Normal View History

namespace Ananta.Server.Protocol.Client4229938;
/// <summary>
/// 搜打撤的格子占位模型 —— 逐条镜像客户端 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 内层(先铺满一行再换行),无旋转优先、旋转次之。
/// </summary>
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; }
/// <summary>把 Volume 按旋转换算成实际占格 (宽, 高)。</summary>
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];
/// <summary>整块 (x,y,w,h) 是否都在界内且没被占。对应客户端 CheckCanAddItemByCell。</summary>
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;
}
/// <summary>
/// 按客户端的顺序找第一个能放下 (w,h) 的格子:y 外层、x 内层。
/// 与 TryGetFreeCellByOrder 的扫描顺序一致 —— 顺序不一致会导致服务端认为"放得下"、
/// 客户端认为"放不下",玩家看到的就是拖过去弹回。
/// </summary>
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;
}
/// <summary>先试不旋转,再试旋转。对应 TryGetFreeCellByOrderWithRotation。</summary>
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;
}
}