#130, #135: a vessel's death takes a moment, and leaves an interceptor

#130 asked for the hull to break away from the fleet, drift a few metres,
and go up further off with something more explosive than a light animation.

The wreck is a separate node rather than the Ship staying alive to animate,
and that is the load-bearing part. A dying Ship still in the tree is still
an Area2D in the player_ships group with live weapon components - it would
keep firing, keep being fired at, keep counting as a target, and keep
colliding on the way out. So the ship's own lifecycle is untouched: it
leaves the fleet, emits destroyed, and is freed on exactly the frame it
always was. What is left behind is scenery, and the test pins that.

The explosion at the end was also the quieter of the two the game has.
Killing an enemy was made deliberately loud by #87 while losing one of your
own kept the base class's twelve-particle default - the reward louder than
the loss. It is now the bigger of the two.

#135 is the same scene from the other end: the prologue's convoy flies its
escorts on the corners of the box, which is the exposed part of it, and the
debris belt can take all four. With no interceptor left, the only hulls the
vortex could spare were holds, and the player flew out of the prologue in a
carrier. Two guards now: one interceptor is put back into the convoy at the
top of the vortex, where a vessel appearing among ten being thrown around a
black hole is not something anyone will catch; and the handover checks the
TYPES it hands over rather than only the count - one surviving carrier is
also a fleet of one, and it is not the fleet a new game starts with.
This commit is contained in:
Loïc Wisniewski 2026-08-28 15:04:26 +02:00
commit 710dcbf230
8 changed files with 419 additions and 6 deletions

View file

@ -166,7 +166,11 @@ func finish() -> void:
enemy_spawner.set_process(true)
asteroid_field.set_process(true)
# Exactly a new game's opening position: one interceptor, nothing else.
if fleet.ships.size() != GameState.STARTING_SHIP_COUNT:
#
# #135: the COUNT is not the whole of that. One surviving carrier is also
# a fleet of one, and it is not the fleet a new game starts with - so the
# types are checked too, and anything else is replaced.
if not _is_starting_fleet():
fleet.spawn_ships(GameState.STARTING_SHIP_COUNT, fleet.get_centroid(),
PackedStringArray([GameState.VESSEL_INTERCEPTOR]))
finished.emit()
@ -177,6 +181,37 @@ func skip() -> void:
finish()
func _is_starting_fleet() -> bool:
if fleet.ships.size() != GameState.STARTING_SHIP_COUNT:
return false
for ship in fleet.ships:
if not is_instance_valid(ship) \
or String(ship.vessel_type) != GameState.VESSEL_INTERCEPTOR:
return false
return true
# #135: the game starts from an interceptor, so an interceptor is what has to
# come out of the vortex.
#
# The convoy's four escorts fly the corners of the box, which is the most
# exposed part of it, and the debris belt can take all four on the way
# through. With none left, the only hulls the whirl could spare were holds -
# and the player flew out of the prologue in a carrier, which is the report.
#
# One is put back into the convoy here, at the top of the vortex, where a
# vessel appearing among ten being thrown around a black hole is not
# something anyone is going to catch. Doing it here rather than only in
# finish() means the ship the player watches survive is the ship they then
# fly, instead of one being swapped for the other at the end.
func _ensure_survivor_hull() -> void:
for ship in fleet.ships:
if is_instance_valid(ship) \
and String(ship.vessel_type) == GameState.VESSEL_INTERCEPTOR:
return
fleet.add_ship(GameState.VESSEL_INTERCEPTOR)
func _apply_convoy_formation() -> void:
for i in range(mini(CONVOY_FORMATION.size(), GameState.formation.size())):
GameState.formation[i] = CONVOY_FORMATION[i]
@ -255,6 +290,7 @@ func _begin(next: int) -> void:
narration.say("Kestrel, your heading is - something is opening ahead of you -", "CONVOY CONTROL")
Beat.VORTEX:
vortex_time = 0.0
_ensure_survivor_hull()
narration.clear()
Beat.AFTERMATH:
GameState.intro_locks_movement = false