Files
2026-07-09 21:33:33 +02:00

180 lines
6.0 KiB
C#

using System;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Objects.Unit.Actions.Exceptions;
namespace VOID.Generic.Objects.Unit.Actions
{
public enum ActionState
{
Ready,
Running,
Finished,
Cancelled
}
/// <summary>
/// Action for <see cref="UnitObject"/>.<br/>
///
/// Executing order when <see cref="DoIt"/> used:<br/>
/// <ul>
/// <li><see cref="CanDoIt"/></li>
/// <li><see cref="OnDo"/></li>
/// <li><see cref="OnDoIt"/></li>
/// <li>Inside OnDoIt: <see cref="OnBeforePerform"/></li>
/// <li>Inside OnDoIt: Action's logic here</li>
/// <li>Inside OnDoIt: <see cref="OnAfterPerform"/> - obligatory if action ends immediately</li>
/// </ul>
///
/// Executing order when <see cref="EndIt"/> used:<br/>
/// <ul>
/// <li><see cref="OnEndIt"/></li>
/// <li>Inside OnEndIt: <see cref="OnAfterPerform"/> - obligatory if not used in OnDoIt</li>
/// <li><see cref="OnEnd"/></li>
/// </ul>
///
/// Executing order when <see cref="CancelIt"/> used:<br/>
/// <ul>
/// <li><see cref="OnCancelIt"/></li>
/// <li>Inside OnCancelIt: <see cref="OnAfterPerform"/> - obligatory if not used in OnDoIt</li>
/// <li><see cref="OnCancel"/></li>
/// </ul>
/// </summary>
[Serializable]
public abstract class AbstractAction
{
/// <summary>Run after checking and before executing action.</summary>
public event Action OnDo;
/// <summary>Defined by referenced action. Usually right *before* main point of that action.</summary>
public event Action OnBeforePerform;
/// <summary>Defined by referenced action. Usually right *after* main point of that action.</summary>
public event Action OnAfterPerform;
/// <summary>Run after action ended.</summary>
public event Action OnEnd;
/// <summary>Run after action ended forcefully.</summary>
public event Action OnCancel;
/// <summary>Current state of action.</summary>
public ActionState state { get; protected set; } = ActionState.Ready;
/// <summary>If given, current action will be executed only if given one is finished successfully</summary>
public AbstractAction runAfterAction;
/// <summary>Simple check if unit is in range to execute this action.</summary>
public abstract bool IsInRange();
/// <summary>Determines whether this action CAN be executed INSTANTLY outside the queue.</summary>
public virtual bool CanSkipQueue() => false;
/// <summary>Determines whether this action CAN be stopped early, by something else besides itself.</summary>
public virtual bool CanBeStopped() => true;
/// <summary>Action's checks if action can be executed. <see cref="Check"/> have to be used here.</summary>
public abstract void CanDoIt();
/// <summary>Action's logic.</summary>
protected abstract void OnDoIt();
/// <summary>Action's logic when whole action ends successfully.</summary>
protected abstract void OnEndIt();
/// <summary>Action's logic when whole action is forced to end.</summary>
protected abstract void OnCancelIt();
public bool CanDoItBoolean()
{
try
{
CanDoIt();
return true;
}
catch (UnitActionErrorException)
{
return false;
}
}
/// <summary>
/// 1. Checking if action can be done by function in child: <see cref="CanDoIt"/><br/>
/// 2. Executing main login of this action
/// </summary>
public void DoIt()
{
Check(
runAfterAction == null || runAfterAction.state == ActionState.Finished,
UnitActionErrorType.PreviousActionNotFinished);
CanDoIt();
state = ActionState.Running;
OnDo?.Invoke();
OnDoIt();
}
/// <summary>
/// Executed by action itself when everything is done correctly.
/// </summary>
protected void EndIt()
{
if (state is ActionState.Finished or ActionState.Cancelled)
{
Debug.LogWarning($"{GetType().GetNiceName()} can't be ended. State: '{state}'");
return;
}
OnEndIt();
state = ActionState.Finished;
OnEnd?.Invoke();
}
/// <summary>
/// Executed by external source. Stopping this action forcefully.
/// </summary>
public void CancelIt()
{
// Cancel events only when action really started otherwise there will be nothing to cancel
if (state == ActionState.Running)
{
OnCancelIt();
OnCancel?.Invoke();
}
state = ActionState.Cancelled;
}
/// <summary>
/// Should be executed from child right before main logic is started, usually in <see cref="OnDoIt"/>
/// </summary>
protected void BeforePerform()
{
OnBeforePerform?.Invoke();
}
/// <summary>
/// Should be executed from child right after main logic is done, usually in <see cref="OnDoIt"/>
/// </summary>
protected void AfterPerform()
{
OnAfterPerform?.Invoke();
}
/// <summary>
/// Execute <see cref="Throw"/> if check is false
/// </summary>
protected void Check(bool condition, UnitActionErrorType elseError)
{
if (!condition) Throw(elseError);
}
/// <summary>
/// Cancels this Action and then throw <see cref="UnitActionErrorException"/>
/// </summary>
protected void Throw(UnitActionErrorType errorType)
{
CancelIt();
throw new UnitActionErrorException(this, errorType);
}
}
}