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>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
using Theriapolis.Core.Entities;
|
||||
using Theriapolis.Core.Util;
|
||||
|
||||
namespace Theriapolis.Core.Rules.Combat;
|
||||
|
||||
/// <summary>
|
||||
/// Per-tick check used by <see cref="Game.Screens.PlayScreen"/>:
|
||||
/// "is there a hostile NPC within encounter trigger range that has line of
|
||||
/// sight?" Returns the closest qualifying actor (or null) so the caller can
|
||||
/// kick off an encounter.
|
||||
///
|
||||
/// Friendly / Neutral proximity is the same shape but uses a tighter radius
|
||||
/// — see <see cref="FindInteractCandidate"/>.
|
||||
/// </summary>
|
||||
public static class EncounterTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// Find the closest live <em>hostile</em> NPC within
|
||||
/// <see cref="C.ENCOUNTER_TRIGGER_TILES"/> of the player that the
|
||||
/// <paramref name="losBlocked"/> predicate can see (no blocking tile
|
||||
/// between). Returns null if none found.
|
||||
/// </summary>
|
||||
public static NpcActor? FindHostileTrigger(
|
||||
ActorManager actors,
|
||||
System.Func<int, int, bool>? losBlocked = null)
|
||||
{
|
||||
var player = actors.Player;
|
||||
if (player is null) return null;
|
||||
|
||||
var blocked = losBlocked ?? LineOfSight.AlwaysClear;
|
||||
NpcActor? best = null;
|
||||
int bestDistSq = C.ENCOUNTER_TRIGGER_TILES * C.ENCOUNTER_TRIGGER_TILES;
|
||||
|
||||
foreach (var npc in actors.Npcs)
|
||||
{
|
||||
if (!npc.IsAlive) continue;
|
||||
if (npc.Allegiance != Rules.Character.Allegiance.Hostile) continue;
|
||||
int distSq = ChebyshevDistSq(player.Position, npc.Position);
|
||||
if (distSq > bestDistSq) continue;
|
||||
if (!LineOfSight.HasLine(player.Position, npc.Position, blocked)) continue;
|
||||
if (best is null || distSq < bestDistSq) { best = npc; bestDistSq = distSq; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Friendly / Neutral NPCs within
|
||||
/// <see cref="C.INTERACT_PROMPT_TILES"/> of the player. The HUD shows
|
||||
/// "[F] Talk to ..." for the closest match.
|
||||
/// </summary>
|
||||
public static NpcActor? FindInteractCandidate(
|
||||
ActorManager actors,
|
||||
System.Func<int, int, bool>? losBlocked = null)
|
||||
{
|
||||
var player = actors.Player;
|
||||
if (player is null) return null;
|
||||
|
||||
var blocked = losBlocked ?? LineOfSight.AlwaysClear;
|
||||
NpcActor? best = null;
|
||||
int bestDistSq = C.INTERACT_PROMPT_TILES * C.INTERACT_PROMPT_TILES;
|
||||
|
||||
foreach (var npc in actors.Npcs)
|
||||
{
|
||||
if (!npc.IsAlive) continue;
|
||||
if (npc.Allegiance != Rules.Character.Allegiance.Friendly &&
|
||||
npc.Allegiance != Rules.Character.Allegiance.Neutral) continue;
|
||||
int distSq = ChebyshevDistSq(player.Position, npc.Position);
|
||||
if (distSq > bestDistSq) continue;
|
||||
if (!LineOfSight.HasLine(player.Position, npc.Position, blocked)) continue;
|
||||
if (best is null || distSq < bestDistSq) { best = npc; bestDistSq = distSq; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private static int ChebyshevDistSq(Vec2 a, Vec2 b)
|
||||
{
|
||||
int dx = (int)System.Math.Abs(a.X - b.X);
|
||||
int dy = (int)System.Math.Abs(a.Y - b.Y);
|
||||
int d = System.Math.Max(dx, dy);
|
||||
return d * d;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user