SneakAwayDecision.cs 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. namespace MazeGen
  2. {
  3. /// <summary>
  4. /// When the threat is too high to fight, tries to sneak out of the room.
  5. /// If the sneak fails, declares a forced fight instead.
  6. /// </summary>
  7. public class SneakAwayDecision : AgentDecision
  8. {
  9. private CombatDirector _director;
  10. private float _retreatThreshold;
  11. public override string DecisionName => "Sneak Away";
  12. public SneakAwayDecision(CombatDirector director, float retreatThreshold)
  13. {
  14. _director = director;
  15. _retreatThreshold = retreatThreshold;
  16. }
  17. public override bool CanExecute(MazeAgent agent)
  18. {
  19. if (agent == null || _director == null) return false;
  20. if (agent.IsEngaged) return false;
  21. Room room = agent.CurrentRoom;
  22. if (room == null || room.Encounter == null || !room.Encounter.HasLivingMonsters)
  23. return false;
  24. // Already failed here before
  25. if (agent.HasFledFrom(room.Id))
  26. return false;
  27. // Would we reject this threat level?
  28. float threat = room.Encounter.GetPerceivedChallengeRating(agent.CharacterSheet);
  29. float effectiveThreshold = GetEffectiveReteatThreshold(agent);
  30. return threat > effectiveThreshold;
  31. }
  32. public override bool Execute(MazeAgent agent)
  33. {
  34. // Try to sneak
  35. if (_director.TrySneakOut(agent, out float odds))
  36. {
  37. // Successfully sneaked away
  38. return false; // Return false so MoveOn can proceed this same tick
  39. }
  40. else
  41. {
  42. // Sneak failed - now forced to fight
  43. Room room = agent.CurrentRoom;
  44. int hostiles = room.Encounter.AliveMonsterCount;
  45. _director.DeclareFight(room, agent, $"was caught trying to sneak out past {hostiles} hostile(s)");
  46. return true; // Consumed the turn
  47. }
  48. }
  49. public override void Think(MazeAgent agent)
  50. {
  51. Room room = agent.CurrentRoom;
  52. float threat = room.Encounter.GetPerceivedChallengeRating(agent.CharacterSheet);
  53. float effectiveThreshold = GetEffectiveReteatThreshold(agent);
  54. int hostiles = room.Encounter.AliveMonsterCount;
  55. agent.Thoughts.Add(ThoughtKind.Avoid,
  56. $"Peeked: {hostiles} hostile(s) at threat {threat:0.00}, over my limit {effectiveThreshold:0.00} - trying to slip out instead of fighting.");
  57. if (_director.TrySneakOut(agent, out float odds))
  58. agent.Thoughts.Add(ThoughtKind.Sneak, $"Slipped away unnoticed ({odds:P0} odds).");
  59. else
  60. agent.Thoughts.Add(ThoughtKind.Sneak, $"Botched the sneak ({odds:P0} odds) - they've seen me, no choice but to fight.");
  61. }
  62. private float GetEffectiveReteatThreshold(MazeAgent agent)
  63. {
  64. CharacterSheet sheet = agent.CharacterSheet;
  65. float maxHealth = sheet.Stats.MaxHealth;
  66. if (maxHealth <= 0) return _retreatThreshold;
  67. float healthPercent = sheet.Stats.CurrentHealth / maxHealth;
  68. if (healthPercent < 0.3f)
  69. return _retreatThreshold * 0.5f;
  70. else if (healthPercent < 0.5f)
  71. {
  72. float t = (healthPercent - 0.3f) / 0.2f;
  73. return UnityEngine.Mathf.Lerp(_retreatThreshold * 0.5f, _retreatThreshold, t);
  74. }
  75. return _retreatThreshold;
  76. }
  77. }
  78. }