Files
TheriapolisV3/Theriapolis.Core/Rules/Combat/LineOfSight.cs
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

49 lines
1.7 KiB
C#

using Theriapolis.Core.Util;
namespace Theriapolis.Core.Rules.Combat;
/// <summary>
/// Tactical-tile line-of-sight via Bresenham. The caller supplies a
/// "blocked at (x, y)?" predicate so this helper stays free of a hard
/// dependency on TacticalChunk / WorldState — Phase 5 M4 tests use a flat
/// arena (always-clear); M5 plugs in the live tactical-tile sampler.
/// </summary>
public static class LineOfSight
{
/// <summary>
/// True if a straight line from <paramref name="from"/> to
/// <paramref name="to"/> traverses only un-blocked tiles. Endpoints
/// themselves are NOT consulted — only the intermediate tiles.
/// </summary>
public static bool HasLine(Vec2 from, Vec2 to, System.Func<int, int, bool> isBlockedAt)
{
int x0 = (int)System.Math.Floor(from.X);
int y0 = (int)System.Math.Floor(from.Y);
int x1 = (int)System.Math.Floor(to.X);
int y1 = (int)System.Math.Floor(to.Y);
int dx = System.Math.Abs(x1 - x0);
int dy = System.Math.Abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx - dy;
int x = x0, y = y0;
while (true)
{
// Skip the endpoint itself
if (!(x == x0 && y == y0) && !(x == x1 && y == y1))
{
if (isBlockedAt(x, y)) return false;
}
if (x == x1 && y == y1) return true;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; x += sx; }
if (e2 < dx) { err += dx; y += sy; }
}
}
/// <summary>Convenience: always-clear arena. Used by combat-duel and most M4 tests.</summary>
public static readonly System.Func<int, int, bool> AlwaysClear = (_, _) => false;
}