using Theriapolis.Core.Rules.Stats; using Theriapolis.Core.Util; namespace Theriapolis.Core.Rules.Combat; /// /// Size-aware spatial helpers for combat. Combatants occupy /// ² tactical tiles /// anchored at their integer ; this helper /// computes edge-to-edge Chebyshev distance and reach predicates. /// public static class ReachAndCover { /// /// Edge-to-edge Chebyshev distance — number of empty tiles between two /// footprints. Adjacent (sharing an edge or corner) returns 0; one /// empty tile between returns 1; overlapping returns 0. /// public static int EdgeToEdgeChebyshev(Combatant a, Combatant b) { int aSize = a.Size.FootprintTiles(); int bSize = b.Size.FootprintTiles(); int aMinX = (int)System.Math.Floor(a.Position.X); int aMinY = (int)System.Math.Floor(a.Position.Y); int aMaxX = aMinX + aSize - 1; int aMaxY = aMinY + aSize - 1; int bMinX = (int)System.Math.Floor(b.Position.X); int bMinY = (int)System.Math.Floor(b.Position.Y); int bMaxX = bMinX + bSize - 1; int bMaxY = bMinY + bSize - 1; // Per-axis gap: positive = number of tile-steps to bring edges to // touching (then -1 because touching = 0 empty tiles between). int dx = System.Math.Max(0, System.Math.Max(aMinX - bMaxX, bMinX - aMaxX) - 1); int dy = System.Math.Max(0, System.Math.Max(aMinY - bMaxY, bMinY - aMaxY) - 1); return System.Math.Max(dx, dy); } /// True if is within the attack's reach (melee) or short range (ranged). public static bool IsInReach(Combatant attacker, Combatant defender, AttackOption attack) { int dist = EdgeToEdgeChebyshev(attacker, defender); if (attack.IsRanged) return dist <= attack.RangeLongTiles; return dist <= attack.ReachTiles; } /// True if the defender sits past short range (disadvantage on the attack). public static bool IsLongRange(Combatant attacker, Combatant defender, AttackOption attack) { if (!attack.IsRanged) return false; int dist = EdgeToEdgeChebyshev(attacker, defender); return dist > attack.RangeShortTiles && dist <= attack.RangeLongTiles; } /// /// One step of greedy movement toward . Returns /// the new position one tile closer in 8-connected (Chebyshev) space. /// Movement budget is ignored — the caller is responsible for charging it. /// public static Vec2 StepToward(Vec2 from, Vec2 goal) { int dx = System.Math.Sign(goal.X - from.X); int dy = System.Math.Sign(goal.Y - from.Y); return new Vec2((int)from.X + dx, (int)from.Y + dy); } }