extends Node2D # Minimal cockpit HUD — maximum screen space for the game. # During play: only 4 corner L-brackets + tiny top-right status line. # On gameover: full terminal overlay. var W: float = 960.0 var H: float = 600.0 # Cockpit palette const COL_PRIMARY := Color(0.0, 1.0, 0.533, 0.55) # phosphor green, semi-dim const COL_ACCENT := Color(0.27, 1.0, 0.8, 0.80) const COL_DIM := Color(0.0, 0.27, 0.13, 0.45) const COL_WARNING := Color(1.0, 0.67, 0.0, 0.90) const COL_DANGER := Color(1.0, 0.2, 0.0, 0.90) const COL_WHITE := Color(1.0, 1.0, 1.0, 0.90) const BRACKET_LEN := 14.0 const BRACKET_W := 1.5 const MARGIN := 10.0 var hud: Node = null func _process(_delta: float) -> void: queue_redraw() func _draw() -> void: if hud == null: return var vs: Vector2 = get_viewport_rect().size W = vs.x; H = vs.y var font := ThemeDB.fallback_font var gw: Node = hud.game_world # ── Countdown ──────────────────────────────────────────────────────────── if hud.show_countdown_flag and hud.countdown_value > 0: _draw_corner_brackets() var ct := str(hud.countdown_value) _draw_text_centered(font, ct, W * 0.5, H * 0.5 - 36.0, 64, COL_ACCENT) _draw_text_centered(font, Tr.t("hud_launching"), W * 0.5, H * 0.5 + 28.0, 10, COL_DIM) return if not hud.is_gameover and gw == null: return # ── Normal gameplay HUD ────────────────────────────────────────────────── if not hud.is_gameover and gw != null: _draw_corner_brackets() # ── Wave timer bar ──────────────────────────────────────────────── var wave_dur: float = float(gw.get("wave_duration") if gw.get("wave_duration") != null else 60.0) var wave_t: float = float(gw.get("wave_timer") if gw.get("wave_timer") != null else 0.0) var wave_frac: float = clamp(wave_t / wave_dur, 0.0, 1.0) var wave_remaining: float = max(0.0, wave_dur - wave_t) var last_10: bool = wave_remaining <= 10.0 and gw.get("is_playing") # Thin bar at top — shrinks from right as time passes var bar_col: Color if last_10: bar_col = Color(COL_WARNING.r, COL_WARNING.g, COL_WARNING.b, 0.55 + 0.45 * sin(hud.blink_phase * 8.0)) else: bar_col = Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.35) draw_rect(Rect2(0.0, 0.0, W * (1.0 - wave_frac), 2.0), bar_col) # Wave label top-left: "W1 0:42" var w_mins: int = int(wave_remaining / 60.0) var w_secs: int = int(wave_remaining) % 60 var wave_label := "W%d %d:%02d" % [hud.wave_number, w_mins, w_secs] var wlabel_col: Color = COL_WARNING if last_10 else COL_DIM _draw_text(font, wave_label, MARGIN, 10.0, 9, wlabel_col) # ── Wave cleared overlay ────────────────────────────────────────── if hud.wave_cleared: var wc_a: float = 0.7 + 0.3 * sin(hud.blink_phase * 5.0) _draw_text_centered(font, Tr.t("hud_wave_clear") % (hud.wave_number - 1), W * 0.5, H * 0.5 - 22.0, 16, Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b, wc_a)) _draw_text_centered(font, Tr.t("hud_to_shop"), W * 0.5, H * 0.5 + 8.0, 11, Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, wc_a * 0.7)) # Top-right micro status — lives dots + credits + time + kills var players: Array = gw.players var lives_str := "" for li in 3: lives_str += ("● " if li < hud.lives else "○ ") var cr_str := "CR:%d" % hud.credits if not gw.is_multiplayer: if players.size() > 0: var p = players[0] var mins: int = int(p.survival_time / 60.0) var secs: int = int(p.survival_time) % 60 var time_str := "%02d:%02d" % [mins, secs] var shields_str := "" for _si in p.current_shields: shields_str += "▣" var status_str := "%s %s %s %s x%d" % [lives_str, cr_str, shields_str, time_str, p.kills] _draw_text(font, status_str, W - 200.0, 10.0, 9, COL_PRIMARY) else: if players.size() >= 1: var p1 = players[0] var t1 := "%02d:%02d" % [int(p1.survival_time / 60.0), int(p1.survival_time) % 60] _draw_text(font, "P1 %s x%d %s" % [t1, p1.kills, cr_str], 10.0, 10.0, 9, COL_PRIMARY) if players.size() >= 2: var p2 = players[1] var t2 := "%02d:%02d" % [int(p2.survival_time / 60.0), int(p2.survival_time) % 60] _draw_text(font, "P2 %s x%d" % [t2, p2.kills], W - 110.0, 10.0, 9, Color(0.27, 1.0, 0.67, 0.55)) # ── Boost-Cooldown-Leisten (nur Schiffe mit has_boost) ──────────── for player in players: var sp: Spaceship = player if sp == null or sp.dead: continue if sp.stats == null or not sp.stats.has_boost: continue var ratio: float = sp.boost_ratio() var bar_w := 40.0; var bar_h := 4.0 var bx2: float = sp.x - bar_w * 0.5 var by2: float = sp.y + 18.0 # Background draw_rect(Rect2(bx2, by2, bar_w, bar_h), Color(0.0, 0.04, 0.02, 0.7)) # Fill — remaining cooldown shown as shrinking bar (ratio 1 = full cd, 0 = ready) var fill_w: float = bar_w * (1.0 - ratio) var is_ready: bool = ratio <= 0.0 var fill_col: Color if is_ready: fill_col = Color(1.0, 0.9, 0.3, 0.7 + 0.3 * sin(hud.blink_phase * 6.0)) else: fill_col = Color(COL_WARNING.r, COL_WARNING.g, COL_WARNING.b, 0.75) draw_rect(Rect2(bx2, by2, fill_w, bar_h), fill_col) # Border draw_rect(Rect2(bx2, by2, bar_w, bar_h), Color(COL_DIM.r, COL_DIM.g, COL_DIM.b, 0.7), false, 1.0) # Big wipe warning — bold centred flash, no decoration if hud.wipe_warning: if sin(hud.blink_phase * 8.0) > 0.0: _draw_text_centered(font, Tr.hint("hud_wipe_key"), W * 0.5, H * 0.5 - 8.0, 13, COL_WARNING) return # ── Gameover / Returning ───────────────────────────────────────────────── if hud.is_gameover or hud.is_returning: # Dim overlay draw_rect(get_viewport_rect(), Color(0.0, 0.0, 0.04, 0.62)) var sd: Dictionary = hud.score_data var p1_time: float = sd.get("p1_time", 0.0) var p1_kills: int = sd.get("p1_kills", 0) var total: int = int(p1_time) + p1_kills * 50 + int(sd.get("p1_wipe", 0)) if sd.get("multiplayer", false): total += int(sd.get("p2_time", 0.0)) + int(sd.get("p2_kills", 0)) * 50 + int(sd.get("p2_wipe", 0)) # Central terminal box — taller when asking for name var bx := W * 0.5 - 160.0 var by := H * 0.5 - 100.0 var bw := 320.0 var bh2 := 215.0 if hud.awaiting_name else 200.0 _draw_terminal_box(bx, by, bw, bh2, COL_PRIMARY) _draw_text_centered(font, Tr.t("hud_mission_over"), W * 0.5, by + 22.0, 12, COL_ACCENT) _draw_hline(bx + 16.0, bx + bw - 16.0, by + 42.0, COL_DIM) _draw_text_centered(font, Tr.t("hud_score") % total, W * 0.5, by + 60.0, 18, COL_WHITE) var mins1: int = int(p1_time / 60.0) var secs1: int = int(p1_time) % 60 _draw_text_centered(font, "P1 %02d:%02d KILLS %d" % [mins1, secs1, p1_kills], W * 0.5, by + 92.0, 10, COL_PRIMARY) if sd.get("multiplayer", false): var p2t: float = sd.get("p2_time", 0.0) var mins2: int = int(p2t / 60.0); var secs2: int = int(p2t) % 60 _draw_text_centered(font, "P2 %02d:%02d KILLS %d" % [mins2, secs2, sd.get("p2_kills", 0)], W * 0.5, by + 112.0, 10, Color(0.27, 1.0, 0.67, 0.8)) _draw_hline(bx + 16.0, bx + bw - 16.0, by + 135.0, COL_DIM) if hud.is_returning: _draw_text_centered(font, Tr.t("hud_transfer"), W * 0.5, by + 155.0, 10, COL_WARNING) elif hud.awaiting_name: _draw_text_centered(font, Tr.t("lb_enter_name"), W * 0.5, by + 148.0, 10, COL_DIM) var cursor_vis: bool = fmod(hud.blink_phase, 1.0) < 0.6 var field_str: String = hud.name_input + ("_" if cursor_vis else " ") var display_str: String = field_str if field_str.strip_edges().length() > 0 else "_" _draw_text_centered(font, display_str, W * 0.5, by + 163.0, 13, COL_ACCENT) _draw_text_centered(font, Tr.hint("lb_name_hint"), W * 0.5, by + 186.0, 8, COL_DIM) elif hud.name_saved: _draw_text_centered(font, Tr.t("lb_saved"), W * 0.5, by + 155.0, 11, COL_ACCENT) elif hud.gameover_blink: _draw_text_centered(font, Tr.hint("hud_press_key"), W * 0.5, by + 155.0, 11, COL_ACCENT) # ── Drawing helpers ─────────────────────────────────────────────────────────── func _draw_corner_brackets() -> void: # Four L-shaped corner brackets as the only permanent chrome var m := MARGIN var bl := BRACKET_LEN var bw := BRACKET_W # Top-left draw_line(Vector2(m, m), Vector2(m + bl, m), COL_DIM, bw) draw_line(Vector2(m, m), Vector2(m, m + bl), COL_DIM, bw) # Top-right draw_line(Vector2(W-m, m), Vector2(W-m-bl, m), COL_DIM, bw) draw_line(Vector2(W-m, m), Vector2(W-m, m+bl), COL_DIM, bw) # Bottom-left draw_line(Vector2(m, H-m), Vector2(m+bl, H-m), COL_DIM, bw) draw_line(Vector2(m, H-m), Vector2(m, H-m-bl), COL_DIM, bw) # Bottom-right draw_line(Vector2(W-m, H-m), Vector2(W-m-bl, H-m), COL_DIM, bw) draw_line(Vector2(W-m, H-m), Vector2(W-m, H-m-bl), COL_DIM, bw) func _draw_terminal_box(bx: float, by: float, bw: float, bh2: float, col: Color) -> void: # Filled dark panel draw_rect(Rect2(bx, by, bw, bh2), Color(0.0, 0.04, 0.02, 0.88)) # Border lines var bc := Color(col.r, col.g, col.b, col.a * 0.6) draw_line(Vector2(bx, by), Vector2(bx+bw, by), bc, 1.0) draw_line(Vector2(bx, by+bh2), Vector2(bx+bw, by+bh2), bc, 1.0) draw_line(Vector2(bx, by), Vector2(bx, by+bh2), bc, 1.0) draw_line(Vector2(bx+bw, by), Vector2(bx+bw, by+bh2), bc, 1.0) # Corner L-brackets (bright) var cl := 10.0 for cx in [bx, bx+bw]: for cy in [by, by+bh2]: var sx := 1.0 if cx == bx else -1.0 var sy := 1.0 if cy == by else -1.0 draw_line(Vector2(cx, cy), Vector2(cx + sx*cl, cy), col, 1.5) draw_line(Vector2(cx, cy), Vector2(cx, cy + sy*cl), col, 1.5) func _draw_hline(x1: float, x2: float, y: float, col: Color) -> void: draw_line(Vector2(x1, y), Vector2(x2, y), col, 1.0) func _draw_text(font: Font, text: String, x: float, y: float, size: int, col: Color) -> void: draw_string(font, Vector2(x, y + size), text, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col) func _draw_text_centered(font: Font, text: String, x: float, y: float, size: int, col: Color) -> void: var tw := font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, size).x draw_string(font, Vector2(x - tw * 0.5, y + size), text, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col)