83 lines
3.1 KiB
C#
83 lines
3.1 KiB
C#
|
|
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;
|
||
|
|
}
|
||
|
|
}
|