BehaviorTree/Decorator/Cooldown.cs
using System;
using Sandbox;
using Sandbox.Diagnostics;
namespace NPBehave
{

    public class Cooldown : Decorator
    {
        private bool _startAfterDecoratee = false;
        private bool _resetOnFailiure = false;
	    private bool _failOnCooldown = false;
        private float _cooldownTime = 0.0f;
        private float _randomVariation = 0.05f;
        private bool _isReady = true;

        /// <summary>
        /// The Cooldown decorator ensures that the branch can not be started twice within the given cooldown time.
        /// 
        /// The decorator can start the cooldown timer right away or wait until the child stopps, you can control this behavior with the
        /// `startAfterDecoratee` parameter.
        /// 
        /// The default behavior in case the cooldown timer is active and this node is started again is, that the decorator waits until
        /// the cooldown is reached and then executes the underlying node.
        /// You can change this behavior with the `failOnCooldown` parameter to make the decorator immediately fail instead.
        /// 
        /// </summary>
        /// <param name="cooldownTime">time until next execution</param>
        /// <param name="randomVariation">random variation added to the cooldown time</param>
        /// <param name="startAfterDecoratee">If set to <c>true</c> the cooldown timer is started from the point after the decoratee has been started, else it will be started right away.</param>
        /// <param name="resetOnFailiure">If set to <c>true</c> the timer will be reset in case the underlying node fails.</param>
        /// <param name="failOnCooldown">If currently on cooldown and this parameter is set to <c>true</c>, the decorator will immmediately fail instead of waiting for the cooldown.</param>
        /// <param name="decoratee">Decoratee node.</param>
        public Cooldown(float cooldownTime, float randomVariation, bool startAfterDecoratee, bool resetOnFailiure, bool failOnCooldown, Node decoratee) : base("TimeCooldown", decoratee)
        {
        	_startAfterDecoratee = startAfterDecoratee;
        	_cooldownTime = cooldownTime;
        	_resetOnFailiure = resetOnFailiure;
        	_randomVariation = randomVariation;
        	_failOnCooldown = failOnCooldown;
        	Assert.True(cooldownTime > 0f, "cooldownTime has to be set");
        }

        public Cooldown(float cooldownTime, bool startAfterDecoratee, bool resetOnFailiure, bool failOnCooldown, Node decoratee) : base("TimeCooldown", decoratee)
        {
        	_startAfterDecoratee = startAfterDecoratee;
        	_cooldownTime = cooldownTime;
        	_randomVariation = cooldownTime * 0.1f; 
        	_resetOnFailiure = resetOnFailiure;
        	_failOnCooldown = failOnCooldown;
        	Assert.True(cooldownTime > 0f, "cooldownTime has to be set");
        }

        public Cooldown(float cooldownTime, float randomVariation, bool startAfterDecoratee, bool resetOnFailiure, Node decoratee) : base("TimeCooldown", decoratee)
        {
        	_startAfterDecoratee = startAfterDecoratee;
        	_cooldownTime = cooldownTime;
        	_resetOnFailiure = resetOnFailiure;
        	_randomVariation = randomVariation;
        	Assert.True(cooldownTime > 0f, "cooldownTime has to be set");
        }

        public Cooldown(float cooldownTime, bool startAfterDecoratee, bool resetOnFailiure, Node decoratee) : base("TimeCooldown", decoratee)
        {
            _startAfterDecoratee = startAfterDecoratee;
            _cooldownTime = cooldownTime;
            _randomVariation = cooldownTime * 0.1f;
            _resetOnFailiure = resetOnFailiure;
            Assert.True(cooldownTime > 0f, "cooldownTime has to be set");
        }

        public Cooldown(float cooldownTime, float randomVariation, Node decoratee) : base("TimeCooldown", decoratee)
        {
            _startAfterDecoratee = false;
            _cooldownTime = cooldownTime;
            _resetOnFailiure = false;
            _randomVariation = randomVariation;
        	Assert.True(cooldownTime > 0f, "cooldownTime has to be set");
        }

        public Cooldown(float cooldownTime, Node decoratee) : base("TimeCooldown", decoratee)
        {
            _startAfterDecoratee = false;
            _cooldownTime = cooldownTime;
            _resetOnFailiure = false;
            _randomVariation = cooldownTime * 0.1f;
        	Assert.True(cooldownTime > 0f, "cooldownTime has to be set");
        }

        protected override void DoStart()
        {
            if (_isReady)
            {
                _isReady = false;
                if (!_startAfterDecoratee)
                {
                    Clock.AddTimer(_cooldownTime, _randomVariation, 0, TimeoutReached);
                    _started = true;
                    _timeSinceStart = 0;
                }
                Decoratee.Start();
            }
            else
            {
                if (_failOnCooldown)
                {
                    Stopped(false);
                }
            }
        }

        protected override void DoStop()
        {
            if (Decoratee.IsActive)
            {
                _isReady = true;
                Clock.RemoveTimer(TimeoutReached);
                Decoratee.Stop();
            }
            else
            {
                _isReady = true;
                Clock.RemoveTimer(TimeoutReached);
                Stopped(false);
            }
        }

        protected override void DoChildStopped(Node child, bool result)
        {
            if (_resetOnFailiure && !result)
            {
                _isReady = true;
                Clock.RemoveTimer(TimeoutReached);
            }
            else if (_startAfterDecoratee)
            {
                Clock.AddTimer(_cooldownTime, _randomVariation, 0, TimeoutReached);
                _started = true;
                _timeSinceStart = 0;
            }
            Stopped(result);
        }

        private void TimeoutReached()
        {
            if (IsActive && !Decoratee.IsActive)
            {
                Clock.AddTimer(_cooldownTime, _randomVariation, 0, TimeoutReached);
                Decoratee.Start();
            }
            else
            {
                _isReady = true;
                _started = false;
            }
        }

#if DEBUG

	    private TimeSince _timeSinceStart = 0;
	    private bool _started = false;
	    public override string DebugIcon => "timer";
	    public override string ComputedLabel
	    {
		    get
		    {
			    return _started ? $"{_cooldownTime - _timeSinceStart:F1}s (Total: {_cooldownTime:F1})" : $"{_cooldownTime:F1}s";
		    }
	    }
#endif
    }
}