#233: open with fewer enemies, of one kind, arriving in sequence
Measured before changing anything. The ticket's two halves needed opposite
fixes, and the first one made the second worse.
TOO COMPLEX. Variety was gated on fleet size alone, so a player who had
grown early met two kinds of trouble in the first wave they ever saw - a
fleet of four opened on two lancers and three chasers. Each roster entry now
carries a min_wave as well, so the openings teach one enemy at a time
whatever the fleet already looks like.
That alone pushed wave 1 against a fleet of six from a mixed six to SEVEN
chasers: budget that used to buy a lancer now bought two more chasers. So an
early wave also has a size ceiling, 2 + wave_number * 2. Wave 1 is four
chasers at any fleet size; the cap stops binding by wave 6 and nothing about
later balance moves.
before wave 1 fleet 4 -> 2 lancers, 3 chasers
wave 1 fleet 6 -> 1 sniper, 2 lancers, 3 chasers
after wave 1 fleet 4 -> 4 chasers
wave 1 fleet 6 -> 4 chasers
ARRIVE AT THE SAME TIME was literal: the whole wave spawned inside one frame
as a closed ring around the fleet, which reads as a wall rather than as an
attack. The first hull still lands immediately so a wave is never felt as
late, and the rest follow every 0.45s. Same enemies, same bearing, same
total - they simply do not all exist at once.
Four existing cases objected, and each was right to: they encode intents
that still hold LATER - a large wave must be varied, fleet growth must open
more kinds, a wave can field a lancer at all, and killing a wave must bring
the quiet back. The first three now ask at a wave number where variety is
permitted rather than being weakened. The fourth runs the arrivals out
before killing anything, and gained an assertion that doing so ADDS enemies
- which fails if the staggering ever collapses back into one frame.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
This commit is contained in:
parent
885f977768
commit
cb3650e3a9
4 changed files with 138 additions and 21 deletions
|
|
@ -27,15 +27,59 @@ var enemy_scene: PackedScene = preload("res://enemies/Enemy.tscn")
|
|||
var wave_enemies: Array = []
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
# Seconds between one hull of a wave landing and the next.
|
||||
const ARRIVAL_INTERVAL: float = 0.45
|
||||
|
||||
# Hulls of the current wave that have not landed yet, with the position each
|
||||
# was assigned when the wave was aimed.
|
||||
var arriving: Array = []
|
||||
var arrival_timer: float = 0.0
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
wave_enemies = wave_enemies.filter(func(e): return is_instance_valid(e))
|
||||
if GameState.wave_pending:
|
||||
_launch_wave()
|
||||
return
|
||||
if GameState.wave_phase == GameState.WAVE_PHASE_ACTIVE and wave_enemies.is_empty():
|
||||
_land_arrivals(delta)
|
||||
# `arriving` as well as `wave_enemies`: between the first hull landing and
|
||||
# the last, the field can legitimately be empty of live enemies, and
|
||||
# clearing the wave there would end it before it had finished arriving.
|
||||
if GameState.wave_phase == GameState.WAVE_PHASE_ACTIVE \
|
||||
and wave_enemies.is_empty() and arriving.is_empty():
|
||||
GameState.wave_cleared()
|
||||
|
||||
|
||||
# Forgejo #233: a wave used to appear entirely within one frame, as a closed
|
||||
# ring around the fleet. That reads as a wall rather than as an attack, and
|
||||
# it was the "arrive at the same time" half of the complaint - the other half
|
||||
# being how many there were, which wave_composition now limits.
|
||||
#
|
||||
# The first hull still lands immediately, so a wave never feels delayed; the
|
||||
# rest follow at ARRIVAL_INTERVAL. Same enemies, same bearing, same total -
|
||||
# they just do not all exist at once.
|
||||
func _land_arrivals(delta: float) -> void:
|
||||
if arriving.is_empty():
|
||||
return
|
||||
arrival_timer -= delta
|
||||
while arrival_timer <= 0.0 and not arriving.is_empty():
|
||||
_spawn_arrival(arriving.pop_front())
|
||||
arrival_timer += ARRIVAL_INTERVAL
|
||||
|
||||
|
||||
func _spawn_arrival(entry: Dictionary) -> void:
|
||||
var enemy: Node2D = enemy_scene.instantiate()
|
||||
# Before add_child: Enemy._ready() resolves enemy_id into a definition.
|
||||
enemy.enemy_id = String(entry.id)
|
||||
# #113: a wave is an attack on what the player has built. It converges
|
||||
# on the nearest structure from wherever it spawned rather than
|
||||
# waiting to be flown near.
|
||||
enemy.relentless = true
|
||||
enemy.position = entry.at
|
||||
get_parent().add_child(enemy)
|
||||
wave_enemies.append(enemy)
|
||||
|
||||
|
||||
# Public so a test can put a wave in the world without waiting out the peace.
|
||||
func _launch_wave() -> void:
|
||||
if fleet == null or fleet.ships.is_empty():
|
||||
|
|
@ -49,18 +93,20 @@ func _launch_wave() -> void:
|
|||
var radius: float = visible_rect.size.length() / 2.0 + spawn_margin
|
||||
var centre: Vector2 = fleet.get_centroid()
|
||||
var bearing: float = randf() * TAU
|
||||
# Positions are settled now, while the fleet is where the wave was aimed
|
||||
# at - not as each hull lands, or a moving fleet would drag the later
|
||||
# arrivals around behind it and the ring would smear.
|
||||
arriving.clear()
|
||||
for i in range(composition.size()):
|
||||
# Spread around one bearing rather than scattered at random: a wave
|
||||
# should read as an attack arriving, not as the map filling up.
|
||||
var angle: float = bearing + randf_range(-wave_spread, wave_spread) \
|
||||
+ TAU * float(i) / float(maxi(composition.size(), 1)) * wave_spread
|
||||
var enemy: Node2D = enemy_scene.instantiate()
|
||||
# Before add_child: Enemy._ready() resolves enemy_id into a definition.
|
||||
enemy.enemy_id = composition[i]
|
||||
# #113: a wave is an attack on what the player has built. It converges
|
||||
# on the nearest structure from wherever it spawned rather than
|
||||
# waiting to be flown near.
|
||||
enemy.relentless = true
|
||||
enemy.position = centre + Vector2(cos(angle), sin(angle)) * radius
|
||||
get_parent().add_child(enemy)
|
||||
wave_enemies.append(enemy)
|
||||
arriving.append({
|
||||
"id": composition[i],
|
||||
"at": centre + Vector2(cos(angle), sin(angle)) * radius,
|
||||
})
|
||||
# The first lands at once, so a wave is never felt as late.
|
||||
if not arriving.is_empty():
|
||||
_spawn_arrival(arriving.pop_front())
|
||||
arrival_timer = ARRIVAL_INTERVAL
|
||||
|
|
|
|||
Loading…
Reference in a new issue