spaceship/Ship.gd

135 lines
4 KiB
GDScript3
Raw Normal View History

extends Area2D
signal destroyed
const VESSEL_DEFINITIONS = preload("res://VesselDefinitions.gd")
@export var max_speed: float = 460.0
@export var acceleration: float = 450.0
@export var rotation_speed: float = 8.0
@export var arrive_radius: float = 6.0
@export var vessel_type: String = "interceptor"
var definition: Resource
var glow_color: Color = Color(0.25, 0.85, 1.0)
var core_color: Color = Color.WHITE
var velocity: Vector2 = Vector2.ZERO
var fleet_velocity: Vector2 = Vector2.ZERO
var move_target: Vector2
# Compatibility accessors keep old saves/tests readable while shield state
# itself belongs exclusively to the optional ShieldComponent.
var shield_charges: int:
get:
var component: Node = get_component("shield")
return component.charges if component != null else 0
set(value):
var component: Node = get_component("shield")
if component != null:
component.set_charges(value)
var shield_recharge_left: float:
get:
var component: Node = get_component("shield")
return component.recharge_left if component != null else 0.0
set(value):
var component: Node = get_component("shield")
if component != null:
component.recharge_left = value
var projectile_damage: int:
get:
var component: Node = get_component("weapon")
return component.projectile_damage if component != null else 0
func _ready() -> void:
move_target = position
add_to_group("player_ships")
area_entered.connect(_on_area_entered)
definition = VESSEL_DEFINITIONS.get_definition(vessel_type)
if definition == null:
return
glow_color = definition.glow_color
for component_scene in definition.component_scenes:
var component: Node = component_scene.instantiate()
add_child(component)
if component.has_method("setup"):
component.setup()
apply_vessel_stats()
queue_redraw()
func get_component(capability: String) -> Node:
for child in get_children():
if child.get("capability_id") == capability:
return child
return null
func has_capability(capability: String) -> bool:
return get_component(capability) != null
func set_move_target(world_pos: Vector2) -> void:
move_target = world_pos
func apply_vessel_stats() -> void:
if definition == null:
definition = VESSEL_DEFINITIONS.get_definition(vessel_type)
if definition == null:
return
max_speed = GameState.vessel_stat(vessel_type, "max_speed")
acceleration = GameState.vessel_stat(vessel_type, "acceleration")
for child in get_children():
if child.has_method("apply_stats"):
child.apply_stats()
func has_shield() -> bool:
var component: Node = get_component("shield")
return component != null and component.is_active()
func _physics_process(delta: float) -> void:
for child in get_children():
if child.has_method("tick"):
child.tick(delta)
var to_target: Vector2 = move_target - position
var distance: float = to_target.length()
if distance <= arrive_radius:
position = move_target
velocity = Vector2.ZERO
else:
var braking_speed: float = sqrt(2.0 * acceleration * distance)
var desired_speed: float = min(max_speed, braking_speed)
var desired_velocity: Vector2 = to_target.normalized() * desired_speed
velocity = velocity.move_toward(desired_velocity, acceleration * delta)
position += velocity * delta
var motion: Vector2 = velocity + fleet_velocity
if motion.length() > 8.0:
var target_angle: float = motion.angle() + PI / 2.0
rotation = lerp_angle(rotation, target_angle, rotation_speed * delta)
func _on_area_entered(area: Area2D) -> void:
if not (area.is_in_group("enemies") or area.is_in_group("asteroids")):
return
for child in get_children():
if child.has_method("absorb_hit") and child.absorb_hit():
if area.is_in_group("asteroids") and is_instance_valid(area):
area.queue_free()
return
ShipExplosionVFX.spawn(get_tree().root, position)
destroyed.emit()
queue_free()
func _draw() -> void:
var points: PackedVector2Array = definition.hull_points if definition != null else PackedVector2Array([
Vector2(0, -22), Vector2(-14, 16), Vector2(14, 16),
])
NeonDraw.draw_glowing_polygon(self, points, glow_color, 1.0, core_color)