From 19cc8cb573826e6314b800b85968c063ee77f92a Mon Sep 17 00:00:00 2001 From: Dan Baker Date: Wed, 7 May 2025 11:52:30 +0100 Subject: [PATCH] Implements modifier and stats system Adds a modifier and stats system to manage combat-related attributes. Introduces StatsComponent for storing entity statistics and ModifierManager for applying dynamic modifiers. Recalculates stats based on modifier type and priority. Updates projectile and weapon components to utilize the new stats system. --- combat/modifiers/_scripts/modifier_manager.gd | 90 ++++++++++++++++++- .../projectiles/_scripts/projectile_base.gd | 14 +++ combat/projectiles/rock/RockProjectile.tscn | 6 -- .../rock/scripts/rock_projectile.gd | 16 +++- .../_scripts/melee_weapon_component.gd | 14 +-- .../_scripts/ranged_weapon_component.gd | 26 ++---- combat/weapons/_scripts/weapon_component.gd | 18 ---- .../weapons/_scripts/weapon_component.gd.uid | 1 - entities/StatsComponent.tscn | 6 ++ entities/scripts/stats_component.gd | 32 +++++++ entities/scripts/stats_component.gd.uid | 1 + player/player.tscn | 8 +- utility/Globals.gd | 11 ++- 13 files changed, 178 insertions(+), 65 deletions(-) delete mode 100644 combat/weapons/_scripts/weapon_component.gd delete mode 100644 combat/weapons/_scripts/weapon_component.gd.uid create mode 100644 entities/StatsComponent.tscn create mode 100644 entities/scripts/stats_component.gd create mode 100644 entities/scripts/stats_component.gd.uid diff --git a/combat/modifiers/_scripts/modifier_manager.gd b/combat/modifiers/_scripts/modifier_manager.gd index 955fe0d..4d5ca30 100644 --- a/combat/modifiers/_scripts/modifier_manager.gd +++ b/combat/modifiers/_scripts/modifier_manager.gd @@ -2,5 +2,91 @@ extends Node2D class_name ModifierManagerTwo -func _init() -> void: - Log.pr("ModifierManagerTwo initialized") \ No newline at end of file +signal modifier_added(modifier) +signal modifier_removed(modifier) +signal stats_updated() + +var stats: StatsComponent + +# Stores all active modifiers +var modifiers: Array[Modifier] = [] + +# Base stats (before modifiers) +var base_stats: Dictionary = {} + +# Final calculated stats +var final_stats: Dictionary = {} + +func _ready() -> void: + Log.pr("ModifierManager initialized") + Log.pr("Stats: ", stats) + +func set_stats(stats_component: StatsComponent) -> void: + self.stats = stats_component + +func add_modifier(modifier: Modifier) -> void: + modifiers.append(modifier) + modifier.on_equip(get_parent()) + emit_signal("modifier_added", modifier) + recalculate_stats() + +func remove_modifier(modifier_id: String) -> void: + for i in range(modifiers.size()): + if modifiers[i].id == modifier_id: + var modifier = modifiers[i] + modifier.on_unequip(get_parent()) + modifiers.remove_at(i) + emit_signal("modifier_removed", modifier) + recalculate_stats() + break + +func recalculate_stats() -> void: + # Reset stats to base values + final_stats = base_stats.duplicate() + + # Sort modifiers by priority + modifiers.sort_custom(func(a, b): return a.priority > b.priority) + + # First pass: Apply OVERRIDE modifiers (highest priority first) + for modifier in modifiers: + if modifier.modifier_type == Modifier.ModifierType.OVERRIDE: + _apply_modifier_stats(modifier) + + # Second pass: Apply ADDITIVE modifiers + for modifier in modifiers: + if modifier.modifier_type == Modifier.ModifierType.ADDITIVE: + _apply_modifier_stats(modifier) + + # Third pass: Apply MULTIPLICATIVE modifiers + for modifier in modifiers: + if modifier.modifier_type == Modifier.ModifierType.MULTIPLICATIVE: + _apply_modifier_stats(modifier) + + # Last pass: Apply CONDITIONAL modifiers + for modifier in modifiers: + if modifier.modifier_type == Modifier.ModifierType.CONDITIONAL: + _apply_modifier_stats(modifier) + + # Apply caps and floors to stats + _apply_stat_limits() + + emit_signal("stats_updated") + +func _apply_modifier_stats(modifier: Modifier) -> void: + if modifier.has_method("apply_stats_modification"): + modifier.apply_stats_modification(final_stats, base_stats) + +func _apply_stat_limits() -> void: + pass + # Example: Cap fire rate + #if final_stats.has("fire_rate"): + # final_stats.fire_rate = min(final_stats.fire_rate, 20.0) # Max 20 shots per second + # final_stats.fire_rate = max(final_stats.fire_rate, 0.5) # Min 0.5 shots per second + + # Example: Cap projectile size + #if final_stats.has("projectile_size"): + # final_stats.projectile_size = min(final_stats.projectile_size, 5.0) # Max 5x normal size + # final_stats.projectile_size = max(final_stats.projectile_size, 0.2) # Min 0.2x normal size + +func get_stat(stat_name: String, default_value = 0): + return final_stats.get(stat_name, default_value) diff --git a/combat/projectiles/_scripts/projectile_base.gd b/combat/projectiles/_scripts/projectile_base.gd index 1451a4a..ad7be2c 100644 --- a/combat/projectiles/_scripts/projectile_base.gd +++ b/combat/projectiles/_scripts/projectile_base.gd @@ -3,12 +3,26 @@ class_name ProjectileBaseTwo var lifetime_timer: Timer +var direction: Vector2 +var target_position: Vector2 +var ELEMENTS = Global.ELEMENTS + +var has_collided: bool = false + var stats = { "speed": 500.0, "damage": 10.0, + "element": ELEMENTS.NONE, "lifetime": 2, + "pierce_count": 0 } +func set_stat(stat_name: String, value: Variant): + stats[stat_name] = value + +func get_stat(stat_name: String) -> Variant: + return stats.get(stat_name, null) + func destroy(): emit_signal("on_destroyed", self) queue_free() diff --git a/combat/projectiles/rock/RockProjectile.tscn b/combat/projectiles/rock/RockProjectile.tscn index 9372e7b..bd7b6ae 100644 --- a/combat/projectiles/rock/RockProjectile.tscn +++ b/combat/projectiles/rock/RockProjectile.tscn @@ -4,9 +4,3 @@ [node name="RockProjectile" type="Node2D"] script = ExtResource("1_8myby") -speed = null -damage = null -lifetime = null -direction = null -target_position = null -is_friendly = null diff --git a/combat/projectiles/rock/scripts/rock_projectile.gd b/combat/projectiles/rock/scripts/rock_projectile.gd index fde1073..44e2441 100644 --- a/combat/projectiles/rock/scripts/rock_projectile.gd +++ b/combat/projectiles/rock/scripts/rock_projectile.gd @@ -5,7 +5,7 @@ func _ready(): lifetime_timer = Timer.new() add_child(lifetime_timer) lifetime_timer.one_shot = true - lifetime_timer.wait_time = lifetime + lifetime_timer.wait_time = stats.lifetime lifetime_timer.connect("timeout", _on_lifetime_timeout) lifetime_timer.start() @@ -13,7 +13,19 @@ func _ready(): connect("body_entered", _on_body_entered) func _physics_process(delta): - position += direction * speed * delta + position += direction * stats.speed * delta + +func _on_body_entered(body): + if body.is_in_group("enemies"): + body.take_damage(stats.damage) + + ## Check if the projectile is piercing + if not get_stat("pierce_count"): + # If not piercing, destroy the projectile + destroy() + else: + # If piercing, reduce the pierce count + set_stat("pierce_count", get_stat("pierce_count") - 1) func _on_lifetime_timeout(): super._on_lifetime_timeout() diff --git a/combat/weapons/_scripts/melee_weapon_component.gd b/combat/weapons/_scripts/melee_weapon_component.gd index 8601446..0137586 100644 --- a/combat/weapons/_scripts/melee_weapon_component.gd +++ b/combat/weapons/_scripts/melee_weapon_component.gd @@ -1,18 +1,6 @@ @icon("res://assets/editor/64x64/fc728.png") -extends WeaponComponent +extends Node2D class_name MeleeWeaponComponent -var stats = { - "piercing": 3, -} -var combined_stats = {} - func _init() -> void: Log.pr("MeleeWeaponComponent initialized") - super._init() - - # Combine the base stats with the stats from the parent class - combined_stats = base_stats.duplicate() - combined_stats.merge(stats) - - Log.pr("Combined stats: ", combined_stats) \ No newline at end of file diff --git a/combat/weapons/_scripts/ranged_weapon_component.gd b/combat/weapons/_scripts/ranged_weapon_component.gd index cc6c05e..892a5ca 100644 --- a/combat/weapons/_scripts/ranged_weapon_component.gd +++ b/combat/weapons/_scripts/ranged_weapon_component.gd @@ -1,25 +1,11 @@ @icon("res://assets/editor/64x64/fc1515.png") -extends WeaponComponent +extends Node2D class_name RangedWeaponComponent -var stats = { - "projectile_speed": 500.0, - "projectile_size": 1.0, - "projectile_lifetime": 1.0, - "projectile_quantity": 1, - "projectile_spread": 33, - "max_pierce": 0 -} -var combined_stats = {} +@export var stats: StatsComponent -func _init() -> void: +@onready var modifier_manager = $ModifierManager + +func _ready() -> void: Log.pr("RangedWeaponComponent initialized") - super._init() - - # Combine the base stats with the stats from the parent class - combined_stats = base_stats.duplicate() - combined_stats.merge(stats) - - Log.pr("Combined stats: ", combined_stats) - - Log.pr("ModifierManager: ", modifier_manager) \ No newline at end of file + modifier_manager.set_stats(stats) diff --git a/combat/weapons/_scripts/weapon_component.gd b/combat/weapons/_scripts/weapon_component.gd deleted file mode 100644 index a912cf9..0000000 --- a/combat/weapons/_scripts/weapon_component.gd +++ /dev/null @@ -1,18 +0,0 @@ -@icon("res://assets/editor/64x64/fc729.png") -extends Node2D -class_name WeaponComponent - -var modifier_manager - -var base_stats = { - "damage": 10.0, - "attack_rate": 3.0 -} - -func _init() -> void: - Log.pr("WeaponComponent initialized") - -func _ready() -> void: - await get_tree().process_frame - modifier_manager = $ModifierManager - Log.pr("ModifierManager: ", modifier_manager) diff --git a/combat/weapons/_scripts/weapon_component.gd.uid b/combat/weapons/_scripts/weapon_component.gd.uid deleted file mode 100644 index 392a122..0000000 --- a/combat/weapons/_scripts/weapon_component.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dl3kvbu0rwxky diff --git a/entities/StatsComponent.tscn b/entities/StatsComponent.tscn new file mode 100644 index 0000000..74bc246 --- /dev/null +++ b/entities/StatsComponent.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bjybfg0xrowb5"] + +[ext_resource type="Script" uid="uid://bd63bbuh27fj0" path="res://entities/scripts/stats_component.gd" id="1_ll8eh"] + +[node name="StatsComponent" type="Node2D"] +script = ExtResource("1_ll8eh") diff --git a/entities/scripts/stats_component.gd b/entities/scripts/stats_component.gd new file mode 100644 index 0000000..f202eda --- /dev/null +++ b/entities/scripts/stats_component.gd @@ -0,0 +1,32 @@ +@icon("res://assets/editor/64x64/fc681.png") +extends Node2D +class_name StatsComponent + +var stats: Dictionary[String, Variant] = { + "base": { + "health": 100, + "max_health": 100, + "armor": 0, + "max_armor": 0, + "energy": 100, + "max_energy": 100, + "speed": 200.0, + }, + "melee": { + "damage": 10, + "attack_rate": 1, + "element": Global.ELEMENTS.NONE, + }, + "ranged": { + "damage": 10, + "attack_rate": 1, + "element": Global.ELEMENTS.NONE, + "projectile_speed": 500.0, + "projectile_size": 1.0, + "projectile_lifetime": 1.0, + "projectile_quantity": 1, + "projectile_explode_quantity": 0, + "projectile_explode_damage": 0.5, + "pierce_count": 0 + } +} diff --git a/entities/scripts/stats_component.gd.uid b/entities/scripts/stats_component.gd.uid new file mode 100644 index 0000000..85ab3c1 --- /dev/null +++ b/entities/scripts/stats_component.gd.uid @@ -0,0 +1 @@ +uid://bd63bbuh27fj0 diff --git a/player/player.tscn b/player/player.tscn index 6f348ce..e469c46 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=85 format=3 uid="uid://bo5aw2cad3akl"] +[gd_scene load_steps=86 format=3 uid="uid://bo5aw2cad3akl"] [ext_resource type="Script" uid="uid://bq038uo4cm6nv" path="res://player/scripts/player.gd" id="1_oul6g"] [ext_resource type="Texture2D" uid="uid://dqgq2c1h6yk3k" path="res://assets/sprites/characters/pink/Pink_Monster_Attack1_4.png" id="2_yllr7"] @@ -16,6 +16,7 @@ [ext_resource type="PackedScene" uid="uid://cgxn1f4p4vik6" path="res://assets/weapons/ranged_weapon.tscn" id="14_kb6p2"] [ext_resource type="PackedScene" uid="uid://dud7c465danl4" path="res://combat/weapons/RangedWeaponComponent.tscn" id="15_wodsf"] [ext_resource type="PackedScene" uid="uid://dqful6et42ok8" path="res://combat/weapons/MeleeWeaponComponent.tscn" id="16_32hag"] +[ext_resource type="PackedScene" uid="uid://bjybfg0xrowb5" path="res://entities/StatsComponent.tscn" id="17_tqiix"] [sub_resource type="CircleShape2D" id="CircleShape2D_rkbax"] @@ -565,6 +566,9 @@ zoom = Vector2(2, 2) [node name="RangedWeapon" parent="." instance=ExtResource("14_kb6p2")] -[node name="RangedWeaponComponent" parent="." instance=ExtResource("15_wodsf")] +[node name="RangedWeaponComponent" parent="." node_paths=PackedStringArray("stats") instance=ExtResource("15_wodsf")] +stats = NodePath("../StatsComponent") [node name="MeleeWeaponComponent" parent="." instance=ExtResource("16_32hag")] + +[node name="StatsComponent" parent="." instance=ExtResource("17_tqiix")] diff --git a/utility/Globals.gd b/utility/Globals.gd index 534d90f..d63192a 100644 --- a/utility/Globals.gd +++ b/utility/Globals.gd @@ -11,4 +11,13 @@ const MAP_EMPTY = 0 const MAP_PATH = 1 const MAP_START = 2 const MAP_FINISH = 4 -const MAP_UP_CELL = 3 \ No newline at end of file +const MAP_UP_CELL = 3 + +const ELEMENTS = { + "NONE": 0, + "FIRE": 1, + "WATER": 2, + "EARTH": 3, + "AIR": 4, + "THUNDER": 5, +} \ No newline at end of file