Quellcode durchsuchen

Added godot code

Khemin vor 7 Monaten
Ursprung
Commit
b5d8d4053a
100 geänderte Dateien mit 5024 neuen und 0 gelöschten Zeilen
  1. 2 0
      Godot Exploration - 2d/.gitattributes
  2. 2 0
      Godot Exploration - 2d/.gitignore
  3. 89 0
      Godot Exploration - 2d/BouncingBalls/bouncing_ball.gd
  4. 1 0
      Godot Exploration - 2d/BouncingBalls/bouncing_ball.gd.uid
  5. 13 0
      Godot Exploration - 2d/BouncingBalls/bouncing_ball.tscn
  6. 90 0
      Godot Exploration - 2d/BouncingBalls/bouncing_ball_sc.gd
  7. 1 0
      Godot Exploration - 2d/BouncingBalls/bouncing_ball_sc.gd.uid
  8. 118 0
      Godot Exploration - 2d/BouncingBalls/bouncing_ball_sc.tscn
  9. 20 0
      Godot Exploration - 2d/BouncingBalls/bouncing_balls.gd
  10. 1 0
      Godot Exploration - 2d/BouncingBalls/bouncing_balls.gd.uid
  11. 14 0
      Godot Exploration - 2d/BouncingBalls/bouncing_balls.tscn
  12. 22 0
      Godot Exploration - 2d/BouncingBalls/sc_semantics.gd
  13. 1 0
      Godot Exploration - 2d/BouncingBalls/sc_semantics.gd.uid
  14. 611 0
      Godot Exploration - 2d/BouncingBalls/sc_semantics.tscn
  15. 606 0
      Godot Exploration - 2d/BouncingBalls/sc_semantics.tscn40879525.tmp
  16. 18 0
      Godot Exploration - 2d/BouncingBalls/semantics_test.gd
  17. 1 0
      Godot Exploration - 2d/BouncingBalls/semantics_test.gd.uid
  18. 140 0
      Godot Exploration - 2d/BouncingBalls/semantics_tests.tscn
  19. 28 0
      Godot Exploration - 2d/BouncingBalls/walls.tscn
  20. 37 0
      Godot Exploration - 2d/Camera2D.gd
  21. 1 0
      Godot Exploration - 2d/Camera2D.gd.uid
  22. 19 0
      Godot Exploration - 2d/FPSctr.gd
  23. 1 0
      Godot Exploration - 2d/FPSctr.gd.uid
  24. 68 0
      Godot Exploration - 2d/OutputHandler.gd
  25. 1 0
      Godot Exploration - 2d/OutputHandler.gd.uid
  26. 154 0
      Godot Exploration - 2d/Platooning/Sensors/vision.gd
  27. 1 0
      Godot Exploration - 2d/Platooning/Sensors/vision.gd.uid
  28. 123 0
      Godot Exploration - 2d/Platooning/Sensors/vision.tscn
  29. 62 0
      Godot Exploration - 2d/Platooning/StrategicDeciders/infantry_strategy.gd
  30. 1 0
      Godot Exploration - 2d/Platooning/StrategicDeciders/infantry_strategy.gd.uid
  31. 108 0
      Godot Exploration - 2d/Platooning/StrategicDeciders/infantry_strategy.tscn
  32. 45 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/explore_control.gd
  33. 1 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/explore_control.gd.uid
  34. 48 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/explore_control.tscn
  35. 50 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/group_planner.gd
  36. 1 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/group_planner.gd.uid
  37. 73 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/group_planner.tscn
  38. 50 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/infantry_attack_planner.gd
  39. 1 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/infantry_attack_planner.gd.uid
  40. 67 0
      Godot Exploration - 2d/Platooning/TacticalDeciders/infantry_attack_planner.tscn
  41. 60 0
      Godot Exploration - 2d/Platooning/communicator.gd
  42. 1 0
      Godot Exploration - 2d/Platooning/communicator.gd.uid
  43. 70 0
      Godot Exploration - 2d/Platooning/communicator.tscn
  44. 167 0
      Godot Exploration - 2d/Platooning/infantry.gd
  45. 1 0
      Godot Exploration - 2d/Platooning/infantry.gd.uid
  46. 116 0
      Godot Exploration - 2d/Platooning/infantry.tscn
  47. 13 0
      Godot Exploration - 2d/Platooning/infantry_base.gd
  48. 1 0
      Godot Exploration - 2d/Platooning/infantry_base.gd.uid
  49. 29 0
      Godot Exploration - 2d/Platooning/platoon_test_world.gd
  50. 1 0
      Godot Exploration - 2d/Platooning/platoon_test_world.gd.uid
  51. 54 0
      Godot Exploration - 2d/Platooning/platoon_test_world.tscn
  52. 20 0
      Godot Exploration - 2d/Platooning/regroup_point.gd
  53. 1 0
      Godot Exploration - 2d/Platooning/regroup_point.gd.uid
  54. 8 0
      Godot Exploration - 2d/Platooning/regroup_point.tscn
  55. 17 0
      Godot Exploration - 2d/Platooning/screenshot_map.tscn
  56. 70 0
      Godot Exploration - 2d/References/AStar.gd
  57. 1 0
      Godot Exploration - 2d/References/AStar.gd.uid
  58. 257 0
      Godot Exploration - 2d/References/Grid2D.gd
  59. 1 0
      Godot Exploration - 2d/References/Grid2D.gd.uid
  60. 15 0
      Godot Exploration - 2d/References/HeapNode.gd
  61. 1 0
      Godot Exploration - 2d/References/HeapNode.gd.uid
  62. 26 0
      Godot Exploration - 2d/References/MaxHeap.gd
  63. 1 0
      Godot Exploration - 2d/References/MaxHeap.gd.uid
  64. 62 0
      Godot Exploration - 2d/References/PriorityQueue.gd
  65. 1 0
      Godot Exploration - 2d/References/PriorityQueue.gd.uid
  66. 28 0
      Godot Exploration - 2d/References/Utils.gd
  67. 1 0
      Godot Exploration - 2d/References/Utils.gd.uid
  68. 32 0
      Godot Exploration - 2d/TankWars/Actuators/tank_body.gd
  69. 1 0
      Godot Exploration - 2d/TankWars/Actuators/tank_body.gd.uid
  70. 53 0
      Godot Exploration - 2d/TankWars/Actuators/tank_body.tscn
  71. 37 0
      Godot Exploration - 2d/TankWars/Actuators/turret.gd
  72. 1 0
      Godot Exploration - 2d/TankWars/Actuators/turret.gd.uid
  73. 49 0
      Godot Exploration - 2d/TankWars/Actuators/turret.tscn
  74. 52 0
      Godot Exploration - 2d/TankWars/Memorizers/enemy_tracker.gd
  75. 1 0
      Godot Exploration - 2d/TankWars/Memorizers/enemy_tracker.gd.uid
  76. 60 0
      Godot Exploration - 2d/TankWars/Memorizers/enemy_tracker.tscn
  77. 54 0
      Godot Exploration - 2d/TankWars/Memorizers/obstacle_map.gd
  78. 1 0
      Godot Exploration - 2d/TankWars/Memorizers/obstacle_map.gd.uid
  79. 35 0
      Godot Exploration - 2d/TankWars/Memorizers/obstacle_map.tscn
  80. 23 0
      Godot Exploration - 2d/TankWars/Sensors/fuel_tank.gd
  81. 1 0
      Godot Exploration - 2d/TankWars/Sensors/fuel_tank.gd.uid
  82. 53 0
      Godot Exploration - 2d/TankWars/Sensors/fuel_tank.tscn
  83. 124 0
      Godot Exploration - 2d/TankWars/Sensors/radar.gd
  84. 1 0
      Godot Exploration - 2d/TankWars/Sensors/radar.gd.uid
  85. 102 0
      Godot Exploration - 2d/TankWars/Sensors/radar.tscn
  86. 36 0
      Godot Exploration - 2d/TankWars/StrategicDeciders/pilot_strategy.gd
  87. 1 0
      Godot Exploration - 2d/TankWars/StrategicDeciders/pilot_strategy.gd.uid
  88. 63 0
      Godot Exploration - 2d/TankWars/StrategicDeciders/pilot_strategy.tscn
  89. 58 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/attack_planner.gd
  90. 1 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/attack_planner.gd.uid
  91. 72 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/attack_planner.tscn
  92. 44 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/explore_planner.gd
  93. 1 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/explore_planner.gd.uid
  94. 48 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/explore_planner.tscn
  95. 86 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/pathfinder.gd
  96. 1 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/pathfinder.gd.uid
  97. 85 0
      Godot Exploration - 2d/TankWars/TacticalDeciders/pathfinder.tscn
  98. 134 0
      Godot Exploration - 2d/TankWars/tank.gd
  99. 1 0
      Godot Exploration - 2d/TankWars/tank.gd.uid
  100. 0 0
      Godot Exploration - 2d/TankWars/tank.tscn

+ 2 - 0
Godot Exploration - 2d/.gitattributes

@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf

+ 2 - 0
Godot Exploration - 2d/.gitignore

@@ -0,0 +1,2 @@
+# Godot 4+ specific ignores
+.godot/

+ 89 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_ball.gd

@@ -0,0 +1,89 @@
+extends CharacterBody2D
+
+enum State {BOUNCING, DRAGGING, SELECTED}
+
+@onready var collision_shape: CollisionShape2D = $CollisionShape2D
+var _radius: float = 0.0
+var _speed: float = 250.0
+var _direction: Vector2 = Vector2(1.0, 0.0)
+
+var white: Color = Color.WHITE
+var red: Color = Color.RED
+var yellow: Color = Color.YELLOW
+
+var current_color: Color = Color.BLACK : set = set_color, get = get_color
+var previous_color: Color = Color.BLACK
+var mouse_inside: bool = false
+
+func _ready() -> void:
+	self.current_color = white
+	self._radius = collision_shape.shape.radius
+	self._direction = get_random_direction()
+	self.velocity = self._direction * self._speed
+	
+	# Initialize logic to detect if mouse is inside ball.
+	self.mouse_inside = false
+	self.mouse_entered.connect(self._on_mouse_entered)
+	self.mouse_exited.connect(self._on_mouse_exited)
+
+func _draw() -> void:
+	draw_circle(Vector2(0.0, 0.0), self._radius, self.current_color)
+
+func _input(event: InputEvent) -> void:
+	if self.mouse_inside and event.is_action_pressed("MMB"):
+		self.queue_free()
+	
+	if self.mouse_inside and event.is_action_pressed("LMB"):
+		if self.current_color == red:
+			self.set_color(yellow)
+		if self.current_color == white:
+			self.set_color(red)
+	
+	if self.mouse_inside and event.is_action_released("LMB"):
+		if self.current_color == yellow:
+			self.set_color(white)
+	
+	if self.current_color == yellow and event is InputEventMouseMotion:
+		self.velocity = event.velocity
+
+func _physics_process(delta: float) -> void:
+	match current_color:
+		# A white colored ball is in a BOUNCING state
+		Color.WHITE:
+			var motion: Vector2 = self.velocity * delta
+			var collision = move_and_collide(motion)
+
+			if collision:
+				self.velocity = self.velocity.bounce(collision.get_normal())
+				motion = self.velocity * delta
+				move_and_collide(motion)
+		
+		# A yellow colored ball is in a DRAGGING state.
+		Color.YELLOW:
+			# In the DRAGGING state, the ball will follow the mouse.
+			self.global_position = get_global_mouse_position()
+		
+		# A red colored ball is in a SELECTED state.
+		Color.RED:
+			# In the SELECTED state, a ball will stop moving. It can still be collided with.
+			pass
+
+func get_color() -> Color:
+	return current_color
+
+func set_color(new_color: Color):
+	if current_color == new_color:
+		return # early exit if no change needed
+	previous_color = current_color
+	current_color = new_color
+	queue_redraw()
+
+func get_random_direction() -> Vector2:
+	var new_direction: Vector2 = Vector2(randf_range(-1.0, 1.0), randf_range(-1.0, 1.0))
+	return new_direction.normalized()
+
+func _on_mouse_entered() -> void:
+	self.mouse_inside = true
+
+func _on_mouse_exited() -> void:
+	self.mouse_inside = false

+ 1 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_ball.gd.uid

@@ -0,0 +1 @@
+uid://c8a4qbm4lfxq5

+ 13 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_ball.tscn

@@ -0,0 +1,13 @@
+[gd_scene load_steps=3 format=3 uid="uid://c2m0ar51dfgq1"]
+
+[ext_resource type="Script" uid="uid://c8a4qbm4lfxq5" path="res://BouncingBalls/bouncing_ball.gd" id="1_0orak"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_2pak7"]
+radius = 36.0
+
+[node name="BouncingBall" type="CharacterBody2D"]
+input_pickable = true
+script = ExtResource("1_0orak")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_2pak7")

+ 90 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_ball_sc.gd

@@ -0,0 +1,90 @@
+extends CharacterBody2D
+
+@onready var collision_shape: CollisionShape2D = $CollisionShape2D
+@onready var state_chart: StateChart = $StateChart
+
+var _radius: float = 0.0
+var _speed: float = 250.0
+var _direction: Vector2 = Vector2(1.0, 0.0)
+var current_color: Color = Color.BLACK
+
+func _draw() -> void:
+	draw_circle(Vector2(0.0, 0.0), self._radius, self.current_color)
+
+## Setting some initial variables when entering the Birth state. 
+## Is done only once when the ball is first created.
+func _on_birth_state_entered() -> void:
+	self._radius = collision_shape.shape.radius
+	self._direction = Vector2(randf_range(-1.0, 1.0), randf_range(-1.0, 1.0)).normalized()
+	self.velocity = self._direction * self._speed
+
+## Destroy the ball
+func _on_death_state_entered() -> void:
+	self.queue_free()
+
+
+# ----------------------------------------------------------------------------#
+# BOUNCING STATE (White).
+func _on_bouncing_state_entered() -> void:
+	self.current_color = Color.WHITE
+	queue_redraw()
+
+
+func _on_bouncing_state_processing(delta: float) -> void:
+	var motion: Vector2 = self.velocity * delta
+	var collision = move_and_collide(motion)
+
+	if collision:
+		self.velocity = self.velocity.bounce(collision.get_normal())
+		motion = self.velocity * delta
+		move_and_collide(motion)
+
+
+# ----------------------------------------------------------------------------#
+# DRAGGING STATE (Yellow).
+func _on_dragging_state_entered() -> void:
+	self.current_color = Color.YELLOW
+	queue_redraw()
+
+
+func _on_dragging_state_processing(delta: float) -> void:
+	self.global_position = get_global_mouse_position()
+	self.velocity = Input.get_last_mouse_velocity()
+
+
+# ----------------------------------------------------------------------------#
+# SELECTED STATE (Red).
+func _on_selected_state_entered() -> void:
+	self.current_color = Color.RED
+	queue_redraw()
+
+
+func _on_selected_state_physics_processing(delta: float) -> void:
+	pass
+
+
+# ----------------------------------------------------------------------------#
+# MouseOutside State
+func _on_mouse_outside_state_processing(delta: float) -> void:
+	pass # Replace with function body.
+
+
+# ----------------------------------------------------------------------------#
+# MouseInside State
+func _on_mouse_inside_state_processing(delta: float) -> void:
+	if Input.is_action_just_pressed("LMB"):
+		state_chart.send_event("left_press")
+	
+	if Input.is_action_just_released("LMB"):
+		state_chart.send_event("left_release")
+	
+	if Input.is_action_just_pressed("MMB"):
+		state_chart.send_event("destroy_ball")
+
+
+func _on_mouse_entered() -> void:
+	state_chart.send_event("mouse_entered")
+
+
+func _on_mouse_exited() -> void:
+	state_chart.send_event("mouse_exited")

+ 1 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_ball_sc.gd.uid

@@ -0,0 +1 @@
+uid://cxr1omreq1huq

+ 118 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_ball_sc.tscn

@@ -0,0 +1,118 @@
+[gd_scene load_steps=8 format=3 uid="uid://mv20ndglt1iu"]
+
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="1_lfyqp"]
+[ext_resource type="Script" uid="uid://cxr1omreq1huq" path="res://BouncingBalls/bouncing_ball_sc.gd" id="1_tgkp6"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="2_tgkp6"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_gbv8n"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_mwe3m"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="6_gbv8n"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_ala5b"]
+radius = 36.0
+
+[node name="BouncingBallSC" type="CharacterBody2D"]
+input_pickable = true
+script = ExtResource("1_tgkp6")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_ala5b")
+
+[node name="StateChart" type="Node" parent="."]
+script = ExtResource("1_lfyqp")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="ParallelState" type="Node" parent="StateChart"]
+script = ExtResource("2_tgkp6")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="Lifespan" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_gbv8n")
+initial_state = NodePath("Birth")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Birth" type="Node" parent="StateChart/ParallelState/Lifespan"]
+script = ExtResource("4_mwe3m")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="destroy_ball" type="Node" parent="StateChart/ParallelState/Lifespan/Birth"]
+script = ExtResource("6_gbv8n")
+to = NodePath("../../Death")
+event = &"destroy_ball"
+delay_in_seconds = "0.0"
+
+[node name="Death" type="Node" parent="StateChart/ParallelState/Lifespan"]
+script = ExtResource("4_mwe3m")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Modes" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_gbv8n")
+initial_state = NodePath("Bouncing")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Bouncing" type="Node" parent="StateChart/ParallelState/Modes"]
+script = ExtResource("4_mwe3m")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="to_selected" type="Node" parent="StateChart/ParallelState/Modes/Bouncing"]
+script = ExtResource("6_gbv8n")
+to = NodePath("../../Selected")
+event = &"left_press"
+delay_in_seconds = "0.0"
+
+[node name="Dragging" type="Node" parent="StateChart/ParallelState/Modes"]
+script = ExtResource("4_mwe3m")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="to_bouncing" type="Node" parent="StateChart/ParallelState/Modes/Dragging"]
+script = ExtResource("6_gbv8n")
+to = NodePath("../../Bouncing")
+event = &"left_release"
+delay_in_seconds = "0.0"
+
+[node name="Selected" type="Node" parent="StateChart/ParallelState/Modes"]
+script = ExtResource("4_mwe3m")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="to_dragging" type="Node" parent="StateChart/ParallelState/Modes/Selected"]
+script = ExtResource("6_gbv8n")
+to = NodePath("../../Dragging")
+event = &"left_press"
+delay_in_seconds = "0.0"
+
+[node name="MouseInteraction" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_gbv8n")
+initial_state = NodePath("MouseOutside")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="MouseOutside" type="Node" parent="StateChart/ParallelState/MouseInteraction"]
+script = ExtResource("4_mwe3m")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="mouse_enters" type="Node" parent="StateChart/ParallelState/MouseInteraction/MouseOutside"]
+script = ExtResource("6_gbv8n")
+to = NodePath("../../MouseInside")
+event = &"mouse_entered"
+delay_in_seconds = "0.0"
+
+[node name="MouseInside" type="Node" parent="StateChart/ParallelState/MouseInteraction"]
+script = ExtResource("4_mwe3m")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="mouse_exits" type="Node" parent="StateChart/ParallelState/MouseInteraction/MouseInside"]
+script = ExtResource("6_gbv8n")
+to = NodePath("../../MouseOutside")
+event = &"mouse_exited"
+delay_in_seconds = "0.0"
+
+[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"]
+[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"]
+[connection signal="state_entered" from="StateChart/ParallelState/Lifespan/Birth" to="." method="_on_birth_state_entered"]
+[connection signal="state_entered" from="StateChart/ParallelState/Lifespan/Death" to="." method="_on_death_state_entered"]
+[connection signal="state_entered" from="StateChart/ParallelState/Modes/Bouncing" to="." method="_on_bouncing_state_entered"]
+[connection signal="state_processing" from="StateChart/ParallelState/Modes/Bouncing" to="." method="_on_bouncing_state_processing"]
+[connection signal="state_entered" from="StateChart/ParallelState/Modes/Dragging" to="." method="_on_dragging_state_entered"]
+[connection signal="state_processing" from="StateChart/ParallelState/Modes/Dragging" to="." method="_on_dragging_state_processing"]
+[connection signal="state_entered" from="StateChart/ParallelState/Modes/Selected" to="." method="_on_selected_state_entered"]
+[connection signal="state_physics_processing" from="StateChart/ParallelState/Modes/Selected" to="." method="_on_selected_state_physics_processing"]
+[connection signal="state_processing" from="StateChart/ParallelState/MouseInteraction/MouseOutside" to="." method="_on_mouse_outside_state_processing"]
+[connection signal="state_processing" from="StateChart/ParallelState/MouseInteraction/MouseInside" to="." method="_on_mouse_inside_state_processing"]

+ 20 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_balls.gd

@@ -0,0 +1,20 @@
+extends Node2D
+
+@onready var ball_preload = preload("res://BouncingBalls/bouncing_ball_sc.tscn")
+@onready var balls: Node2D = $Balls
+
+func _ready() -> void:
+	pass
+
+func _process(delta: float) -> void:
+	self.inputhandler()
+
+func inputhandler() -> void:
+	if Input.is_action_just_pressed("RMB"):
+		self.create_ball()
+
+func create_ball() -> void:
+	var ball_instance = ball_preload.instantiate()
+	self.balls.add_child(ball_instance)
+	ball_instance.global_position = get_global_mouse_position()
+	

+ 1 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_balls.gd.uid

@@ -0,0 +1 @@
+uid://cm6n0wn7wiy0c

+ 14 - 0
Godot Exploration - 2d/BouncingBalls/bouncing_balls.tscn

@@ -0,0 +1,14 @@
+[gd_scene load_steps=3 format=3 uid="uid://dltvhg7apsp8u"]
+
+[ext_resource type="Script" uid="uid://cm6n0wn7wiy0c" path="res://BouncingBalls/bouncing_balls.gd" id="1_id6qv"]
+[ext_resource type="PackedScene" uid="uid://d37y1x1bje22r" path="res://BouncingBalls/walls.tscn" id="2_id6qv"]
+
+[node name="BouncingBalls" type="Node2D"]
+script = ExtResource("1_id6qv")
+
+[node name="Camera2D" type="Camera2D" parent="."]
+anchor_mode = 0
+
+[node name="Walls" parent="." instance=ExtResource("2_id6qv")]
+
+[node name="Balls" type="Node2D" parent="."]

+ 22 - 0
Godot Exploration - 2d/BouncingBalls/sc_semantics.gd

@@ -0,0 +1,22 @@
+extends Node2D
+@onready var state_chart: StateChart = $StateChart
+@onready var got_event1: AtomicState = $StateChart/ParallelState/InternalEventLifeline/RegionReceive1/GotEvent
+@onready var got_event2: AtomicState = $StateChart/ParallelState/InternalEventLifeline/RegionReceive2/GotEvent
+
+
+#-----------------------------------------------------------------------------#
+# MemoryProtocol
+func _on_assign_x_taken() -> void:
+	state_chart.set_expression_property("x", 1)
+
+
+#-----------------------------------------------------------------------------#
+# InputEventLifeline
+func _on_input_event_lifeline_state_entered() -> void:
+	state_chart.send_event("input0")
+
+
+#-----------------------------------------------------------------------------#
+# InternalEventLifeline
+func _on_internal_broadcast_taken() -> void:
+	state_chart.send_event("internal0")

+ 1 - 0
Godot Exploration - 2d/BouncingBalls/sc_semantics.gd.uid

@@ -0,0 +1 @@
+uid://cl75pkl5l8lau

+ 611 - 0
Godot Exploration - 2d/BouncingBalls/sc_semantics.tscn

@@ -0,0 +1,611 @@
+[gd_scene load_steps=48 format=3 uid="uid://dpw4gqwmpfe5e"]
+
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="1_0ag1n"]
+[ext_resource type="Script" uid="uid://cl75pkl5l8lau" path="res://BouncingBalls/sc_semantics.gd" id="1_7xljh"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="2_7xljh"]
+[ext_resource type="PackedScene" uid="uid://bcwkugn6v3oy7" path="res://addons/godot_state_charts/utilities/state_chart_debugger.tscn" id="2_umojb"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_umojb"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_1iswr"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_ploh2"]
+[ext_resource type="Script" uid="uid://cgpbecy3dmigh" path="res://addons/godot_state_charts/expression_guard.gd" id="7_7xljh"]
+[ext_resource type="Script" uid="uid://c41gily0vosn7" path="res://addons/godot_state_charts/guard.gd" id="8_kkye2"]
+[ext_resource type="Script" uid="uid://bpmcjqjecisib" path="res://addons/godot_state_charts/state_is_active_guard.gd" id="9_g1l08"]
+[ext_resource type="Script" uid="uid://j83wx22slqlm" path="res://addons/godot_state_charts/not_guard.gd" id="10_8bunp"]
+[ext_resource type="Script" uid="uid://pw277ma67hja" path="res://addons/godot_state_charts/all_of_guard.gd" id="11_yttom"]
+
+[sub_resource type="Resource" id="Resource_ggnlb"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive1/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_8t620"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive2/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_fsrxh"]
+script = ExtResource("10_8bunp")
+guard = SubResource("Resource_8t620")
+metadata/_custom_type_script = "uid://j83wx22slqlm"
+
+[sub_resource type="Resource" id="Resource_seyq0"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_ggnlb"), SubResource("Resource_fsrxh")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_plpm3"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive1/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_lya64"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive2/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_olyqa"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_plpm3"), SubResource("Resource_lya64")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_kkye2"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive1/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_g1l08"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive2/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_8bunp"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_kkye2"), SubResource("Resource_g1l08")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_umojb"]
+script = ExtResource("7_7xljh")
+expression = "x == 1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_1iswr"]
+script = ExtResource("7_7xljh")
+expression = "x == 0"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_7xljh"]
+script = ExtResource("7_7xljh")
+expression = "x == 1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_yttom"]
+script = ExtResource("7_7xljh")
+expression = "x==0"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_ybjrp"]
+script = ExtResource("7_7xljh")
+expression = "x==0"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_1yp0e"]
+script = ExtResource("7_7xljh")
+expression = "x==1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_kcoy7"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../MemoryProtocol/MemoryProtocol/ComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_0ryux"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../MemoryProtocolDeducer/TakeOne")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_gcgaa"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_1yp0e"), SubResource("Resource_kcoy7"), SubResource("Resource_0ryux")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_x6ssm"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InputEventLifeline/FirstComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_lnngb"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InputEventDeducer/TakeOne")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_tk11p"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_x6ssm"), SubResource("Resource_lnngb")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_pwu1q"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InternalEventLifeline/InternalEventLifeline/NextComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_w3bnh"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InternalEventDeducer/TakeOne")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_7132v"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_pwu1q"), SubResource("Resource_w3bnh")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_mxhyw"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InputEventLifeline/FirstComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_8epgs"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InputEventDeducer/TakeMany")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_ugi1x"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_mxhyw"), SubResource("Resource_8epgs")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_d1k7b"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InternalEventLifeline/InternalEventLifeline/NextComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_tav7g"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InternalEventDeducer/TakeMany")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_dy56r"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_d1k7b"), SubResource("Resource_tav7g")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_ettqd"]
+script = ExtResource("7_7xljh")
+expression = "x==1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_75830"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../MemoryProtocol/MemoryProtocol/ComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_1wlx4"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../MemoryProtocolDeducer/TakeMany")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_avby4"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_ettqd"), SubResource("Resource_75830"), SubResource("Resource_1wlx4")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[node name="SC_semantics" type="Node2D"]
+script = ExtResource("1_7xljh")
+
+[node name="StateChartDebugger" parent="." instance=ExtResource("2_umojb")]
+offset_left = -685.0
+offset_top = -476.0
+offset_right = 690.0
+offset_bottom = 464.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+initial_node_to_watch = NodePath("../StateChart")
+maximum_lines = 150
+
+[node name="StateChart" type="Node" parent="."]
+script = ExtResource("1_0ag1n")
+initial_expression_properties = {
+"x": 0
+}
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="ParallelState" type="Node" parent="StateChart"]
+script = ExtResource("2_7xljh")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="BigStepMaximality" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/BigStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/BigStepMaximality/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/BigStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/BigStepMaximality/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/BigStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="InputEventLifeline" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("FirstSmallStep")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="FirstSmallStep" type="Node" parent="StateChart/ParallelState/InputEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InputEventLifeline/FirstSmallStep"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../FirstComboStep")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="FirstComboStep" type="Node" parent="StateChart/ParallelState/InputEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InputEventLifeline/FirstComboStep"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Whole")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="Whole" type="Node" parent="StateChart/ParallelState/InputEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="InternalEventLifeline" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("2_7xljh")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="RegionReceive2" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive2"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive2/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../GotEvent")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="GotEvent" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive2"]
+script = ExtResource("4_1iswr")
+
+[node name="RegionReceive1" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive1"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive1/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../GotEvent")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="GotEvent" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive1"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="RegionBroadcast" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="internal_broadcast" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Done")
+delay_in_seconds = "0.0"
+
+[node name="Done" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="InternalEventLifeline" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NextSmallStep")
+guard = SubResource("Resource_seyq0")
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Remainder")
+guard = SubResource("Resource_olyqa")
+delay_in_seconds = "0.0"
+
+[node name="Transition3" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Queue")
+delay_in_seconds = "0.0"
+
+[node name="NextSmallStep" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Remainder" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Queue" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Queue"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NextComboStep")
+guard = SubResource("Resource_8bunp")
+delay_in_seconds = "0.0"
+
+[node name="NextComboStep" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="MemoryProtocol" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("2_7xljh")
+
+[node name="RegionAssign" type="Node" parent="StateChart/ParallelState/MemoryProtocol"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/MemoryProtocol/RegionAssign"]
+script = ExtResource("4_1iswr")
+
+[node name="assign_x" type="Node" parent="StateChart/ParallelState/MemoryProtocol/RegionAssign/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Assigned")
+delay_in_seconds = "0.0"
+
+[node name="Assigned" type="Node" parent="StateChart/ParallelState/MemoryProtocol/RegionAssign"]
+script = ExtResource("4_1iswr")
+
+[node name="MemoryProtocol" type="Node" parent="StateChart/ParallelState/MemoryProtocol"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../SmallStep")
+guard = SubResource("Resource_umojb")
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../BigStep")
+guard = SubResource("Resource_1iswr")
+delay_in_seconds = "0.0"
+
+[node name="SmallStep" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="BigStep" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol/BigStep"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../ComboStep")
+guard = SubResource("Resource_7xljh")
+delay_in_seconds = "0.0"
+
+[node name="ComboStep" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="Priority" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Composite")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Composite" type="Node" parent="StateChart/ParallelState/Priority"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Basic")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Basic" type="Node" parent="StateChart/ParallelState/Priority/Composite"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/Priority/Composite/Basic"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../../SourceChild")
+delay_in_seconds = "0.0"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/Priority/Composite"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../SourceParent")
+delay_in_seconds = "0.0"
+
+[node name="SourceParent" type="Node" parent="StateChart/ParallelState/Priority"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="SourceChild" type="Node" parent="StateChart/ParallelState/Priority"]
+script = ExtResource("4_1iswr")
+
+[node name="ComboStepMaximality" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("2_7xljh")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="InputEventDeducer" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="InternalEventDeducer" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="MemoryProtocolDeducer" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_yttom")
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_ybjrp")
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="ComboStepMaximality" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("NoComboSteps")
+
+[node name="NoComboSteps" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_gcgaa")
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_tk11p")
+delay_in_seconds = "0.0"
+
+[node name="Transition3" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_7132v")
+delay_in_seconds = "0.0"
+
+[node name="Transition4" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_ugi1x")
+delay_in_seconds = "0.0"
+
+[node name="Transition5" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_dy56r")
+delay_in_seconds = "0.0"
+
+[node name="Transition6" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_avby4")
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeMany"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeMany"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+
+[connection signal="state_entered" from="StateChart/ParallelState/InputEventLifeline" to="." method="_on_input_event_lifeline_state_entered"]
+[connection signal="taken" from="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast/Initial/internal_broadcast" to="." method="_on_internal_broadcast_taken"]
+[connection signal="state_entered" from="StateChart/ParallelState/MemoryProtocol/RegionAssign/Initial" to="." method="_on_initial_state_entered"]
+[connection signal="taken" from="StateChart/ParallelState/MemoryProtocol/RegionAssign/Initial/assign_x" to="." method="_on_assign_x_taken"]

+ 606 - 0
Godot Exploration - 2d/BouncingBalls/sc_semantics.tscn40879525.tmp

@@ -0,0 +1,606 @@
+[gd_scene load_steps=48 format=3 uid="uid://dpw4gqwmpfe5e"]
+
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="1_0ag1n"]
+[ext_resource type="Script" uid="uid://cl75pkl5l8lau" path="res://BouncingBalls/sc_semantics.gd" id="1_7xljh"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="2_7xljh"]
+[ext_resource type="PackedScene" uid="uid://bcwkugn6v3oy7" path="res://addons/godot_state_charts/utilities/state_chart_debugger.tscn" id="2_umojb"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_umojb"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_1iswr"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_ploh2"]
+[ext_resource type="Script" uid="uid://cgpbecy3dmigh" path="res://addons/godot_state_charts/expression_guard.gd" id="7_7xljh"]
+[ext_resource type="Script" uid="uid://c41gily0vosn7" path="res://addons/godot_state_charts/guard.gd" id="8_kkye2"]
+[ext_resource type="Script" uid="uid://bpmcjqjecisib" path="res://addons/godot_state_charts/state_is_active_guard.gd" id="9_g1l08"]
+[ext_resource type="Script" uid="uid://j83wx22slqlm" path="res://addons/godot_state_charts/not_guard.gd" id="10_8bunp"]
+[ext_resource type="Script" uid="uid://pw277ma67hja" path="res://addons/godot_state_charts/all_of_guard.gd" id="11_yttom"]
+
+[sub_resource type="Resource" id="Resource_ggnlb"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive1/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_8t620"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive2/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_fsrxh"]
+script = ExtResource("10_8bunp")
+guard = SubResource("Resource_8t620")
+metadata/_custom_type_script = "uid://j83wx22slqlm"
+
+[sub_resource type="Resource" id="Resource_seyq0"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_ggnlb"), SubResource("Resource_fsrxh")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_plpm3"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive1/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_lya64"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive2/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_olyqa"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_plpm3"), SubResource("Resource_lya64")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_kkye2"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive1/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_g1l08"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../RegionReceive2/GotEvent")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_8bunp"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_kkye2"), SubResource("Resource_g1l08")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_umojb"]
+script = ExtResource("7_7xljh")
+expression = "x == 1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_1iswr"]
+script = ExtResource("7_7xljh")
+expression = "x == 0"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_7xljh"]
+script = ExtResource("7_7xljh")
+expression = "x == 1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_yttom"]
+script = ExtResource("7_7xljh")
+expression = "x==0"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_ybjrp"]
+script = ExtResource("7_7xljh")
+expression = "x==0"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_1yp0e"]
+script = ExtResource("7_7xljh")
+expression = "x==1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_kcoy7"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../MemoryProtocol/MemoryProtocol/ComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_0ryux"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../MemoryProtocolDeducer/TakeOne")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_gcgaa"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_1yp0e"), SubResource("Resource_kcoy7"), SubResource("Resource_0ryux")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_x6ssm"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InputEventLifeline/FirstComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_lnngb"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InputEventDeducer/TakeOne")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_tk11p"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_x6ssm"), SubResource("Resource_lnngb")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_pwu1q"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InternalEventLifeline/InternalEventLifeline/NextComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_w3bnh"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InternalEventDeducer/TakeOne")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_7132v"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_pwu1q"), SubResource("Resource_w3bnh")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_mxhyw"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InputEventLifeline/FirstComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_8epgs"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InputEventDeducer/TakeMany")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_ugi1x"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_mxhyw"), SubResource("Resource_8epgs")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_d1k7b"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../InternalEventLifeline/InternalEventLifeline/NextComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_tav7g"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../InternalEventDeducer/TakeMany")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_dy56r"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_d1k7b"), SubResource("Resource_tav7g")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[sub_resource type="Resource" id="Resource_ettqd"]
+script = ExtResource("7_7xljh")
+expression = "x==1"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_75830"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../../MemoryProtocol/MemoryProtocol/ComboStep")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_1wlx4"]
+script = ExtResource("9_g1l08")
+state = NodePath("../../../MemoryProtocolDeducer/TakeMany")
+metadata/_custom_type_script = "uid://bpmcjqjecisib"
+
+[sub_resource type="Resource" id="Resource_avby4"]
+script = ExtResource("11_yttom")
+guards = Array[ExtResource("8_kkye2")]([SubResource("Resource_ettqd"), SubResource("Resource_75830"), SubResource("Resource_1wlx4")])
+metadata/_custom_type_script = "uid://pw277ma67hja"
+
+[node name="SC_semantics" type="Node2D"]
+script = ExtResource("1_7xljh")
+
+[node name="StateChartDebugger" parent="." instance=ExtResource("2_umojb")]
+offset_left = -685.0
+offset_top = -476.0
+offset_right = 690.0
+offset_bottom = 464.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+initial_node_to_watch = NodePath("../StateChart")
+
+[node name="StateChart" type="Node" parent="."]
+script = ExtResource("1_0ag1n")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="ParallelState" type="Node" parent="StateChart"]
+script = ExtResource("2_7xljh")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="BigStepMaximality" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/BigStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/BigStepMaximality/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/BigStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/BigStepMaximality/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/BigStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="InputEventLifeline" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("FirstSmallStep")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="FirstSmallStep" type="Node" parent="StateChart/ParallelState/InputEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InputEventLifeline/FirstSmallStep"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../FirstComboStep")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="FirstComboStep" type="Node" parent="StateChart/ParallelState/InputEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InputEventLifeline/FirstComboStep"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Whole")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="Whole" type="Node" parent="StateChart/ParallelState/InputEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="InternalEventLifeline" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("2_7xljh")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="RegionReceive1" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive1"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive1/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../GotEvent")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="GotEvent" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive1"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="RegionBroadcast" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="internal_broadcast" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Done")
+delay_in_seconds = "0.0"
+
+[node name="Done" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="RegionReceive2" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive2"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive2/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../GotEvent")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="GotEvent" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/RegionReceive2"]
+script = ExtResource("4_1iswr")
+
+[node name="InternalEventLifeline" type="Node" parent="StateChart/ParallelState/InternalEventLifeline"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NextSmallStep")
+guard = SubResource("Resource_seyq0")
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Remainder")
+guard = SubResource("Resource_olyqa")
+delay_in_seconds = "0.0"
+
+[node name="Transition3" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Queue")
+delay_in_seconds = "0.0"
+
+[node name="NextSmallStep" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Remainder" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Queue" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline/Queue"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NextComboStep")
+guard = SubResource("Resource_8bunp")
+delay_in_seconds = "0.0"
+
+[node name="NextComboStep" type="Node" parent="StateChart/ParallelState/InternalEventLifeline/InternalEventLifeline"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="MemoryProtocol" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("2_7xljh")
+
+[node name="RegionAssign" type="Node" parent="StateChart/ParallelState/MemoryProtocol"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/MemoryProtocol/RegionAssign"]
+script = ExtResource("4_1iswr")
+
+[node name="assign_x" type="Node" parent="StateChart/ParallelState/MemoryProtocol/RegionAssign/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../Assigned")
+delay_in_seconds = "0.0"
+
+[node name="Assigned" type="Node" parent="StateChart/ParallelState/MemoryProtocol/RegionAssign"]
+script = ExtResource("4_1iswr")
+
+[node name="MemoryProtocol" type="Node" parent="StateChart/ParallelState/MemoryProtocol"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../SmallStep")
+guard = SubResource("Resource_umojb")
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../BigStep")
+guard = SubResource("Resource_1iswr")
+delay_in_seconds = "0.0"
+
+[node name="SmallStep" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="BigStep" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol/BigStep"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../ComboStep")
+guard = SubResource("Resource_7xljh")
+delay_in_seconds = "0.0"
+
+[node name="ComboStep" type="Node" parent="StateChart/ParallelState/MemoryProtocol/MemoryProtocol"]
+script = ExtResource("4_1iswr")
+
+[node name="Priority" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Composite")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Composite" type="Node" parent="StateChart/ParallelState/Priority"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Basic")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Basic" type="Node" parent="StateChart/ParallelState/Priority/Composite"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/Priority/Composite/Basic"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../../SourceChild")
+delay_in_seconds = "0.0"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/Priority/Composite"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../SourceParent")
+delay_in_seconds = "0.0"
+
+[node name="SourceParent" type="Node" parent="StateChart/ParallelState/Priority"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="SourceChild" type="Node" parent="StateChart/ParallelState/Priority"]
+script = ExtResource("4_1iswr")
+
+[node name="ComboStepMaximality" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("2_7xljh")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="InputEventDeducer" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer"]
+script = ExtResource("4_1iswr")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InputEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="InternalEventDeducer" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/InternalEventDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="MemoryProtocolDeducer" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("Initial")
+
+[node name="Initial" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer/Initial"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_yttom")
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_ybjrp")
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/MemoryProtocolDeducer"]
+script = ExtResource("4_1iswr")
+
+[node name="ComboStepMaximality" type="Node" parent="StateChart/ParallelState/ComboStepMaximality"]
+script = ExtResource("3_umojb")
+initial_state = NodePath("NoComboSteps")
+
+[node name="NoComboSteps" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_gcgaa")
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_tk11p")
+delay_in_seconds = "0.0"
+
+[node name="Transition3" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeOne")
+guard = SubResource("Resource_7132v")
+delay_in_seconds = "0.0"
+
+[node name="Transition4" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_ugi1x")
+delay_in_seconds = "0.0"
+
+[node name="Transition5" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_dy56r")
+delay_in_seconds = "0.0"
+
+[node name="Transition6" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/NoComboSteps"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../TakeMany")
+guard = SubResource("Resource_avby4")
+delay_in_seconds = "0.0"
+
+[node name="TakeOne" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeOne"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="TakeMany" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality"]
+script = ExtResource("4_1iswr")
+
+[node name="Transition" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeMany"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"input0"
+delay_in_seconds = "0.0"
+
+[node name="Transition2" type="Node" parent="StateChart/ParallelState/ComboStepMaximality/ComboStepMaximality/TakeMany"]
+script = ExtResource("5_ploh2")
+to = NodePath("../../NoComboSteps")
+event = &"internal0"
+delay_in_seconds = "0.0"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+
+[connection signal="state_entered" from="StateChart/ParallelState/InputEventLifeline" to="." method="_on_input_event_lifeline_state_entered"]
+[connection signal="taken" from="StateChart/ParallelState/InternalEventLifeline/RegionBroadcast/Initial/internal_broadcast" to="." method="_on_internal_broadcast_taken"]
+[connection signal="taken" from="StateChart/ParallelState/MemoryProtocol/RegionAssign/Initial/assign_x" to="." method="_on_assign_x_taken"]

+ 18 - 0
Godot Exploration - 2d/BouncingBalls/semantics_test.gd

@@ -0,0 +1,18 @@
+extends Node2D
+@onready var state_chart: StateChart = $StateChart
+
+
+func _on_init_state_entered() -> void:
+	self.state_chart.send_event("input")
+
+
+func _on_transition_order_init_state_entered() -> void:
+	self.state_chart.send_event("event0")
+
+
+func _on_double_taken() -> void:
+	self.state_chart.set_expression_property("x", 2 * self.state_chart.get_expression_property("x"))
+
+
+func _on_decrease_taken() -> void:
+	self.state_chart.set_expression_property("x", self.state_chart.get_expression_property("x") - 2)

+ 1 - 0
Godot Exploration - 2d/BouncingBalls/semantics_test.gd.uid

@@ -0,0 +1 @@
+uid://sqfqbmefkx8i

+ 140 - 0
Godot Exploration - 2d/BouncingBalls/semantics_tests.tscn

@@ -0,0 +1,140 @@
+[gd_scene load_steps=8 format=3 uid="uid://c763m4x7kqnwh"]
+
+[ext_resource type="Script" uid="uid://sqfqbmefkx8i" path="res://BouncingBalls/semantics_test.gd" id="1_tdwt6"]
+[ext_resource type="PackedScene" uid="uid://bcwkugn6v3oy7" path="res://addons/godot_state_charts/utilities/state_chart_debugger.tscn" id="2_4hxua"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="3_agnct"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="4_6wxnv"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="5_shv4o"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="6_iw6vv"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="7_5gnxj"]
+
+[node name="semantics_tests" type="Node2D"]
+script = ExtResource("1_tdwt6")
+
+[node name="StateChartDebugger" parent="." instance=ExtResource("2_4hxua")]
+offset_left = -346.0
+offset_top = -324.0
+offset_right = 349.0
+offset_bottom = 311.0
+initial_node_to_watch = NodePath("../StateChart")
+
+[node name="StateChart" type="Node" parent="."]
+script = ExtResource("3_agnct")
+initial_expression_properties = {
+&"x": 5
+}
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="ParallelState" type="Node" parent="StateChart"]
+script = ExtResource("4_6wxnv")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="EventvsNonEvent" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("5_shv4o")
+initial_state = NodePath("Init")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Init" type="Node" parent="StateChart/ParallelState/EventvsNonEvent"]
+script = ExtResource("6_iw6vv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="input" type="Node" parent="StateChart/ParallelState/EventvsNonEvent/Init"]
+script = ExtResource("7_5gnxj")
+to = NodePath("../../GotEvent")
+event = &"input"
+delay_in_seconds = "0.0"
+
+[node name="second" type="Node" parent="StateChart/ParallelState/EventvsNonEvent/Init"]
+script = ExtResource("7_5gnxj")
+to = NodePath("../../Transitioned")
+delay_in_seconds = "0.0"
+
+[node name="Transitioned" type="Node" parent="StateChart/ParallelState/EventvsNonEvent"]
+script = ExtResource("6_iw6vv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="input" type="Node" parent="StateChart/ParallelState/EventvsNonEvent/Transitioned"]
+script = ExtResource("7_5gnxj")
+to = NodePath("../../GotEvent")
+event = &"input"
+delay_in_seconds = "0.0"
+
+[node name="GotEvent" type="Node" parent="StateChart/ParallelState/EventvsNonEvent"]
+script = ExtResource("6_iw6vv")
+
+[node name="TransitionOrder" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("5_shv4o")
+initial_state = NodePath("Init")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Init" type="Node" parent="StateChart/ParallelState/TransitionOrder"]
+script = ExtResource("6_iw6vv")
+
+[node name="first" type="Node" parent="StateChart/ParallelState/TransitionOrder/Init"]
+script = ExtResource("7_5gnxj")
+to = NodePath("../../TookFirst")
+event = &"event0"
+delay_in_seconds = "0.0"
+
+[node name="second" type="Node" parent="StateChart/ParallelState/TransitionOrder/Init"]
+script = ExtResource("7_5gnxj")
+to = NodePath("../../TookSecond")
+event = &"event0"
+delay_in_seconds = "0.0"
+
+[node name="TookFirst" type="Node" parent="StateChart/ParallelState/TransitionOrder"]
+script = ExtResource("6_iw6vv")
+
+[node name="TookSecond" type="Node" parent="StateChart/ParallelState/TransitionOrder"]
+script = ExtResource("6_iw6vv")
+
+[node name="RegionPriority" type="Node" parent="StateChart/ParallelState"]
+script = ExtResource("4_6wxnv")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="Doubling" type="Node" parent="StateChart/ParallelState/RegionPriority"]
+script = ExtResource("5_shv4o")
+initial_state = NodePath("Init")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Init" type="Node" parent="StateChart/ParallelState/RegionPriority/Doubling"]
+script = ExtResource("6_iw6vv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="double" type="Node" parent="StateChart/ParallelState/RegionPriority/Doubling/Init"]
+script = ExtResource("7_5gnxj")
+to = NodePath("../../Done")
+event = &"input"
+delay_in_seconds = "0.0"
+metadata/_custom_type_script = "uid://m8bym6l05tkl"
+
+[node name="Done" type="Node" parent="StateChart/ParallelState/RegionPriority/Doubling"]
+script = ExtResource("6_iw6vv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Decrease" type="Node" parent="StateChart/ParallelState/RegionPriority"]
+script = ExtResource("5_shv4o")
+initial_state = NodePath("Init")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Init" type="Node" parent="StateChart/ParallelState/RegionPriority/Decrease"]
+script = ExtResource("6_iw6vv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="decrease" type="Node" parent="StateChart/ParallelState/RegionPriority/Decrease/Init"]
+script = ExtResource("7_5gnxj")
+to = NodePath("../../Done")
+event = &"input"
+delay_in_seconds = "0.0"
+metadata/_custom_type_script = "uid://m8bym6l05tkl"
+
+[node name="Done" type="Node" parent="StateChart/ParallelState/RegionPriority/Decrease"]
+script = ExtResource("6_iw6vv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+
+[connection signal="state_entered" from="StateChart/ParallelState/EventvsNonEvent/Init" to="." method="_on_init_state_entered"]
+[connection signal="state_entered" from="StateChart/ParallelState/TransitionOrder/Init" to="." method="_on_transition_order_init_state_entered"]
+[connection signal="taken" from="StateChart/ParallelState/RegionPriority/Doubling/Init/double" to="." method="_on_double_taken"]
+[connection signal="taken" from="StateChart/ParallelState/RegionPriority/Decrease/Init/decrease" to="." method="_on_decrease_taken"]

+ 28 - 0
Godot Exploration - 2d/BouncingBalls/walls.tscn

@@ -0,0 +1,28 @@
+[gd_scene load_steps=5 format=3 uid="uid://d37y1x1bje22r"]
+
+[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_ybrl2"]
+normal = Vector2(0, 1)
+
+[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_38jwr"]
+normal = Vector2(1, 0)
+
+[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_t8f6e"]
+normal = Vector2(-1, 0)
+
+[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_5620d"]
+
+[node name="Walls" type="StaticBody2D"]
+
+[node name="top" type="CollisionShape2D" parent="."]
+shape = SubResource("WorldBoundaryShape2D_ybrl2")
+
+[node name="left" type="CollisionShape2D" parent="."]
+shape = SubResource("WorldBoundaryShape2D_38jwr")
+
+[node name="right" type="CollisionShape2D" parent="."]
+position = Vector2(1920, 0)
+shape = SubResource("WorldBoundaryShape2D_t8f6e")
+
+[node name="bottom" type="CollisionShape2D" parent="."]
+position = Vector2(0, 1080)
+shape = SubResource("WorldBoundaryShape2D_5620d")

+ 37 - 0
Godot Exploration - 2d/Camera2D.gd

@@ -0,0 +1,37 @@
+# This code is borrowed from work by Joshua Moelans
+# https://github.com/JoshuaMoelans/Master-Thesis-Godot-exploration (accessed March 2025)
+# Originally developed for his master's thesis at the University of Antwerp
+
+extends Camera2D
+
+var player_ref
+
+var zoomSpeed = 5
+var zoomStep = 0.01
+
+var zoomMin = 0.03
+var zoomMax = 1.5
+
+@export var new_zoom = 1.5
+
+func _ready():
+	player_ref = get_parent()
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta):
+	zoom.x = lerp(zoom.x, new_zoom, zoomSpeed * delta)
+	zoom.y = lerp(zoom.y, new_zoom, zoomSpeed * delta)
+	
+	zoom.x = clamp(zoom.x, zoomMin, zoomMax)
+	zoom.y = clamp(zoom.y, zoomMin, zoomMax)
+
+	player_ref.SPEED = 750*(zoomMax*1.5-zoom.x)
+
+	
+
+func _input(event):
+	if Input.is_action_pressed("zoom_in"):
+		new_zoom += zoomStep
+	elif Input.is_action_pressed("zoom_out"):
+		new_zoom -= zoomStep
+	new_zoom = float(clamp(new_zoom, zoomMin, zoomMax))

+ 1 - 0
Godot Exploration - 2d/Camera2D.gd.uid

@@ -0,0 +1 @@
+uid://dmv5ty0jp0usw

+ 19 - 0
Godot Exploration - 2d/FPSctr.gd

@@ -0,0 +1,19 @@
+# This code is borrowed from work by Joshua Moelans
+# https://github.com/JoshuaMoelans/Master-Thesis-Godot-exploration (accessed March 2025)
+# Originally developed for his master's thesis at the University of Antwerp
+
+extends Label
+
+var total_frames = 0
+var total_fps = 0
+var running_avg = 0
+
+func _process(delta: float) -> void:
+	var fps = Engine.get_frames_per_second()
+	
+	total_frames += 1
+	total_fps += fps
+	
+	running_avg = int(total_fps / total_frames)
+	
+	set_text("FPS: " + str(fps) + "\nAverage FPS: " + str(running_avg))

+ 1 - 0
Godot Exploration - 2d/FPSctr.gd.uid

@@ -0,0 +1 @@
+uid://dne3na8bihmog

+ 68 - 0
Godot Exploration - 2d/OutputHandler.gd

@@ -0,0 +1,68 @@
+# This code is borrowed from work by Joshua Moelans
+# https://github.com/JoshuaMoelans/Master-Thesis-Godot-exploration (accessed March 2025)
+# Originally developed for his master's thesis at the University of Antwerp
+
+extends Node
+class_name OutputHandler
+
+@export var write_buffered_game_data = false
+var gamesOutput = [] # list containing string output for games
+var gamesOutputFileNames = []
+# Called when the node enters the scene tree for the first time.
+func _ready():
+	pass # Replace with function body.
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta):
+	pass
+
+func init(instance_count):
+	for i in range(instance_count):
+		var instance_file_name = "game_instance_" + str(i) # TODO also add timestamp identifier
+		gamesOutputFileNames.append(instance_file_name)
+		var file = FileAccess.open("user://logs/" + instance_file_name + ".txt", FileAccess.WRITE) # should create empty file
+		gamesOutput.append("")
+
+# writes the given data to the given filename
+func write_to_file(filename, data, append=false):
+	if not FileAccess.file_exists("user://logs/" + filename + ".txt"):
+		var file = FileAccess.open("user://logs/" + filename + ".txt", FileAccess.WRITE) # should create empty file
+		file.store_string(data)
+		file.close()
+	else:
+		var access = FileAccess.WRITE
+		if append:
+			access = FileAccess.READ_WRITE
+		var file = FileAccess.open("user://logs/" + filename + ".txt", access)
+		if append:
+			file.seek_end()
+		file.store_string(data)
+		file.close()
+
+# writes the given data to the given instance IDs file
+func write_to_instance_file(i:int, data:String):
+	var instance_file_name = gamesOutputFileNames[i]
+	var instance_file = FileAccess.open("user://logs/" + instance_file_name + ".txt", FileAccess.READ_WRITE)
+	instance_file.seek_end()
+	instance_file.store_string(data)
+	instance_file.close()
+
+# writes the given data to the given instance file's buffer
+# gets flushed periodically to a file every 5s
+func write_to_instance_buffer(i:int, data):
+	pass
+	#gamesOutput[i] += "\n" + data
+
+# writes buffered data for each instance
+func write_buffered_data():
+	for i in range(len(gamesOutputFileNames)):
+		var data = gamesOutput[i]
+		write_to_instance_file(i, data)
+		gamesOutput[i] = ""  # reset current instance buffer
+
+
+# periodically triggers a write of all buffered data
+func _on_write_timer_timeout():
+	if write_buffered_game_data:
+		write_buffered_data()

+ 1 - 0
Godot Exploration - 2d/OutputHandler.gd.uid

@@ -0,0 +1 @@
+uid://qgcjii7lqy0a

+ 154 - 0
Godot Exploration - 2d/Platooning/Sensors/vision.gd

@@ -0,0 +1,154 @@
+extends Node2D
+class_name Vision
+
+signal danger
+signal enemy_sighted(enemy_position: Vector2)
+signal enemy_lost
+signal obstacles_sighted(data: Dictionary, ray_origin: Vector2)
+signal ready_to_fight
+
+# TODO use the check range to make infantry check the strength of the enemy.
+# If the difference is to great: flee
+# else: continue attack == do nothing else here
+
+@export var _range: int = 300
+@export var _check_range: int = 200
+@export var _fighting_range: int = 100
+@onready var behavior: StateChart = $Behavior
+@onready var vision_cone: Node2D = $VisionCone
+@onready var ray_front: RayCast2D = $VisionCone/ray_front
+
+var _team: String = ""
+var _strength: int = 0
+
+
+func set_team(team_name: String) -> void:
+	self._team = team_name
+
+
+func set_strength(strength: int) -> void:
+	self._strength = strength
+
+
+func kill() -> void:
+	for ray: RayCast2D in vision_cone.get_children():
+		if ray.is_colliding() and ray.get_collider() is InfantryBase and ray.get_collider().get_team() != self._team:
+			ray.get_collider().dies()
+
+
+func enemy_in_front() -> bool:
+	if ray_front.is_colliding() and ray_front.get_collider() is InfantryBase and ray_front.get_collider().get_team() != self._team:
+		return true
+	return false
+
+
+func enemy_present() -> bool:
+	for ray: RayCast2D in vision_cone.get_children():
+		if ray.is_colliding() and ray.get_collider() is InfantryBase and ray.get_collider().get_team() != self._team:
+			return true
+	return false
+
+
+func check_enemy_strength() -> int:
+	for ray: RayCast2D in vision_cone.get_children():
+		if ray.is_colliding() and ray.get_collider() is InfantryBase and ray.get_collider().get_team() != self._team:
+			return ray.get_collider().get_strength()
+	return -1
+
+
+func get_enemy_position():
+	var enemy_collisions: Array[Vector2] = []
+	for ray: RayCast2D in vision_cone.get_children():
+		if ray.is_colliding() and ray.get_collider() is InfantryBase and ray.get_collider().get_team() != self._team:
+			var collision = ray.get_collision_point()
+			enemy_collisions.append(collision)
+	
+	if not enemy_collisions.is_empty():
+		var average_collision = Vector2.ZERO
+		for point in enemy_collisions:
+			average_collision += point
+		average_collision /= enemy_collisions.size()
+		return average_collision
+	
+	return null
+
+
+func enemy_distance() -> float:
+	var enemy_position = self.get_enemy_position()
+	return self.global_position.distance_to(enemy_position)
+
+
+func get_obstacles() -> Dictionary:
+	var data: Dictionary = {}
+	var collisions: Array[Vector2] = []
+	var normals: Array[Vector2] = []
+	for ray: RayCast2D in vision_cone.get_children():
+		if ray.is_colliding():
+			# Colliders that are not the environment are ignored as obstacles.
+			if ray.get_collider() is not TileMapLayer:
+				continue
+		
+			var collision = ray.get_collision_point()
+			var normal = ray.get_collision_normal()
+		
+			collisions.append(collision)
+			normals.append(normal)
+		else:
+			collisions.append(to_global(ray.target_position))
+			normals.append(Vector2.ZERO)
+	data["collisions"] = collisions
+	data["normals"] = normals
+	return data
+
+
+func target_in_front(target: Vector2) -> bool:
+	var direction = self.global_position.direction_to(target)
+	var angle_diff = angle_difference(direction.angle(), self.global_rotation)
+	if abs(angle_diff) <= 0.02:
+		return true
+	return false
+
+
+#-----------------------------------------------------------------------------#
+# NoEnemy State
+func _on_no_enemy_state_processing(delta: float) -> void:
+	if self.enemy_present():
+		self.behavior.send_event("enemy_sighted")
+
+
+#-----------------------------------------------------------------------------#
+# EnemySighted State
+func _on_enemy_sighted_state_processing(delta: float) -> void:
+	if not self.enemy_present():
+		self.behavior.send_event("enemy_lost")
+	else:
+		var enemy_position = self.get_enemy_position()
+		self.enemy_sighted.emit(enemy_position)
+	
+		if self.enemy_distance() <= self._check_range and self.enemy_distance() > self._fighting_range:
+			var enemy_strength = self.check_enemy_strength()
+			
+			if enemy_strength > 0 and enemy_strength > self._strength and abs(enemy_strength - self._strength) >= 30:
+				self.danger.emit()
+		
+		if self.enemy_distance() <= self._fighting_range:
+			self.ready_to_fight.emit()
+
+
+func _on_enemy_lost_taken() -> void:
+	self.enemy_lost.emit()
+
+
+#-----------------------------------------------------------------------------#
+# Checking State
+func _on_processing_taken() -> void:
+	var data = self.get_obstacles()
+	self.obstacles_sighted.emit(data, self.global_position)
+
+
+#-----------------------------------------------------------------------------#
+# Waiting State
+
+# Callable to connect to ObstacleMap's update_done signal.
+func _on_obstacle_map_update_done() -> void:
+	self.behavior.send_event("done")

+ 1 - 0
Godot Exploration - 2d/Platooning/Sensors/vision.gd.uid

@@ -0,0 +1 @@
+uid://cprd0pf1mfpxr

+ 123 - 0
Godot Exploration - 2d/Platooning/Sensors/vision.tscn

@@ -0,0 +1,123 @@
+[gd_scene load_steps=7 format=3 uid="uid://dwt5d54cictjf"]
+
+[ext_resource type="Script" uid="uid://cprd0pf1mfpxr" path="res://Platooning/Sensors/vision.gd" id="1_s5nqj"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_77nhy"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="3_7ipin"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="4_ua3sg"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="5_a3v4x"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="6_31emf"]
+
+[node name="Vision" type="Node2D"]
+script = ExtResource("1_s5nqj")
+
+[node name="VisionCone" type="Node2D" parent="."]
+
+[node name="ray1" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(274, -120)
+collision_mask = 3
+collide_with_areas = true
+
+[node name="ray2" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(284, -90)
+collision_mask = 3
+collide_with_areas = true
+
+[node name="ray3" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(292, -60)
+collision_mask = 3
+
+[node name="ray4" type="RayCast2D" parent="VisionCone"]
+position = Vector2(0, -1)
+target_position = Vector2(298, -30)
+collision_mask = 3
+
+[node name="ray_front" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(300, 0)
+collision_mask = 3
+collide_with_areas = true
+
+[node name="ray5" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(298, 30)
+collision_mask = 3
+
+[node name="ray6" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(293, 60)
+collision_mask = 3
+collide_with_areas = true
+
+[node name="ray7" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(284, 90)
+collision_mask = 3
+
+[node name="ray8" type="RayCast2D" parent="VisionCone"]
+target_position = Vector2(274, 120)
+collision_mask = 3
+collide_with_areas = true
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_77nhy")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="Announcements" type="Node" parent="Behavior"]
+script = ExtResource("3_7ipin")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="AnnounceEnemy" type="Node" parent="Behavior/Announcements"]
+script = ExtResource("4_ua3sg")
+initial_state = NodePath("NoEnemy")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="NoEnemy" type="Node" parent="Behavior/Announcements/AnnounceEnemy"]
+script = ExtResource("5_a3v4x")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_sighted" type="Node" parent="Behavior/Announcements/AnnounceEnemy/NoEnemy"]
+script = ExtResource("6_31emf")
+to = NodePath("../../EnemySighted")
+event = &"enemy_sighted"
+delay_in_seconds = "0.0"
+
+[node name="EnemySighted" type="Node" parent="Behavior/Announcements/AnnounceEnemy"]
+script = ExtResource("5_a3v4x")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_lost" type="Node" parent="Behavior/Announcements/AnnounceEnemy/EnemySighted"]
+script = ExtResource("6_31emf")
+to = NodePath("../../NoEnemy")
+event = &"enemy_lost"
+delay_in_seconds = "0.0"
+
+[node name="AnnounceObstacles" type="Node" parent="Behavior/Announcements"]
+script = ExtResource("4_ua3sg")
+initial_state = NodePath("Checking")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Checking" type="Node" parent="Behavior/Announcements/AnnounceObstacles"]
+script = ExtResource("5_a3v4x")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="processing" type="Node" parent="Behavior/Announcements/AnnounceObstacles/Checking"]
+script = ExtResource("6_31emf")
+to = NodePath("../../Waiting")
+delay_in_seconds = "0.1"
+
+[node name="Waiting" type="Node" parent="Behavior/Announcements/AnnounceObstacles"]
+script = ExtResource("5_a3v4x")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="done" type="Node" parent="Behavior/Announcements/AnnounceObstacles/Waiting"]
+script = ExtResource("6_31emf")
+to = NodePath("../../Checking")
+event = &"done"
+delay_in_seconds = "0.0"
+
+[node name="check_again" type="Node" parent="Behavior/Announcements/AnnounceObstacles/Waiting"]
+script = ExtResource("6_31emf")
+to = NodePath("../../Checking")
+delay_in_seconds = "0.5"
+
+[connection signal="state_processing" from="Behavior/Announcements/AnnounceEnemy/NoEnemy" to="." method="_on_no_enemy_state_processing"]
+[connection signal="taken" from="Behavior/Announcements/AnnounceEnemy/NoEnemy/enemy_sighted" to="." method="_on_enemy_sighted_taken"]
+[connection signal="state_processing" from="Behavior/Announcements/AnnounceEnemy/EnemySighted" to="." method="_on_enemy_sighted_state_processing"]
+[connection signal="taken" from="Behavior/Announcements/AnnounceEnemy/EnemySighted/enemy_lost" to="." method="_on_enemy_lost_taken"]
+[connection signal="taken" from="Behavior/Announcements/AnnounceObstacles/Checking/processing" to="." method="_on_processing_taken"]

+ 62 - 0
Godot Exploration - 2d/Platooning/StrategicDeciders/infantry_strategy.gd

@@ -0,0 +1,62 @@
+extends Node2D
+class_name InfantryStrategy
+
+signal attack
+signal explore
+signal regroup
+
+@onready var behavior: StateChart = $Behavior
+
+
+#-----------------------------------------------------------------------------#
+# Explore State
+func _on_exploring_state_entered() -> void:
+	self.explore.emit()
+
+
+# Callable to connect to EnemyTracker's enemy_position_known signal
+func _on_enemy_tracker_enemy_position_known() -> void:
+	self.behavior.send_event("attack")
+
+
+func _on_explore_planner_done() -> void:
+	self.behavior.send_event("regroup")
+
+
+func _on_team_group() -> void:
+	self.behavior.send_event("regroup")
+
+
+func _on_team_explore() -> void:
+	self.behavior.send_event("explore")
+
+
+#-----------------------------------------------------------------------------#
+# Attack State
+func _on_attacking_state_entered() -> void:
+	self.attack.emit()
+
+
+#Callable to connect to EnemyTracker's enemy_position_unsure signal
+func _on_enemy_tracker_enemy_position_unsure() -> void:
+	self.behavior.send_event("enemy_lost")
+
+
+#-----------------------------------------------------------------------------#
+# NormalOperation State
+
+# Callable to connect to danger signal
+func _on_enemy_danger() -> void:
+	self.behavior.send_event("flee")
+
+
+#-----------------------------------------------------------------------------#
+# Grouping State
+func _on_grouping_state_entered() -> void:
+	self.regroup.emit()
+
+
+#-----------------------------------------------------------------------------#
+# Fleeing State
+func _on_fleeing_state_entered() -> void:
+	self.regroup.emit()

+ 1 - 0
Godot Exploration - 2d/Platooning/StrategicDeciders/infantry_strategy.gd.uid

@@ -0,0 +1 @@
+uid://k3jgt78r52tt

+ 108 - 0
Godot Exploration - 2d/Platooning/StrategicDeciders/infantry_strategy.tscn

@@ -0,0 +1,108 @@
+[gd_scene load_steps=7 format=3 uid="uid://biqkgdijpe10c"]
+
+[ext_resource type="Script" uid="uid://k3jgt78r52tt" path="res://Platooning/StrategicDeciders/infantry_strategy.gd" id="1_ejj7o"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_5tqvn"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_bsbwr"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_1xst6"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_ejj7o"]
+[ext_resource type="Script" uid="uid://cs7l2ibshw7ya" path="res://addons/godot_state_charts/history_state.gd" id="6_5tqvn"]
+
+[node name="InfantryStrategy" type="Node2D"]
+script = ExtResource("1_ejj7o")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_5tqvn")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="InfantryStrategy" type="Node" parent="Behavior"]
+script = ExtResource("3_bsbwr")
+initial_state = NodePath("NormalOperation")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="NormalOperation" type="Node" parent="Behavior/InfantryStrategy"]
+script = ExtResource("3_bsbwr")
+initial_state = NodePath("Passive")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Passive" type="Node" parent="Behavior/InfantryStrategy/NormalOperation"]
+script = ExtResource("3_bsbwr")
+initial_state = NodePath("Exploring")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Exploring" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Passive"]
+script = ExtResource("4_1xst6")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="regroup" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Passive/Exploring"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../Grouping")
+event = &"regroup"
+delay_in_seconds = "0.0"
+
+[node name="Grouping" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Passive"]
+script = ExtResource("4_1xst6")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="explore" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Passive/Grouping"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../Exploring")
+event = &"explore"
+delay_in_seconds = "0.0"
+
+[node name="PassiveHistory" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Passive"]
+script = ExtResource("6_5tqvn")
+default_state = NodePath("../Exploring")
+metadata/_custom_type_script = "uid://cs7l2ibshw7ya"
+
+[node name="attack" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Passive"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../Attacking")
+event = &"attack"
+delay_in_seconds = "0.0"
+
+[node name="Attacking" type="Node" parent="Behavior/InfantryStrategy/NormalOperation"]
+script = ExtResource("4_1xst6")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_lost" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Attacking"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../Waiting")
+event = &"enemy_lost"
+delay_in_seconds = "0.0"
+
+[node name="Waiting" type="Node" parent="Behavior/InfantryStrategy/NormalOperation"]
+script = ExtResource("4_1xst6")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_found" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Waiting"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../Attacking")
+event = &"attack"
+delay_in_seconds = "0.0"
+
+[node name="stop" type="Node" parent="Behavior/InfantryStrategy/NormalOperation/Waiting"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../Passive/PassiveHistory")
+delay_in_seconds = "5.0"
+
+[node name="flee" type="Node" parent="Behavior/InfantryStrategy/NormalOperation"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../Fleeing")
+event = &"flee"
+delay_in_seconds = "0.0"
+
+[node name="Fleeing" type="Node" parent="Behavior/InfantryStrategy"]
+script = ExtResource("4_1xst6")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="escaped" type="Node" parent="Behavior/InfantryStrategy/Fleeing"]
+script = ExtResource("5_ejj7o")
+to = NodePath("../../NormalOperation")
+delay_in_seconds = "10.0"
+
+[connection signal="state_processing" from="Behavior/InfantryStrategy/NormalOperation" to="." method="_on_normal_operation_state_processing"]
+[connection signal="state_entered" from="Behavior/InfantryStrategy/NormalOperation/Passive/Exploring" to="." method="_on_exploring_state_entered"]
+[connection signal="taken" from="Behavior/InfantryStrategy/NormalOperation/Passive/Exploring/regroup" to="." method="_on_regroup_taken"]
+[connection signal="state_entered" from="Behavior/InfantryStrategy/NormalOperation/Passive/Grouping" to="." method="_on_grouping_state_entered"]
+[connection signal="state_entered" from="Behavior/InfantryStrategy/NormalOperation/Attacking" to="." method="_on_attacking_state_entered"]
+[connection signal="state_entered" from="Behavior/InfantryStrategy/Fleeing" to="." method="_on_fleeing_state_entered"]

+ 45 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/explore_control.gd

@@ -0,0 +1,45 @@
+extends Node2D
+class_name ExploreControl
+
+signal request_exploration_target(pos: Vector2)
+signal new_destination(pos: Vector2)
+
+@onready var behavior: StateChart = $Behavior
+@onready var exploring: AtomicState = $Behavior/ExploreStrategy/Exploring
+
+
+
+#-----------------------------------------------------------------------------#
+# Exploring State
+
+# Callable to connect to Pathfinder's destination_reached signal
+func _on_pathfinder_destination_reached():
+	if exploring.active:
+		self.behavior.send_event("new_target")
+
+
+# Callable to connect to ObstacleMap's send_exploration_target signal
+func _on_obstacle_map_send_exploration_target(target) -> void:
+	if target == null:
+		self.behavior.send_event("new_target")
+		return
+	
+	if self.exploring.active:
+		self.new_destination.emit(target)
+
+
+func _on_exploring_state_entered() -> void:
+	self.request_exploration_target.emit(self.global_position)
+
+
+# Callable to connect to Strategy's attack signal
+func _on_strategy_attack() -> void:
+	self.behavior.send_event("attack")
+
+
+#-----------------------------------------------------------------------------#
+# Idle State
+
+# Callable to connect to Strategy's explore signal
+func _on_strategy_explore() -> void:
+	self.behavior.send_event("explore")

+ 1 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/explore_control.gd.uid

@@ -0,0 +1 @@
+uid://3elksirlfpdb

+ 48 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/explore_control.tscn

@@ -0,0 +1,48 @@
+[gd_scene load_steps=6 format=3 uid="uid://b6ykprim2mi83"]
+
+[ext_resource type="Script" uid="uid://3elksirlfpdb" path="res://Platooning/TacticalDeciders/explore_control.gd" id="1_msvd5"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_0dijq"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_vm5ax"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_hr7vi"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_dvidm"]
+
+[node name="ExploreControl" type="Node2D"]
+script = ExtResource("1_msvd5")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_0dijq")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="ExploreStrategy" type="Node" parent="Behavior"]
+script = ExtResource("3_vm5ax")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/ExploreStrategy"]
+script = ExtResource("4_hr7vi")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="explore" type="Node" parent="Behavior/ExploreStrategy/Idle"]
+script = ExtResource("5_dvidm")
+to = NodePath("../../Exploring")
+event = &"explore"
+delay_in_seconds = "0.0"
+
+[node name="Exploring" type="Node" parent="Behavior/ExploreStrategy"]
+script = ExtResource("4_hr7vi")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="attack" type="Node" parent="Behavior/ExploreStrategy/Exploring"]
+script = ExtResource("5_dvidm")
+to = NodePath("../../Idle")
+event = &"attack"
+delay_in_seconds = "0.0"
+metadata/_custom_type_script = "uid://m8bym6l05tkl"
+
+[node name="new_target" type="Node" parent="Behavior/ExploreStrategy/Exploring"]
+script = ExtResource("5_dvidm")
+to = NodePath("..")
+event = &"new_target"
+delay_in_seconds = "0.5"
+
+[connection signal="state_entered" from="Behavior/ExploreStrategy/Exploring" to="." method="_on_exploring_state_entered"]

+ 50 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/group_planner.gd

@@ -0,0 +1,50 @@
+extends Node2D
+class_name GroupPlanner
+
+signal clear_pathfinding
+signal new_destination(pos: Vector2)
+signal reached_regroup
+signal request_reference
+
+
+var _regroup_point: Vector2 = Vector2(0.0, 0.0)
+@export var _regroup_margin: float = 50.0
+@onready var behavior: StateChart = $Behavior
+
+
+func set_regroup_point(point: Vector2) -> void:
+	self._regroup_point = point
+
+
+# Callable to connect to Strategy's regroup signal
+func _on_strategy_regroup() -> void:
+	self.behavior.send_event("regroup")
+
+
+# Callable to connect to any signal that wants to halt the GroupPlanner
+func _stop_group_planner() -> void:
+	self.behavior.send_event("stop")
+
+
+#------------------------------------------------------------------------------#
+# Regroup State
+func _on_regroup_state_entered() -> void:
+	self.new_destination.emit(self._regroup_point)
+
+
+func _on_regroup_state_processing(delta: float) -> void:
+	if self.global_position.distance_to(self._regroup_point) <= self._regroup_margin:
+		self.clear_pathfinding.emit()
+		self.behavior.send_event("regrouped")
+
+
+#------------------------------------------------------------------------------#
+# Formation/Idle State
+func _on_formation_idle_state_entered() -> void:
+	self.reached_regroup.emit()
+	self.request_reference.emit()
+
+
+# TODO Think about how to set up and maintain formation
+func _on_request_reference() -> void:
+	pass

+ 1 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/group_planner.gd.uid

@@ -0,0 +1 @@
+uid://cr3jibr6q21gb

+ 73 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/group_planner.tscn

@@ -0,0 +1,73 @@
+[gd_scene load_steps=6 format=3 uid="uid://0bkshlrx7wpw"]
+
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="1_lgmk0"]
+[ext_resource type="Script" uid="uid://cr3jibr6q21gb" path="res://Platooning/TacticalDeciders/group_planner.gd" id="1_qhhnw"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="2_qhhnw"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="3_pa0ei"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="4_n3jpv"]
+
+[node name="GroupPlanner" type="Node2D"]
+script = ExtResource("1_qhhnw")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("1_lgmk0")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="CompoundState" type="Node" parent="Behavior"]
+script = ExtResource("2_qhhnw")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/CompoundState"]
+script = ExtResource("3_pa0ei")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="regroup" type="Node" parent="Behavior/CompoundState/Idle"]
+script = ExtResource("4_n3jpv")
+to = NodePath("../../GroupBehavior")
+event = &"regroup"
+delay_in_seconds = "0.0"
+
+[node name="GroupBehavior" type="Node" parent="Behavior/CompoundState"]
+script = ExtResource("2_qhhnw")
+initial_state = NodePath("Regroup")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Regroup" type="Node" parent="Behavior/CompoundState/GroupBehavior"]
+script = ExtResource("3_pa0ei")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="regrouped" type="Node" parent="Behavior/CompoundState/GroupBehavior/Regroup"]
+script = ExtResource("4_n3jpv")
+to = NodePath("../../Formation")
+event = &"regrouped"
+delay_in_seconds = "0.0"
+
+[node name="Formation" type="Node" parent="Behavior/CompoundState/GroupBehavior"]
+script = ExtResource("2_qhhnw")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/CompoundState/GroupBehavior/Formation"]
+script = ExtResource("3_pa0ei")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="found_reference" type="Node" parent="Behavior/CompoundState/GroupBehavior/Formation/Idle"]
+script = ExtResource("4_n3jpv")
+to = NodePath("../../Ready")
+event = &"ready"
+delay_in_seconds = "0.0"
+
+[node name="Ready" type="Node" parent="Behavior/CompoundState/GroupBehavior/Formation"]
+script = ExtResource("3_pa0ei")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="stop" type="Node" parent="Behavior/CompoundState/GroupBehavior"]
+script = ExtResource("4_n3jpv")
+to = NodePath("../../Idle")
+event = &"stop"
+delay_in_seconds = "0.0"
+
+[connection signal="state_entered" from="Behavior/CompoundState/GroupBehavior/Regroup" to="." method="_on_regroup_state_entered"]
+[connection signal="state_processing" from="Behavior/CompoundState/GroupBehavior/Regroup" to="." method="_on_regroup_state_processing"]
+[connection signal="state_entered" from="Behavior/CompoundState/GroupBehavior/Formation/Idle" to="." method="_on_formation_idle_state_entered"]

+ 50 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/infantry_attack_planner.gd

@@ -0,0 +1,50 @@
+extends Node2D
+class_name InfantryAttackPlanner
+
+signal fight
+signal new_destination(pos: Vector2)
+
+var target = null
+@onready var behavior: StateChart = $Behavior
+
+
+func _ready() -> void:
+	pass
+
+
+# Callable to connect to EnemyTracker's enemy_position_changed signal
+func _on_enemy_tracker_enemy_position_changed(pos: Vector2) -> void:
+	self.target = pos
+	self.new_destination.emit(self.target)
+
+
+#-----------------------------------------------------------------------------#
+# Idle State
+
+# Callable to connect to InfantryStrategy's attack signal
+func _on_strategy_attack() -> void:
+	self.behavior.send_event("attack")
+
+
+# Callable to connect to any signal that wants to halt the attack planner, 
+# such as an explore signal or a regroup signal.
+func _stop_attack_planner() -> void:
+	self.behavior.send_event("stop")
+
+
+#-----------------------------------------------------------------------------#
+# FollowEnemy State
+func _on_follow_enemy_state_processing(delta: float) -> void:
+	#self.aim_at.emit(self.target)
+	pass
+
+
+#-----------------------------------------------------------------------------#
+# Ready State
+func _on_fight_taken() -> void:
+	self.fight.emit()
+
+
+# Callable to connect to Infantry Vision's ready_to_fight signal
+func _on_ready_to_fight() -> void:
+	self.behavior.send_event("ready_to_fight")

+ 1 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/infantry_attack_planner.gd.uid

@@ -0,0 +1 @@
+uid://ccql1gqesskr7

+ 67 - 0
Godot Exploration - 2d/Platooning/TacticalDeciders/infantry_attack_planner.tscn

@@ -0,0 +1,67 @@
+[gd_scene load_steps=7 format=3 uid="uid://cctrepqj08p47"]
+
+[ext_resource type="Script" uid="uid://ccql1gqesskr7" path="res://Platooning/TacticalDeciders/infantry_attack_planner.gd" id="1_yvglf"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_k2rqa"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_m8mlq"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_b4ktm"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_j5p14"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="6_jexg7"]
+
+[node name="InfantryAttackPlanner" type="Node2D"]
+script = ExtResource("1_yvglf")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_k2rqa")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="CompoundState" type="Node" parent="Behavior"]
+script = ExtResource("3_m8mlq")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/CompoundState"]
+script = ExtResource("4_b4ktm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="attack" type="Node" parent="Behavior/CompoundState/Idle"]
+script = ExtResource("5_j5p14")
+to = NodePath("../../MovementAndShooting")
+event = &"attack"
+delay_in_seconds = "0.0"
+
+[node name="MovementAndShooting" type="Node" parent="Behavior/CompoundState"]
+script = ExtResource("6_jexg7")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="FollowEnemy" type="Node" parent="Behavior/CompoundState/MovementAndShooting"]
+script = ExtResource("4_b4ktm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Fight" type="Node" parent="Behavior/CompoundState/MovementAndShooting"]
+script = ExtResource("3_m8mlq")
+initial_state = NodePath("Ready")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Ready" type="Node" parent="Behavior/CompoundState/MovementAndShooting/Fight"]
+script = ExtResource("4_b4ktm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="fight" type="Node" parent="Behavior/CompoundState/MovementAndShooting/Fight/Ready"]
+script = ExtResource("5_j5p14")
+to = NodePath("../../Fighting")
+event = &"ready_to_fight"
+delay_in_seconds = "0.0"
+
+[node name="Fighting" type="Node" parent="Behavior/CompoundState/MovementAndShooting/Fight"]
+script = ExtResource("4_b4ktm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="stop" type="Node" parent="Behavior/CompoundState/MovementAndShooting"]
+script = ExtResource("5_j5p14")
+to = NodePath("../../Idle")
+event = &"stop"
+delay_in_seconds = "0.0"
+
+[connection signal="taken" from="Behavior/CompoundState/Idle/attack" to="." method="_on_attack_taken"]
+[connection signal="state_processing" from="Behavior/CompoundState/MovementAndShooting/FollowEnemy" to="." method="_on_follow_enemy_state_processing"]
+[connection signal="taken" from="Behavior/CompoundState/MovementAndShooting/Fight/Ready/fight" to="." method="_on_fight_taken"]

+ 60 - 0
Godot Exploration - 2d/Platooning/communicator.gd

@@ -0,0 +1,60 @@
+extends Node2D
+class_name Communicator
+
+signal send(message: Dictionary)
+
+const DELAY: float = 2.0
+
+@onready var behavior: StateChart = $Behavior
+
+var outgoing: Array = []
+var incoming: Array = []
+
+func _ready() -> void:
+	self.behavior.set_expression_property.call_deferred("delay", DELAY)
+
+
+# Method to add message to communicators outgoing channel
+func _add_outgoing(message: Dictionary) -> void:
+	self.outgoing.append(message)
+	self.behavior.send_event("send")
+
+
+# Method to add message to communicators incoming channel
+func _add_incoming(message: Dictionary) -> void:
+	self.incoming.append(message)
+	self.behavior.send_event("receive")
+
+
+#------------------------------------------------------------------------------#
+# Sender/Idle State
+func _on_sender_idle_state_entered() -> void:
+	if not self.outgoing.is_empty():
+		self.behavior.send_event("send")
+
+
+#------------------------------------------------------------------------------#
+# Sender/Send State
+func _on_send_state_entered() -> void:
+	var message = self.outgoing.pop_front()
+	self.send.emit(message)
+	
+
+
+#------------------------------------------------------------------------------#
+# Receiver/Idle State
+func _on_receiver_idle_state_entered() -> void:
+	if not self.incoming.is_empty():
+		self.behavior.send_event("receive")
+
+
+#------------------------------------------------------------------------------#
+# Receiver/Process State
+func _on_process_state_entered() -> void:
+	var message: Dictionary = self.incoming.pop_front()
+	
+	# TODO implement handler of incoming traffic to send data to appropriate component
+	match message.tag:
+		"map": pass
+		"enemy": pass
+		"...": pass

+ 1 - 0
Godot Exploration - 2d/Platooning/communicator.gd.uid

@@ -0,0 +1 @@
+uid://drgnwv3bhm3v2

+ 70 - 0
Godot Exploration - 2d/Platooning/communicator.tscn

@@ -0,0 +1,70 @@
+[gd_scene load_steps=7 format=3 uid="uid://4upow5w6yesc"]
+
+[ext_resource type="Script" uid="uid://drgnwv3bhm3v2" path="res://Platooning/communicator.gd" id="1_km1ck"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_xwngf"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_5js2r"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="3_xwngf"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_j22e8"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_ic1as"]
+
+[node name="Communicator" type="Node2D"]
+script = ExtResource("1_km1ck")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_xwngf")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="Communication" type="Node" parent="Behavior"]
+script = ExtResource("3_xwngf")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="Sender" type="Node" parent="Behavior/Communication"]
+script = ExtResource("3_5js2r")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/Communication/Sender"]
+script = ExtResource("4_j22e8")
+
+[node name="send" type="Node" parent="Behavior/Communication/Sender/Idle"]
+script = ExtResource("5_ic1as")
+to = NodePath("../../Send")
+event = &"send"
+delay_in_seconds = "delay"
+
+[node name="Send" type="Node" parent="Behavior/Communication/Sender"]
+script = ExtResource("4_j22e8")
+
+[node name="finish" type="Node" parent="Behavior/Communication/Sender/Send"]
+script = ExtResource("5_ic1as")
+to = NodePath("../../Idle")
+delay_in_seconds = "0.0"
+
+[node name="Receiver" type="Node" parent="Behavior/Communication"]
+script = ExtResource("3_5js2r")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/Communication/Receiver"]
+script = ExtResource("4_j22e8")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="receive" type="Node" parent="Behavior/Communication/Receiver/Idle"]
+script = ExtResource("5_ic1as")
+to = NodePath("../../Process")
+event = &"receive"
+delay_in_seconds = "0.0"
+
+[node name="Process" type="Node" parent="Behavior/Communication/Receiver"]
+script = ExtResource("4_j22e8")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="finish" type="Node" parent="Behavior/Communication/Receiver/Process"]
+script = ExtResource("5_ic1as")
+to = NodePath("../../Idle")
+delay_in_seconds = "0.0"
+
+[connection signal="state_entered" from="Behavior/Communication/Sender/Idle" to="." method="_on_sender_idle_state_entered"]
+[connection signal="state_entered" from="Behavior/Communication/Sender/Send" to="." method="_on_send_state_entered"]
+[connection signal="state_entered" from="Behavior/Communication/Receiver/Idle" to="." method="_on_receiver_idle_state_entered"]
+[connection signal="state_entered" from="Behavior/Communication/Receiver/Process" to="." method="_on_process_state_entered"]

+ 167 - 0
Godot Exploration - 2d/Platooning/infantry.gd

@@ -0,0 +1,167 @@
+extends InfantryBase
+class_name Infantry
+
+signal waypoint_reached
+signal clear_pathfinding
+signal share_map(map: Grid2D)
+
+var target_waypoint: Vector2
+var _map_initialized: bool = false
+@export var nav_margin: float = 15.0
+
+@onready var behavior: StateChart = $Behavior
+@onready var enemy_tracker: EnemyTracker = $EnemyTracker
+@onready var explore_planner: ExplorePlanner = $ExplorePlanner
+@onready var group_planner: GroupPlanner = $GroupPlanner
+@onready var infantry_attack_planner: InfantryAttackPlanner = $InfantryAttackPlanner
+@onready var infantry_strategy: InfantryStrategy = $InfantryStrategy
+@onready var obstacle_map: ObstacleMap = $ObstacleMap
+@onready var pathfinder: Pathfinder = $Pathfinder
+@onready var vision: Vision = $Vision
+
+
+func _ready() -> void:
+	# Propagate the team and strength to the vision component. Vision component can compare these to other units.
+	self.vision.set_team(self.TEAM)
+	self.vision.set_strength(self.STRENGTH)
+	
+	# Connecting all signals between components of the infantry soldier -------#
+	
+	self.clear_pathfinding.connect(self.pathfinder._on_clear_pathfinding)
+	self.waypoint_reached.connect(self.pathfinder._on_waypoint_reached)
+	
+	self.enemy_tracker.enemy_position_changed.connect(self.infantry_attack_planner._on_enemy_tracker_enemy_position_changed)
+	self.enemy_tracker.enemy_position_known.connect(self.infantry_strategy._on_enemy_tracker_enemy_position_known)
+	self.enemy_tracker.enemy_position_unsure.connect(self.infantry_strategy._on_enemy_tracker_enemy_position_unsure)
+	
+	self.explore_planner.new_destination.connect(self.pathfinder._on_new_destination)
+	self.explore_planner.request_exploration_target.connect(self.obstacle_map._on_explore_planner_request_exploration_target)
+	self.explore_planner.done.connect(self.infantry_strategy._on_explore_planner_done)
+	
+	self.group_planner.clear_pathfinding.connect(self.pathfinder._on_clear_pathfinding)
+	self.group_planner.new_destination.connect(self.pathfinder._on_new_destination)
+	self.group_planner.reached_regroup.connect(self._share_map_at_regroup_point)
+	
+	self.infantry_attack_planner.new_destination.connect(self.pathfinder._on_new_destination)
+	self.infantry_attack_planner.fight.connect(self._on_fight)
+	
+	self.infantry_strategy.attack.connect(self.explore_planner._stop_explore_planner)
+	self.infantry_strategy.attack.connect(self.group_planner._stop_group_planner)
+	self.infantry_strategy.attack.connect(self.infantry_attack_planner._on_strategy_attack)
+	self.infantry_strategy.explore.connect(self.explore_planner._on_strategy_explore)
+	self.infantry_strategy.explore.connect(self.group_planner._stop_group_planner)
+	self.infantry_strategy.explore.connect(self.infantry_attack_planner._stop_attack_planner)
+	self.infantry_strategy.regroup.connect(self.explore_planner._stop_explore_planner)
+	self.infantry_strategy.regroup.connect(self.group_planner._on_strategy_regroup)
+	self.infantry_strategy.regroup.connect(self.infantry_attack_planner._stop_attack_planner)
+	
+	self.obstacle_map.send_exploration_target.connect(self.explore_planner._on_obstacle_map_send_exploration_target)
+	self.obstacle_map.send_path.connect(self.pathfinder._on_obstacle_map_send_path)
+	self.obstacle_map.update_done.connect(self.vision._on_obstacle_map_update_done)
+	
+	self.pathfinder.destination_reached.connect(self.explore_planner._on_pathfinder_destination_reached)
+	self.pathfinder.new_waypoint.connect(self._on_pathfinder_new_waypoint)
+	self.pathfinder.request_path.connect(self.obstacle_map._on_pathfinder_request_path)
+	
+	self.vision.danger.connect(self.infantry_strategy._on_enemy_danger)
+	self.vision.enemy_sighted.connect(self.enemy_tracker._on_enemy_sighted)
+	self.vision.enemy_lost.connect(self.enemy_tracker._on_enemy_lost)
+	self.vision.obstacles_sighted.connect(self.obstacle_map._on_obstacle_sighted)
+	self.vision.ready_to_fight.connect(self.infantry_attack_planner._on_ready_to_fight)
+	self.vision.ready_to_fight.connect(self._on_ready_to_fight)
+	
+	#--------------------------------------------------------------------------#
+
+
+func dies() -> void:
+	# TODO do some more before removing the agent from the scene.
+	# Add information to collector, make sure this agent is not referenced by others
+	self.queue_free()
+
+
+func target_waypoint_reached() -> bool:
+	var x_dif = abs(self.global_position.x - self.target_waypoint.x)
+	var y_dif = abs(self.global_position.y - self.target_waypoint.y)
+	
+	if x_dif <= self.nav_margin and y_dif <= self.nav_margin:
+		return true
+	
+	return false
+
+
+func target_blocked() -> bool:
+	var target_tile = self.obstacle_map.grid.global_to_tile(self.target_waypoint)
+	if self.obstacle_map.grid.get_tile(target_tile).val == 1:
+		return true
+	return false
+
+
+
+# Callable to connect to Pathfinder's new_waypoint signal
+func _on_pathfinder_new_waypoint(waypoint: Vector2) -> void:
+	self.target_waypoint = waypoint
+	self.behavior.send_event("new_waypoint")
+
+
+# Callable to connect to Vision's ready_to_fight signal
+func _on_ready_to_fight() -> void:
+	self.clear_pathfinding.emit()
+	self.behavior.send_event("stop")
+
+
+func _on_fight() -> void:
+	var enemy_strength = self.vision.check_enemy_strength()
+	if enemy_strength >= self.get_strength():
+		# Lose fight resulting in the death of this agent
+		self.dies()
+	else:
+		self.vision.kill()
+
+
+func _share_map_at_regroup_point() -> void:
+	self.share_map.emit(self.obstacle_map.grid)
+
+
+func _on_map_receive(map: Grid2D) -> void:
+	self.obstacle_map.compare_map(map)
+
+
+#-----------------------------------------------------------------------------#
+# RotateBody State
+func _on_rotate_body_state_processing(delta: float) -> void:
+	var target_direction = self.global_position.direction_to(self.target_waypoint)
+	self.global_rotation = lerp_angle(self.global_rotation, target_direction.angle(), delta * 5)
+	
+	if self.vision.target_in_front(self.target_waypoint):
+		self.behavior.send_event("move")
+
+
+#-----------------------------------------------------------------------------#
+# Moving State
+func _on_moving_state_physics_processing(delta: float) -> void:
+	if self.target_waypoint_reached():
+		self.behavior.send_event("waypoint_reached")
+		return
+	
+	if self.target_blocked():
+		self.clear_pathfinding.emit()
+		self.behavior.send_event("waypoint_reached")
+		return
+	
+	var target_direction = self.global_position.direction_to(self.target_waypoint)
+	self.velocity = target_direction * self.SPEED
+	
+	var motion = self.velocity * delta
+	self.move_and_collide(motion)
+
+
+func _on_waypoint_reached_taken() -> void:
+	self.waypoint_reached.emit()
+
+
+func _on_infantry_movement_state_entered() -> void:
+	if not self._map_initialized:
+		var world: PlatoonTestInstance = self.get_parent()
+		var dimensions = world.get_map_dimensions()
+		self.obstacle_map.build_empty_map(dimensions)
+		self._map_initialized = true

+ 1 - 0
Godot Exploration - 2d/Platooning/infantry.gd.uid

@@ -0,0 +1 @@
+uid://dw1ulgmneo6td

+ 116 - 0
Godot Exploration - 2d/Platooning/infantry.tscn

@@ -0,0 +1,116 @@
+[gd_scene load_steps=17 format=3 uid="uid://blmnl0b0h0tu0"]
+
+[ext_resource type="Script" uid="uid://dw1ulgmneo6td" path="res://Platooning/infantry.gd" id="1_13v80"]
+[ext_resource type="Texture2D" uid="uid://dh10j38lptsbq" path="res://assets/kenney_top-down-shooter/PNG/soldier.png" id="2_jqq1a"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="3_flc01"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="4_qfajf"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="5_6fisv"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="6_76fvj"]
+[ext_resource type="PackedScene" uid="uid://cxhtqmgsfi31e" path="res://TankWars/TacticalDeciders/pathfinder.tscn" id="8_1m8tv"]
+[ext_resource type="PackedScene" uid="uid://b0i3goxsi5vte" path="res://TankWars/Memorizers/enemy_tracker.tscn" id="8_6fisv"]
+[ext_resource type="PackedScene" uid="uid://bsfcprv20pdnf" path="res://TankWars/TacticalDeciders/explore_planner.tscn" id="9_76fvj"]
+[ext_resource type="PackedScene" uid="uid://dwt5d54cictjf" path="res://Platooning/Sensors/vision.tscn" id="9_t0g75"]
+[ext_resource type="PackedScene" uid="uid://0bkshlrx7wpw" path="res://Platooning/TacticalDeciders/group_planner.tscn" id="10_1m8tv"]
+[ext_resource type="PackedScene" uid="uid://biqkgdijpe10c" path="res://Platooning/StrategicDeciders/infantry_strategy.tscn" id="10_3kq28"]
+[ext_resource type="PackedScene" uid="uid://cctrepqj08p47" path="res://Platooning/TacticalDeciders/infantry_attack_planner.tscn" id="10_76fvj"]
+[ext_resource type="PackedScene" uid="uid://bcwkugn6v3oy7" path="res://addons/godot_state_charts/utilities/state_chart_debugger.tscn" id="10_ywxye"]
+[ext_resource type="PackedScene" uid="uid://cxa4n46xuic4k" path="res://TankWars/Memorizers/obstacle_map.tscn" id="11_1m8tv"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_13v80"]
+radius = 23.0
+
+[node name="Infantry" type="CharacterBody2D"]
+collision_layer = 2
+script = ExtResource("1_13v80")
+
+[node name="sprite" type="Sprite2D" parent="."]
+texture = ExtResource("2_jqq1a")
+
+[node name="collision" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_13v80")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("3_flc01")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="InfantryMovement" type="Node" parent="Behavior"]
+script = ExtResource("4_qfajf")
+initial_state = NodePath("Idle")
+
+[node name="Idle" type="Node" parent="Behavior/InfantryMovement"]
+script = ExtResource("5_6fisv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="new_waypoint" type="Node" parent="Behavior/InfantryMovement/Idle"]
+script = ExtResource("6_76fvj")
+to = NodePath("../../RotateBody")
+event = &"new_waypoint"
+delay_in_seconds = "0.0"
+
+[node name="RotateBody" type="Node" parent="Behavior/InfantryMovement"]
+script = ExtResource("5_6fisv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="move" type="Node" parent="Behavior/InfantryMovement/RotateBody"]
+script = ExtResource("6_76fvj")
+to = NodePath("../../Moving")
+event = &"move"
+delay_in_seconds = "0.0"
+
+[node name="stop" type="Node" parent="Behavior/InfantryMovement/RotateBody"]
+script = ExtResource("6_76fvj")
+to = NodePath("../../Idle")
+event = &"stop"
+delay_in_seconds = "0.0"
+
+[node name="Moving" type="Node" parent="Behavior/InfantryMovement"]
+script = ExtResource("5_6fisv")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="waypoint_reached" type="Node" parent="Behavior/InfantryMovement/Moving"]
+script = ExtResource("6_76fvj")
+to = NodePath("../../Idle")
+event = &"waypoint_reached"
+delay_in_seconds = "0.0"
+
+[node name="stop" type="Node" parent="Behavior/InfantryMovement/Moving"]
+script = ExtResource("6_76fvj")
+to = NodePath("../../Idle")
+event = &"stop"
+delay_in_seconds = "0.0"
+
+[node name="EnemyTracker" parent="." instance=ExtResource("8_6fisv")]
+
+[node name="ExplorePlanner" parent="." instance=ExtResource("9_76fvj")]
+
+[node name="GroupPlanner" parent="." instance=ExtResource("10_1m8tv")]
+
+[node name="InfantryAttackPlanner" parent="." instance=ExtResource("10_76fvj")]
+
+[node name="InfantryStrategy" parent="." instance=ExtResource("10_3kq28")]
+
+[node name="ObstacleMap" parent="." instance=ExtResource("11_1m8tv")]
+
+[node name="Pathfinder" parent="." instance=ExtResource("8_1m8tv")]
+
+[node name="Vision" parent="." instance=ExtResource("9_t0g75")]
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+visible = false
+
+[node name="StateChartDebugger" parent="CanvasLayer" instance=ExtResource("10_ywxye")]
+anchors_preset = 1
+anchor_left = 1.0
+anchor_bottom = 0.0
+offset_left = -542.0
+offset_bottom = 628.0
+grow_horizontal = 0
+grow_vertical = 1
+size_flags_horizontal = 8
+size_flags_vertical = 0
+initial_node_to_watch = NodePath("../../InfantryStrategy")
+
+[connection signal="state_entered" from="Behavior/InfantryMovement" to="." method="_on_infantry_movement_state_entered"]
+[connection signal="state_processing" from="Behavior/InfantryMovement/RotateBody" to="." method="_on_rotate_body_state_processing"]
+[connection signal="state_physics_processing" from="Behavior/InfantryMovement/Moving" to="." method="_on_moving_state_physics_processing"]
+[connection signal="taken" from="Behavior/InfantryMovement/Moving/waypoint_reached" to="." method="_on_waypoint_reached_taken"]

+ 13 - 0
Godot Exploration - 2d/Platooning/infantry_base.gd

@@ -0,0 +1,13 @@
+extends CharacterBody2D
+class_name InfantryBase
+
+var STRENGTH: int = randi_range(20, 80)
+var SPEED: int = 200
+
+@export var TEAM: String = "ALLY"
+
+func get_team() -> String:
+	return self.TEAM
+
+func get_strength() -> int:
+	return self.STRENGTH

+ 1 - 0
Godot Exploration - 2d/Platooning/infantry_base.gd.uid

@@ -0,0 +1 @@
+uid://dsabv446vl8u

+ 29 - 0
Godot Exploration - 2d/Platooning/platoon_test_world.gd

@@ -0,0 +1,29 @@
+extends Node2D
+class_name PlatoonTestInstance
+
+@export var TILE_SIZE: int = 64
+@onready var area: CollisionShape2D = $Bounds/area
+
+
+func _ready() -> void:
+	# Set up information and signal connections for 
+	for child in self.get_children():
+		if child is Infantry:
+			for other in self.get_children():
+				if other is Infantry and child.get_team() == other.get_team():
+					child.share_map.connect(other._on_map_receive)
+				if other is RegroupPoint and child.get_team() == other.get_team():
+					child.group_planner.set_regroup_point(other.global_position)
+					child.group_planner.reached_regroup.connect(other._request_group)
+					other.group.connect(child.infantry_strategy._on_team_group)
+					other.explore.connect(child.infantry_strategy._on_team_explore)
+
+
+func get_map_dimensions() -> Dictionary:
+	var dimensions: Dictionary = {}
+	
+	dimensions["size"] = area.get_shape().get_rect().size
+	dimensions["tile_size"] = TILE_SIZE
+	dimensions["instance_offset"] = position
+	
+	return dimensions

+ 1 - 0
Godot Exploration - 2d/Platooning/platoon_test_world.gd.uid

@@ -0,0 +1 @@
+uid://bmuinyb4rfcut

Datei-Diff unterdrückt, da er zu groß ist
+ 54 - 0
Godot Exploration - 2d/Platooning/platoon_test_world.tscn


+ 20 - 0
Godot Exploration - 2d/Platooning/regroup_point.gd

@@ -0,0 +1,20 @@
+extends Sprite2D
+class_name RegroupPoint
+
+signal group
+signal explore
+
+@export var _team: String = "ALLY"
+
+func get_team() -> String:
+	return self._team
+
+func _input(event: InputEvent) -> void:
+	if event.is_action_pressed("group"):
+		self.group.emit()
+	
+	if event.is_action_pressed("explore"):
+		self.explore.emit()
+
+func _request_group() -> void:
+	self.group.emit()

+ 1 - 0
Godot Exploration - 2d/Platooning/regroup_point.gd.uid

@@ -0,0 +1 @@
+uid://5xh2vdn7n8nn

+ 8 - 0
Godot Exploration - 2d/Platooning/regroup_point.tscn

@@ -0,0 +1,8 @@
+[gd_scene load_steps=3 format=3 uid="uid://dcu7do534160e"]
+
+[ext_resource type="Texture2D" uid="uid://bx27n5v3rn0re" path="res://assets/kenney_top-down-shooter/PNG/target.png" id="1_5v4cr"]
+[ext_resource type="Script" uid="uid://5xh2vdn7n8nn" path="res://Platooning/regroup_point.gd" id="2_oujy0"]
+
+[node name="RegroupPoint" type="Sprite2D"]
+texture = ExtResource("1_5v4cr")
+script = ExtResource("2_oujy0")

Datei-Diff unterdrückt, da er zu groß ist
+ 17 - 0
Godot Exploration - 2d/Platooning/screenshot_map.tscn


+ 70 - 0
Godot Exploration - 2d/References/AStar.gd

@@ -0,0 +1,70 @@
+class_name AStar
+extends RefCounted
+
+var NEIGHBOR_DIRECTIONS = [Vector2i(1,0), Vector2i(0,1), Vector2i(-1,0), Vector2i(0,-1)]
+
+# Estimate cost between two grid positions by using Manhattan distance.
+func heuristic(a: Vector2i, b: Vector2i):
+	return abs(a.x - b.x) + abs(a.y - b.y)
+
+func astar(grid: Grid2D, start: Vector2i, goal: Vector2i) -> Array[Vector2i]:
+	var frontier: PriorityQueue = PriorityQueue.new()
+	var reached: Dictionary = {}
+	
+	var start_node = AStarNode.new(start, 0, heuristic(start, goal))
+	frontier.push(start_node)
+	
+	while not frontier.empty():
+		var current: AStarNode = frontier.pop()
+		
+		# Early exit when frontier has found the goal position
+		if current.pos == goal:
+			# Reconstruct path
+			var path: Array[Vector2i] = []
+			while current:
+				path.append(current.pos)
+				current = current.parent
+			path.reverse()
+			return path
+			# TODO maybe not reverse here, so we can just use pop_back to empty out path
+		
+		reached[current.pos] = true
+		
+		for direction in NEIGHBOR_DIRECTIONS:
+			var neighbor_pos: Vector2i = current.pos + direction
+			
+			# Grid Bounds Check
+			if 0 <= neighbor_pos.x and neighbor_pos.x < grid._size.x and 0 <= neighbor_pos.y and neighbor_pos.y < grid._size.y:
+				# Skip neighbor if it is an obstacle or is already reached
+				if grid.get_tile(neighbor_pos).val != 0 or neighbor_pos in reached.keys():
+					continue
+				
+				var g = current.g_cost + 1
+				var h = heuristic(neighbor_pos, goal)
+				
+				var neighbor_node: AStarNode = AStarNode.new(neighbor_pos, g, h, current)
+				
+				# Check frontier for same node with a lower f_cost.
+				# Skip insertion of neighbor if it is already in the frontier
+				var skip = false
+				for n in frontier._data:
+					if n.pos == neighbor_pos and n.val <= neighbor_node.val:
+						skip = true
+						break
+				
+				if not skip:
+					frontier.push(neighbor_node)
+				
+	return [] # No path was found
+
+class AStarNode extends HeapNode:
+	var g_cost: int
+	var h_cost: int
+	var parent: AStarNode
+	
+	func _init(pos: Vector2i, g: int, h: int, parent: AStarNode = null) -> void:
+		self.pos = pos
+		self.g_cost = g
+		self.h_cost = h
+		self.val = g + h
+		self.parent = parent

+ 1 - 0
Godot Exploration - 2d/References/AStar.gd.uid

@@ -0,0 +1 @@
+uid://21ju5v33k4ls

+ 257 - 0
Godot Exploration - 2d/References/Grid2D.gd

@@ -0,0 +1,257 @@
+class_name Grid2D
+extends RefCounted
+
+var NEIGHBOR_DIRECTIONS = [Vector2i(1,0), Vector2i(0,1), Vector2i(-1,0), Vector2i(0,-1)]
+
+var _data: Array[Grid2DTile]
+var _size: Vector2i
+var _tile_size: int
+var _grid_origin: Vector2
+
+func _init(size: Vector2i, tile_size: int, origin: Vector2, start_pos: Vector2, data: Array = []) -> void:
+	if not data.is_empty():
+		assert(data.size() == size.x * size.y, "Given data does not match the specified size.")
+	
+	if not data.is_empty() and data.size() == size.x * size.y:
+		self._data = data
+	else:
+		self._data = []
+	
+	self._size = size
+	self._tile_size = tile_size
+	self._grid_origin = origin
+	
+	# Fill all tiles of the map in with UNKOWN (-1).
+	var cell_count = _size.x * _size.y
+	for i in cell_count:
+		var x: int = i % self._size.x
+		var y: int = i / self._size.x
+		var tile: Grid2DTile = Grid2DTile.new(x, y, -1)
+		_data.append(tile)
+	
+	# Fill in the starting position as FREE (0). The agent using this map will start exploring from this position.
+	self.set_tile_value(self.global_to_tile(start_pos), 0)
+
+func get_tile(pos: Vector2i) -> Grid2DTile:
+	var index = pos.x + (pos.y * self._size.x)
+	return self._data[index]
+
+func set_tile_value(pos: Vector2i, value: int) -> void:
+	var index = pos.x + (pos.y * self._size.x)
+	if index < 0 or index >= self._data.size():
+		return
+	self._data[index].val = value
+
+func update_map(data: Dictionary, ray_origin: Vector2) -> void:
+	var collisions: Array[Vector2] = data["collisions"]
+	var normals: Array[Vector2] = data["normals"]
+	
+	for i in range(collisions.size()):
+		var free_tiles = self.dda_ray_tiles(ray_origin, collisions[i] + normals[i])
+		for tile in free_tiles:
+			self.set_tile_value(tile, 0)
+		
+		if normals[i] != Vector2.ZERO:
+			var obstacle_tile = self.global_to_tile(collisions[i] - normals[i])
+			self.set_tile_value(obstacle_tile, 1)
+
+func manhattan_distance(a: Vector2i, b: Vector2i):
+	return abs(a.x - b.x) + abs(a.y - b.y)
+
+func global_to_tile(global_pos: Vector2) -> Vector2i:
+	# TODO check if global_pos is inside the bounds of this grid
+	var instance_position = global_pos - self._grid_origin
+	var tile_x: int = floori(instance_position.x / self._tile_size)
+	var tile_y: int = floori(instance_position.y / self._tile_size)
+	
+	return Vector2i(tile_x, tile_y)
+
+func tile_to_global(grid_pos: Vector2i) -> Vector2:
+	# TODO check if given grid position is within the bounds of this grid
+	var global_x: float = self._grid_origin.x + (grid_pos.x * self._tile_size) + (self._tile_size/2)
+	var global_y: float = self._grid_origin.y + (grid_pos.y * self._tile_size) + (self._tile_size/2)
+	
+	return Vector2(global_x, global_y)
+
+func path_to_global(path: Array[Vector2i]) -> Array[Vector2]:
+	var global_path: Array[Vector2] = []
+	
+	for waypoint in path:
+		global_path.append(self.tile_to_global(waypoint))
+	
+	return global_path
+
+func explored() -> bool:
+	return self.get_frontier_tiles().is_empty()
+
+func get_neighbor_values(pos: Vector2i) -> Dictionary[Vector2i, int]:
+	var values: Dictionary[Vector2i, int] = {}
+	for direction in NEIGHBOR_DIRECTIONS:
+		var neighbor: Vector2i = pos + direction
+		if 0 <= neighbor.x and neighbor.x < self._size.x and 0 <= neighbor.y and neighbor.y < self._size.y:
+			values[neighbor] = self.get_tile(neighbor).val
+	
+	return values
+
+func get_frontier_tiles() -> Array[Vector2i]:
+	var frontier: Array[Vector2i] = []
+	for y in self._size.y:
+		for x in self._size.x:
+			var pos: Vector2i = Vector2i(x, y)
+			if self.get_tile(pos).val == 0 and self.get_neighbor_values(pos).values().find(-1) != -1:
+				frontier.append(pos)
+	return frontier
+
+func compute_utility(pos: Vector2i, agent_pos: Vector2i, radius: int = 2) -> float:
+	# TODO update utility calculation
+	# NOW: just counting the amount of unknown tiles in a radius around the given tile position
+	# IDEAS: 
+	# 	take position (and/or direction) of the agent into acount ( higher utility for closer tiles and in same direction )
+	# 	keep count of number of visits to a tile. penalize utility for visiting a tile very often ( fresh explore )
+	var count = 0
+	for dx in range(-radius, radius+1):
+		for dy in range(-radius, radius+1):
+			var neighbor: Vector2i = pos + Vector2i(dx, dy)
+			if 0 <= neighbor.x and neighbor.x < self._size.x and 0 <= neighbor.y and neighbor.y < self._size.y and self.get_tile(neighbor).val == -1:
+				count += 2
+	
+	var utility = count
+	var distance = self.manhattan_distance(agent_pos, pos)
+	if distance > 0:
+		utility -= distance
+	
+	return utility
+
+func get_frontier_utilities(agent_pos: Vector2i) -> MaxHeap:
+	var frontier_utilities: MaxHeap = MaxHeap.new() 
+	var frontier_tiles: Array[Vector2i] = self.get_frontier_tiles()
+	for tile in frontier_tiles:
+		var utility: float = self.compute_utility(tile, agent_pos)
+		var heap_node: HeapNode = HeapNode.new(tile, utility)
+		frontier_utilities.push(heap_node)
+	return frontier_utilities
+
+func new_exploration_target(position: Vector2):
+	var current: Vector2i = self.global_to_tile(position)
+	var frontier: MaxHeap = self.get_frontier_utilities(current)
+	if not frontier.empty():
+		var target: Vector2i = frontier.pop().pos
+	
+		# If the first returned target is the same as the current tile, we take the next best tile 
+		# in the frontier.
+		if target == current and not frontier.empty():
+			return frontier.pop().pos
+		elif target == current and frontier.empty():
+			return null
+		return target
+	else: 
+		return null
+
+func compare(grid: Grid2D) -> void:
+	for i in range(self._data.size()):
+		if self._data[i].val == -1 and grid._data[i].val != -1:
+			self._data[i].val = grid._data[i].val
+
+func fill_enclosed_unknown_regions() -> void: 
+	var visited: Dictionary = {}
+	
+	for y in self._size.y:
+		for x in self._size.x:
+			var pos: Vector2i = Vector2i(x, y)
+			if self.get_tile(pos).val == -1 and pos not in visited.keys():
+				
+				var region: Array = []
+				var is_enclosed: bool = true
+				var queue: Array = [pos]
+				
+				# Flood fill loop
+				while not queue.is_empty():
+					var current: Vector2i = queue.pop_front()
+					if current in visited.keys():
+						continue
+					
+					visited[current] = true
+					region.append(current)
+					
+					for direction in NEIGHBOR_DIRECTIONS:
+						var neighbor: Vector2i = current + direction
+						
+						# Grid Bounds Check
+						if 0 <= neighbor.x and neighbor.x < self._size.x and 0 <= neighbor.y and neighbor.y < self._size.y:
+							if self.get_tile(neighbor).val == -1 and neighbor not in visited.keys():
+								queue.push_back(neighbor)
+							elif self.get_tile(neighbor).val == 0:
+								# Connected to FREE (0) tile => region is not enclosed
+								is_enclosed = false
+				
+				# If the region is enclosed, mark all tiles in the region as OBSTACLE (1)
+				if is_enclosed:
+					for el in region:
+						self.set_tile_value(el, 1)
+
+func dda_ray_tiles(global_start: Vector2, global_end: Vector2) -> Array[Vector2i]:
+	var tiles: Array[Vector2i] = []
+	
+	var start: Vector2 = (global_start - self._grid_origin) / self._tile_size
+	var end: Vector2 = (global_end - self._grid_origin) / self._tile_size
+	
+	var dx = end.x - start.x
+	var dy = end.y - start.y
+	
+	var step_x = 1 if dx > 0 else -1
+	var step_y = 1 if dy > 0 else -1
+	
+	var tile = self.global_to_tile(global_start)
+	var end_tile = self.global_to_tile(global_end)
+	
+	var tile_corr_x = 1 if step_x > 0 else 0
+	var tile_corr_y = 1 if step_y > 0 else 0
+	
+	var t_max_x = ((tile.x + tile_corr_x) - start.x) / dx if dx !=0 else INF
+	var t_max_y = ((tile.y + tile_corr_y) - start.y) / dy if dy !=0 else INF
+	
+	var t_delta_x = abs(1/dx) if dx !=0 else INF
+	var t_delta_y = abs(1/dy) if dy !=0 else INF
+	
+	var max_steps = 100
+	for step in max_steps:
+		tiles.append(tile)
+		
+		if t_max_x < t_max_y:
+			t_max_x += t_delta_x
+			tile.x += step_x
+		else:
+			t_max_y += t_delta_y
+			tile.y += step_y
+		
+		if (
+			(step_x > 0 and tile.x > end_tile.x) or
+			(step_x < 0 and tile.x < end_tile.x) or
+			(step_y > 0 and tile.y > end_tile.y) or
+			(step_y < 0 and tile.y < end_tile.y) 
+		):
+			break
+	
+	return tiles
+
+func stringify_grid2d() -> String:
+	var map_string = ""
+	for y in self._size.y:
+		for x in self._size.x:
+			var entry = get_tile(Vector2i(x, y))
+			if entry.val == 1:
+				map_string += "x"
+			elif entry.val == 0:
+				map_string += " "
+			elif entry.val == -1:
+				map_string += "?"
+		map_string += "\n"
+	return map_string
+
+class Grid2DTile:
+	var val: int
+	var pos: Vector2i
+	
+	func _init(x: int, y: int, value: int) -> void:
+		self.val = value
+		self.pos = Vector2i(x, y)

+ 1 - 0
Godot Exploration - 2d/References/Grid2D.gd.uid

@@ -0,0 +1 @@
+uid://bqreaof1be37b

+ 15 - 0
Godot Exploration - 2d/References/HeapNode.gd

@@ -0,0 +1,15 @@
+class_name HeapNode
+extends RefCounted
+
+var pos: Vector2i
+var val
+
+func _init(pos: Vector2i, value) -> void:
+	self.pos = pos
+	self.val = value
+
+func lt(other: HeapNode) -> bool:
+	return self.val < other.val
+
+func ge(other: HeapNode) -> bool:
+	return self.val >= other.val

+ 1 - 0
Godot Exploration - 2d/References/HeapNode.gd.uid

@@ -0,0 +1 @@
+uid://vuyyu2vdiomv

+ 26 - 0
Godot Exploration - 2d/References/MaxHeap.gd

@@ -0,0 +1,26 @@
+class_name MaxHeap
+extends PriorityQueue
+
+func _up_heap(index: int) -> void:
+	var parent_idx = self._get_parent(index)
+	if self._data[parent_idx].ge(self._data[index]):
+		return
+	self._swap(index, parent_idx)
+	self._up_heap(parent_idx)
+
+func _down_heap(index: int) -> void:
+	var left_idx = self._get_left_child(index)
+	var right_idx = self._get_right_child(index)
+	
+	var largest: int = index
+	var size: int = self._data.size()
+	
+	if right_idx < size and self._data[largest].lt(self._data[right_idx]):
+		largest = right_idx
+	
+	if left_idx < size and self._data[largest].lt(self._data[left_idx]):
+		largest = left_idx
+	
+	if largest != index:
+		self._swap(index, largest)
+		self._down_heap(largest)

+ 1 - 0
Godot Exploration - 2d/References/MaxHeap.gd.uid

@@ -0,0 +1 @@
+uid://dhe5tcl7rc62g

+ 62 - 0
Godot Exploration - 2d/References/PriorityQueue.gd

@@ -0,0 +1,62 @@
+class_name PriorityQueue
+extends RefCounted
+
+var _data: Array[HeapNode] = []
+
+func push(el: HeapNode) -> void:
+	self._data.push_back(el)
+	var new_el_idx: int = self._data.size() - 1
+	self._up_heap(new_el_idx)
+
+func pop() -> HeapNode:
+	if self.empty():
+		return null
+	
+	var result: HeapNode = self._data.pop_front()
+	if not self.empty():
+		self._data.push_front(self._data.pop_back())
+		self._down_heap(0)
+	return result
+
+func empty() -> bool:
+	return self._data.is_empty()
+
+func _get_parent(index: int) -> int:
+	return (index - 1) / 2
+
+func _get_left_child(index: int) -> int:
+	return 2 * index + 1
+
+func _get_right_child(index: int) -> int:
+	return 2 * index + 2
+
+func _swap(a_idx: int, b_idx: int) -> void:
+	var a = self._data[a_idx]
+	var b = self._data[b_idx]
+	
+	self._data[a_idx] = b
+	self._data[b_idx] = a
+
+func _up_heap(index: int) -> void:
+	var parent_idx = self._get_parent(index)
+	if self._data[index].ge(self._data[parent_idx]):
+		return
+	self._swap(index, parent_idx)
+	self._up_heap(parent_idx)
+
+func _down_heap(index: int) -> void:
+	var left_idx = self._get_left_child(index)
+	var right_idx = self._get_right_child(index)
+	
+	var smallest: int = index
+	var size: int = self._data.size()
+	
+	if right_idx < size and self._data[right_idx].lt(self._data[smallest]):
+		smallest = right_idx
+	
+	if left_idx < size and self._data[left_idx].lt(self._data[smallest]):
+		smallest = left_idx
+	
+	if smallest != index:
+		self._swap(index, smallest)
+		self._down_heap(smallest)

+ 1 - 0
Godot Exploration - 2d/References/PriorityQueue.gd.uid

@@ -0,0 +1 @@
+uid://c0gga5x28urck

+ 28 - 0
Godot Exploration - 2d/References/Utils.gd

@@ -0,0 +1,28 @@
+class_name Utils
+extends RefCounted
+
+
+# takes "(x, y)" and returns (x,y) as Vector2 
+func str_to_vec2(input:String):
+	var outputstr = input.erase(0) # remove (
+	outputstr = outputstr.erase(outputstr.length()-1) # remove )
+	var outputlist = outputstr.split(",")
+	return Vector2(float(outputlist[0]),float(outputlist[1]))
+
+func path_str_to_vec(input):
+	var output = []
+	for point in input:
+		output.append(str_to_vec2(point))
+	return output
+
+func rotate_vec_left(vec: Vector2) -> Vector2:
+	var result: Vector2 = Vector2(vec.y, -vec.x)
+	return result
+
+func rotate_vec_right(vec: Vector2) -> Vector2:
+	var result: Vector2 = Vector2(-vec.y, vec.x)
+	return result
+
+func turn_vec_around(vec: Vector2) -> Vector2:
+	var result: Vector2 = Vector2(-vec.x, -vec.y)
+	return result

+ 1 - 0
Godot Exploration - 2d/References/Utils.gd.uid

@@ -0,0 +1 @@
+uid://cyewycx4wd5vn

+ 32 - 0
Godot Exploration - 2d/TankWars/Actuators/tank_body.gd

@@ -0,0 +1,32 @@
+extends Area2D
+class_name TankBody
+
+signal rotated
+
+var target: Vector2 = self.global_position + Vector2(1.0, 0.0)
+@onready var radar: Radar = $Radar
+@onready var behavior: StateChart = $Behavior
+
+
+func _draw() -> void:
+	self.draw_rect(Rect2(-30.0, -20.0, 60.0, 40.0), Color.DARK_GREEN)
+
+
+# Callable to connect to Tank's travel_target signal
+func _on_tank_travel_target(pos: Vector2):
+	self.target = pos
+	self.behavior.send_event("rotate")
+
+
+#-----------------------------------------------------------------------------#
+# Rotating State
+func _on_rotating_state_processing(delta: float) -> void:
+	var target_direction = self.global_position.direction_to(target)
+	self.global_rotation = lerp_angle(self.global_rotation, target_direction.angle(), delta * 10)
+	
+	if self.radar.target_in_front(self.target):
+		self.behavior.send_event("rotated")
+
+
+func _on_rotated_taken() -> void:
+	self.rotated.emit()

+ 1 - 0
Godot Exploration - 2d/TankWars/Actuators/tank_body.gd.uid

@@ -0,0 +1 @@
+uid://0cl3jxy74bd8

+ 53 - 0
Godot Exploration - 2d/TankWars/Actuators/tank_body.tscn

@@ -0,0 +1,53 @@
+[gd_scene load_steps=8 format=3 uid="uid://ersgjbcsmvwu"]
+
+[ext_resource type="Script" uid="uid://0cl3jxy74bd8" path="res://TankWars/Actuators/tank_body.gd" id="1_1vct5"]
+[ext_resource type="PackedScene" uid="uid://c6c221fld2khi" path="res://TankWars/Sensors/radar.tscn" id="2_fme24"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="3_fx6b7"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="4_wcy70"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="5_5hxh0"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="6_yrhgf"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_htdy2"]
+size = Vector2(60, 40)
+
+[node name="TankBody" type="Area2D"]
+script = ExtResource("1_1vct5")
+
+[node name="Radar" parent="." instance=ExtResource("2_fme24")]
+
+[node name="collision" type="CollisionShape2D" parent="."]
+shape = SubResource("RectangleShape2D_htdy2")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("3_fx6b7")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="TankBodyRotation" type="Node" parent="Behavior"]
+script = ExtResource("4_wcy70")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/TankBodyRotation"]
+script = ExtResource("5_5hxh0")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="rotate" type="Node" parent="Behavior/TankBodyRotation/Idle"]
+script = ExtResource("6_yrhgf")
+to = NodePath("../../Rotating")
+event = &"rotate"
+delay_in_seconds = "0.0"
+metadata/_custom_type_script = "uid://m8bym6l05tkl"
+
+[node name="Rotating" type="Node" parent="Behavior/TankBodyRotation"]
+script = ExtResource("5_5hxh0")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="rotated" type="Node" parent="Behavior/TankBodyRotation/Rotating"]
+script = ExtResource("6_yrhgf")
+to = NodePath("../../Idle")
+event = &"rotated"
+delay_in_seconds = "0.0"
+metadata/_custom_type_script = "uid://m8bym6l05tkl"
+
+[connection signal="state_processing" from="Behavior/TankBodyRotation/Rotating" to="." method="_on_rotating_state_processing"]
+[connection signal="taken" from="Behavior/TankBodyRotation/Rotating/rotated" to="." method="_on_rotated_taken"]

+ 37 - 0
Godot Exploration - 2d/TankWars/Actuators/turret.gd

@@ -0,0 +1,37 @@
+extends Node2D
+class_name Turret
+
+signal ready_to_shoot
+
+var _radius: float = 18.0
+var _range: int = 150
+var target = null
+@onready var behavior: StateChart = $Behavior
+@onready var radar: Radar = $Radar
+
+
+func _draw() -> void:
+	self.draw_circle(Vector2(0.0, 0.0), self._radius, Color.BLACK)
+	self.draw_rect(Rect2(0.0, -8.0, 48.0, 16.0), Color.BLACK)
+
+
+# Callable to connect to AttackPlanner's aim_at signal
+func _on_attack_planner_aim_at(enemy_position: Vector2) -> void:
+	self.target = enemy_position
+	self.behavior.send_event("aim_at")
+
+
+#-----------------------------------------------------------------------------#
+# Aiming State
+func _on_aiming_state_processing(delta: float) -> void:
+	if self.target != null:
+		var target_direction = self.global_position.direction_to(self.target)
+		self.rotation = lerp_angle(self.rotation, target_direction.angle(), delta * 20)
+		
+		var target_distance = self.global_position.distance_to(self.target)
+		if radar.enemy_in_front() and target_distance <= self._range:
+			self.behavior.send_event("turret_in_range")
+
+
+func _on_turret_in_range_taken() -> void:
+	self.ready_to_shoot.emit()

+ 1 - 0
Godot Exploration - 2d/TankWars/Actuators/turret.gd.uid

@@ -0,0 +1 @@
+uid://bxydeh65qwu7r

+ 49 - 0
Godot Exploration - 2d/TankWars/Actuators/turret.tscn

@@ -0,0 +1,49 @@
+[gd_scene load_steps=7 format=3 uid="uid://5cbnyjvujpxq"]
+
+[ext_resource type="Script" uid="uid://bxydeh65qwu7r" path="res://TankWars/Actuators/turret.gd" id="1_g8fju"]
+[ext_resource type="PackedScene" uid="uid://c6c221fld2khi" path="res://TankWars/Sensors/radar.tscn" id="1_pdd11"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_8uwdq"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_g8fju"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_xtqx7"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_hgl6s"]
+
+[node name="Turret" type="Node2D"]
+script = ExtResource("1_g8fju")
+
+[node name="Radar" parent="." instance=ExtResource("1_pdd11")]
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_8uwdq")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="TurretRotation" type="Node" parent="Behavior"]
+script = ExtResource("3_g8fju")
+initial_state = NodePath("Aiming")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Aiming" type="Node" parent="Behavior/TurretRotation"]
+script = ExtResource("4_xtqx7")
+
+[node name="turret_in_range" type="Node" parent="Behavior/TurretRotation/Aiming"]
+script = ExtResource("5_hgl6s")
+to = NodePath("../../InRange")
+event = &"turret_in_range"
+delay_in_seconds = "0.0"
+
+[node name="InRange" type="Node" parent="Behavior/TurretRotation"]
+script = ExtResource("4_xtqx7")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="check_aim" type="Node" parent="Behavior/TurretRotation/InRange"]
+script = ExtResource("5_hgl6s")
+to = NodePath("../../Aiming")
+delay_in_seconds = "1.0"
+
+[node name="aim_at" type="Node" parent="Behavior/TurretRotation/InRange"]
+script = ExtResource("5_hgl6s")
+to = NodePath("../../Aiming")
+event = &"aim_at"
+delay_in_seconds = "0.0"
+
+[connection signal="state_processing" from="Behavior/TurretRotation/Aiming" to="." method="_on_aiming_state_processing"]
+[connection signal="taken" from="Behavior/TurretRotation/Aiming/turret_in_range" to="." method="_on_turret_in_range_taken"]

+ 52 - 0
Godot Exploration - 2d/TankWars/Memorizers/enemy_tracker.gd

@@ -0,0 +1,52 @@
+extends Node2D
+class_name EnemyTracker
+
+signal enemy_position_known
+signal enemy_position_changed(pos: Vector2)
+signal enemy_position_unsure
+
+var tracked_position = null
+@export var enemy_margin: float = 64.0
+@onready var behavior: StateChart = $Behavior
+
+# TODO implement
+## Check if given new position differs significantly from the current tracked 
+## position. If so, the tracked position is updated and true is returned.
+func enemy_moved(new_position) -> bool:
+	if self.tracked_position == null:
+		self.tracked_position = new_position
+		return true
+	
+	var x_dif = abs(self.tracked_position.x - new_position.x)
+	var y_dif = abs(self.tracked_position.y - new_position.y)
+	
+	if x_dif <= self.enemy_margin and y_dif <= self.enemy_margin:
+		return false
+	
+	self.tracked_position = new_position
+	return true
+
+func get_enemy_position():
+	return self.tracked_position
+
+# Callable to connect to any Radar's enemy_sighted signals.
+func _on_enemy_sighted(enemy_position) -> void:
+	self.behavior.send_event("enemy_sighted")
+	if self.enemy_moved(enemy_position):
+		self.behavior.send_event("enemy_moved")
+		self.enemy_position_changed.emit(self.get_enemy_position())
+
+
+# Callable to connect to any Radar's enemy_lost signals.
+func _on_enemy_lost() -> void:
+	self.behavior.send_event("enemy_lost")
+
+#-----------------------------------------------------------------------------#
+# EnemyPosKnown State
+func _on_enemy_pos_known_state_entered() -> void:
+	self.enemy_position_known.emit()
+
+#-----------------------------------------------------------------------------#
+# EnemyPosUnsure State
+func _on_enemy_pos_unsure_state_entered() -> void:
+	self.enemy_position_unsure.emit()

+ 1 - 0
Godot Exploration - 2d/TankWars/Memorizers/enemy_tracker.gd.uid

@@ -0,0 +1 @@
+uid://54u6segq7ikt

+ 60 - 0
Godot Exploration - 2d/TankWars/Memorizers/enemy_tracker.tscn

@@ -0,0 +1,60 @@
+[gd_scene load_steps=6 format=3 uid="uid://b0i3goxsi5vte"]
+
+[ext_resource type="Script" uid="uid://54u6segq7ikt" path="res://TankWars/Memorizers/enemy_tracker.gd" id="1_awfsd"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_ljt03"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_0ogt6"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_wle05"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_pavsm"]
+
+[node name="EnemyTracker" type="Node2D"]
+script = ExtResource("1_awfsd")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_ljt03")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="TrackEnemy" type="Node" parent="Behavior"]
+script = ExtResource("3_0ogt6")
+initial_state = NodePath("NoEnemy")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="NoEnemy" type="Node" parent="Behavior/TrackEnemy"]
+script = ExtResource("4_wle05")
+
+[node name="enemy_sighted" type="Node" parent="Behavior/TrackEnemy/NoEnemy"]
+script = ExtResource("5_pavsm")
+to = NodePath("../../EnemyPresent/EnemyPosKnown")
+event = &"enemy_sighted"
+delay_in_seconds = "0.0"
+
+[node name="EnemyPresent" type="Node" parent="Behavior/TrackEnemy"]
+script = ExtResource("3_0ogt6")
+initial_state = NodePath("EnemyPosKnown")
+
+[node name="EnemyPosKnown" type="Node" parent="Behavior/TrackEnemy/EnemyPresent"]
+script = ExtResource("4_wle05")
+
+[node name="enemy_lost" type="Node" parent="Behavior/TrackEnemy/EnemyPresent/EnemyPosKnown"]
+script = ExtResource("5_pavsm")
+to = NodePath("../../EnemyPosUnsure")
+event = &"enemy_lost"
+delay_in_seconds = "0.0"
+
+[node name="enemy_moved" type="Node" parent="Behavior/TrackEnemy/EnemyPresent/EnemyPosKnown"]
+script = ExtResource("5_pavsm")
+to = NodePath("..")
+event = &"enemy_moved"
+delay_in_seconds = "0.0"
+
+[node name="EnemyPosUnsure" type="Node" parent="Behavior/TrackEnemy/EnemyPresent"]
+script = ExtResource("4_wle05")
+
+[node name="enemy_sighted" type="Node" parent="Behavior/TrackEnemy/EnemyPresent/EnemyPosUnsure"]
+script = ExtResource("5_pavsm")
+to = NodePath("../../EnemyPosKnown")
+event = &"enemy_sighted"
+delay_in_seconds = "0.0"
+
+[connection signal="state_entered" from="Behavior/TrackEnemy/EnemyPresent/EnemyPosKnown" to="." method="_on_enemy_pos_known_state_entered"]
+[connection signal="state_processing" from="Behavior/TrackEnemy/EnemyPresent/EnemyPosKnown" to="." method="_on_enemy_pos_known_state_processing"]
+[connection signal="state_entered" from="Behavior/TrackEnemy/EnemyPresent/EnemyPosUnsure" to="." method="_on_enemy_pos_unsure_state_entered"]

+ 54 - 0
Godot Exploration - 2d/TankWars/Memorizers/obstacle_map.gd

@@ -0,0 +1,54 @@
+extends Node2D
+class_name ObstacleMap
+
+signal send_path(path: Array[Vector2])
+signal send_exploration_target(target: Vector2)
+signal update_done
+
+@onready var behavior: StateChart = $Behavior
+var grid: Grid2D
+var pathfinding: AStar = AStar.new()
+
+
+func build_empty_map(dimensions: Dictionary):
+	var size: Vector2 = dimensions["size"]
+	var map_tile_size = dimensions["tile_size"]
+	var map_origin = dimensions["instance_offset"]
+	
+	var tile_count_x: int = floori(size.x / map_tile_size)
+	var tile_count_y: int = floori(size.y / map_tile_size)
+	
+	var map_size = Vector2i(tile_count_x, tile_count_y)
+	self.grid = Grid2D.new(map_size, map_tile_size, map_origin, self.global_position)
+
+
+func compare_map(map: Grid2D) -> void:
+	self.grid.compare(map)
+
+
+# Callable to connect to any Radar's obstacle_sighted signals
+func _on_obstacle_sighted(data, ray_origin) -> void:
+	self.behavior.send_event("update_map")
+	self.grid.update_map(data, ray_origin)
+	self.update_done.emit()
+
+
+# Callable to connect to Pathfinder's request_path signal
+func _on_pathfinder_request_path(pos: Vector2, destination: Vector2) -> void:
+	var start = self.grid.global_to_tile(pos)
+	var goal = self.grid.global_to_tile(destination)
+	var path = self.pathfinding.astar(self.grid, start, goal)
+	path = self.grid.path_to_global(path)
+	self.send_path.emit(path)
+
+# Callable to connect to ExplorePlanner's request_exploration_target
+func _on_explore_planner_request_exploration_target(pos: Vector2):
+	var target = self.grid.new_exploration_target(pos)
+	if target != null:
+		target = self.grid.tile_to_global(target)
+	self.send_exploration_target.emit(target)
+
+
+# Timer to show map
+func _on_timer_timeout() -> void:
+	print(self.grid.stringify_grid2d())

+ 1 - 0
Godot Exploration - 2d/TankWars/Memorizers/obstacle_map.gd.uid

@@ -0,0 +1 @@
+uid://bqdnjekr8moer

+ 35 - 0
Godot Exploration - 2d/TankWars/Memorizers/obstacle_map.tscn

@@ -0,0 +1,35 @@
+[gd_scene load_steps=6 format=3 uid="uid://cxa4n46xuic4k"]
+
+[ext_resource type="Script" uid="uid://bqdnjekr8moer" path="res://TankWars/Memorizers/obstacle_map.gd" id="1_slopa"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_4nank"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_4nank"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="3_qcyp6"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="4_kgtyo"]
+
+[node name="ObstacleMap" type="Node2D"]
+script = ExtResource("1_slopa")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_4nank")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="UpdateMap" type="Node" parent="Behavior"]
+script = ExtResource("3_4nank")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/UpdateMap"]
+script = ExtResource("3_qcyp6")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="update" type="Node" parent="Behavior/UpdateMap/Idle"]
+script = ExtResource("4_kgtyo")
+to = NodePath("..")
+event = &"update_map"
+delay_in_seconds = "0.0"
+
+[node name="Timer" type="Timer" parent="."]
+wait_time = 10.0
+autostart = true
+
+[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]

+ 23 - 0
Godot Exploration - 2d/TankWars/Sensors/fuel_tank.gd

@@ -0,0 +1,23 @@
+extends Node2D
+class_name FuelTank
+
+signal fuel_low
+signal fuel_full
+
+var fuelLevel: int = 100
+@onready var behavior: StateChart = $Behavior
+
+func _ready() -> void:
+	self.behavior.set_expression_property.call_deferred("fuelLevel", self.fuelLevel)
+
+func update_fuel_level(amount: int) -> void:
+	self.fuelLevel += amount
+	self.behavior.set_expression_property("fuelLevel", self.fuelLevel)
+
+
+func _on_fuel_low_taken() -> void:
+	self.fuel_low.emit()
+
+
+func _on_fuel_full_taken() -> void:
+	self.fuel_full.emit()

+ 1 - 0
Godot Exploration - 2d/TankWars/Sensors/fuel_tank.gd.uid

@@ -0,0 +1 @@
+uid://v4xlufxrcnwl

+ 53 - 0
Godot Exploration - 2d/TankWars/Sensors/fuel_tank.tscn

@@ -0,0 +1,53 @@
+[gd_scene load_steps=9 format=3 uid="uid://c3uxintwm3bhr"]
+
+[ext_resource type="Script" uid="uid://v4xlufxrcnwl" path="res://TankWars/Sensors/fuel_tank.gd" id="1_8u1hk"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_ux321"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_hh5n2"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_vufmb"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_siw6a"]
+[ext_resource type="Script" uid="uid://cgpbecy3dmigh" path="res://addons/godot_state_charts/expression_guard.gd" id="6_58sxy"]
+
+[sub_resource type="Resource" id="Resource_jrmpm"]
+script = ExtResource("6_58sxy")
+expression = "fuelLevel < 10"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[sub_resource type="Resource" id="Resource_sn4km"]
+script = ExtResource("6_58sxy")
+expression = "fuelLevel == 100"
+metadata/_custom_type_script = "uid://cgpbecy3dmigh"
+
+[node name="FuelTank" type="Node2D"]
+script = ExtResource("1_8u1hk")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_ux321")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="MonitorTank" type="Node" parent="Behavior"]
+script = ExtResource("3_hh5n2")
+initial_state = NodePath("FuelLevelOK")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="FuelLevelOK" type="Node" parent="Behavior/MonitorTank"]
+script = ExtResource("4_vufmb")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="fuel_low" type="Node" parent="Behavior/MonitorTank/FuelLevelOK"]
+script = ExtResource("5_siw6a")
+to = NodePath("../../FuelLevelLow")
+guard = SubResource("Resource_jrmpm")
+delay_in_seconds = "0.0"
+
+[node name="FuelLevelLow" type="Node" parent="Behavior/MonitorTank"]
+script = ExtResource("4_vufmb")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="fuel_full" type="Node" parent="Behavior/MonitorTank/FuelLevelLow"]
+script = ExtResource("5_siw6a")
+to = NodePath("../../FuelLevelOK")
+guard = SubResource("Resource_sn4km")
+delay_in_seconds = "0.0"
+
+[connection signal="taken" from="Behavior/MonitorTank/FuelLevelOK/fuel_low" to="." method="_on_fuel_low_taken"]
+[connection signal="taken" from="Behavior/MonitorTank/FuelLevelLow/fuel_full" to="." method="_on_fuel_full_taken"]

+ 124 - 0
Godot Exploration - 2d/TankWars/Sensors/radar.gd

@@ -0,0 +1,124 @@
+extends Node2D
+class_name Radar
+
+signal enemy_sighted(enemy_position: Vector2)
+signal enemy_lost
+signal obstacles_sighted(data: Dictionary, ray_origin: Vector2)
+
+@export var _range: int = 300
+@onready var behavior: StateChart = $Behavior
+@onready var vision: Node2D = $Vision
+@onready var ray_front: RayCast2D = $Vision/ray_front
+
+
+func enemy_in_front() -> bool:
+	if ray_front.is_colliding() and ray_front.get_collider() is TankBody:
+		return true
+	return false
+
+
+func enemy_present() -> bool:
+	for ray: RayCast2D in vision.get_children():
+		if ray.is_colliding() and ray.get_collider() is TankBody:
+			return true
+	return false
+
+
+func get_enemy_position():
+	var enemy_collisions: Array[Vector2] = []
+	for ray: RayCast2D in vision.get_children():
+		if ray.is_colliding() and ray.get_collider() is TankBody:
+			var collision = ray.get_collision_point()
+			enemy_collisions.append(collision)
+	
+	if not enemy_collisions.is_empty():
+		var average_collision = Vector2.ZERO
+		for point in enemy_collisions:
+			average_collision += point
+		average_collision /= enemy_collisions.size()
+		return average_collision
+	
+	return null
+
+
+func enemy_distance() -> float:
+	var enemy_position = self.get_enemy_position()
+	return self.global_position.distance_to(enemy_position)
+
+
+func obstacle_present() -> bool:
+	for ray: RayCast2D in vision.get_children():
+		if ray.is_colliding() and not ray.get_collider() is TankBody:
+			return true
+	return false
+
+
+func get_obstacles() -> Dictionary:
+	var data: Dictionary = {}
+	var collisions: Array[Vector2] = []
+	var normals: Array[Vector2] = []
+	for ray: RayCast2D in vision.get_children():
+		if ray.is_colliding():
+			# Other Tanks are ignored as obstacles.
+			if ray.get_collider() is TankBody:
+				continue
+		
+			var collision = ray.get_collision_point()
+			var normal = ray.get_collision_normal()
+		
+			collisions.append(collision)
+			normals.append(normal)
+		else:
+			collisions.append(to_global(ray.target_position))
+			normals.append(Vector2.ZERO)
+	data["collisions"] = collisions
+	data["normals"] = normals
+	return data
+
+
+func target_in_front(target: Vector2) -> bool:
+	var direction = self.global_position.direction_to(target)
+	var angle_diff = angle_difference(direction.angle(), self.global_rotation)
+	if abs(angle_diff) <= 0.02:
+		return true
+	return false
+
+
+#-----------------------------------------------------------------------------#
+# NoEnemy State
+func _on_no_enemy_state_processing(delta: float) -> void:
+	if self.enemy_present():
+		self.behavior.send_event("enemy_sighted")
+
+
+#-----------------------------------------------------------------------------#
+# EnemySighted State
+func _on_enemy_sighted_state_processing(delta: float) -> void:
+	if not self.enemy_present():
+		self.behavior.send_event("enemy_lost")
+	else:
+		var enemy_position = self.get_enemy_position()
+		self.enemy_sighted.emit(enemy_position)
+
+
+func _on_enemy_lost_taken() -> void:
+	self.enemy_lost.emit()
+
+
+#-----------------------------------------------------------------------------#
+# Checking State
+func _on_checking_state_processing(delta: float) -> void:
+	pass
+
+
+func _on_processing_taken() -> void:
+	var data = self.get_obstacles()
+	self.obstacles_sighted.emit(data, self.global_position)
+
+
+#-----------------------------------------------------------------------------#
+# Waiting State
+
+# Callable to connect to ObstacleMap's update_done signal.
+func _on_obstacle_map_update_done() -> void:
+	self.behavior.send_event("done")

+ 1 - 0
Godot Exploration - 2d/TankWars/Sensors/radar.gd.uid

@@ -0,0 +1 @@
+uid://dt064r7g5spd6

+ 102 - 0
Godot Exploration - 2d/TankWars/Sensors/radar.tscn

@@ -0,0 +1,102 @@
+[gd_scene load_steps=7 format=3 uid="uid://c6c221fld2khi"]
+
+[ext_resource type="Script" uid="uid://dt064r7g5spd6" path="res://TankWars/Sensors/radar.gd" id="1_sjdv2"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_epnyi"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="3_k2bxq"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="4_f37m6"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="5_cgwxm"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="6_epnyi"]
+
+[node name="Radar" type="Node2D"]
+script = ExtResource("1_sjdv2")
+
+[node name="Vision" type="Node2D" parent="."]
+
+[node name="ray_left" type="RayCast2D" parent="Vision"]
+target_position = Vector2(282, -100)
+collide_with_areas = true
+
+[node name="ray_middle_left" type="RayCast2D" parent="Vision"]
+target_position = Vector2(295, -50)
+collide_with_areas = true
+
+[node name="ray_front" type="RayCast2D" parent="Vision"]
+target_position = Vector2(300, 0)
+collide_with_areas = true
+
+[node name="ray_middle_right" type="RayCast2D" parent="Vision"]
+target_position = Vector2(295, 50)
+collide_with_areas = true
+
+[node name="ray_right" type="RayCast2D" parent="Vision"]
+target_position = Vector2(282, 100)
+collide_with_areas = true
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_epnyi")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="Announcements" type="Node" parent="Behavior"]
+script = ExtResource("3_k2bxq")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="AnnounceEnemy" type="Node" parent="Behavior/Announcements"]
+script = ExtResource("4_f37m6")
+initial_state = NodePath("NoEnemy")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="NoEnemy" type="Node" parent="Behavior/Announcements/AnnounceEnemy"]
+script = ExtResource("5_cgwxm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_sighted" type="Node" parent="Behavior/Announcements/AnnounceEnemy/NoEnemy"]
+script = ExtResource("6_epnyi")
+to = NodePath("../../EnemySighted")
+event = &"enemy_sighted"
+delay_in_seconds = "0.0"
+
+[node name="EnemySighted" type="Node" parent="Behavior/Announcements/AnnounceEnemy"]
+script = ExtResource("5_cgwxm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_lost" type="Node" parent="Behavior/Announcements/AnnounceEnemy/EnemySighted"]
+script = ExtResource("6_epnyi")
+to = NodePath("../../NoEnemy")
+event = &"enemy_lost"
+delay_in_seconds = "0.0"
+
+[node name="AnnounceObstacles" type="Node" parent="Behavior/Announcements"]
+script = ExtResource("4_f37m6")
+initial_state = NodePath("Checking")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Checking" type="Node" parent="Behavior/Announcements/AnnounceObstacles"]
+script = ExtResource("5_cgwxm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="processing" type="Node" parent="Behavior/Announcements/AnnounceObstacles/Checking"]
+script = ExtResource("6_epnyi")
+to = NodePath("../../Waiting")
+delay_in_seconds = "0.1"
+
+[node name="Waiting" type="Node" parent="Behavior/Announcements/AnnounceObstacles"]
+script = ExtResource("5_cgwxm")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="done" type="Node" parent="Behavior/Announcements/AnnounceObstacles/Waiting"]
+script = ExtResource("6_epnyi")
+to = NodePath("../../Checking")
+event = &"done"
+delay_in_seconds = "0.0"
+
+[node name="check_again" type="Node" parent="Behavior/Announcements/AnnounceObstacles/Waiting"]
+script = ExtResource("6_epnyi")
+to = NodePath("../../Checking")
+delay_in_seconds = "0.5"
+
+[connection signal="state_processing" from="Behavior/Announcements/AnnounceEnemy/NoEnemy" to="." method="_on_no_enemy_state_processing"]
+[connection signal="taken" from="Behavior/Announcements/AnnounceEnemy/NoEnemy/enemy_sighted" to="." method="_on_enemy_sighted_taken"]
+[connection signal="state_processing" from="Behavior/Announcements/AnnounceEnemy/EnemySighted" to="." method="_on_enemy_sighted_state_processing"]
+[connection signal="taken" from="Behavior/Announcements/AnnounceEnemy/EnemySighted/enemy_lost" to="." method="_on_enemy_lost_taken"]
+[connection signal="state_processing" from="Behavior/Announcements/AnnounceObstacles/Checking" to="." method="_on_checking_state_processing"]
+[connection signal="taken" from="Behavior/Announcements/AnnounceObstacles/Checking/processing" to="." method="_on_processing_taken"]

+ 36 - 0
Godot Exploration - 2d/TankWars/StrategicDeciders/pilot_strategy.gd

@@ -0,0 +1,36 @@
+extends Node2D
+class_name PilotStrategy
+
+signal attack
+signal explore
+@onready var behavior: StateChart = $Behavior
+
+# TODO further enhance the pilot strategy. Include FUEL, REPAIR, DAMAGE
+
+
+#-----------------------------------------------------------------------------#
+# Explore State
+func _on_exploring_state_entered() -> void:
+	self.explore.emit()
+
+
+# Callable to connect to EnemyTracker's enemy_position_known signal
+func _on_enemy_tracker_enemy_position_known() -> void:
+	self.behavior.send_event("attack")
+
+
+#-----------------------------------------------------------------------------#
+# Attack State
+func _on_attacking_state_entered() -> void:
+	self.attack.emit()
+
+
+#Callable to connect to EnemyTracker's enemy_position_unsure signal
+func _on_enemy_tracker_enemy_position_unsure() -> void:
+	self.behavior.send_event("enemy_lost")
+
+
+#-----------------------------------------------------------------------------#
+# NormalOperation State
+func _on_normal_operation_state_processing(delta: float) -> void:
+	pass # Replace with function body.

+ 1 - 0
Godot Exploration - 2d/TankWars/StrategicDeciders/pilot_strategy.gd.uid

@@ -0,0 +1 @@
+uid://d2w63423vmavl

+ 63 - 0
Godot Exploration - 2d/TankWars/StrategicDeciders/pilot_strategy.tscn

@@ -0,0 +1,63 @@
+[gd_scene load_steps=6 format=3 uid="uid://bc6vvry8wo4ob"]
+
+[ext_resource type="Script" uid="uid://d2w63423vmavl" path="res://TankWars/StrategicDeciders/pilot_strategy.gd" id="1_urghy"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_xgbsq"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_kjhfg"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_lxeuo"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_74f71"]
+
+[node name="PilotStrategy" type="Node2D"]
+script = ExtResource("1_urghy")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_xgbsq")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="PilotStrategy" type="Node" parent="Behavior"]
+script = ExtResource("3_kjhfg")
+initial_state = NodePath("NormalOperation")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="NormalOperation" type="Node" parent="Behavior/PilotStrategy"]
+script = ExtResource("3_kjhfg")
+initial_state = NodePath("Exploring")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Exploring" type="Node" parent="Behavior/PilotStrategy/NormalOperation"]
+script = ExtResource("4_lxeuo")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="attack" type="Node" parent="Behavior/PilotStrategy/NormalOperation/Exploring"]
+script = ExtResource("5_74f71")
+to = NodePath("../../Attacking")
+event = &"attack"
+delay_in_seconds = "0.0"
+
+[node name="Attacking" type="Node" parent="Behavior/PilotStrategy/NormalOperation"]
+script = ExtResource("4_lxeuo")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_lost" type="Node" parent="Behavior/PilotStrategy/NormalOperation/Attacking"]
+script = ExtResource("5_74f71")
+to = NodePath("../../Waiting")
+event = &"enemy_lost"
+delay_in_seconds = "0.0"
+
+[node name="Waiting" type="Node" parent="Behavior/PilotStrategy/NormalOperation"]
+script = ExtResource("4_lxeuo")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="enemy_found" type="Node" parent="Behavior/PilotStrategy/NormalOperation/Waiting"]
+script = ExtResource("5_74f71")
+to = NodePath("../../Attacking")
+event = &"attack"
+delay_in_seconds = "0.0"
+
+[node name="explore" type="Node" parent="Behavior/PilotStrategy/NormalOperation/Waiting"]
+script = ExtResource("5_74f71")
+to = NodePath("../../Exploring")
+delay_in_seconds = "5.0"
+
+[connection signal="state_processing" from="Behavior/PilotStrategy/NormalOperation" to="." method="_on_normal_operation_state_processing"]
+[connection signal="state_entered" from="Behavior/PilotStrategy/NormalOperation/Exploring" to="." method="_on_exploring_state_entered"]
+[connection signal="state_entered" from="Behavior/PilotStrategy/NormalOperation/Attacking" to="." method="_on_attacking_state_entered"]

+ 58 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/attack_planner.gd

@@ -0,0 +1,58 @@
+extends Node2D
+class_name AttackPlanner
+
+const RELOADTIME: float = 4.0
+
+signal shoot
+signal new_destination(pos: Vector2)
+signal aim_at(target: Vector2)
+
+var target = null
+@onready var behavior: StateChart = $Behavior
+
+
+func _ready() -> void:
+	self.behavior.set_expression_property.call_deferred("reload_time", RELOADTIME)
+
+
+# Callable to connect to EnemyTracker's enemy_position_changed signal
+func _on_enemy_tracker_enemy_position_changed(pos: Vector2) -> void:
+	self.target = pos
+	self.aim_at.emit(self.target)
+	self.new_destination.emit(self.target)
+
+
+#-----------------------------------------------------------------------------#
+# Idle State
+func _on_attack_taken() -> void:
+	if target != null:
+		self.aim_at.emit(self.target)
+
+
+# Callable to connect to PilotStrategy's attack signal
+func _on_strategy_attack() -> void:
+	self.behavior.send_event("attack")
+
+
+# Callable to connect to PilotStrategy's explore signal
+func _stop_attack_planner() -> void:
+	self.behavior.send_event("stop")
+
+
+#-----------------------------------------------------------------------------#
+# FollowEnemy State
+func _on_follow_enemy_state_processing(delta: float) -> void:
+	#self.aim_at.emit(self.target)
+	pass
+
+
+#-----------------------------------------------------------------------------#
+# Ready State
+func _on_shoot_taken() -> void:
+	print("BANG")
+	self.shoot.emit()
+
+
+# Callable to connect to Turret's ready_to_shoot signal.
+func _on_turret_ready_to_shoot() -> void:
+	behavior.send_event("ready_to_shoot")

+ 1 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/attack_planner.gd.uid

@@ -0,0 +1 @@
+uid://6m4qtogvr57

+ 72 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/attack_planner.tscn

@@ -0,0 +1,72 @@
+[gd_scene load_steps=7 format=3 uid="uid://bfieuhe2in16j"]
+
+[ext_resource type="Script" uid="uid://6m4qtogvr57" path="res://TankWars/TacticalDeciders/attack_planner.gd" id="1_klhpx"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="3_n54wo"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="4_watj6"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="5_dyfgg"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="6_k4lu7"]
+[ext_resource type="Script" uid="uid://dtlmgewt76wtf" path="res://addons/godot_state_charts/parallel_state.gd" id="7_228ao"]
+
+[node name="AttackPlanner" type="Node2D"]
+script = ExtResource("1_klhpx")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("3_n54wo")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="CompoundState" type="Node" parent="Behavior"]
+script = ExtResource("4_watj6")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/CompoundState"]
+script = ExtResource("5_dyfgg")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="attack" type="Node" parent="Behavior/CompoundState/Idle"]
+script = ExtResource("6_k4lu7")
+to = NodePath("../../MovementAndShooting")
+event = &"attack"
+delay_in_seconds = "0.0"
+
+[node name="MovementAndShooting" type="Node" parent="Behavior/CompoundState"]
+script = ExtResource("7_228ao")
+metadata/_custom_type_script = "uid://dtlmgewt76wtf"
+
+[node name="FollowEnemy" type="Node" parent="Behavior/CompoundState/MovementAndShooting"]
+script = ExtResource("5_dyfgg")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="Shooting" type="Node" parent="Behavior/CompoundState/MovementAndShooting"]
+script = ExtResource("4_watj6")
+initial_state = NodePath("Ready")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Ready" type="Node" parent="Behavior/CompoundState/MovementAndShooting/Shooting"]
+script = ExtResource("5_dyfgg")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="shoot" type="Node" parent="Behavior/CompoundState/MovementAndShooting/Shooting/Ready"]
+script = ExtResource("6_k4lu7")
+to = NodePath("../../Shooting")
+event = &"ready_to_shoot"
+delay_in_seconds = "0.0"
+
+[node name="Shooting" type="Node" parent="Behavior/CompoundState/MovementAndShooting/Shooting"]
+script = ExtResource("5_dyfgg")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="reloaded" type="Node" parent="Behavior/CompoundState/MovementAndShooting/Shooting/Shooting"]
+script = ExtResource("6_k4lu7")
+to = NodePath("../../Ready")
+delay_in_seconds = "reload_time"
+
+[node name="stop" type="Node" parent="Behavior/CompoundState/MovementAndShooting"]
+script = ExtResource("6_k4lu7")
+to = NodePath("../../Idle")
+event = &"stop"
+delay_in_seconds = "0.0"
+
+[connection signal="taken" from="Behavior/CompoundState/Idle/attack" to="." method="_on_attack_taken"]
+[connection signal="state_processing" from="Behavior/CompoundState/MovementAndShooting/FollowEnemy" to="." method="_on_follow_enemy_state_processing"]
+[connection signal="taken" from="Behavior/CompoundState/MovementAndShooting/Shooting/Ready/shoot" to="." method="_on_shoot_taken"]

+ 44 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/explore_planner.gd

@@ -0,0 +1,44 @@
+extends Node2D
+class_name ExplorePlanner
+
+signal request_exploration_target(pos: Vector2)
+signal new_destination(pos: Vector2)
+signal done
+
+@onready var behavior: StateChart = $Behavior
+@onready var exploring: AtomicState = $Behavior/ExploreStrategy/Exploring
+
+#-----------------------------------------------------------------------------#
+# Exploring State
+
+# Callable to connect to Pathfinder's destination_reached signal
+func _on_pathfinder_destination_reached():
+	if exploring.active:
+		self.behavior.send_event("new_target")
+
+
+# Callable to connect to ObstacleMap's send_exploration_target signal
+func _on_obstacle_map_send_exploration_target(target) -> void:
+	if target == null:
+		self.done.emit()
+		return
+	
+	if self.exploring.active:
+		self.new_destination.emit(target)
+
+
+func _on_exploring_state_entered() -> void:
+	self.request_exploration_target.emit(self.global_position)
+
+
+# Callable to connect to PilotStrategy's attack signal
+func _stop_explore_planner() -> void:
+	self.behavior.send_event("stop")
+
+
+#-----------------------------------------------------------------------------#
+# Idle State
+
+# Callable to connect to PilotStrategy's explore signal
+func _on_strategy_explore() -> void:
+	self.behavior.send_event("explore")

+ 1 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/explore_planner.gd.uid

@@ -0,0 +1 @@
+uid://bg6p6l6rljy4p

+ 48 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/explore_planner.tscn

@@ -0,0 +1,48 @@
+[gd_scene load_steps=6 format=3 uid="uid://bsfcprv20pdnf"]
+
+[ext_resource type="Script" uid="uid://bg6p6l6rljy4p" path="res://TankWars/TacticalDeciders/explore_planner.gd" id="1_tqayn"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_84mbj"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_jxfi2"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_b4k2x"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_vjp1p"]
+
+[node name="ExplorePlanner" type="Node2D"]
+script = ExtResource("1_tqayn")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_84mbj")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="ExploreStrategy" type="Node" parent="Behavior"]
+script = ExtResource("3_jxfi2")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/ExploreStrategy"]
+script = ExtResource("4_b4k2x")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="explore" type="Node" parent="Behavior/ExploreStrategy/Idle"]
+script = ExtResource("5_vjp1p")
+to = NodePath("../../Exploring")
+event = &"explore"
+delay_in_seconds = "0.5"
+
+[node name="Exploring" type="Node" parent="Behavior/ExploreStrategy"]
+script = ExtResource("4_b4k2x")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="stop" type="Node" parent="Behavior/ExploreStrategy/Exploring"]
+script = ExtResource("5_vjp1p")
+to = NodePath("../../Idle")
+event = &"stop"
+delay_in_seconds = "0.0"
+metadata/_custom_type_script = "uid://m8bym6l05tkl"
+
+[node name="new_target" type="Node" parent="Behavior/ExploreStrategy/Exploring"]
+script = ExtResource("5_vjp1p")
+to = NodePath("..")
+event = &"new_target"
+delay_in_seconds = "0.5"
+
+[connection signal="state_entered" from="Behavior/ExploreStrategy/Exploring" to="." method="_on_exploring_state_entered"]

+ 86 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/pathfinder.gd

@@ -0,0 +1,86 @@
+extends Node2D
+class_name Pathfinder
+
+signal new_waypoint(waypoint: Vector2)
+signal destination_reached
+signal request_path(start: Vector2, goal: Vector2)
+
+@onready var behavior: StateChart = $Behavior
+
+var waypoints: Array[Vector2] = []
+var destination = null
+
+func set_destination(dest: Vector2) -> void:
+	self.destination = dest
+
+func more_waypoints() -> bool:
+	if self.waypoints.is_empty():
+		return false
+	else:
+		return true
+
+#-----------------------------------------------------------------------------#
+# Idle State
+
+# Callable to connect to any new_destination signals
+func _on_new_destination(pos):
+	self.set_destination(pos)
+	self.behavior.send_event("new_destination")
+
+
+func _check_target_agent(pos, agent_name):
+	if agent_name == self.name:
+		self._on_new_destination(pos)
+
+
+# Callable to connect to Tank's waypoint_reached signal.
+func _on_waypoint_reached():
+	self.behavior.send_event("waypoint_reached")
+
+
+#-----------------------------------------------------------------------------#
+# RequestPath State
+func _on_request_path_state_entered() -> void:
+	self.request_path.emit(self.global_position, self.destination)
+
+# Callable to connect to ObstacleMap's send_path signal
+func _on_obstacle_map_send_path(path: Array[Vector2]) -> void:
+	self.waypoints = path
+	self.behavior.send_event("received_path")
+
+#-----------------------------------------------------------------------------#
+# ReceivedPath State
+func _on_received_path_state_entered() -> void:
+	if self.more_waypoints():
+		self.behavior.send_event("new_waypoint")
+	else:
+		self.behavior.send_event("no_path")
+
+
+func _on_new_waypoint_taken() -> void:
+	self.new_waypoint.emit(self.waypoints.pop_front())
+
+
+#-----------------------------------------------------------------------------#
+# CheckWaypoints State
+func _on_check_waypoints_state_entered() -> void:
+	if self.more_waypoints():
+		self.behavior.send_event("next_waypoint")
+	else:
+		self.behavior.send_event("destination_reached")
+
+
+func _on_next_waypoint_taken() -> void:
+	self.new_waypoint.emit(self.waypoints.pop_front())
+
+
+func _on_destination_reached_taken() -> void:
+	self.destination_reached.emit()
+
+
+#-----------------------------------------------------------------------------#
+
+# Callable to connect to Tank's clear_pathfinding signal
+func _on_clear_pathfinding() -> void:
+	self.waypoints = []
+	self.destination = null

+ 1 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/pathfinder.gd.uid

@@ -0,0 +1 @@
+uid://bbvvo20trsp8r

+ 85 - 0
Godot Exploration - 2d/TankWars/TacticalDeciders/pathfinder.tscn

@@ -0,0 +1,85 @@
+[gd_scene load_steps=6 format=3 uid="uid://cxhtqmgsfi31e"]
+
+[ext_resource type="Script" uid="uid://bbvvo20trsp8r" path="res://TankWars/TacticalDeciders/pathfinder.gd" id="1_c04e7"]
+[ext_resource type="Script" uid="uid://cau6j0o0julfq" path="res://addons/godot_state_charts/state_chart.gd" id="2_r1unm"]
+[ext_resource type="Script" uid="uid://bou0yn8lpwcuh" path="res://addons/godot_state_charts/compound_state.gd" id="3_4i764"]
+[ext_resource type="Script" uid="uid://dyiggrr357tov" path="res://addons/godot_state_charts/atomic_state.gd" id="4_nw0px"]
+[ext_resource type="Script" uid="uid://m8bym6l05tkl" path="res://addons/godot_state_charts/transition.gd" id="5_r1unm"]
+
+[node name="Pathfinder" type="Node2D"]
+script = ExtResource("1_c04e7")
+
+[node name="Behavior" type="Node" parent="."]
+script = ExtResource("2_r1unm")
+metadata/_custom_type_script = "uid://cau6j0o0julfq"
+
+[node name="PathFinder" type="Node" parent="Behavior"]
+script = ExtResource("3_4i764")
+initial_state = NodePath("Idle")
+metadata/_custom_type_script = "uid://bou0yn8lpwcuh"
+
+[node name="Idle" type="Node" parent="Behavior/PathFinder"]
+script = ExtResource("4_nw0px")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="new_destination" type="Node" parent="Behavior/PathFinder/Idle"]
+script = ExtResource("5_r1unm")
+to = NodePath("../../RequestPath")
+event = &"new_destination"
+delay_in_seconds = "0.0"
+
+[node name="waypoint_reached" type="Node" parent="Behavior/PathFinder/Idle"]
+script = ExtResource("5_r1unm")
+to = NodePath("../../CheckWaypoints")
+event = &"waypoint_reached"
+delay_in_seconds = "0.0"
+
+[node name="RequestPath" type="Node" parent="Behavior/PathFinder"]
+script = ExtResource("4_nw0px")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="received_path" type="Node" parent="Behavior/PathFinder/RequestPath"]
+script = ExtResource("5_r1unm")
+to = NodePath("../../ReceivedPath")
+event = &"received_path"
+delay_in_seconds = "0.0"
+
+[node name="ReceivedPath" type="Node" parent="Behavior/PathFinder"]
+script = ExtResource("4_nw0px")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="new_waypoint" type="Node" parent="Behavior/PathFinder/ReceivedPath"]
+script = ExtResource("5_r1unm")
+to = NodePath("../../Idle")
+event = &"new_waypoint"
+delay_in_seconds = "0.0"
+
+[node name="no_path" type="Node" parent="Behavior/PathFinder/ReceivedPath"]
+script = ExtResource("5_r1unm")
+to = NodePath("../../Idle")
+event = &"no_path"
+delay_in_seconds = "0.0"
+metadata/_custom_type_script = "uid://m8bym6l05tkl"
+
+[node name="CheckWaypoints" type="Node" parent="Behavior/PathFinder"]
+script = ExtResource("4_nw0px")
+metadata/_custom_type_script = "uid://dyiggrr357tov"
+
+[node name="next_waypoint" type="Node" parent="Behavior/PathFinder/CheckWaypoints"]
+script = ExtResource("5_r1unm")
+to = NodePath("../../Idle")
+event = &"next_waypoint"
+delay_in_seconds = "0.0"
+
+[node name="destination_reached" type="Node" parent="Behavior/PathFinder/CheckWaypoints"]
+script = ExtResource("5_r1unm")
+to = NodePath("../../Idle")
+event = &"destination_reached"
+delay_in_seconds = "0.0"
+
+[connection signal="state_entered" from="Behavior/PathFinder/RequestPath" to="." method="_on_request_path_state_entered"]
+[connection signal="state_entered" from="Behavior/PathFinder/ReceivedPath" to="." method="_on_received_path_state_entered"]
+[connection signal="taken" from="Behavior/PathFinder/ReceivedPath/new_waypoint" to="." method="_on_new_waypoint_taken"]
+[connection signal="state_entered" from="Behavior/PathFinder/CheckWaypoints" to="." method="_on_check_waypoints_state_entered"]
+[connection signal="taken" from="Behavior/PathFinder/CheckWaypoints/next_waypoint" to="." method="_on_next_waypoint_taken"]
+[connection signal="taken" from="Behavior/PathFinder/CheckWaypoints/destination_reached" to="." method="_on_destination_reached_taken"]

+ 134 - 0
Godot Exploration - 2d/TankWars/tank.gd

@@ -0,0 +1,134 @@
+extends CharacterBody2D
+class_name Tank
+
+# TODO further enhance the Tank behavior. Include FUEL, REPAIR, DAMAGE
+
+signal travel_target(waypoint: Vector2)
+signal waypoint_reached
+signal clear_pathfinding
+
+
+var SPEED: int = 300
+var target_waypoint: Vector2
+var _map_initialized: bool = false
+@export var nav_margin: float = 15.0
+
+@onready var behavior: StateChart = $Behavior
+@onready var moving: AtomicState = $Behavior/TankMovement/Moving
+
+@onready var tank_body: TankBody = $TankBody
+@onready var turret: Turret = $Turret
+
+@onready var enemy_tracker: EnemyTracker = $EnemyTracker
+@onready var obstacle_map: ObstacleMap = $ObstacleMap
+
+@onready var attack_planner: AttackPlanner = $AttackPlanner
+@onready var explore_planner: ExplorePlanner = $ExplorePlanner
+@onready var pathfinder: Pathfinder = $Pathfinder
+
+@onready var pilot_strategy: PilotStrategy = $PilotStrategy
+
+
+func _ready() -> void:
+	# Connecting all signals between components of the tank ------------------#
+	
+	self.obstacle_map.update_done.connect(self.tank_body.radar._on_obstacle_map_update_done)
+	self.obstacle_map.update_done.connect(self.turret.radar._on_obstacle_map_update_done)
+	
+	self.travel_target.connect(self.tank_body._on_tank_travel_target)
+	
+	self.attack_planner.aim_at.connect(self.turret._on_attack_planner_aim_at)
+	
+	self.tank_body.radar.enemy_sighted.connect(self.enemy_tracker._on_enemy_sighted)
+	self.tank_body.radar.enemy_lost.connect(self.enemy_tracker._on_enemy_lost)
+	self.turret.radar.enemy_sighted.connect(self.enemy_tracker._on_enemy_sighted)
+	self.turret.radar.enemy_lost.connect(self.enemy_tracker._on_enemy_lost)
+	
+	self.tank_body.radar.obstacles_sighted.connect(self.obstacle_map._on_obstacle_sighted)
+	self.turret.radar.obstacles_sighted.connect(self.obstacle_map._on_obstacle_sighted)
+	self.pathfinder.request_path.connect(self.obstacle_map._on_pathfinder_request_path)
+	self.explore_planner.request_exploration_target.connect(self.obstacle_map._on_explore_planner_request_exploration_target)
+	
+	self.turret.ready_to_shoot.connect(self.attack_planner._on_turret_ready_to_shoot)
+	self.pilot_strategy.attack.connect(self.attack_planner._on_strategy_attack)
+	self.pilot_strategy.explore.connect(self.attack_planner._stop_attack_planner)
+	self.enemy_tracker.enemy_position_changed.connect(self.attack_planner._on_enemy_tracker_enemy_position_changed)
+	
+	self.pathfinder.destination_reached.connect(self.explore_planner._on_pathfinder_destination_reached)
+	self.obstacle_map.send_exploration_target.connect(self.explore_planner._on_obstacle_map_send_exploration_target)
+	self.pilot_strategy.attack.connect(self.explore_planner._stop_explore_planner)
+	self.pilot_strategy.explore.connect(self.explore_planner._on_strategy_explore)
+	
+	self.attack_planner.new_destination.connect(self.pathfinder._on_new_destination)
+	self.explore_planner.new_destination.connect(self.pathfinder._on_new_destination)
+	self.waypoint_reached.connect(self.pathfinder._on_waypoint_reached)
+	self.obstacle_map.send_path.connect(self.pathfinder._on_obstacle_map_send_path)
+	self.clear_pathfinding.connect(self.pathfinder._on_clear_pathfinding)
+	
+	self.enemy_tracker.enemy_position_known.connect(self.pilot_strategy._on_enemy_tracker_enemy_position_known)
+	self.enemy_tracker.enemy_position_unsure.connect(self.pilot_strategy._on_enemy_tracker_enemy_position_unsure)
+	
+	self.pathfinder.new_waypoint.connect(self._on_pathfinder_new_waypoint)
+	self.tank_body.rotated.connect(self._on_tank_body_rotated)
+	self.turret.ready_to_shoot.connect(self._on_turret_ready_to_shoot)
+	
+	#-------------------------------------------------------------------------#
+
+
+func target_waypoint_reached() -> bool:
+	var x_dif = abs(self.global_position.x - self.target_waypoint.x)
+	var y_dif = abs(self.global_position.y - self.target_waypoint.y)
+	
+	if x_dif <= self.nav_margin and y_dif <= self.nav_margin:
+		return true
+	
+	return false
+
+
+# Callable to connect to Turret's ready_to_shoot signal.
+func _on_turret_ready_to_shoot() -> void:
+	self.behavior.send_event("stop")
+	self.clear_pathfinding.emit()
+
+# Callable to connect to Pathfinder's new_waypoint signal
+func _on_pathfinder_new_waypoint(waypoint: Vector2) -> void:
+	self.target_waypoint = waypoint
+	self.behavior.send_event("new_waypoint")
+
+
+#-----------------------------------------------------------------------------#
+# RotateBody State
+func _on_rotate_body_state_entered() -> void:
+	self.travel_target.emit(self.target_waypoint)
+
+
+# Callable to connect to TankBody's rotated signal
+func _on_tank_body_rotated() -> void:
+	self.behavior.send_event("move")
+
+
+#-----------------------------------------------------------------------------#
+# Moving State
+func _on_moving_state_physics_processing(delta: float) -> void:
+	if self.target_waypoint_reached():
+		self.behavior.send_event("waypoint_reached")
+		return
+	
+	var target_direction = self.global_position.direction_to(self.target_waypoint)
+	self.velocity = target_direction * self.SPEED
+	
+	var motion = self.velocity * delta
+	self.move_and_collide(motion)
+
+
+func _on_waypoint_reached_taken() -> void:
+	self.waypoint_reached.emit()
+
+
+func _on_tank_movement_state_entered() -> void:
+	if not self._map_initialized:
+		# Initialize the empty ObstacleMap by getting the dimensions from the world.
+		var world: TankWarsInstance = self.get_parent()
+		var dimensions = world.get_map_dimensions()
+		self.obstacle_map.build_empty_map(dimensions)
+		self._map_initialized = true

+ 1 - 0
Godot Exploration - 2d/TankWars/tank.gd.uid

@@ -0,0 +1 @@
+uid://dk2iio3sliuqo

+ 0 - 0
Godot Exploration - 2d/TankWars/tank.tscn


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.