Files
urm03/scripts/hand_hud.gd
T
vh 4ae0876e34 feat: salvage-pickup loop — kill-fed charges, scrap drops, salvage-gated card levels
Per canon decisions 2026-08-07 (advisor CONCEPT.md f12cb15, falsifier
response): charges now generate on kills (kills_per_charge, chemistry
chain kills feed the hand; per-hit generation removed). Kills drop
scrap as physical pickups (amber diamond, magnet radius 70, drift to
mech, 20s fade; grunt 1 / burster 3) — collection is the positioning
reward gradient. Cumulative collected scrap gates card levels 1-3
(thresholds 12/30; per-level radius x1.25/x1.5 and power x1.15/x1.5 —
HEAT L3 hits one-cast detonation threshold). HUD: level notches under
slots + scrap counter. Salvage spawn is call_deferred: death can fire
inside a physics signal flush where Area2D setup is blocked.

Smoke gate: charge accrual now kill-fed, salvage drop + collection +
level gating phases added, heat-target selection made retry-tolerant.
4/4 green on nh3-dev headless 4.7.1.
2026-08-07 12:46:59 -07:00

74 lines
2.8 KiB
GDScript

extends Control
## Greybox hand HUD: two card slots (bottom center), charge pips above
## them, dilation knob readout (dev instrumentation, not game UI).
## Iconic, not textual — slots are tag-hue blocks, no card text.
@onready var _hand: CanvasLayer = get_parent()
func _draw() -> void:
var slot_w := 24.0
var slot_h := 32.0
var gap := 6.0
var total := slot_w * 2 + gap
var x0 := (640.0 - total) / 2.0
var y0 := 320.0
var lvl: int = _hand.card_level()
for i in _hand.cards.size():
var card: Dictionary = _hand.cards[i]
var rect := Rect2(x0 + i * (slot_w + gap), y0, slot_w, slot_h)
var affordable: bool = _hand.charges >= card.cost_of.call()
var hue: Color = card.hue
if not affordable:
hue = Color(hue.r, hue.g, hue.b, 0.25)
elif not _hand.hand_open:
hue = Color(hue.r, hue.g, hue.b, 0.6)
draw_rect(rect, hue)
if _hand.hand_open:
draw_rect(rect.grow(1.0), Color(1, 1, 1, 0.9 if affordable else 0.3), false, 1.0)
# Card level: notches under the slot.
for n in lvl:
draw_rect(Rect2(rect.position.x + n * 8.0, rect.end.y + 3.0, 5.0, 3.0), Color(0.95, 0.8, 0.35))
# Salvage counter: scrap diamond + running total, right of the slots.
var dx := x0 + total + 16.0
var dy := y0 + 14.0
var dia := PackedVector2Array([Vector2(dx, dy - 5), Vector2(dx + 4, dy), Vector2(dx, dy + 5), Vector2(dx - 4, dy)])
draw_colored_polygon(dia, Color(0.95, 0.8, 0.35))
draw_string(ThemeDB.fallback_font, Vector2(dx + 8.0, dy + 4.0), str(_hand.salvage_total),
HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.85, 0.85, 0.9))
var pip := 5.0
var pips_w: float = float(_hand.max_charges) * (pip + 3.0) - 3.0
var px0: float = (640.0 - pips_w) / 2.0
for i in int(_hand.max_charges):
var filled: bool = i < int(_hand.charges)
var c := Color(0.9, 0.9, 0.95) if filled else Color(0.9, 0.9, 0.95, 0.18)
draw_rect(Rect2(px0 + i * (pip + 3.0), y0 - 10.0, pip, pip), c)
if _hand.meter_mode:
# Freeze meter: fill color names the band you'd open into.
var bw := 84.0
var bh := 4.0
var bx := (640.0 - bw) / 2.0
var by := y0 - 20.0
draw_rect(Rect2(bx, by, bw, bh), Color(0.9, 0.9, 0.95, 0.12))
var f: float = _hand.frac()
var col := Color(0.85, 0.95, 1.0)
if f < _hand.band_unavailable:
col = Color(0.8, 0.3, 0.25)
elif f < _hand.band_realtime:
col = Color(0.55, 0.57, 0.6)
elif f < _hand.band_thaw:
col = Color(0.55, 0.75, 0.95)
draw_rect(Rect2(bx, by, bw * f, bh), col)
for b: float in [_hand.band_unavailable, _hand.band_realtime, _hand.band_thaw]:
var tx := bx + bw * b
draw_line(Vector2(tx, by - 1), Vector2(tx, by + bh + 1), Color(0.1, 0.1, 0.12, 0.9), 1.0)
var font := ThemeDB.fallback_font
var label: String = "frz %.1f/%.1fs" % [_hand.meter, _hand.meter_max] if _hand.meter_mode else "dt x%.2f" % _hand.dilation_scale
draw_string(font, Vector2(560, 352), label,
HORIZONTAL_ALIGNMENT_LEFT, -1, 8, Color(0.6, 0.65, 0.7, 0.7))