diff --git a/addons/beehave/icons/cooldown.svg b/addons/beehave/icons/cooldown.svg
new file mode 100644
index 00000000..fbdfd6a8
--- /dev/null
+++ b/addons/beehave/icons/cooldown.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/delayer.svg b/addons/beehave/icons/delayer.svg
new file mode 100644
index 00000000..21cb6172
--- /dev/null
+++ b/addons/beehave/icons/delayer.svg
@@ -0,0 +1,39 @@
+
+
diff --git a/addons/beehave/nodes/decorators/cooldown.gd b/addons/beehave/nodes/decorators/cooldown.gd
new file mode 100644
index 00000000..ee084f03
--- /dev/null
+++ b/addons/beehave/nodes/decorators/cooldown.gd
@@ -0,0 +1,51 @@
+@tool
+@icon("../../icons/cooldown.svg")
+extends Decorator
+class_name CooldownDecorator
+
+## The Cooldown Decorator will return 'FAILURE' for a set amount of time
+## after executing its child.
+## The timer resets the next time its child is executed and it is not `RUNNING`
+
+## The wait time in seconds
+@export var wait_time: = 0.0
+
+@onready var cache_key = 'cooldown_%s' % self.get_instance_id()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+ var remaining_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
+ var response
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ if remaining_time > 0:
+ response = FAILURE
+
+ remaining_time -= get_physics_process_delta_time()
+ blackboard.set_value(cache_key, remaining_time, str(actor.get_instance_id()))
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response)
+ else:
+ response = c.tick(actor, blackboard)
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING and c is ActionLeaf:
+ running_child = c
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+
+ if response != RUNNING:
+ blackboard.set_value(cache_key, wait_time, str(actor.get_instance_id()))
+
+ return response
+
+
diff --git a/addons/beehave/nodes/decorators/delayer.gd b/addons/beehave/nodes/decorators/delayer.gd
new file mode 100644
index 00000000..2fdac858
--- /dev/null
+++ b/addons/beehave/nodes/decorators/delayer.gd
@@ -0,0 +1,49 @@
+@tool
+@icon("../../icons/delayer.svg")
+extends Decorator
+class_name DelayDecorator
+
+## The Delay Decorator will return 'RUNNING' for a set amount of time
+## before executing its child.
+## The timer resets when both it and its child are not `RUNNING`
+
+## The wait time in seconds
+@export var wait_time: = 0.0
+
+@onready var cache_key = 'time_limiter_%s' % self.get_instance_id()
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+ var total_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
+ var response
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ if total_time < wait_time:
+ response = RUNNING
+
+ total_time += get_physics_process_delta_time()
+ blackboard.set_value(cache_key, total_time, str(actor.get_instance_id()))
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response)
+ else:
+ response = c.tick(actor, blackboard)
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING and c is ActionLeaf:
+ running_child = c
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+
+ if response != RUNNING:
+ blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id()))
+
+ return response
+
diff --git a/docs/manual/decorators.md b/docs/manual/decorators.md
index 23472d97..eb627701 100644
--- a/docs/manual/decorators.md
+++ b/docs/manual/decorators.md
@@ -29,3 +29,13 @@ The `TimeLimiter` node only gives its `RUNNING` child a set amount of time to fi
This note is useful when you want to limit the execution time of a long running action. Once a time limiter reaches its time limit, it will start interrupting its child on every tick.
**Example:** A mob aggros and tries to chase you, the chase action will last a maximum of 10 seconds before being aborted if not complete.
+
+## Delayer
+When first executing the `Delayer` node, it will start an internal timer and return `RUNNING` until the timer is complete, after which it will execute its child node. The delayer resets its time after its child returns either `SUCCESS` or `FAILURE`.
+
+**Example:** You stun a boss mob and it waits a certain amount of time before resuming its attack patterns.
+
+## Cooldown
+The `Cooldown` node executes its child until it either returns `SUCCESS` or `FAILURE`, after which it will start an internal timer and return `FAILURE` until the timer is complete. The cooldown is then able to execute its child again.
+
+**Example:** A mob attacks you and has to wait before it can attack you again.
diff --git a/test/nodes/decorators/cooldown_test.gd b/test/nodes/decorators/cooldown_test.gd
new file mode 100644
index 00000000..07d64c5c
--- /dev/null
+++ b/test/nodes/decorators/cooldown_test.gd
@@ -0,0 +1,42 @@
+# GdUnit generated TestSuite
+class_name CooldownDecoratorTest
+extends GdUnitTestSuite
+@warning_ignore('unused_parameter')
+@warning_ignore('return_value_discarded')
+
+# TestSuite generated from
+const __source = 'res://addons/beehave/nodes/decorators/cooldown.gd'
+const __action = "res://test/actions/count_up_action.gd"
+const __tree = "res://addons/beehave/nodes/beehave_tree.gd"
+const __blackboard = "res://addons/beehave/blackboard.gd"
+
+var tree: BeehaveTree
+var action: ActionLeaf
+var cooldown: CooldownDecorator
+var runner:GdUnitSceneRunner
+
+func before_test() -> void:
+ tree = auto_free(load(__tree).new())
+ action = auto_free(load(__action).new())
+ cooldown = auto_free(load(__source).new())
+
+ var actor = auto_free(Node2D.new())
+ var blackboard = auto_free(load(__blackboard).new())
+
+ tree.add_child(cooldown)
+ cooldown.add_child(action)
+
+ tree.actor = actor
+ tree.blackboard = blackboard
+ runner = scene_runner(tree)
+
+func test_running_then_fail() -> void:
+ cooldown.wait_time = 1.0
+ action.status = BeehaveNode.RUNNING
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
+ action.status = BeehaveNode.SUCCESS
+ assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS)
+ action.status = BeehaveNode.RUNNING
+ assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE)
+ await runner.simulate_frames(1, 2000)
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
diff --git a/test/nodes/decorators/delayer_test.gd b/test/nodes/decorators/delayer_test.gd
new file mode 100644
index 00000000..e7bcaede
--- /dev/null
+++ b/test/nodes/decorators/delayer_test.gd
@@ -0,0 +1,56 @@
+# GdUnit generated TestSuite
+class_name DelayDecoratorTest
+extends GdUnitTestSuite
+@warning_ignore('unused_parameter')
+@warning_ignore('return_value_discarded')
+
+# TestSuite generated from
+const __source = 'res://addons/beehave/nodes/decorators/delayer.gd'
+const __action = "res://test/actions/count_up_action.gd"
+const __tree = "res://addons/beehave/nodes/beehave_tree.gd"
+const __blackboard = "res://addons/beehave/blackboard.gd"
+
+var tree: BeehaveTree
+var action: ActionLeaf
+var delayer: DelayDecorator
+var runner:GdUnitSceneRunner
+
+func before_test() -> void:
+ tree = auto_free(load(__tree).new())
+ action = auto_free(load(__action).new())
+ delayer = auto_free(load(__source).new())
+
+ var actor = auto_free(Node2D.new())
+ var blackboard = auto_free(load(__blackboard).new())
+
+ tree.add_child(delayer)
+ delayer.add_child(action)
+
+ tree.actor = actor
+ tree.blackboard = blackboard
+ runner = scene_runner(tree)
+
+func test_return_success_after_delay() -> void:
+ delayer.wait_time = get_physics_process_delta_time()
+ action.status = BeehaveNode.SUCCESS
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
+ assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS)
+ # Assure that the delayer properly resets
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
+ assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS)
+
+func test_return_running_after_delay() -> void:
+ delayer.wait_time = 1.0
+ action.status = BeehaveNode.RUNNING
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
+ await runner.simulate_frames(1, 1000)
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
+ action.status = BeehaveNode.SUCCESS
+ assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS)
+ # Assure that the delayer properly resets
+ action.status = BeehaveNode.RUNNING
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
+ await runner.simulate_frames(1, 1000)
+ assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)
+ action.status = BeehaveNode.SUCCESS
+ assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS)