204 lines
6.2 KiB
GDScript
204 lines
6.2 KiB
GDScript
extends CharacterBody2D
|
|
|
|
signal health_changed(current_hp: int, max_hp: int)
|
|
signal died(attacker)
|
|
|
|
var camera
|
|
@onready var perk_effects = get_node("/root/Game/PerkEffects")
|
|
var is_casting = false
|
|
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 purge = preload("res://scenes/purge.tscn")
|
|
var shuriken_count = 1
|
|
var _fire_sfx = preload("res://assets/music&sfx/sfx/fire.wav")
|
|
var _laser_sfx = preload("res://assets/music&sfx/sfx/laser.wav")
|
|
|
|
var max_hp: int = 50
|
|
var current_hp: int = 50
|
|
var is_invincible: bool = false
|
|
var fireball_max_targets: int = 5
|
|
var fireball_pellet_count: int = 6
|
|
var fireball_minigun_shots: int = 6
|
|
var fireball_minigun_interval: float = 0.08
|
|
|
|
const HP_BAR_WIDTH = 20
|
|
const HP_BAR_HEIGHT = 3
|
|
var _hp_bar_fill: ColorRect
|
|
|
|
func _ready() -> void:
|
|
$CauldronBar.witch = self
|
|
camera = get_node("/root/Game/Camera2D")
|
|
_setup_hp_bar()
|
|
|
|
func _setup_hp_bar() -> void:
|
|
var bg = ColorRect.new()
|
|
bg.color = Color(0.15, 0.15, 0.15, 0.85)
|
|
bg.size = Vector2(HP_BAR_WIDTH, HP_BAR_HEIGHT)
|
|
bg.position = Vector2(-HP_BAR_WIDTH / 2.0, 18)
|
|
add_child(bg)
|
|
_hp_bar_fill = ColorRect.new()
|
|
_hp_bar_fill.color = Color(0.85, 0.1, 0.1, 1.0)
|
|
_hp_bar_fill.size = Vector2(HP_BAR_WIDTH, HP_BAR_HEIGHT)
|
|
_hp_bar_fill.position = Vector2(-HP_BAR_WIDTH / 2.0, 18)
|
|
add_child(_hp_bar_fill)
|
|
|
|
func _update_hp_bar() -> void:
|
|
_hp_bar_fill.size.x = HP_BAR_WIDTH * (float(current_hp) / float(max_hp))
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
# Add the gravity.
|
|
pass
|
|
|
|
func _on_collect(DropsBase):
|
|
if is_casting:
|
|
return
|
|
$CauldronBar.progres_bar(DropsBase)
|
|
|
|
|
|
func shoot_fireballs():
|
|
StatsManager.on_spell_cast("fireball")
|
|
match perk_effects.get_mode("fireball.cast"):
|
|
"shotgun":
|
|
await _cast_fireball_shotgun()
|
|
"minigun":
|
|
await _cast_fireball_minigun()
|
|
_:
|
|
await _cast_fireball_base()
|
|
|
|
func _cast_fireball_base() -> void:
|
|
var enemies = get_tree().get_nodes_in_group("enemies")
|
|
enemies = enemies.filter(func(e): return is_instance_valid(e))
|
|
enemies.sort_custom(func(a, b): return global_position.distance_to(a.global_position) < global_position.distance_to(b.global_position))
|
|
var targets = enemies.slice(0, fireball_max_targets)
|
|
for enemy in targets:
|
|
if not is_instance_valid(enemy):
|
|
continue
|
|
var dir = global_position.direction_to(enemy.global_position)
|
|
_face_direction(dir)
|
|
_spawn_fireball_toward(enemy.global_position)
|
|
_play_fire_sfx()
|
|
camera.shake(0.15, 0.5)
|
|
await get_tree().create_timer(0.12).timeout
|
|
$AnimatedSprite2D.play("south")
|
|
|
|
func _cast_fireball_shotgun() -> void:
|
|
var target = get_nearest_enemy(global_position)
|
|
var aim_pos = target.global_position if target != null else global_position + Vector2.DOWN * 100.0
|
|
var base_dir = global_position.direction_to(aim_pos)
|
|
_face_direction(base_dir)
|
|
var spread_deg = 40.0
|
|
for i in range(fireball_pellet_count):
|
|
var t = float(i) / float(max(1, fireball_pellet_count - 1))
|
|
var angle_offset = deg_to_rad(lerp(-spread_deg / 2.0, spread_deg / 2.0, t))
|
|
var dir = base_dir.rotated(angle_offset)
|
|
_spawn_fireball_toward(global_position + dir * 300.0)
|
|
_play_fire_sfx()
|
|
camera.shake(0.2, 0.6)
|
|
$AnimatedSprite2D.play("south")
|
|
|
|
func _cast_fireball_minigun() -> void:
|
|
for i in range(fireball_minigun_shots):
|
|
var target = get_nearest_enemy(global_position)
|
|
if target == null:
|
|
break
|
|
var dir = global_position.direction_to(target.global_position)
|
|
_face_direction(dir)
|
|
_spawn_fireball_toward(target.global_position)
|
|
_play_fire_sfx()
|
|
camera.shake(0.08, 0.3)
|
|
await get_tree().create_timer(fireball_minigun_interval).timeout
|
|
$AnimatedSprite2D.play("south")
|
|
|
|
func _spawn_fireball_toward(target_pos: Vector2) -> void:
|
|
var fb = fireball.instantiate()
|
|
fb.global_position = global_position
|
|
get_parent().add_child(fb)
|
|
fb.launch(target_pos)
|
|
|
|
func _face_direction(dir: Vector2) -> void:
|
|
if abs(dir.x) >= abs(dir.y):
|
|
$AnimatedSprite2D.play("east" if dir.x > 0 else "west")
|
|
else:
|
|
$AnimatedSprite2D.play("south" if dir.y > 0 else "north")
|
|
|
|
func _play_fire_sfx() -> void:
|
|
var asp = AudioStreamPlayer.new()
|
|
asp.stream = _fire_sfx
|
|
asp.volume_db = -10
|
|
asp.pitch_scale = randf_range(0.9, 1.1)
|
|
get_parent().add_child(asp)
|
|
asp.play()
|
|
asp.finished.connect(asp.queue_free)
|
|
|
|
func shoot_fire_swirl():
|
|
StatsManager.on_spell_cast("fire_swirl")
|
|
var fs = fire_swirl.instantiate()
|
|
fs.global_position = global_position
|
|
get_parent().add_child(fs)
|
|
camera.shake(0.3, 0.8)
|
|
|
|
func shoot_shuriken():
|
|
StatsManager.on_spell_cast("shuriken")
|
|
for i in range(shuriken_count):
|
|
var sh = shuriken.instantiate()
|
|
sh.global_position = global_position
|
|
get_parent().add_child(sh)
|
|
await get_tree().create_timer(0.2).timeout
|
|
|
|
func shoot_tornado():
|
|
StatsManager.on_spell_cast("tornado")
|
|
var target = get_nearest_enemy(global_position)
|
|
var tw = tornado.instantiate()
|
|
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():
|
|
StatsManager.on_spell_cast("laser")
|
|
var ls = laser.instantiate()
|
|
ls.global_position = global_position
|
|
get_parent().add_child(ls)
|
|
camera.shake(0.4, 1.2)
|
|
var asp = AudioStreamPlayer.new()
|
|
asp.stream = _laser_sfx
|
|
asp.volume_db = 6
|
|
asp.bus = "SFX"
|
|
get_parent().add_child(asp)
|
|
asp.play()
|
|
asp.finished.connect(asp.queue_free)
|
|
|
|
func shoot_purge():
|
|
StatsManager.on_spell_cast("purge")
|
|
var pg = purge.instantiate()
|
|
pg.global_position = global_position
|
|
get_parent().add_child(pg)
|
|
|
|
func take_damage(amount: int, attacker = null) -> void:
|
|
if is_invincible:
|
|
return
|
|
current_hp -= amount
|
|
current_hp = max(current_hp, 0)
|
|
health_changed.emit(current_hp, max_hp)
|
|
_update_hp_bar()
|
|
if current_hp <= 0:
|
|
died.emit(attacker)
|
|
return
|
|
is_invincible = true
|
|
await get_tree().create_timer(0.5).timeout
|
|
is_invincible = false
|
|
|
|
func get_nearest_enemy(from: Vector2, filter: Callable = Callable()) -> Node:
|
|
var nearest = null
|
|
var min_distance = INF
|
|
for enemy in get_tree().get_nodes_in_group("enemies"):
|
|
if filter.is_valid() and not filter.call(enemy):
|
|
continue
|
|
var dist = from.distance_to(enemy.global_position)
|
|
if dist < min_distance:
|
|
min_distance = dist
|
|
nearest = enemy
|
|
return nearest
|