#100 (#97b): waves replace the roaming enemy spawner
The old spawner put one enemy on the map every 3-6 seconds for ever. There
was no rhythm to the danger and nothing to prepare for. Enemies now arrive
in waves: a long quiet, a warning, an attack, and quiet again once the wave
is dead.
The clock lives in GameState, because Main.tscn is destroyed on every trip
to a planet surface and a wave must not be cancelled, nor restarted from
zero, by a landing. The spawning does not: GameState cannot see the world,
so EnemySpawner reads the phase and puts the enemies in it. That also
settles what happens when a wave turns while the player is on a surface -
the wave is waiting in space when they come back.
EnemySpawner keeps its name deliberately. It is still the thing that puts
enemies in the world, and 88 test files already say
`get_node("EnemySpawner").set_process(false)` to ask for a quiet map.
Strength is a function of the FLEET, per the ticket, with the wave number as
a slower second term so a player who never grows still meets escalation. The
budget is spent most-expensive-first, so a large wave is heavier rather than
merely longer, and min_strength keeps the first waves to chasers whatever
the arithmetic says.
"No timer should be visible" is enforced rather than merely observed:
GameState offers wave_warning_pulse(), a 0..1 throb, and no
remaining-seconds accessor at all, so the alert cannot drift into being a
countdown. The test pins both - that no such accessor exists, and that the
alert's own text contains no digits.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c221584848
commit
26f3728546
6 changed files with 593 additions and 62 deletions
68
ui/WaveAlert.gd
Normal file
68
ui/WaveAlert.gd
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
extends Control
|
||||
|
||||
# #100: "an small alert on top right appear when a new wave is coming. No
|
||||
# timer should be visible."
|
||||
#
|
||||
# The second half of that is a constraint on this file, not just a note.
|
||||
# GameState deliberately offers no remaining-seconds accessor - only
|
||||
# wave_warning_pulse(), which is a 0..1 throb - so there is no number here
|
||||
# that could be rendered as a countdown even by accident. The urgency is
|
||||
# carried by the pulse rate, which says "soon" without saying "in 4".
|
||||
|
||||
const GLOW: Color = Color(1.0, 0.45, 0.2)
|
||||
const TEXT: String = "WAVE INCOMING"
|
||||
|
||||
var label: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
visible = false
|
||||
label = Label.new()
|
||||
label.text = TEXT
|
||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
label.add_theme_font_size_override("font_size", 22)
|
||||
label.add_theme_color_override("font_color", GLOW)
|
||||
label.add_theme_color_override("font_outline_color", Color(0.05, 0.01, 0.0, 0.95))
|
||||
label.add_theme_constant_override("outline_size", 3)
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
add_child(label)
|
||||
resized.connect(_layout)
|
||||
_layout()
|
||||
|
||||
|
||||
func _layout() -> void:
|
||||
if label == null:
|
||||
return
|
||||
# Room on the left for the chevron _draw() puts there.
|
||||
label.position = Vector2(size.y, 0.0)
|
||||
label.size = Vector2(maxf(size.x - size.y, 0.0), size.y)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
var incoming: bool = GameState.wave_is_incoming()
|
||||
if incoming != visible:
|
||||
visible = incoming
|
||||
if incoming:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not visible:
|
||||
return
|
||||
var pulse: float = GameState.wave_warning_pulse()
|
||||
var alpha: float = 0.35 + 0.65 * pulse
|
||||
draw_rect(Rect2(Vector2.ZERO, size), Color(0.16, 0.04, 0.01, 0.62))
|
||||
NeonDraw.draw_glowing_path(self, PackedVector2Array([
|
||||
Vector2(0.0, 0.0), Vector2(size.x, 0.0), Vector2(size.x, size.y),
|
||||
Vector2(0.0, size.y), Vector2(0.0, 0.0),
|
||||
]), Color(GLOW, alpha), 0.9, 0.7)
|
||||
# A warning triangle, not a clock face.
|
||||
var centre: Vector2 = Vector2(size.y * 0.5, size.y * 0.5)
|
||||
var radius: float = size.y * 0.28
|
||||
NeonDraw.draw_glowing_polygon(self, PackedVector2Array([
|
||||
centre + Vector2(0.0, -radius),
|
||||
centre + Vector2(radius * 0.92, radius * 0.72),
|
||||
centre + Vector2(-radius * 0.92, radius * 0.72),
|
||||
]), Color(GLOW, alpha), 1.0)
|
||||
Loading…
Reference in a new issue