using System; using System.Collections.Generic; using UnityEngine; namespace MazeGen { /// /// Autonomous agent decision-making system based on a prioritized decision tree. /// /// Each decision encapsulates: /// - A precondition (CanExecute) /// - An action (Execute) /// - Narration (Think) /// /// The decision tree evaluates decisions in priority order and returns the first /// one that can execute. This keeps behavior and narration synchronized and makes /// the agent's decisions easy to understand and extend. /// /// To extend agent behavior, simply create a new AgentDecision subclass and add /// it to BuildDecisionTree(). No need to modify the Update loop or narration logic. /// /// All timing runs off SimulationClock, not Time.deltaTime, so paused simulation /// genuinely freezes every agent. /// public class AutonomousAgentBrain : MonoBehaviour { private MazeAgentController _controller; private MazeGenerator _generator; private CombatDirector _director; private float _decisionInterval; private float _exploreChance; private float _retreatThreshold = 1.5f; private Func _isLevelEnded; private float _timer; private AgentDecisionTree _decisionTree; public void Initialize(MazeAgentController controller, MazeGenerator generator, CombatDirector director, float initialDelay, float decisionInterval, float exploreChance, float retreatThreshold, Func isLevelEnded) { _controller = controller; _generator = generator; _director = director; _decisionInterval = Mathf.Max(0.05f, decisionInterval); _exploreChance = Mathf.Clamp01(exploreChance); _retreatThreshold = Mathf.Max(0, retreatThreshold); _isLevelEnded = isLevelEnded; _timer = Mathf.Max(0f, initialDelay); // Build the decision tree in priority order BuildDecisionTree(); } /// /// Constructs the decision tree with all available decisions in priority order. /// Add new decisions here to extend agent behavior without modifying Update(). /// /// Example of adding a custom decision: /// _decisionTree.Add(new MyCustomDecision(...)); /// private void BuildDecisionTree() { _decisionTree = new AgentDecisionTree(); // Priority order: // 1. Heal if needed (health is more important than combat) _decisionTree.Add(new UseHealthPotionDecision()); // 2. Handle existing fights (join or sneak past) _decisionTree.Add(new HandleExistingFightDecision(_director, _retreatThreshold)); // 3. Declare new fights if appropriate _decisionTree.Add(new DeclareFightDecision(_director, _retreatThreshold)); // 4. Sneak away if threatened _decisionTree.Add(new SneakAwayDecision(_director, _retreatThreshold)); // 5. Catch-all: move to next room (always succeeds) _decisionTree.Add(new MoveOnDecision(_generator, _controller, _exploreChance, _retreatThreshold)); } private void Update() { MazeAgent agent = _controller?.Agent; if (agent == null || agent.IsFinished || agent.IsDead) return; if (_isLevelEnded != null && _isLevelEnded()) return; // Committed to a fight: the CombatDirector owns this agent until it resolves. if (agent.IsEngaged) return; _timer -= SimulationClock.DeltaTime; if (_timer > 0f) return; _timer = _decisionInterval; // Evaluate decision tree: returns true if a decision executed and was narrated _decisionTree?.TryDecide(agent); } } }