gae_wild_jam/scripts/laser.gd

141 lines
4.0 KiB
GDScript

extends Node2D
const BEAM_DURATION := 3.0
const PRIMARY_TICK_DMG := 40
const SPLASH_TICK_DMG := 3
const TICK_INTERVAL := 0.5
const SWEEP_RATE := 22.0
@export var segment_size := 16.0
@export var start_offset := 24.0
@onready var perk_effects = get_node("/root/Game/PerkEffects")
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
var current_angle := 0.0
var sweep_to := 0.0
var sweeping := false
func _ready() -> void:
target = get_highest_hp_enemy()
if target == null:
queue_free()
return
current_angle = global_position.direction_to(target.global_position).angle()
rebuild_segments()
func _process(delta: float) -> void:
if done:
return
elapsed += delta
tick_timer += delta
if sweeping:
current_angle = lerp_angle(current_angle, sweep_to, 1.0 - exp(-SWEEP_RATE * delta))
if abs(angle_difference(current_angle, sweep_to)) < 0.005:
current_angle = sweep_to
sweeping = false
rebuild_segments()
if tick_timer >= TICK_INTERVAL:
tick_timer -= TICK_INTERVAL
do_damage_tick()
if elapsed >= BEAM_DURATION:
done = true
cleanup()
func rebuild_segments() -> void:
var dir := Vector2.from_angle(current_angle)
var beam_start := global_position + dir * start_offset
var dest_dist := global_position.distance_to(target.global_position)
var beam_dist := dest_dist - start_offset
var n_mid := int(max(0.0, beam_dist - segment_size) / segment_size)
var positions: Array = [beam_start]
var types: Array = ["start"]
for i in range(n_mid):
positions.append(beam_start + dir * (segment_size * float(i + 1)))
types.append("middle")
positions.append(beam_start + dir * (segment_size * float(n_mid + 1)))
types.append("end")
while all_segs.size() < positions.size():
var seg = beam_seg.instantiate()
get_parent().add_child(seg)
all_segs.append(seg)
while all_segs.size() > positions.size():
var seg = all_segs.pop_back()
if is_instance_valid(seg):
seg.queue_free()
mid_segs.clear()
for i in range(all_segs.size()):
var seg = all_segs[i]
if not is_instance_valid(seg):
continue
seg.global_position = positions[i]
seg.rotation = current_angle
if seg.beam_type != types[i]:
seg.beam_type = types[i]
seg.shape.disabled = types[i] != "middle"
if seg.sprite.sprite_frames.has_animation(types[i]):
seg.sprite.play(types[i])
if types[i] == "middle":
mid_segs.append(seg)
if all_segs.size() > 1 and is_instance_valid(all_segs[0]):
var ref_frame: int = all_segs[0].sprite.frame
var ref_progress: float = all_segs[0].sprite.frame_progress
for i in range(1, all_segs.size()):
var seg = all_segs[i]
if is_instance_valid(seg):
seg.sprite.frame = ref_frame
seg.sprite.frame_progress = ref_progress
func retarget(new_target: Node2D) -> void:
target = new_target
sweep_to = global_position.direction_to(new_target.global_position).angle()
sweeping = true
func do_damage_tick() -> void:
if (not is_instance_valid(target) or target.is_dying) and perk_effects.laser_retarget_enabled:
var new_target = get_highest_hp_enemy()
if new_target != null:
retarget(new_target)
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() -> Node2D:
var best: Node2D = 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