From 89446660c053e85ea0b9a63376374614fe7b46e0 Mon Sep 17 00:00:00 2001 From: Artur <2123806@stud.th-mannheim.de> Date: Tue, 2 Jun 2026 16:12:08 +0200 Subject: [PATCH] Added Laser Spell, no recipe yet --- scenes/beam.tscn | 122 ++++++++++++++++++++++++++++++++++++++++ scenes/laser.tscn | 6 ++ scripts/SpellLibrary.gd | 2 + scripts/beam.gd | 11 ++++ scripts/beam.gd.uid | 1 + scripts/debug_menu.gd | 1 + scripts/laser.gd | 91 ++++++++++++++++++++++++++++++ scripts/laser.gd.uid | 1 + scripts/witch.gd | 7 +++ 9 files changed, 242 insertions(+) create mode 100644 scenes/beam.tscn create mode 100644 scenes/laser.tscn create mode 100644 scripts/beam.gd create mode 100644 scripts/beam.gd.uid create mode 100644 scripts/laser.gd create mode 100644 scripts/laser.gd.uid diff --git a/scenes/beam.tscn b/scenes/beam.tscn new file mode 100644 index 0000000..685ca99 --- /dev/null +++ b/scenes/beam.tscn @@ -0,0 +1,122 @@ +[gd_scene format=3 uid="uid://d2mpjsuueg0bn"] + +[ext_resource type="Script" uid="uid://bdhx27edemfce" path="res://scripts/beam.gd" id="1_beam00"] +[ext_resource type="Texture2D" uid="uid://dxox0vjihmukh" path="res://assets/Fire Pixel Bullet 16x16/All_Fire_Bullet_Pixel_16x16_05.png" id="2_hl8vi"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_daiji"] +atlas = ExtResource("2_hl8vi") +region = Rect2(96, 0, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bawts"] +atlas = ExtResource("2_hl8vi") +region = Rect2(112, 0, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8wlve"] +atlas = ExtResource("2_hl8vi") +region = Rect2(128, 0, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bt3ye"] +atlas = ExtResource("2_hl8vi") +region = Rect2(144, 0, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_a06w7"] +atlas = ExtResource("2_hl8vi") +region = Rect2(96, 16, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5fvdj"] +atlas = ExtResource("2_hl8vi") +region = Rect2(112, 16, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_djxf5"] +atlas = ExtResource("2_hl8vi") +region = Rect2(128, 16, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_eeead"] +atlas = ExtResource("2_hl8vi") +region = Rect2(144, 16, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_qfoqh"] +atlas = ExtResource("2_hl8vi") +region = Rect2(96, 32, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tuykp"] +atlas = ExtResource("2_hl8vi") +region = Rect2(112, 32, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2dcgn"] +atlas = ExtResource("2_hl8vi") +region = Rect2(128, 32, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7703o"] +atlas = ExtResource("2_hl8vi") +region = Rect2(144, 32, 16, 16) + +[sub_resource type="SpriteFrames" id="SpriteFrames_beam0"] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_daiji") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_bawts") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8wlve") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_bt3ye") +}], +"loop": true, +"name": &"end", +"speed": 10.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_a06w7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5fvdj") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_djxf5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_eeead") +}], +"loop": true, +"name": &"middle", +"speed": 10.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_qfoqh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tuykp") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2dcgn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7703o") +}], +"loop": true, +"name": &"start", +"speed": 10.0 +}] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_beam0"] +size = Vector2(16, 8) + +[node name="Beam" type="Area2D" unique_id=968931754] +script = ExtResource("1_beam00") + +[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=100000001] +texture_filter = 1 +sprite_frames = SubResource("SpriteFrames_beam0") +animation = &"end" +frame_progress = 0.02647079 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=100000002] +shape = SubResource("RectangleShape2D_beam0") +disabled = true diff --git a/scenes/laser.tscn b/scenes/laser.tscn new file mode 100644 index 0000000..edeb025 --- /dev/null +++ b/scenes/laser.tscn @@ -0,0 +1,6 @@ +[gd_scene format=3 uid="uid://claser0spell1"] + +[ext_resource type="Script" path="res://scripts/laser.gd" id="1_laser0"] + +[node name="Laser" type="Node2D"] +script = ExtResource("1_laser0") diff --git a/scripts/SpellLibrary.gd b/scripts/SpellLibrary.gd index 88d06b0..24aac26 100644 --- a/scripts/SpellLibrary.gd +++ b/scripts/SpellLibrary.gd @@ -8,6 +8,7 @@ const SHURIKEN = "SHURIKEN" const FIREBALL = "FIREBALL" const FIRE_SWIRL = "FIRE_SWIRL" const TORNADO = "TORNADO" +const LASER = "LASER" # Each spell's display recipe: sorted apples-first, grapes-last var recipes: Dictionary = { @@ -15,6 +16,7 @@ var recipes: Dictionary = { FIREBALL: [APPLE, APPLE, APPLE], FIRE_SWIRL: [APPLE, APPLE, GRAPE], TORNADO: [GRAPE, GRAPE, GRAPE], + LASER: [], } # Takes cauldron slot_states (uses texture indices: 2 = apple, 4 = grape) diff --git a/scripts/beam.gd b/scripts/beam.gd new file mode 100644 index 0000000..90fd51f --- /dev/null +++ b/scripts/beam.gd @@ -0,0 +1,11 @@ +extends Area2D + +@export var beam_type := "middle" + +@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D +@onready var shape: CollisionShape2D = $CollisionShape2D + +func _ready() -> void: + if sprite.sprite_frames.has_animation(beam_type): + sprite.play(beam_type) + shape.disabled = beam_type != "middle" diff --git a/scripts/beam.gd.uid b/scripts/beam.gd.uid new file mode 100644 index 0000000..9d496cd --- /dev/null +++ b/scripts/beam.gd.uid @@ -0,0 +1 @@ +uid://bdhx27edemfce diff --git a/scripts/debug_menu.gd b/scripts/debug_menu.gd index 09dc8e9..6d44c4a 100644 --- a/scripts/debug_menu.gd +++ b/scripts/debug_menu.gd @@ -14,6 +14,7 @@ func _ready() -> void: SpellLibrary.SHURIKEN: _witch.shoot_shuriken, SpellLibrary.FIRE_SWIRL: _witch.shoot_fire_swirl, SpellLibrary.TORNADO: _witch.shoot_tornado, + SpellLibrary.LASER: _witch.shoot_laser, } _build_ui() hide() diff --git a/scripts/laser.gd b/scripts/laser.gd new file mode 100644 index 0000000..befebd8 --- /dev/null +++ b/scripts/laser.gd @@ -0,0 +1,91 @@ +extends Node2D + +const BEAM_DURATION := 3.0 +const PRIMARY_TICK_DMG := 15 +const SPLASH_TICK_DMG := 3 +const TICK_INTERVAL := 0.5 + +@export var segment_size := 16.0 + +var beam_seg := preload("res://scenes/beam.tscn") +var target: Node2D = null +var all_segs: Array = [] +var mid_segs: Array = [] +var tick_timer := 0.0 +var elapsed := 0.0 +var done := false + +func _ready() -> void: + target = get_highest_hp_enemy() + if target == null: + queue_free() + return + spawn_beam() + +func _process(delta: float) -> void: + if done: + return + elapsed += delta + tick_timer += delta + if tick_timer >= TICK_INTERVAL: + tick_timer -= TICK_INTERVAL + do_damage_tick() + if elapsed >= BEAM_DURATION: + done = true + cleanup() + +func spawn_beam() -> void: + var origin := global_position + var dest := target.global_position + var dir := origin.direction_to(dest) + var dist := origin.distance_to(dest) + var angle := dir.angle() + + place_seg("start", origin, angle) + + var n_mid := int(max(0.0, dist - segment_size * 2.0) / segment_size) + for i in range(n_mid): + var pos := origin + dir * (segment_size * (float(i) + 1.0)) + mid_segs.append(place_seg("middle", pos, angle)) + + place_seg("end", dest, angle) + +func place_seg(type: String, pos: Vector2, angle: float) -> Node: + var seg = beam_seg.instantiate() + seg.beam_type = type + seg.global_position = pos + seg.rotation = angle + get_parent().add_child(seg) + all_segs.append(seg) + return seg + +func do_damage_tick() -> void: + if is_instance_valid(target) and not target.is_dying: + target.take_damage(PRIMARY_TICK_DMG) + + var hit: Array = [] + for seg in mid_segs: + if not is_instance_valid(seg): + continue + for body in seg.get_overlapping_bodies(): + if body.is_in_group("enemies") and not body.is_dying \ + and body != target and not hit.has(body): + body.take_damage(SPLASH_TICK_DMG) + hit.append(body) + +func cleanup() -> void: + for seg in all_segs: + if is_instance_valid(seg): + seg.queue_free() + queue_free() + +func get_highest_hp_enemy() -> Node: + var best: Node = null + var best_hp: int = -1 + for enemy in get_tree().get_nodes_in_group("enemies"): + if not is_instance_valid(enemy) or enemy.is_dying: + continue + if enemy.hp > best_hp: + best_hp = enemy.hp + best = enemy + return best diff --git a/scripts/laser.gd.uid b/scripts/laser.gd.uid new file mode 100644 index 0000000..b174485 --- /dev/null +++ b/scripts/laser.gd.uid @@ -0,0 +1 @@ +uid://dp6b3imslv10d diff --git a/scripts/witch.gd b/scripts/witch.gd index 435d3fb..e6d73c3 100644 --- a/scripts/witch.gd +++ b/scripts/witch.gd @@ -8,6 +8,7 @@ var fireball = preload("res://scenes/fireball.tscn") var shuriken = preload("res://scenes/shuriken.tscn") var fire_swirl = preload("res://scenes/fire_swirl.tscn") var tornado = preload("res://scenes/tornado.tscn") +var laser = preload("res://scenes/laser.tscn") var shuriken_count = 1 var _fire_sfx = preload("res://assets/music&sfx/sfx/fire.wav") @@ -99,6 +100,12 @@ func shoot_tornado(): tw.global_position = target.global_position if target != null else global_position get_parent().add_child(tw) camera.shake(0.3, 0.8) + +func shoot_laser(): + var ls = laser.instantiate() + ls.global_position = global_position + get_parent().add_child(ls) + camera.shake(0.4, 1.2) func take_damage(amount: int) -> void: if is_invincible: