Newer
Older
Hierarchical-Task-Network-Unity-3D / Assets / Scripts / HTN / Plan.cs
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Plan
{
    // ================== VARIABLES ==================
    public enum PlanStatus
    {
        Running,
        Paused,
        Failed,
        Successful,
        Completed
    }

    // ================== VARIABLES ==================
    private ITask task;
    private ITask currentTask;
    private PlanStatus status;
    private Worldstate state;
    private Coroutine currentCoroutine;

    // ================== PROPERTIES ==================
    /// <summary>
    /// Pauses or resumes plan execution. 
    /// Setting this to true changes the plan's status to <see cref="PlanStatus.Paused"/>;
    /// setting it to false resumes execution (<see cref="PlanStatus.Running"/>).
    /// </summary>
    /// <remarks>
    /// - Use this to temporarily halt plan progress (e.g., for user input or higher-priority interruptions).
    /// - Does not reset subtask progress; the plan will continue from its current state when unpaused.
    /// </remarks>
    public bool Pause
    {
        get => status == PlanStatus.Paused;
        set
        {
            status = value ? PlanStatus.Paused : PlanStatus.Running;
        }
    }

    /// <summary>
    /// Plan Task
    /// </summary>
    public ITask Task => task;

    /// <summary>
    /// Plan Primitive Tasks in Queue
    /// </summary>
    public Queue<ITask> SubTasks { get; private set; } = new();

    /// <summary>
    /// Current Primitive Task in Execution
    /// </summary>
    public ITask CurrentTask => currentTask;

    /// <summary>
    /// Current Plan Status
    /// </summary>
    public PlanStatus Status => status;

    // ================== CONSTRUCTOR ==================
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="task">Primitive Task or CompoundTask</param>
    /// <param name="planner">Planner object</param>
    public Plan(ITask task, HTNPlanner planner)
    {
        if (task == null && state == null)
        {
            Debug.LogError("TASK IS NULL");
            status = PlanStatus.Failed;
            return;
        }

        this.task = task;

        if (task.IsCompound)
        {
            CompoundTask compoundTask = task as CompoundTask;

            if (compoundTask == null) Debug.LogError("The Task is Null");

            List<ITask> subTasks = compoundTask.Decompose(planner);

            if (subTasks.Count > 0) {
                SubTasks = new Queue<ITask>(subTasks);
                currentTask = SubTasks.Dequeue();
                status = PlanStatus.Running;
                return;
            }
            status = PlanStatus.Failed;
            return;

        }

        else
        {
            status = PlanStatus.Running;
            SubTasks.Enqueue(task);
            currentTask = SubTasks.Dequeue();
        }

        Debug.LogError($"Setting the task {task.Name}");
    }

    // ================== PLAN EXECUTION ==================
    /// <summary>
    /// Executes the Plan
    /// </summary>
    /// <param name="planner">Planner object</param>
    public void ExecutePlan(HTNPlanner planner)
    {
        if (status == PlanStatus.Running)
        {
            if (IsPlanValid(planner))
            {
                if (!currentTask.IsTaskFinished(planner))
                {
                    PrimitiveTask task = currentTask as PrimitiveTask;
                    if (task != null)
                    {
                        if (task.HasCoroutineImplementation())
                        {
                            // If we're not already running a coroutine for this task
                            if (currentCoroutine == null)
                            {
                                currentCoroutine = planner.StartCoroutine(task.ExecuteRoutine(planner));
                            }
                        }
                        else
                        {
                            // Regular execution
                            task.Execute(planner);
                        }
                    }
                }
                else
                {
                    // Clean up any running coroutine
                    if (currentCoroutine != null)
                    {
                        planner.StopCoroutine(currentCoroutine);
                        currentCoroutine = null;
                    }

                    if (SubTasks.Count > 0) SwitchTask(SubTasks.Dequeue());
                    else status = PlanStatus.Completed;
                }
            }
            else
            {
                // Clean up any running coroutine
                if (currentCoroutine != null)
                {
                    planner.StopCoroutine(currentCoroutine);
                    currentCoroutine = null;
                }
                // Do replanning
                // 
                //
                //
                //
                //
            }
        }
    }

    // ================== PLAN EXECUTION ==================
    /// <summary>
    /// Verifies if the Plan is still valid
    /// </summary>
    /// <param name="task">Primitive Task/param>
    /// <returns>
    /// True if all task conditions are satisfied in order to keep running; otherwise, false.
    /// </returns>
    private bool IsPlanValid(HTNPlanner planner)
    {
        PrimitiveTask primitiveTask = currentTask as PrimitiveTask;
        if (primitiveTask != null) return primitiveTask.IsTaskValid(planner);

        Debug.Log($"The {task.Name} task is not a primitive task");
        return false;
    }

    /// <summary>
    /// Switches task if available
    /// </summary>
    /// <param name="task">Primitive Task object/param>
    private void SwitchTask(ITask task) => currentTask = task;
}