using Theriapolis.Core.Rules.Combat; using Theriapolis.Core.Util; namespace Theriapolis.Core.Entities.Ai; /// /// Like , but flees when reduced below /// FLEE_HP_FRACTION of max HP — wild animals don't have honor. /// public sealed class WildAnimalBehavior : INpcBehavior { public const float FLEE_HP_FRACTION = 0.25f; public void TakeTurn(Combatant self, AiContext ctx) { if (self.IsDown) return; var target = ctx.FindClosestHostile(self); if (target is null) return; // Flee at low HP — move directly away, don't attack. if (self.CurrentHp <= self.MaxHp * FLEE_HP_FRACTION) { int tiles = ctx.Encounter.CurrentTurn.RemainingMovementFt / 5; for (int i = 0; i < tiles; i++) { var away = StepAwayFrom(self.Position, target.Position); if ((int)away.X == (int)self.Position.X && (int)away.Y == (int)self.Position.Y) break; self.Position = away; ctx.Encounter.AppendLog(CombatLogEntry.Kind.Move, $"{self.Name} flees to ({(int)away.X},{(int)away.Y})."); } ctx.Encounter.CurrentTurn.ConsumeMovement(tiles * 5); return; } // Otherwise, identical to Brigand: close + attack. var attack = self.AttackOptions[0]; int tilesAvail = ctx.Encounter.CurrentTurn.RemainingMovementFt / 5; while (!ReachAndCover.IsInReach(self, target, attack) && tilesAvail > 0) { var next = ReachAndCover.StepToward(self.Position, target.Position); if ((int)next.X == (int)self.Position.X && (int)next.Y == (int)self.Position.Y) break; self.Position = next; ctx.Encounter.AppendLog(CombatLogEntry.Kind.Move, $"{self.Name} moves to ({(int)next.X},{(int)next.Y})."); tilesAvail--; } int consumedTiles = (ctx.Encounter.CurrentTurn.RemainingMovementFt / 5) - tilesAvail; ctx.Encounter.CurrentTurn.ConsumeMovement(consumedTiles * 5); if (!ReachAndCover.IsInReach(self, target, attack)) return; Resolver.AttemptAttack(ctx.Encounter, self, target, attack); ctx.Encounter.CurrentTurn.ConsumeAction(); } private static Vec2 StepAwayFrom(Vec2 from, Vec2 menace) { // Reverse of StepToward — increment by sign of (from - menace) so we // move away from the menace. int dx = System.Math.Sign(from.X - menace.X); int dy = System.Math.Sign(from.Y - menace.Y); if (dx == 0 && dy == 0) dx = 1; // pick a direction if standing on top return new Vec2((int)from.X + dx, (int)from.Y + dy); } }