From d1c249000a6966c1ae1c25bfc80232be5a8ab1a4 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Fri, 12 Jun 2026 22:41:25 +0200 Subject: [PATCH 01/12] Add health component --- scenes/game_logic/health_component.gd | 38 +++++++++++++++++++++++ scenes/game_logic/health_component.gd.uid | 1 + 2 files changed, 39 insertions(+) create mode 100644 scenes/game_logic/health_component.gd create mode 100644 scenes/game_logic/health_component.gd.uid diff --git a/scenes/game_logic/health_component.gd b/scenes/game_logic/health_component.gd new file mode 100644 index 000000000..04a6e19f6 --- /dev/null +++ b/scenes/game_logic/health_component.gd @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +class_name HealthComponent +extends Node2D + +signal health_depleted +signal health_changed(current_health: int, has_depleted_health: bool) + +@export_range(1, 100) var max_health: int = 4 + +var current_health: int = max_health: + set(value): + var old_health: int = current_health + current_health = value + if value <= 0: + health_depleted.emit() + elif value != old_health: + health_changed.emit(value, has_depleted_health) + +var damage_taken: int: + get: + return max_health - current_health + +var damage_taken_percentage: float: + get: + return float(damage_taken) / max_health + +var has_depleted_health: bool: + get: + return current_health <= 0 + + +func damage(amount: int) -> void: + current_health -= amount + + +func heal(amount: int) -> void: + current_health += amount diff --git a/scenes/game_logic/health_component.gd.uid b/scenes/game_logic/health_component.gd.uid new file mode 100644 index 000000000..4c03ccc36 --- /dev/null +++ b/scenes/game_logic/health_component.gd.uid @@ -0,0 +1 @@ +uid://b0qrfv6upnqtu From b9c60b297c8e6560740bc6bd2d757b5be4d255b5 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Fri, 12 Jun 2026 23:10:38 +0200 Subject: [PATCH 02/12] Refactor fragile barrel to use health component Changed break_barrel and take_damage functions to be called by health component signals Replaced existing health logic with health component --- .../components/fragile_barrel.gd | 23 +++++-------------- .../props/filling_barrel/fragile_barrel.tscn | 10 +++++++- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd index 82b8b348f..fa44c9ace 100644 --- a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd +++ b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd @@ -7,50 +7,39 @@ extends FillingBarrel ## Emitted when [member current_health] reaches 0. signal barrel_destroyed(barrel_instance: FragileBarrel) -## Maximum hits the barrel can take before breaking. -@export_range(1, 100) var max_health: int = 4 - -var current_health: int - @onready var crack_overlay_node: AnimatedSprite2D = %CrackOverlay @onready var crack_sound: AudioStreamPlayer2D = %CrackSound @onready var shatter_sound: AudioStreamPlayer2D = %ShatterSound +@onready var health_component: HealthComponent = %HealthComponent func _ready() -> void: super._ready() - current_health = max_health crack_overlay_node.visible = false # Logic called by Projectile when it hits this object func hit_by_droplet(droplet_label: String) -> void: # Ignore if already full or destroyed - if _amount >= needed_amount or current_health <= 0: + if _amount >= needed_amount or health_component.has_depleted_health: return if droplet_label == self.label: increment() else: - take_damage() - + health_component.damage(1) -func take_damage() -> void: - current_health -= 1 - if current_health <= 0: - break_barrel() - else: +func take_damage(_current_health: int, has_depleted_health: bool) -> void: + if not has_depleted_health: crack_sound.play() update_cracks() func update_cracks() -> void: - var damage_taken: int = max_health - current_health - # IMPROVEMENT: Calculate frame index proportionally based on damage percentage. var total_frames: int = crack_overlay_node.sprite_frames.get_frame_count("default") - var frame_index: int = int(floor((float(damage_taken) / max_health) * total_frames)) + var frame_index: int = int(floor((health_component.damage_taken_percentage) * total_frames)) # Clamp to ensure we don't exceed available frames (0-based index) frame_index = clamp(frame_index, 0, total_frames - 1) diff --git a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn index 9ec6d6780..9557b9803 100644 --- a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn +++ b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn @@ -5,10 +5,10 @@ [ext_resource type="Script" uid="uid://d05hp7ascndlr" path="res://scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd" id="2_wal8y"] [ext_resource type="AudioStream" uid="uid://bknpb07lvbded" path="res://scenes/game_elements/props/filling_barrel/components/sfx_barrel_crack.tres" id="4_sdj6j"] [ext_resource type="AudioStream" uid="uid://c3iuv5ax8i78v" path="res://scenes/game_elements/props/filling_barrel/components/sfx_barrel_breaking.tres" id="5_v7k7g"] +[ext_resource type="Script" uid="uid://b0qrfv6upnqtu" path="res://scenes/game_logic/health_component.gd" id="6_v7k7g"] [node name="FragileBarrel" unique_id=2064522120 instance=ExtResource("1_ab25j")] script = ExtResource("2_wal8y") -max_health = 4 [node name="CrackOverlay" type="AnimatedSprite2D" parent="." index="1" unique_id=473951260] unique_name_in_owner = true @@ -25,3 +25,11 @@ bus = &"SFX" unique_name_in_owner = true stream = ExtResource("5_v7k7g") bus = &"SFX" + +[node name="HealthComponent" type="Node2D" parent="." index="10" unique_id=1654759649] +unique_name_in_owner = true +script = ExtResource("6_v7k7g") +metadata/_custom_type_script = "uid://b0qrfv6upnqtu" + +[connection signal="health_changed" from="HealthComponent" to="." method="take_damage"] +[connection signal="health_depleted" from="HealthComponent" to="." method="break_barrel"] From e9c816fdc4900d4fcf6061fc0c9c91d0d8612c48 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Fri, 12 Jun 2026 23:49:46 +0200 Subject: [PATCH 03/12] Adjust `current_health` setter logic This should make the setter more readable Also removed the redundant `has_depleted_health` signal argument --- scenes/game_logic/health_component.gd | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scenes/game_logic/health_component.gd b/scenes/game_logic/health_component.gd index 04a6e19f6..feb924f67 100644 --- a/scenes/game_logic/health_component.gd +++ b/scenes/game_logic/health_component.gd @@ -4,18 +4,20 @@ class_name HealthComponent extends Node2D signal health_depleted -signal health_changed(current_health: int, has_depleted_health: bool) +signal health_changed(current_health: int) @export_range(1, 100) var max_health: int = 4 var current_health: int = max_health: set(value): - var old_health: int = current_health + if value == current_health: + pass + current_health = value - if value <= 0: + if current_health <= 0: health_depleted.emit() - elif value != old_health: - health_changed.emit(value, has_depleted_health) + else: + health_changed.emit(current_health) var damage_taken: int: get: From d9575858c50a87de1f87180413edf5465f94302f Mon Sep 17 00:00:00 2001 From: Litraxx Date: Fri, 12 Jun 2026 23:51:11 +0200 Subject: [PATCH 04/12] Adjust signal connection to health component The check is not needed since the signal is only ever called when the health change does not deplete the health --- .../props/filling_barrel/components/fragile_barrel.gd | 7 +++---- .../game_elements/props/filling_barrel/fragile_barrel.tscn | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd index fa44c9ace..652767860 100644 --- a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd +++ b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd @@ -30,10 +30,9 @@ func hit_by_droplet(droplet_label: String) -> void: health_component.damage(1) -func take_damage(_current_health: int, has_depleted_health: bool) -> void: - if not has_depleted_health: - crack_sound.play() - update_cracks() +func take_damage() -> void: + crack_sound.play() + update_cracks() func update_cracks() -> void: diff --git a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn index 9557b9803..068b80348 100644 --- a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn +++ b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn @@ -31,5 +31,5 @@ unique_name_in_owner = true script = ExtResource("6_v7k7g") metadata/_custom_type_script = "uid://b0qrfv6upnqtu" -[connection signal="health_changed" from="HealthComponent" to="." method="take_damage"] +[connection signal="health_changed" from="HealthComponent" to="." method="take_damage" unbinds=1] [connection signal="health_depleted" from="HealthComponent" to="." method="break_barrel"] From b5ba79a10b067e58697802a62716432d8bd5a5e2 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Mon, 15 Jun 2026 18:20:18 +0200 Subject: [PATCH 05/12] Update current_health setter logic Added a clamp to the current_health to avoid unexpected values in other members --- scenes/game_logic/health_component.gd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scenes/game_logic/health_component.gd b/scenes/game_logic/health_component.gd index feb924f67..045da8c3d 100644 --- a/scenes/game_logic/health_component.gd +++ b/scenes/game_logic/health_component.gd @@ -11,9 +11,9 @@ signal health_changed(current_health: int) var current_health: int = max_health: set(value): if value == current_health: - pass + return - current_health = value + current_health = clamp(value, 0, max_health) if current_health <= 0: health_depleted.emit() else: From 3812bf9a19308084c12eca6b9878380d1e797eba Mon Sep 17 00:00:00 2001 From: Litraxx Date: Mon, 15 Jun 2026 18:21:33 +0200 Subject: [PATCH 06/12] Rename damage_taken_percentage to align with godot convention --- scenes/game_logic/health_component.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/game_logic/health_component.gd b/scenes/game_logic/health_component.gd index 045da8c3d..894b5a1a2 100644 --- a/scenes/game_logic/health_component.gd +++ b/scenes/game_logic/health_component.gd @@ -23,7 +23,7 @@ var damage_taken: int: get: return max_health - current_health -var damage_taken_percentage: float: +var damage_taken_ratio: float: get: return float(damage_taken) / max_health From df934abc3d4d40405d1422c37f5e5aad54ddc149 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Mon, 15 Jun 2026 18:22:14 +0200 Subject: [PATCH 07/12] Add code documentation to signals and current_health --- scenes/game_logic/health_component.gd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scenes/game_logic/health_component.gd b/scenes/game_logic/health_component.gd index 894b5a1a2..315f94fa0 100644 --- a/scenes/game_logic/health_component.gd +++ b/scenes/game_logic/health_component.gd @@ -3,11 +3,17 @@ class_name HealthComponent extends Node2D +## Emitted when [member current_health] reaches zero. signal health_depleted + +## Emitted when [member current_health] changes.[br] +## Is not emitted when the health reaches zero, [signal health_depleted] is emitted instead. signal health_changed(current_health: int) @export_range(1, 100) var max_health: int = 4 +## Represents the current health.[br] +## The value will always be in the following range [code]0 <= current_health <= max_health[/code]. var current_health: int = max_health: set(value): if value == current_health: From 1e852affd1fa8521a6c4b0bf7bdfaec87bbb2ff2 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Mon, 15 Jun 2026 18:24:08 +0200 Subject: [PATCH 08/12] Rename take_damage function to reflect new behaviour --- .../props/filling_barrel/components/fragile_barrel.gd | 10 +++++----- .../props/filling_barrel/fragile_barrel.tscn | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd index 652767860..88b68dc1b 100644 --- a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd +++ b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd @@ -30,11 +30,6 @@ func hit_by_droplet(droplet_label: String) -> void: health_component.damage(1) -func take_damage() -> void: - crack_sound.play() - update_cracks() - - func update_cracks() -> void: # IMPROVEMENT: Calculate frame index proportionally based on damage percentage. var total_frames: int = crack_overlay_node.sprite_frames.get_frame_count("default") @@ -57,3 +52,8 @@ func break_barrel() -> void: await animated_sprite_2d.animation_finished barrel_destroyed.emit(self) + + +func _on_health_component_health_changed() -> void: + crack_sound.play() + update_cracks() diff --git a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn index 068b80348..b152c4753 100644 --- a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn +++ b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn @@ -31,5 +31,5 @@ unique_name_in_owner = true script = ExtResource("6_v7k7g") metadata/_custom_type_script = "uid://b0qrfv6upnqtu" -[connection signal="health_changed" from="HealthComponent" to="." method="take_damage" unbinds=1] +[connection signal="health_changed" from="HealthComponent" to="." method="_on_health_component_health_changed" unbinds=1] [connection signal="health_depleted" from="HealthComponent" to="." method="break_barrel"] From dfcee69e49059d7eb2242bfbb7325b4466729469 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Mon, 15 Jun 2026 18:24:47 +0200 Subject: [PATCH 09/12] Update member reference --- .../props/filling_barrel/components/fragile_barrel.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd index 88b68dc1b..bbc8b921e 100644 --- a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd +++ b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd @@ -33,7 +33,7 @@ func hit_by_droplet(droplet_label: String) -> void: func update_cracks() -> void: # IMPROVEMENT: Calculate frame index proportionally based on damage percentage. var total_frames: int = crack_overlay_node.sprite_frames.get_frame_count("default") - var frame_index: int = int(floor((health_component.damage_taken_percentage) * total_frames)) + var frame_index: int = int(floor((health_component.damage_taken_ratio) * total_frames)) # Clamp to ensure we don't exceed available frames (0-based index) frame_index = clamp(frame_index, 0, total_frames - 1) From 0676869200adafb39183a51abf420430ddffd200 Mon Sep 17 00:00:00 2001 From: Litraxx Date: Mon, 15 Jun 2026 18:25:27 +0200 Subject: [PATCH 10/12] Update documentation of signal --- .../props/filling_barrel/components/fragile_barrel.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd index bbc8b921e..08a527d68 100644 --- a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd +++ b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd @@ -4,7 +4,7 @@ class_name FragileBarrel extends FillingBarrel -## Emitted when [member current_health] reaches 0. +## Emitted when barrel's health reaches 0. signal barrel_destroyed(barrel_instance: FragileBarrel) @onready var crack_overlay_node: AnimatedSprite2D = %CrackOverlay From ebef24bcc86bd8ae65975fd2b3fd96b0ddfa775f Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Tue, 16 Jun 2026 08:56:28 +0100 Subject: [PATCH 11/12] HealthComponent: Change base type to Node This component does not need a position in 2D space. --- scenes/game_elements/props/filling_barrel/fragile_barrel.tscn | 2 +- scenes/game_logic/health_component.gd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn index b152c4753..9cec7bfb5 100644 --- a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn +++ b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn @@ -26,7 +26,7 @@ unique_name_in_owner = true stream = ExtResource("5_v7k7g") bus = &"SFX" -[node name="HealthComponent" type="Node2D" parent="." index="10" unique_id=1654759649] +[node name="HealthComponent" type="Node" parent="." index="10" unique_id=707508929] unique_name_in_owner = true script = ExtResource("6_v7k7g") metadata/_custom_type_script = "uid://b0qrfv6upnqtu" diff --git a/scenes/game_logic/health_component.gd b/scenes/game_logic/health_component.gd index 315f94fa0..38425cb93 100644 --- a/scenes/game_logic/health_component.gd +++ b/scenes/game_logic/health_component.gd @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: The Threadbare Authors # SPDX-License-Identifier: MPL-2.0 class_name HealthComponent -extends Node2D +extends Node ## Emitted when [member current_health] reaches zero. signal health_depleted From 7f2fa8c2e616caec2795d917f1497d41cdc0d655 Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Tue, 16 Jun 2026 08:57:13 +0100 Subject: [PATCH 12/12] FragileBarrel: Rename health_depleted callback --- .../props/filling_barrel/components/fragile_barrel.gd | 2 +- scenes/game_elements/props/filling_barrel/fragile_barrel.tscn | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd index 08a527d68..b2c719020 100644 --- a/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd +++ b/scenes/game_elements/props/filling_barrel/components/fragile_barrel.gd @@ -42,7 +42,7 @@ func update_cracks() -> void: crack_overlay_node.frame = frame_index -func break_barrel() -> void: +func _on_health_component_health_depleted() -> void: crack_overlay_node.visible = false shatter_sound.play() diff --git a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn index 9cec7bfb5..73b2b209a 100644 --- a/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn +++ b/scenes/game_elements/props/filling_barrel/fragile_barrel.tscn @@ -32,4 +32,4 @@ script = ExtResource("6_v7k7g") metadata/_custom_type_script = "uid://b0qrfv6upnqtu" [connection signal="health_changed" from="HealthComponent" to="." method="_on_health_component_health_changed" unbinds=1] -[connection signal="health_depleted" from="HealthComponent" to="." method="break_barrel"] +[connection signal="health_depleted" from="HealthComponent" to="." method="_on_health_component_health_depleted"]