using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; /// <summary> /// Base class for primitive tasks in the HTN. These are actionable tasks /// that directly modify the world state. /// </summary> public abstract class PrimitiveTask : ITask { // ================== VARIABLES ================== private static readonly bool isCompound = false; protected bool hasRun = false; // ================== PROPERTIES ================== public string Name { get; private set; } public bool IsCompound => isCompound; /// <summary> /// Conditions to keep task valid if any /// </summary> public virtual List<Condition> Conditions { get; } = new(); /// <summary> /// Conditions to tag quest as finished /// </summary> public virtual List<Condition> Postconditions { get; } = new(); // ================== UTILS ================== /// <summary> /// Set's the task name /// </summary> protected void SetName(string name) => Name = name; // ================== CONDITIONS ================== /// <summary> /// Shared method for adding conditions to any condition list with null checking /// </summary> /// <param name="condition">Condition to add</param> /// <param name="targetList">Target list (Conditions or Postconditions)</param> protected void AddConditionToTarget(Condition condition, List<Condition> targetList) { if (condition == null) { Debug.LogError("Attempted to add null condition"); return; } targetList.Add(condition); } /// <summary> /// Adds a single precondition that must be met for this task to be valid /// </summary> /// <param name="condition">The condition to add</param> /// <returns>The current task instance for method chaining</returns> public PrimitiveTask AddCondition(Condition condition) { AddConditionToTarget(condition, Conditions); return this; } /// <summary> /// Adds multiple preconditions that must be met for this task to be valid /// </summary> /// <param name="conditions">List of conditions to add</param> /// <returns>The current task instance for method chaining</returns> public PrimitiveTask AddCondition(List<Condition> conditions) { conditions?.ForEach(c => AddConditionToTarget(c, Conditions)); return this; } /// <summary> /// Adds a single postcondition that defines when this task is complete /// </summary> /// <param name="condition">The condition to add</param> /// <returns>The current task instance for method chaining</returns> public PrimitiveTask AddPostCondition(Condition condition) { AddConditionToTarget(condition, Postconditions); return this; } /// <summary> /// Adds multiple postconditions that define when this task is complete /// </summary> /// <param name="conditions">List of conditions to add</param> /// <returns>The current task instance for method chaining</returns> public PrimitiveTask AddPostCondition(List<Condition> conditions) { conditions?.ForEach(c => AddConditionToTarget(c, Postconditions)); return this; } // ================== VALIDATORS ================== /// <summary> /// Checks whether the task's preconditions are still valid in the current world state. /// If any condition fails, the task should be aborted or replanned. /// </summary> /// <param name="worldstate">The current state of the world used for condition checks.</param> /// <returns> /// True if all conditions are met; otherwise, false. /// </returns> public bool IsTaskValid(HTNPlanner planner) { if (Conditions.Count != 0) //if there are conditions { foreach (Condition condition in Conditions) { if (!condition.IsConditionMet(planner)) return false; } } return true; } /// <summary> /// Determines whether the task has achieved its goal by verifying all postconditions. /// </summary> /// <param name="worldstate">The world state to check against.</param> /// <returns> /// True if all postconditions are satisfied (task is complete); otherwise, false. /// </returns> public bool IsTaskFinished(HTNPlanner planner) { if (Postconditions.Count > 0) { foreach (Condition condition in Postconditions) { if (!condition.IsConditionMet(planner)) return false; } return true; } return hasRun; } /// <summary> /// Checks if this task has a coroutine implementation /// </summary> public bool HasCoroutineImplementation() { MethodInfo method = GetType().GetMethod("ExecuteRoutine", BindingFlags.Instance | BindingFlags.Public); return method != null && method.DeclaringType != typeof(PrimitiveTask); } // ================== TASK ACTIONS METHODS ================== /// <summary> /// Executes the task's logic and modifies the world state through the provided planner. /// This is where the actual work of the primitive task (e.g., moving, interacting) is performed. /// </summary> /// <param name="planner">The HTN planner managing the task execution. Provides access to the agent's world state and other planning utilities.</param> /// <remarks> /// - This method should only be called if <see cref="IsTaskValid"/> returns true. /// - Implementations should update the world state (via <paramref name="planner"/>) to reflect task completion. /// - For tasks with duration or asynchronous operations, override this method to handle progress tracking. /// </remarks> public virtual void Execute(HTNPlanner planner) { } /// <summary> /// Executes the task's logic over time using a coroutine, allowing for asynchronous behavior such as waiting, movement, or animations. /// </summary> /// <param name="planner">The HTN planner managing the task execution. Provides access to the agent's world state, navigation, and task context.</param> /// <returns> /// An <see cref="IEnumerator"/> to be used in a coroutine, yielding control during time-based operations and resuming as needed. /// </returns> /// <remarks> /// - Use this method for tasks that require multiple frames to complete (e.g., waiting in a queue, interacting with delays). /// - Should be used in combination with Unity's coroutine system (e.g., <c>StartCoroutine</c>). /// - Override this method only if your task has duration or side effects that occur over time. /// </remarks> public virtual IEnumerator ExecuteRoutine(HTNPlanner planner) { yield return null; } public PrimitiveTask Build(string name) { Name = name; return this; } }