Add a behavior tree to the enemy
Add the Beehave addon. Make the enemy do the same thing as before but with a behavior tree.
This commit is contained in:
parent
09f6925a00
commit
1aed988149
92 changed files with 4025 additions and 25 deletions
49
addons/beehave/nodes/beehave_node.gd
Normal file
49
addons/beehave/nodes/beehave_node.gd
Normal file
|
@ -0,0 +1,49 @@
|
|||
## A node in the behavior tree. Every node must return `SUCCESS`, `FAILURE` or
|
||||
## `RUNNING` when ticked.
|
||||
@tool
|
||||
class_name BeehaveNode extends Node
|
||||
|
||||
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
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
## Called before the first time it ticks by the parent.
|
||||
func before_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
pass
|
||||
|
||||
|
||||
## Called after the last time it ticks and returns
|
||||
## [code]SUCCESS[/code] or [code]FAILURE[/code].
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
return [&"BeehaveNode"]
|
||||
|
||||
|
||||
func can_send_message(blackboard: Blackboard) -> bool:
|
||||
return blackboard.get_value("can_send_message", false)
|
224
addons/beehave/nodes/beehave_tree.gd
Normal file
224
addons/beehave/nodes/beehave_tree.gd
Normal file
|
@ -0,0 +1,224 @@
|
|||
## Controls the flow of execution of the entire behavior tree.
|
||||
@tool
|
||||
@icon("../icons/tree.svg")
|
||||
class_name BeehaveTree extends Node
|
||||
|
||||
enum {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
RUNNING
|
||||
}
|
||||
|
||||
signal tree_enabled
|
||||
signal tree_disabled
|
||||
|
||||
## Wether this behavior tree should be enabled or not.
|
||||
@export var enabled: bool = true:
|
||||
set(value):
|
||||
enabled = value
|
||||
set_physics_process(enabled)
|
||||
|
||||
if value:
|
||||
tree_enabled.emit()
|
||||
else:
|
||||
interrupt()
|
||||
tree_disabled.emit()
|
||||
|
||||
get:
|
||||
return enabled
|
||||
|
||||
## An optional node path this behavior tree should apply to.
|
||||
@export_node_path var actor_node_path : NodePath
|
||||
|
||||
## Custom blackboard node. An internal blackboard will be used
|
||||
## if no blackboard is provided explicitly.
|
||||
@export var blackboard:Blackboard:
|
||||
set(b):
|
||||
blackboard = b
|
||||
if blackboard and _internal_blackboard:
|
||||
remove_child(_internal_blackboard)
|
||||
_internal_blackboard.free()
|
||||
_internal_blackboard = null
|
||||
elif not blackboard and not _internal_blackboard:
|
||||
_internal_blackboard = Blackboard.new()
|
||||
add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
|
||||
get:
|
||||
return blackboard if blackboard else _internal_blackboard
|
||||
|
||||
## When enabled, this tree is tracked individually
|
||||
## as a custom monitor.
|
||||
@export var custom_monitor = false:
|
||||
set(b):
|
||||
custom_monitor = b
|
||||
if custom_monitor and _process_time_metric_name != '':
|
||||
Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
|
||||
BeehaveGlobalMetrics.register_tree(self)
|
||||
else:
|
||||
if _process_time_metric_name != '':
|
||||
# Remove tree metric from the engine
|
||||
Performance.remove_custom_monitor(_process_time_metric_name)
|
||||
BeehaveGlobalMetrics.unregister_tree(self)
|
||||
|
||||
BeehaveDebuggerMessages.unregister_tree(get_instance_id())
|
||||
|
||||
var actor : Node
|
||||
var status : int = -1
|
||||
|
||||
var _internal_blackboard: Blackboard
|
||||
var _process_time_metric_name : String
|
||||
var _process_time_metric_value : float = 0.0
|
||||
var _can_send_message: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if self.get_child_count() > 0 and not self.get_child(0) is BeehaveNode:
|
||||
push_warning("Beehave error: Root %s should have only one child of type BeehaveNode (NodePath: %s)" % [self.name, self.get_path()])
|
||||
disable()
|
||||
return
|
||||
|
||||
if not blackboard:
|
||||
_internal_blackboard = Blackboard.new()
|
||||
add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
|
||||
|
||||
actor = get_parent()
|
||||
if actor_node_path:
|
||||
actor = get_node(actor_node_path)
|
||||
|
||||
# Get the name of the parent node name for metric
|
||||
var parent_name = actor.name
|
||||
_process_time_metric_name = "beehave [microseconds]/process_time_%s-%s" % [parent_name, get_instance_id()]
|
||||
|
||||
# Register custom metric to the engine
|
||||
if custom_monitor:
|
||||
Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
|
||||
BeehaveGlobalMetrics.register_tree(self)
|
||||
|
||||
set_physics_process(enabled)
|
||||
BeehaveGlobalDebugger.register_tree(self)
|
||||
BeehaveDebuggerMessages.register_tree(_get_debugger_data(self))
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
# Start timing for metric
|
||||
var start_time = Time.get_ticks_usec()
|
||||
|
||||
blackboard.set_value("can_send_message", _can_send_message)
|
||||
|
||||
if _can_send_message:
|
||||
BeehaveDebuggerMessages.process_begin(get_instance_id())
|
||||
|
||||
if self.get_child_count() == 1:
|
||||
tick()
|
||||
|
||||
if _can_send_message:
|
||||
BeehaveDebuggerMessages.process_end(get_instance_id())
|
||||
|
||||
# Check the cost for this frame and save it for metric report
|
||||
_process_time_metric_value = Time.get_ticks_usec() - start_time
|
||||
|
||||
|
||||
func tick() -> int:
|
||||
var child := self.get_child(0)
|
||||
if status != RUNNING:
|
||||
child.before_run(actor, blackboard)
|
||||
|
||||
status = child.tick(actor, blackboard)
|
||||
if _can_send_message:
|
||||
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), status)
|
||||
BeehaveDebuggerMessages.process_tick(get_instance_id(), status)
|
||||
|
||||
# Clear running action if nothing is running
|
||||
if status != RUNNING:
|
||||
blackboard.set_value("running_action", null, str(actor.get_instance_id()))
|
||||
child.after_run(actor, blackboard)
|
||||
|
||||
return status
|
||||
|
||||
|
||||
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.")
|
||||
|
||||
if get_child_count() != 1:
|
||||
warnings.append("BeehaveTree should have exactly one child node.")
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
## Returns the currently running action
|
||||
func get_running_action() -> ActionLeaf:
|
||||
return blackboard.get_value("running_action", null, str(actor.get_instance_id()))
|
||||
|
||||
|
||||
## Returns the last condition that was executed
|
||||
func get_last_condition() -> ConditionLeaf:
|
||||
return blackboard.get_value("last_condition", null, str(actor.get_instance_id()))
|
||||
|
||||
|
||||
## Returns the status of the last executed condition
|
||||
func get_last_condition_status() -> String:
|
||||
if blackboard.has_value("last_condition_status", str(actor.get_instance_id())):
|
||||
var status = blackboard.get_value("last_condition_status", null, str(actor.get_instance_id()))
|
||||
if status == SUCCESS:
|
||||
return "SUCCESS"
|
||||
elif status == FAILURE:
|
||||
return "FAILURE"
|
||||
else:
|
||||
return "RUNNING"
|
||||
return ""
|
||||
|
||||
## interrupts this tree if anything was running
|
||||
func interrupt() -> void:
|
||||
if self.get_child_count() != 0:
|
||||
var first_child = self.get_child(0)
|
||||
if "interrupt" in first_child:
|
||||
first_child.interrupt(actor, blackboard)
|
||||
|
||||
|
||||
## Enables this tree.
|
||||
func enable() -> void:
|
||||
self.enabled = true
|
||||
|
||||
|
||||
## Disables this tree.
|
||||
func disable() -> void:
|
||||
self.enabled = false
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if custom_monitor:
|
||||
if _process_time_metric_name != '':
|
||||
# Remove tree metric from the engine
|
||||
Performance.remove_custom_monitor(_process_time_metric_name)
|
||||
BeehaveGlobalMetrics.unregister_tree(self)
|
||||
|
||||
BeehaveDebuggerMessages.unregister_tree(get_instance_id())
|
||||
|
||||
|
||||
# Called by the engine to profile this tree
|
||||
func _get_process_time_metric_value() -> int:
|
||||
return _process_time_metric_value
|
||||
|
||||
|
||||
func _get_debugger_data(node: Node) -> Dictionary:
|
||||
if not node is BeehaveTree and not node is BeehaveNode:
|
||||
return {}
|
||||
var data := { path = node.get_path(), name = node.name, type = node.get_class_name(), id = str(node.get_instance_id()) }
|
||||
if node.get_child_count() > 0:
|
||||
data.children = []
|
||||
for child in node.get_children():
|
||||
var child_data := _get_debugger_data(child)
|
||||
if not child_data.is_empty():
|
||||
data.children.push_back(child_data)
|
||||
return data
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
return [&"BeehaveTree"]
|
40
addons/beehave/nodes/composites/composite.gd
Normal file
40
addons/beehave/nodes/composites/composite.gd
Normal file
|
@ -0,0 +1,40 @@
|
|||
## A Composite node controls the flow of execution of its children in a specific manner.
|
||||
@tool
|
||||
@icon("../../icons/category_composite.svg")
|
||||
class_name Composite extends BeehaveNode
|
||||
|
||||
|
||||
var running_child: BeehaveNode = null
|
||||
|
||||
|
||||
func _ready():
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if self.get_child_count() < 1:
|
||||
push_warning("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
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
running_child = null
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"Composite")
|
||||
return classes
|
57
addons/beehave/nodes/composites/selector.gd
Normal file
57
addons/beehave/nodes/composites/selector.gd
Normal file
|
@ -0,0 +1,57 @@
|
|||
## 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`.
|
||||
## If a child returns `RUNNING` it will tick again.
|
||||
@tool
|
||||
@icon("../../icons/selector.svg")
|
||||
class_name SelectorComposite extends Composite
|
||||
|
||||
var last_execution_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c.get_index() < last_execution_index:
|
||||
continue
|
||||
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
FAILURE:
|
||||
last_execution_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return FAILURE
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
last_execution_index = 0
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
after_run(actor, blackboard)
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SelectorComposite")
|
||||
return classes
|
88
addons/beehave/nodes/composites/selector_random.gd
Normal file
88
addons/beehave/nodes/composites/selector_random.gd
Normal file
|
@ -0,0 +1,88 @@
|
|||
## 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
|
||||
@icon("../../icons/selector_random.svg")
|
||||
class_name SelectorRandomComposite extends Composite
|
||||
|
||||
## Sets a predicable seed
|
||||
@export var random_seed:int = 0:
|
||||
set(rs):
|
||||
random_seed = rs
|
||||
if random_seed != 0:
|
||||
seed(random_seed)
|
||||
else:
|
||||
randomize()
|
||||
|
||||
|
||||
## A shuffled list of the children that will be executed in reverse order.
|
||||
var _children_bag: Array[Node] = []
|
||||
var c: Node
|
||||
|
||||
func _ready() -> void:
|
||||
if random_seed == 0:
|
||||
randomize()
|
||||
|
||||
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]
|
||||
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
_children_bag.erase(c)
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
FAILURE:
|
||||
_children_bag.erase(c)
|
||||
c.after_run(actor, blackboard)
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return FAILURE
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
after_run(actor, blackboard)
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _get_reversed_indexes() -> Array[int]:
|
||||
var reversed: Array[int]
|
||||
reversed.assign(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()
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SelectorRandomComposite")
|
||||
return classes
|
45
addons/beehave/nodes/composites/selector_reactive.gd
Normal file
45
addons/beehave/nodes/composites/selector_reactive.gd
Normal file
|
@ -0,0 +1,45 @@
|
|||
## Selector Reactive 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`.
|
||||
## If a child returns `RUNNING` it will restart.
|
||||
@tool
|
||||
@icon("../../icons/selector_reactive.svg")
|
||||
class_name SelectorReactiveComposite extends Composite
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
# Interrupt any child that was RUNNING before.
|
||||
if c != running_child:
|
||||
interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
FAILURE:
|
||||
c.after_run(actor, blackboard)
|
||||
RUNNING:
|
||||
if c != running_child:
|
||||
interrupt(actor, blackboard)
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return FAILURE
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SelectorReactiveComposite")
|
||||
return classes
|
64
addons/beehave/nodes/composites/sequence.gd
Normal file
64
addons/beehave/nodes/composites/sequence.gd
Normal file
|
@ -0,0 +1,64 @@
|
|||
## 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` and restart.
|
||||
## In case a child returns `RUNNING` this node will tick again.
|
||||
@tool
|
||||
@icon("../../icons/sequence.svg")
|
||||
class_name SequenceComposite extends Composite
|
||||
|
||||
|
||||
var successful_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
|
||||
if c.get_index() < successful_index:
|
||||
continue
|
||||
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
successful_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
# Interrupt any child that was RUNNING before.
|
||||
interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
if c != running_child:
|
||||
if running_child != null:
|
||||
running_child.interrupt(actor, blackboard)
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
_reset()
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
func _reset() -> void:
|
||||
successful_index = 0
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceComposite")
|
||||
return classes
|
96
addons/beehave/nodes/composites/sequence_random.gd
Normal file
96
addons/beehave/nodes/composites/sequence_random.gd
Normal file
|
@ -0,0 +1,96 @@
|
|||
## 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
|
||||
@icon("../../icons/sequence_random.svg")
|
||||
class_name SequenceRandomComposite extends Composite
|
||||
|
||||
|
||||
## 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
|
||||
## Sets a predicable seed
|
||||
@export var random_seed: int = 0:
|
||||
set(rs):
|
||||
random_seed = rs
|
||||
if random_seed != 0:
|
||||
seed(random_seed)
|
||||
else:
|
||||
randomize()
|
||||
|
||||
## A shuffled list of the children that will be executed in reverse order.
|
||||
var _children_bag: Array[Node] = []
|
||||
var c: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if random_seed == 0:
|
||||
randomize()
|
||||
|
||||
|
||||
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]
|
||||
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
_children_bag.erase(c)
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
_children_bag.erase(c)
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
if not resume_on_failure:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
if not resume_on_interrupt:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _get_reversed_indexes() -> Array[int]:
|
||||
var reversed: Array[int]
|
||||
reversed.assign(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()
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceRandomComposite")
|
||||
return classes
|
62
addons/beehave/nodes/composites/sequence_reactive.gd
Normal file
62
addons/beehave/nodes/composites/sequence_reactive.gd
Normal file
|
@ -0,0 +1,62 @@
|
|||
## Reactive 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` and restart.
|
||||
## In case a child returns `RUNNING` this node will restart.
|
||||
@tool
|
||||
@icon("../../icons/sequence_reactive.svg")
|
||||
class_name SequenceReactiveComposite extends Composite
|
||||
|
||||
|
||||
var successful_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c.get_index() < successful_index:
|
||||
continue
|
||||
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
successful_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
# Interrupt any child that was RUNNING before.
|
||||
interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
_reset()
|
||||
if running_child != c:
|
||||
interrupt(actor, blackboard)
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
_reset()
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
func _reset() -> void:
|
||||
successful_index = 0
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceReactiveComposite")
|
||||
return classes
|
58
addons/beehave/nodes/composites/sequence_star.gd
Normal file
58
addons/beehave/nodes/composites/sequence_star.gd
Normal file
|
@ -0,0 +1,58 @@
|
|||
## 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` and tick again.
|
||||
## In case a child returns `RUNNING` this node will restart.
|
||||
@tool
|
||||
@icon("../../icons/sequence_reactive.svg")
|
||||
class_name SequenceStarComposite extends Composite
|
||||
|
||||
|
||||
var successful_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c.get_index() < successful_index:
|
||||
continue
|
||||
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
successful_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
_reset()
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
successful_index = 0
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceStarComposite")
|
||||
return classes
|
41
addons/beehave/nodes/decorators/decorator.gd
Normal file
41
addons/beehave/nodes/decorators/decorator.gd
Normal file
|
@ -0,0 +1,41 @@
|
|||
## Decorator nodes are used to transform the result received by its child.
|
||||
## Must only have one child.
|
||||
@tool
|
||||
@icon("../../icons/category_decorator.svg")
|
||||
class_name Decorator extends BeehaveNode
|
||||
|
||||
|
||||
var running_child: BeehaveNode = null
|
||||
|
||||
|
||||
func _ready():
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if self.get_child_count() != 1:
|
||||
push_warning("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
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
if running_child != null:
|
||||
running_child.interrupt(actor, blackboard)
|
||||
running_child = null
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
running_child = null
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"Decorator")
|
||||
return classes
|
34
addons/beehave/nodes/decorators/failer.gd
Normal file
34
addons/beehave/nodes/decorators/failer.gd
Normal file
|
@ -0,0 +1,34 @@
|
|||
## A Failer node will always return a `FAILURE` status code.
|
||||
@tool
|
||||
@icon("../../icons/failer.svg")
|
||||
class_name AlwaysFailDecorator extends Decorator
|
||||
|
||||
|
||||
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
|
||||
else:
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"AlwaysFailDecorator")
|
||||
return classes
|
42
addons/beehave/nodes/decorators/inverter.gd
Normal file
42
addons/beehave/nodes/decorators/inverter.gd
Normal file
|
@ -0,0 +1,42 @@
|
|||
## 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
|
||||
@icon("../../icons/inverter.svg")
|
||||
class_name InverterDecorator extends Decorator
|
||||
|
||||
|
||||
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()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
FAILURE:
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
_:
|
||||
push_error("This should be unreachable")
|
||||
return -1
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"InverterDecorator")
|
||||
return classes
|
41
addons/beehave/nodes/decorators/limiter.gd
Normal file
41
addons/beehave/nodes/decorators/limiter.gd
Normal file
|
@ -0,0 +1,41 @@
|
|||
## 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
|
||||
@icon("../../icons/limiter.svg")
|
||||
class_name LimiterDecorator extends Decorator
|
||||
|
||||
@onready var cache_key = 'limiter_%s' % self.get_instance_id()
|
||||
|
||||
@export var max_count : float = 0
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var child = self.get_child(0)
|
||||
var current_count = blackboard.get_value(cache_key, 0, str(actor.get_instance_id()))
|
||||
|
||||
if current_count == 0:
|
||||
child.before_run(actor, blackboard)
|
||||
|
||||
if current_count < max_count:
|
||||
blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id()))
|
||||
var response = child.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
|
||||
|
||||
if child is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if child is ActionLeaf and response == RUNNING:
|
||||
running_child = child
|
||||
blackboard.set_value("running_action", child, str(actor.get_instance_id()))
|
||||
|
||||
return response
|
||||
else:
|
||||
child.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"LimiterDecorator")
|
||||
return classes
|
34
addons/beehave/nodes/decorators/succeeder.gd
Normal file
34
addons/beehave/nodes/decorators/succeeder.gd
Normal file
|
@ -0,0 +1,34 @@
|
|||
## A succeeder node will always return a `SUCCESS` status code.
|
||||
@tool
|
||||
@icon("../../icons/succeeder.svg")
|
||||
class_name AlwaysSucceedDecorator extends Decorator
|
||||
|
||||
|
||||
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
|
||||
else:
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"AlwaysSucceedDecorator")
|
||||
return classes
|
46
addons/beehave/nodes/decorators/time_limiter.gd
Normal file
46
addons/beehave/nodes/decorators/time_limiter.gd
Normal file
|
@ -0,0 +1,46 @@
|
|||
## The Time Limit Decorator will give its child a set amount of time to finish
|
||||
## before interrupting it and return a `FAILURE` status code. The timer is reset
|
||||
## every time before the node runs.
|
||||
@tool
|
||||
@icon("../../icons/limiter.svg")
|
||||
class_name TimeLimiterDecorator extends Decorator
|
||||
|
||||
@export var wait_time: = 0.0
|
||||
|
||||
var time_left: = 0.0
|
||||
|
||||
@onready var child: BeehaveNode = get_child(0)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
if time_left < wait_time:
|
||||
time_left += get_physics_process_delta_time()
|
||||
var response = child.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
|
||||
|
||||
if child is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING:
|
||||
running_child = child
|
||||
if child is ActionLeaf:
|
||||
blackboard.set_value("running_action", child, str(actor.get_instance_id()))
|
||||
|
||||
return response
|
||||
else:
|
||||
child.after_run(actor, blackboard)
|
||||
interrupt(actor, blackboard)
|
||||
return FAILURE
|
||||
|
||||
|
||||
func before_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
time_left = 0.0
|
||||
child.before_run(actor, blackboard)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"TimeLimiterDecorator")
|
||||
return classes
|
13
addons/beehave/nodes/leaves/action.gd
Normal file
13
addons/beehave/nodes/leaves/action.gd
Normal file
|
@ -0,0 +1,13 @@
|
|||
## 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
|
||||
@icon("../../icons/action.svg")
|
||||
class_name ActionLeaf extends Leaf
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"ActionLeaf")
|
||||
return classes
|
61
addons/beehave/nodes/leaves/blackboard_compare.gd
Normal file
61
addons/beehave/nodes/leaves/blackboard_compare.gd
Normal file
|
@ -0,0 +1,61 @@
|
|||
## Compares two values using the specified comparison operator.
|
||||
## Returns [code]FAILURE[/code] if any of the expression fails or the
|
||||
## comparison operation returns [code]false[/code], otherwise it returns [code]SUCCESS[/code].
|
||||
@tool
|
||||
class_name BlackboardCompareCondition extends ConditionLeaf
|
||||
|
||||
|
||||
enum Operators {
|
||||
EQUAL,
|
||||
NOT_EQUAL,
|
||||
GREATER,
|
||||
LESS,
|
||||
GREATER_EQUAL,
|
||||
LESS_EQUAL,
|
||||
}
|
||||
|
||||
|
||||
## Expression represetning left operand.
|
||||
## This value can be any valid GDScript expression.
|
||||
## In order to use the existing blackboard keys for comparison,
|
||||
## use get_value("key_name") e.g. get_value("direction").length()
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var left_operand: String = ""
|
||||
## Comparison operator.
|
||||
@export_enum("==", "!=", ">", "<", ">=", "<=") var operator: int = 0
|
||||
## Expression represetning right operand.
|
||||
## This value can be any valid GDScript expression.
|
||||
## In order to use the existing blackboard keys for comparison,
|
||||
## use get_value("key_name") e.g. get_value("direction").length()
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = ""
|
||||
|
||||
|
||||
@onready var _left_expression: Expression = _parse_expression(left_operand)
|
||||
@onready var _right_expression: Expression = _parse_expression(right_operand)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var left: Variant = _left_expression.execute([], blackboard)
|
||||
|
||||
if _left_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
var right: Variant = _right_expression.execute([], blackboard)
|
||||
|
||||
if _right_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
var result: bool = false
|
||||
|
||||
match operator:
|
||||
Operators.EQUAL: result = left == right
|
||||
Operators.NOT_EQUAL: result = left != right
|
||||
Operators.GREATER: result = left > right
|
||||
Operators.LESS: result = left < right
|
||||
Operators.GREATER_EQUAL: result = left >= right
|
||||
Operators.LESS_EQUAL: result = left <= right
|
||||
|
||||
return SUCCESS if result else FAILURE
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [left_operand, right_operand]
|
25
addons/beehave/nodes/leaves/blackboard_erase.gd
Normal file
25
addons/beehave/nodes/leaves/blackboard_erase.gd
Normal file
|
@ -0,0 +1,25 @@
|
|||
## Erases the specified key from the blackboard.
|
||||
## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
|
||||
@tool
|
||||
class_name BlackboardEraseAction extends ActionLeaf
|
||||
|
||||
|
||||
## Expression representing a blackboard key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
|
||||
|
||||
@onready var _key_expression: Expression = _parse_expression(key)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var key_value: Variant = _key_expression.execute([], blackboard)
|
||||
|
||||
if _key_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
blackboard.erase_value(key_value)
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [key]
|
23
addons/beehave/nodes/leaves/blackboard_has.gd
Normal file
23
addons/beehave/nodes/leaves/blackboard_has.gd
Normal file
|
@ -0,0 +1,23 @@
|
|||
## Returns [code]FAILURE[/code] if expression execution fails or the specified key doesn't exist.
|
||||
## Returns [code]SUCCESS[/code] if blackboard has the specified key.
|
||||
@tool
|
||||
class_name BlackboardHasCondition extends ConditionLeaf
|
||||
|
||||
|
||||
## Expression representing a blackboard key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
|
||||
|
||||
@onready var _key_expression: Expression = _parse_expression(key)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var key_value: Variant = _key_expression.execute([], blackboard)
|
||||
|
||||
if _key_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
return SUCCESS if blackboard.has_value(key_value) else FAILURE
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [key]
|
34
addons/beehave/nodes/leaves/blackboard_set.gd
Normal file
34
addons/beehave/nodes/leaves/blackboard_set.gd
Normal file
|
@ -0,0 +1,34 @@
|
|||
## Sets the specified key to the specified value.
|
||||
## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
|
||||
@tool
|
||||
class_name BlackboardSetAction extends ActionLeaf
|
||||
|
||||
|
||||
## Expression representing a blackboard key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
|
||||
## Expression representing a blackboard value to assign to the specified key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = ""
|
||||
|
||||
|
||||
@onready var _key_expression: Expression = _parse_expression(key)
|
||||
@onready var _value_expression: Expression = _parse_expression(value)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var key_value: Variant = _key_expression.execute([], blackboard)
|
||||
|
||||
if _key_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
var value_value: Variant = _value_expression.execute([], blackboard)
|
||||
|
||||
if _value_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
blackboard.set_value(key_value, value_value)
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [key, value]
|
11
addons/beehave/nodes/leaves/condition.gd
Normal file
11
addons/beehave/nodes/leaves/condition.gd
Normal file
|
@ -0,0 +1,11 @@
|
|||
## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on
|
||||
## a single simple condition. They should never return `RUNNING`.
|
||||
@tool
|
||||
@icon("../../icons/condition.svg")
|
||||
class_name ConditionLeaf extends Leaf
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"ConditionLeaf")
|
||||
return classes
|
46
addons/beehave/nodes/leaves/leaf.gd
Normal file
46
addons/beehave/nodes/leaves/leaf.gd
Normal file
|
@ -0,0 +1,46 @@
|
|||
## Base class for all leaf nodes of the tree.
|
||||
@tool
|
||||
@icon("../../icons/category_leaf.svg")
|
||||
class_name Leaf extends BeehaveNode
|
||||
|
||||
|
||||
const EXPRESSION_PLACEHOLDER: String = "Insert an expression..."
|
||||
|
||||
|
||||
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.")
|
||||
|
||||
for source in _get_expression_sources():
|
||||
var error_text: String = _parse_expression(source).get_error_text()
|
||||
if not error_text.is_empty():
|
||||
warnings.append("Expression `%s` is invalid! Error text: `%s`" % [source, error_text])
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
func _parse_expression(source: String) -> Expression:
|
||||
var result: Expression = Expression.new()
|
||||
var error: int = result.parse(source)
|
||||
|
||||
if not Engine.is_editor_hint() and error != OK:
|
||||
push_error(
|
||||
"[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`" %\
|
||||
[source, result.get_error_text()]
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]: # virtual
|
||||
return []
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"Leaf")
|
||||
return classes
|
Loading…
Add table
Add a link
Reference in a new issue