neon-sprawl/server/NeonSprawl.Server/Game/PositionState/MoveCommandValidation.cs

54 lines
2.0 KiB
C#

namespace NeonSprawl.Server.Game.PositionState;
/// <summary>Pure movement validation for <see cref="MoveCommandRequest"/> targets (NS-19).</summary>
/// <remarks>
/// <para><b>Precedence</b> when multiple rules fail: <see cref="MoveCommandReasonCodes.HorizontalStepExceeded"/>,
/// then <see cref="MoveCommandReasonCodes.VerticalStepExceeded"/>, then <see cref="MoveCommandReasonCodes.OutOfBounds"/>.</para>
/// <para>Vertical uses <c>abs(ΔY)</c>, so <b>descending</b> (target Y lower than current) is allowed when the
/// drop is within <see cref="MovementValidationOptions.MaxVerticalStep"/> — same as climbing.</para>
/// </remarks>
public static class MoveCommandValidation
{
/// <summary>Returns false and sets <paramref name="reasonCode"/> when the move must be rejected.</summary>
public static bool TryValidate(
in PositionSnapshot from,
PositionVector to,
MovementValidationOptions rules,
out string reasonCode)
{
reasonCode = "";
if (rules.HorizontalStepEnabled)
{
var dx = to.X - from.X;
var dz = to.Z - from.Z;
var horizontal = Math.Sqrt((dx * dx) + (dz * dz));
if (horizontal > rules.MaxHorizontalStep)
{
reasonCode = MoveCommandReasonCodes.HorizontalStepExceeded;
return false;
}
}
var dy = Math.Abs(to.Y - from.Y);
if (dy > rules.MaxVerticalStep)
{
reasonCode = MoveCommandReasonCodes.VerticalStepExceeded;
return false;
}
if (rules.DistrictBoundsEnabled)
{
if (to.X < rules.DistrictMinX || to.X > rules.DistrictMaxX ||
to.Y < rules.DistrictMinY || to.Y > rules.DistrictMaxY ||
to.Z < rules.DistrictMinZ || to.Z > rules.DistrictMaxZ)
{
reasonCode = MoveCommandReasonCodes.OutOfBounds;
return false;
}
}
return true;
}
}