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.
This commit is contained in:
parent
f97521decc
commit
19cc8cb573
13 changed files with 178 additions and 65 deletions
|
|
@ -2,5 +2,91 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
class_name ModifierManagerTwo
|
class_name ModifierManagerTwo
|
||||||
|
|
||||||
func _init() -> void:
|
signal modifier_added(modifier)
|
||||||
Log.pr("ModifierManagerTwo initialized")
|
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)
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,26 @@ class_name ProjectileBaseTwo
|
||||||
|
|
||||||
var lifetime_timer: Timer
|
var lifetime_timer: Timer
|
||||||
|
|
||||||
|
var direction: Vector2
|
||||||
|
var target_position: Vector2
|
||||||
|
var ELEMENTS = Global.ELEMENTS
|
||||||
|
|
||||||
|
var has_collided: bool = false
|
||||||
|
|
||||||
var stats = {
|
var stats = {
|
||||||
"speed": 500.0,
|
"speed": 500.0,
|
||||||
"damage": 10.0,
|
"damage": 10.0,
|
||||||
|
"element": ELEMENTS.NONE,
|
||||||
"lifetime": 2,
|
"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():
|
func destroy():
|
||||||
emit_signal("on_destroyed", self)
|
emit_signal("on_destroyed", self)
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,3 @@
|
||||||
|
|
||||||
[node name="RockProjectile" type="Node2D"]
|
[node name="RockProjectile" type="Node2D"]
|
||||||
script = ExtResource("1_8myby")
|
script = ExtResource("1_8myby")
|
||||||
speed = null
|
|
||||||
damage = null
|
|
||||||
lifetime = null
|
|
||||||
direction = null
|
|
||||||
target_position = null
|
|
||||||
is_friendly = null
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ func _ready():
|
||||||
lifetime_timer = Timer.new()
|
lifetime_timer = Timer.new()
|
||||||
add_child(lifetime_timer)
|
add_child(lifetime_timer)
|
||||||
lifetime_timer.one_shot = true
|
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.connect("timeout", _on_lifetime_timeout)
|
||||||
lifetime_timer.start()
|
lifetime_timer.start()
|
||||||
|
|
||||||
|
|
@ -13,7 +13,19 @@ func _ready():
|
||||||
connect("body_entered", _on_body_entered)
|
connect("body_entered", _on_body_entered)
|
||||||
|
|
||||||
func _physics_process(delta):
|
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():
|
func _on_lifetime_timeout():
|
||||||
super._on_lifetime_timeout()
|
super._on_lifetime_timeout()
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
@icon("res://assets/editor/64x64/fc728.png")
|
@icon("res://assets/editor/64x64/fc728.png")
|
||||||
extends WeaponComponent
|
extends Node2D
|
||||||
class_name MeleeWeaponComponent
|
class_name MeleeWeaponComponent
|
||||||
|
|
||||||
var stats = {
|
|
||||||
"piercing": 3,
|
|
||||||
}
|
|
||||||
var combined_stats = {}
|
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
Log.pr("MeleeWeaponComponent initialized")
|
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)
|
|
||||||
|
|
@ -1,25 +1,11 @@
|
||||||
@icon("res://assets/editor/64x64/fc1515.png")
|
@icon("res://assets/editor/64x64/fc1515.png")
|
||||||
extends WeaponComponent
|
extends Node2D
|
||||||
class_name RangedWeaponComponent
|
class_name RangedWeaponComponent
|
||||||
|
|
||||||
var stats = {
|
@export var stats: StatsComponent
|
||||||
"projectile_speed": 500.0,
|
|
||||||
"projectile_size": 1.0,
|
|
||||||
"projectile_lifetime": 1.0,
|
|
||||||
"projectile_quantity": 1,
|
|
||||||
"projectile_spread": 33,
|
|
||||||
"max_pierce": 0
|
|
||||||
}
|
|
||||||
var combined_stats = {}
|
|
||||||
|
|
||||||
func _init() -> void:
|
@onready var modifier_manager = $ModifierManager
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
Log.pr("RangedWeaponComponent initialized")
|
Log.pr("RangedWeaponComponent initialized")
|
||||||
super._init()
|
modifier_manager.set_stats(stats)
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://dl3kvbu0rwxky
|
|
||||||
6
entities/StatsComponent.tscn
Normal file
6
entities/StatsComponent.tscn
Normal file
|
|
@ -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")
|
||||||
32
entities/scripts/stats_component.gd
Normal file
32
entities/scripts/stats_component.gd
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
1
entities/scripts/stats_component.gd.uid
Normal file
1
entities/scripts/stats_component.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bd63bbuh27fj0
|
||||||
|
|
@ -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="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"]
|
[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://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://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://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"]
|
[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="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="MeleeWeaponComponent" parent="." instance=ExtResource("16_32hag")]
|
||||||
|
|
||||||
|
[node name="StatsComponent" parent="." instance=ExtResource("17_tqiix")]
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,13 @@ const MAP_EMPTY = 0
|
||||||
const MAP_PATH = 1
|
const MAP_PATH = 1
|
||||||
const MAP_START = 2
|
const MAP_START = 2
|
||||||
const MAP_FINISH = 4
|
const MAP_FINISH = 4
|
||||||
const MAP_UP_CELL = 3
|
const MAP_UP_CELL = 3
|
||||||
|
|
||||||
|
const ELEMENTS = {
|
||||||
|
"NONE": 0,
|
||||||
|
"FIRE": 1,
|
||||||
|
"WATER": 2,
|
||||||
|
"EARTH": 3,
|
||||||
|
"AIR": 4,
|
||||||
|
"THUNDER": 5,
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue