diff --git a/addons/beehave/icons/until_fail.svg b/addons/beehave/icons/until_fail.svg new file mode 100644 index 00000000..c64a0a06 --- /dev/null +++ b/addons/beehave/icons/until_fail.svg @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/addons/beehave/nodes/decorators/until_fail.gd b/addons/beehave/nodes/decorators/until_fail.gd new file mode 100644 index 00000000..d8a97689 --- /dev/null +++ b/addons/beehave/nodes/decorators/until_fail.gd @@ -0,0 +1,33 @@ +@tool +@icon("../../icons/until_fail.svg") +class_name UntilFailDecorator +extends Decorator + +## The UntilFail Decorator will return `RUNNING` if its child returns +## `SUCCESS` or `RUNNING` or it will return `SUCCESS` if its child returns +## `FAILURE` + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var 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: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + if response == SUCCESS: + return RUNNING + + return SUCCESS + diff --git a/addons/gdUnit4/GdUnitRunner.cfg b/addons/gdUnit4/GdUnitRunner.cfg index 8cf8cfc0..59dd7687 100644 --- a/addons/gdUnit4/GdUnitRunner.cfg +++ b/addons/gdUnit4/GdUnitRunner.cfg @@ -1 +1 @@ -{"included":{"res://test/blackboard/blackboard_test.gd":["test_blackboard_property_shared_between_trees"]},"server_port":31002,"skipped":{},"version":"1.0"} \ No newline at end of file +{"included":{"res://test/":[]},"server_port":31002,"skipped":{},"version":"1.0"} \ No newline at end of file diff --git a/docs/manual/decorators.md b/docs/manual/decorators.md index eb627701..522c5cfd 100644 --- a/docs/manual/decorators.md +++ b/docs/manual/decorators.md @@ -39,3 +39,9 @@ When first executing the `Delayer` node, it will start an internal timer and ret 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. + +## UntilFail + +The `UntilFail` node executes its child and returns `RUNNING` as long as it returns either `RUNNING` or `SUCCESS`. If its child returns `FAILURE`, it will instead return `SUCCESS`. + +**Example:** A turret fires upon any NPC in range until it does not detect any more NPCs. diff --git a/test/nodes/decorators/until_fail_test.gd b/test/nodes/decorators/until_fail_test.gd new file mode 100644 index 00000000..74e9b637 --- /dev/null +++ b/test/nodes/decorators/until_fail_test.gd @@ -0,0 +1,44 @@ +# GdUnit generated TestSuite +class_name UntilFailDecoratorTest +extends GdUnitTestSuite +@warning_ignore('unused_parameter') +@warning_ignore('return_value_discarded') + +# TestSuite generated from +const __source = 'res://addons/beehave/nodes/decorators/until_fail.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 until_fail: UntilFailDecorator + + +func before_test() -> void: + tree = auto_free(load(__tree).new()) + action = auto_free(load(__action).new()) + until_fail = auto_free(load(__source).new()) + + var actor = auto_free(Node2D.new()) + var blackboard = auto_free(load(__blackboard).new()) + + tree.add_child(until_fail) + until_fail.add_child(action) + + tree.actor = actor + tree.blackboard = blackboard + +func test_failure() -> void: + action.status = BeehaveNode.RUNNING + + for i in range(100): + assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + + action.status = BeehaveNode.SUCCESS + + for i in range(100): + assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + + action.status = BeehaveNode.FAILURE + assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS)