spaceship/vessels/Projectile.gd
2026-09-03 00:12:08 +02:00

70 lines
2.7 KiB
GDScript

extends Area2D
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
# #86: faster. The bolt used to crawl far enough behind a moving target that
# leading a shot was guesswork rather than skill.
# #155: was 850, which took 1.1s to cross the gun's own range - long enough
# that a shot read as leaving the ship rather than as hitting something. The
# range below is derived from this (see set_range), so raising it makes shots
# faster without making them fall short.
@export var speed: float = 1400.0
# How long a bolt lives if nothing sets a range. VesselWeaponComponent always
# does - see set_range() - so this only covers a projectile fired by something
# that has no range of its own.
@export var lifetime: float = 1.5
@export var damage: int = 1
@export var bolt_length: float = 20.0
var velocity: Vector2 = Vector2.ZERO
var age: float = 0.0
func _ready() -> void:
add_to_group("player_projectiles")
area_entered.connect(_on_area_entered)
# Draw once. The earlier invisible-bolt bug came from _draw() deriving
# the bolt's look from `age` - drawing only at age==0 froze it at
# whatever the age==0 case rendered as. _draw() below is now a fixed
# shape with no age dependency, so one draw is safe and matches the
# Ship.gd/Asteroid.gd pattern: position/rotation are transform
# properties Godot applies at render time without re-tessellating.
queue_redraw()
# The gun's reach, in world units. Converted to a lifetime here rather than
# stored separately, so the distance a bolt COVERS and the distance the aim
# guide DRAWS are the same number rather than two that have to be kept in
# step. They were not: the guide showed fire_range (320) while a bolt actually
# flew speed * lifetime (750), so shots landed well past the line promising
# where they would stop.
func set_range(world_units: float) -> void:
if world_units > 0.0 and speed > 0.0:
lifetime = world_units / speed
func launch(direction: Vector2) -> void:
velocity = direction.normalized() * speed
rotation = direction.angle() + PI / 2.0
func _physics_process(delta: float) -> void:
position += velocity * delta
age += delta
if age >= lifetime:
queue_free()
func _on_area_entered(area: Area2D) -> void:
# Anything that opted into the weapon-target contract answers take_damage(),
# so adding a new target composition needs no projectile type check.
if area.is_in_group(COMBAT_TARGET.GROUP):
area.take_damage(damage)
queue_free()
func _draw() -> void:
# Fixed-length, constant-brightness bolt. Drawn once and left alone.
var tip: Vector2 = Vector2(0, -bolt_length * 0.6)
var tail: Vector2 = Vector2(0, bolt_length * 0.4)
NeonDraw.draw_glowing_line(self, tail, tip, Color(1.0, 0.85, 0.3), 1.0, 0.5)