From fac3327c226ec4bf7373c836b43bad5c25e29980 Mon Sep 17 00:00:00 2001 From: Dan Baker Date: Fri, 9 May 2025 13:42:09 +0100 Subject: [PATCH] Improves projectile handling and modifiers Refactors projectile spawning to allow for customized spawn locations and stat assignment. Applies modifiers to projectiles at the time of spawning, enabling dynamic adjustments to projectile behavior. --- assets/projectiles/components/bolt.gd | 2 + assets/projectiles/components/bolt.tscn | 5 +++ .../_scripts/ranged_weapon_component.gd | 26 ++++++++++--- map/Map.tscn | 2 +- player/weapons/projectile_base.gd | 37 ++++++++++++++++++- player/weapons/projectile_lightning.gd | 13 +++++-- 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/assets/projectiles/components/bolt.gd b/assets/projectiles/components/bolt.gd index 5e2b89b..bf335bd 100644 --- a/assets/projectiles/components/bolt.gd +++ b/assets/projectiles/components/bolt.gd @@ -10,6 +10,7 @@ var emitting = true var final_goal: Vector2 @export var angle_var: float = 15 @onready var line: Line2D = $Line2D +#@onready var particles: CPUParticles2D = $Particles func _ready(): line.width = 2 @@ -48,6 +49,7 @@ func update_points(): curr_line_len = start_point.length() points.append(final_goal) + #particles.position = final_goal func set_line_width(amount): line.width = amount \ No newline at end of file diff --git a/assets/projectiles/components/bolt.tscn b/assets/projectiles/components/bolt.tscn index 406966d..ed51cdf 100644 --- a/assets/projectiles/components/bolt.tscn +++ b/assets/projectiles/components/bolt.tscn @@ -19,4 +19,9 @@ gradient = SubResource("Gradient_rrby1") [node name="Timer" type="Timer" parent="."] +[node name="Particles" type="CPUParticles2D" parent="."] +visible = false +emission_shape = 1 +emission_sphere_radius = 6.9 + [connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"] diff --git a/combat/weapons/_scripts/ranged_weapon_component.gd b/combat/weapons/_scripts/ranged_weapon_component.gd index 1f91bc0..eabf209 100644 --- a/combat/weapons/_scripts/ranged_weapon_component.gd +++ b/combat/weapons/_scripts/ranged_weapon_component.gd @@ -11,6 +11,8 @@ class_name RangedWeaponComponent var can_fire: bool = true var cooldown: Timer +var projectile_spawn_location: Vector2 + func _init() -> void: cooldown = Timer.new() add_child(cooldown) @@ -33,7 +35,7 @@ func fire(direction: Vector2, target_position: Vector2) -> void: if !can_fire: return - spawn_projectile(global_position, direction, target_position) + spawn_projectile(direction, target_position) can_fire = false cooldown.start(1 / stats.get_stat("ranged.attack_rate")) @@ -42,21 +44,33 @@ func set_projectile_scene() -> void: projectile_scene = basic_projectile modifier_manager.check_and_call("set_projectile_scene", [self]) -func spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2, target_position: Vector2) -> void: +## Get the projectile spawn location +## This defaults to the global position of the player +## but can be overridden by modifiers +func get_projectile_spawn_location() -> Vector2: + projectile_spawn_location = global_position + modifier_manager.check_and_call("get_projectile_spawn_location", [self]) + return projectile_spawn_location + +func spawn_projectile(spawn_direction: Vector2, target_position: Vector2) -> void: Log.pr("Spawning projectile") modifier_manager.check_and_call("spawn_projectile", [self]) ## TODO: Handle multiple shots per fire var projectile = projectile_scene.instantiate() - projectile.global_position = spawn_position + projectile.global_position = get_projectile_spawn_location() projectile.target_position = target_position - projectile.speed = 200 # stats.get_stat("projectile_speed") - projectile.damage = 10 # stats.get_stat("damage") - projectile.lifetime = 2 # stats.get_stat("projectile_lifetime") + projectile.set_stats(stats.duplicate()) projectile.source_weapon = self var size = stats.get_stat("ranged.projectile_size") projectile.set_projectile_scale(Vector2(size, size)) + + for modifier in modifier_manager.modifiers: + Log.pr("Adding modifier to projectile: ", modifier) + if projectile.has_method("add_modifier"): + projectile.add_modifier(modifier) + if get_tree() and get_tree().get_root(): get_tree().get_root().add_child(projectile) diff --git a/map/Map.tscn b/map/Map.tscn index 671f18d..6534dc2 100644 --- a/map/Map.tscn +++ b/map/Map.tscn @@ -18,7 +18,7 @@ tile_set = ExtResource("2_3nv2f") tile_set = ExtResource("2_3nv2f") [node name="Player" parent="." instance=ExtResource("5_3nv2f")] -position = Vector2(20, 20) +position = Vector2(6, 0) [node name="TestEnemy" parent="." instance=ExtResource("4_raxr4")] position = Vector2(87, 72) diff --git a/player/weapons/projectile_base.gd b/player/weapons/projectile_base.gd index bb26a66..5ab16cd 100644 --- a/player/weapons/projectile_base.gd +++ b/player/weapons/projectile_base.gd @@ -5,6 +5,9 @@ signal on_hit(projectile, target) signal on_spawned(projectile) signal on_destroyed(projectile) +var stats = StatsComponent +var modifier_manager: ModifierManager + @export var speed: float = 500.0 @export var damage: float = 10.0 @export var lifetime: float = 2 @@ -26,6 +29,38 @@ var lifetime_timer: Timer # Add a variable to track the entity that triggered the explosion var ignore_target = [] +func _init() -> void: + Log.pr('Setting up ModifierManager for projectile...') + modifier_manager = ModifierManager.new() + +func _ready() -> void: + pass + +func set_stats(setting_stats: StatsComponent) -> void: + Log.pr('Snapshotting stats for projectile:', setting_stats) + self.stats = setting_stats + modifier_manager.set_stats(stats) + + # Set up the lifetime timer + lifetime_timer = Timer.new() + add_child(lifetime_timer) + lifetime_timer.one_shot = true + lifetime_timer.wait_time = lifetime + #lifetime_timer.connect("timeout", self, "_on_lifetime_timeout") + lifetime_timer.start() + + # Ensure we have a collision shape + #ensure_collision_shape() + + emit_signal("on_spawned", self) + #connect("body_entered", self, "_on_body_entered") + +func add_modifier(modifier: Modifier) -> void: + modifier_manager.add_modifier(modifier) + +func remove_modifier(modifier_id: String) -> void: + modifier_manager.remove_modifier(modifier_id) + func _on_body_entered(body): if ignore_target.has(body): Log.pr("Ignoring body: ", body.name) @@ -98,4 +133,4 @@ func destroy(): queue_free() func _on_lifetime_timeout(): - destroy() \ No newline at end of file + destroy() diff --git a/player/weapons/projectile_lightning.gd b/player/weapons/projectile_lightning.gd index 437c780..bed9cf6 100644 --- a/player/weapons/projectile_lightning.gd +++ b/player/weapons/projectile_lightning.gd @@ -7,13 +7,16 @@ extends ProjectileBase # Add variables to track collision state var has_collided: bool = false var collision_point: Vector2 = Vector2.ZERO +var existing_shape: CollisionShape2D = null func _init() -> void: - #super._init() + super._init() Log.pr("ProjectileLightning _init") func _ready(): + super._ready() Log.pr(ignore_target) + Log.pr('Source Weapon: ', source_weapon) lifetime_timer = Timer.new() add_child(lifetime_timer) @@ -36,7 +39,7 @@ func _ready(): func ensure_collision_shape(): # Check if we already have a collision shape var has_collision_shape = false - var existing_shape = null + existing_shape = null for child in get_children(): if child is CollisionShape2D: @@ -75,6 +78,9 @@ func update_collision_shape(collision_shape = null): if collision_shape: var capsule = collision_shape.shape as CapsuleShape2D if capsule: + #collision_shape.global_position = source_weapon.global_position + global_position = source_weapon.global_position + var target = collision_point if has_collided else target_position var distance = global_position.distance_to(target) var dir = (target - global_position).normalized() @@ -87,12 +93,13 @@ func update_collision_shape(collision_shape = null): # Fix position: Move the shape so it starts at origin collision_shape.position = dir * (distance / 2) - + func _process(delta): # Update target positions for lightning bolts for child in lightning: if child is LightningBolt: + child.global_position = source_weapon.global_position child.goal_point = collision_point if has_collided else target_position # Update collision shape if it exists