b451f83174
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>
38 lines
1.5 KiB
C#
38 lines
1.5 KiB
C#
using Theriapolis.Core.Rules.Combat;
|
|
|
|
namespace Theriapolis.Core.Entities.Ai;
|
|
|
|
/// <summary>
|
|
/// "Move toward target, attack when in reach" — the baseline aggressive
|
|
/// melee behavior. Used by Brigand* and Patrol templates that aren't
|
|
/// scripted to flee.
|
|
/// </summary>
|
|
public sealed class BrigandBehavior : INpcBehavior
|
|
{
|
|
public void TakeTurn(Combatant self, AiContext ctx)
|
|
{
|
|
if (self.IsDown) return;
|
|
var target = ctx.FindClosestHostile(self);
|
|
if (target is null) return;
|
|
var attack = self.AttackOptions[0];
|
|
|
|
// Move budget: 5 ft. = 1 tactical tile per d20 standard.
|
|
int tiles = ctx.Encounter.CurrentTurn.RemainingMovementFt / 5;
|
|
while (!ReachAndCover.IsInReach(self, target, attack) && tiles > 0)
|
|
{
|
|
var next = ReachAndCover.StepToward(self.Position, target.Position);
|
|
if (next.X == self.Position.X && next.Y == self.Position.Y) break;
|
|
self.Position = next;
|
|
ctx.Encounter.AppendLog(CombatLogEntry.Kind.Move,
|
|
$"{self.Name} moves to ({(int)next.X},{(int)next.Y}).");
|
|
tiles--;
|
|
}
|
|
int consumedTiles = (ctx.Encounter.CurrentTurn.RemainingMovementFt / 5) - tiles;
|
|
ctx.Encounter.CurrentTurn.ConsumeMovement(consumedTiles * 5);
|
|
|
|
if (!ReachAndCover.IsInReach(self, target, attack)) return;
|
|
Resolver.AttemptAttack(ctx.Encounter, self, target, attack);
|
|
ctx.Encounter.CurrentTurn.ConsumeAction();
|
|
}
|
|
}
|