Add addons

This commit is contained in:
Joey Yakimowich-Payne 2023-01-03 16:53:53 -07:00
commit 3367bad24f
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
527 changed files with 36197 additions and 0 deletions

View file

@ -0,0 +1,14 @@
## A node in the behaviour tree. Every node must return `SUCCESS`, `FAILURE` or
## `RUNNING` when ticked.
@tool
class_name BeehaveNode extends BeehaveTree
## Executes this node and returns a status code.
## This method must be overwritten.
func tick(actor: Node, blackboard: Blackboard) -> int:
return SUCCESS
## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS.
func interrupt(actor: Node, blackboard: Blackboard) -> void:
pass

View file

@ -0,0 +1,93 @@
## Controls the flow of execution of the entire behaviour tree.
@tool
class_name BeehaveRoot extends BeehaveTree
@icon("../icons/tree.svg")
@export var enabled: bool = true:
set(value):
enabled = value
set_physics_process(enabled)
get:
return enabled
@export_node_path var actor_node_path : NodePath
var actor : Node
@onready var blackboard: Blackboard = Blackboard.new()
func _ready() -> void:
if Engine.is_editor_hint():
return
if self.get_child_count() != 1:
push_error("Beehave error: Root %s should have one child (NodePath: %s)" % [self.name, self.get_path()])
disable()
return
actor = get_parent()
if actor_node_path:
actor = get_node(actor_node_path)
set_physics_process(enabled)
func _physics_process(delta: float) -> void:
if Engine.is_editor_hint():
return
blackboard.set_value("delta", delta)
var status = self.get_child(0).tick(actor, blackboard)
# Updates the current running action.
var running_action = get_running_action() if status == RUNNING else null
blackboard.set_value("running_action", running_action)
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = super._get_configuration_warnings()
if get_child_count() != 1:
warnings.append("BeehaveRoot should have exactly one child node.")
return warnings
func get_running_action() -> ActionLeaf:
var node = get_child(0)
while node != null:
if node is Composite:
node = (node as Composite).running_child
elif node is ActionLeaf:
return node
push_error("Beehave error: Could not find running action in tree root '%s'." % name)
return null
func get_last_condition() -> Variant:
if blackboard.has_value("last_condition"):
return blackboard.get_value("last_condition")
return null
func get_last_condition_status() -> String:
if blackboard.has_value("last_condition_status"):
var status = blackboard.get_value("last_condition_status")
if status == SUCCESS:
return "SUCCESS"
elif status == FAILURE:
return "FAILURE"
else:
return "RUNNING"
return ""
func enable() -> void:
enabled = true
func disable() -> void:
enabled = false

View file

@ -0,0 +1,20 @@
## Base class for all parts of the behaviour tree.
@tool
class_name BeehaveTree extends Node
@icon("../icons/category_bt.svg")
enum {
SUCCESS,
FAILURE,
RUNNING
}
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = []
if get_children().any(func(x): return not (x is BeehaveNode)):
warnings.append("All children of this node should inherit from BeehaveNode class.")
return warnings

View file

@ -0,0 +1,30 @@
## A Composite node controls the flow of execution of its children in a specific manner.
@tool
class_name Composite extends BeehaveNode
@icon("../../icons/category_composite.svg")
var running_child: BeehaveNode = null
func _ready():
if Engine.is_editor_hint():
return
if self.get_child_count() < 1:
push_error("BehaviorTree Error: Composite %s should have at least one child (NodePath: %s)" % [self.name, self.get_path()])
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = super._get_configuration_warnings()
if get_children().filter(func(x): return x is BeehaveNode).size() < 2:
warnings.append("Any composite node should have at least two children. Otherwise it is not useful.")
return warnings
func interrupt(actor: Node, blackboard: Blackboard) -> void:
if running_child != null:
running_child.interrupt(actor, blackboard)
running_child = null

View file

@ -0,0 +1,25 @@
## Selector nodes will attempt to execute each of its children until one of
## them return `SUCCESS`. If all children return `FAILURE`, this node will also
## return `FAILURE`. This node will attempt to process all its children every
## single tick, even if one of them is currently `RUNNING` already.
@tool
class_name SelectorComposite extends Composite
@icon("../../icons/selector.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != FAILURE:
if response == SUCCESS:
# Interrupt any child that was RUNNING before.
interrupt(actor, blackboard)
else: # RUNNING
running_child = c
return response
return FAILURE

View file

@ -0,0 +1,54 @@
## This node will attempt to execute all of its children just like a
## [code]SelectorStar[/code] would, with the exception that the children
## will be executed in a random order.
@tool
class_name SelectorRandomComposite extends Composite
@icon("../../icons/selector_random.svg")
## A shuffled list of the children that will be executed in reverse order.
var _children_bag: Array[Node] = []
var c: Node
func tick(actor: Node, blackboard: Blackboard) -> int:
if _children_bag.is_empty():
_reset()
# We need to traverse the array in reverse since we will be manipulating it.
for i in _get_reversed_indexes():
c = _children_bag[i]
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response == RUNNING:
running_child = c
else:
_children_bag.erase(c)
if response != FAILURE:
if response == SUCCESS:
_reset()
return response
return FAILURE
func interrupt(actor: Node, blackboard: Blackboard) -> void:
_reset()
super(actor, blackboard)
func _get_reversed_indexes() -> Array[int]:
var reversed = range(_children_bag.size())
reversed.reverse()
return reversed
## Generates a new shuffled list of the children.
func _reset() -> void:
_children_bag = get_children().duplicate()
_children_bag.shuffle()

View file

@ -0,0 +1,32 @@
## Selector Star nodes will attempt to execute each of its children until one of
## them return `SUCCESS`. If all children return `FAILURE`, this node will also
## return `FAILURE`. This node will skip all previous child nodes that were
## executed prior, in case one of the children is currently in `RUNNING` state.
@tool
class_name SelectorStarComposite extends Composite
@icon("../../icons/selector_reactive.svg")
var last_execution_index = 0
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
if c.get_index() < last_execution_index:
continue
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != FAILURE:
if response == SUCCESS:
last_execution_index = 0
else: # RUNNING
running_child = c
return response
else:
last_execution_index += 1
last_execution_index = 0
return FAILURE

View file

@ -0,0 +1,26 @@
## Sequence nodes will attempt to execute all of its children and report
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
## If at least one child reports a `FAILURE` status code, this node will also
## return `FAILURE`. This node will attempt to process all its children every
## single tick, even if one of them is currently `RUNNING` already.
@tool
class_name SequenceComposite extends Composite
@icon("../../icons/sequence.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != SUCCESS:
if response == FAILURE:
# Interrupt any child that was RUNNING before.
interrupt(actor, blackboard)
else: # RUNNING
running_child = c
return response
return SUCCESS

View file

@ -0,0 +1,60 @@
## This node will attempt to execute all of its children just like a
## [code]SequenceStar[/code] would, with the exception that the children
## will be executed in a random order.
@tool
class_name SequenceRandomComposite extends Composite
@icon("../../icons/sequence_random.svg")
## Whether the sequence should start where it left off after a previous failure.
@export var resume_on_failure: bool = false
## Whether the sequence should start where it left off after a previous interruption.
@export var resume_on_interrupt: bool = false
## A shuffled list of the children that will be executed in reverse order.
var _children_bag: Array[Node] = []
var c: Node
func tick(actor: Node, blackboard: Blackboard) -> int:
if _children_bag.is_empty():
_reset()
# We need to traverse the array in reverse since we will be manipulating it.
for i in _get_reversed_indexes():
c = _children_bag[i]
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response == RUNNING:
running_child = c
else:
_children_bag.erase(c)
if response != SUCCESS:
if not resume_on_failure and response == FAILURE:
_reset()
return response
return SUCCESS
func interrupt(actor: Node, blackboard: Blackboard) -> void:
if not resume_on_interrupt:
_reset()
super(actor, blackboard)
func _get_reversed_indexes() -> Array[int]:
var reversed = range(_children_bag.size())
reversed.reverse()
return reversed
## Generates a new shuffled list of the children.
func _reset() -> void:
_children_bag = get_children().duplicate()
_children_bag.shuffle()

View file

@ -0,0 +1,37 @@
## Sequence Star nodes will attempt to execute all of its children and report
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
## If at least one child reports a `FAILURE` status code, this node will also
## return `FAILURE`. This node will skip all previous child nodes that succeeded
## prior, in case one of the children is currently in `RUNNING` state
@tool
class_name SequenceStarComposite extends Composite
@icon("../../icons/sequence_reactive.svg")
var successful_index = 0
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
if c.get_index() < successful_index:
continue
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != SUCCESS:
if response == FAILURE:
successful_index = 0
else: # RUNNING
running_child = c
return response
else:
successful_index += 1
if successful_index == get_child_count():
successful_index = 0
return SUCCESS
else:
successful_index = 0
return FAILURE

View file

@ -0,0 +1,22 @@
## Decorator nodes are used to transform the result received by its child.
## Must only have one child.
@tool
class_name Decorator extends BeehaveNode
@icon("../../icons/category_decorator.svg")
func _ready():
if Engine.is_editor_hint():
return
if self.get_child_count() != 1:
push_error("Beehave Error: Decorator %s should have only one child (NodePath: %s)" % [self.name, self.get_path()])
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = super._get_configuration_warnings()
if get_child_count() != 1:
warnings.append("Decorator should have exactly one child node.")
return warnings

View file

@ -0,0 +1,12 @@
## A Failer node will always return a `FAILURE` status code.
@tool
class_name AlwaysFailDecorator extends Decorator
@icon("../../icons/failer.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if response == RUNNING:
return RUNNING
return FAILURE

View file

@ -0,0 +1,21 @@
## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status
## code or `SUCCESS` in case its child returns a `FAILURE` status code.
@tool
class_name InverterDecorator extends Decorator
@icon("../../icons/inverter.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if response == SUCCESS:
return FAILURE
if response == FAILURE:
return SUCCESS
if c is Leaf and response == RUNNING:
blackboard.set_value("running_action", c)
return RUNNING
# Decorators must have a child. This should be unreachable code.
return FAILURE

View file

@ -0,0 +1,21 @@
## The limiter will execute its child `x` amount of times. When the number of
## maximum ticks is reached, it will return a `FAILURE` status code.
@tool
class_name LimiterDecorator extends Decorator
@icon("../../icons/limiter.svg")
@onready var cache_key = 'limiter_%s' % self.get_instance_id()
@export var max_count : float = 0
func tick(actor: Node, blackboard: Blackboard) -> int:
var current_count = blackboard.get_value(cache_key)
if current_count == null:
current_count = 0
if current_count <= max_count:
blackboard.set_value(cache_key, current_count + 1)
return self.get_child(0).tick(actor, blackboard)
else:
return FAILURE

View file

@ -0,0 +1,12 @@
## A succeeder node will always return a `SUCCESS` status code.
@tool
class_name AlwaysSucceedDecorator extends Decorator
@icon("../../icons/succeeder.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if response == RUNNING:
return RUNNING
return SUCCESS

View file

@ -0,0 +1,7 @@
## Actions are leaf nodes that define a task to be performed by an actor.
## Their execution can be long running, potentially being called across multiple
## frame executions. In this case, the node should return `RUNNING` until the
## action is completed.
@tool
class_name ActionLeaf extends Leaf
@icon("../../icons/action.svg")

View file

@ -0,0 +1,5 @@
## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on
## a single simple condition. They should never return `RUNNING`.
@tool
class_name ConditionLeaf extends Leaf
@icon("../../icons/condition.svg")

View file

@ -0,0 +1,15 @@
## Base class for all leaf nodes of the tree.
@tool
class_name Leaf extends BeehaveNode
@icon("../../icons/category_leaf.svg")
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = []
var children: Array[Node] = get_children()
if children.any(func(x): return x is BeehaveNode):
warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.")
return warnings