extends Control ## #503: Auto-checklist HUD overlay — shows condition progress in Gauntlet mode. ## Renders below the GauntletHUD timer. Each condition shows a check/dash + description. ## Only visible in gauntlet_mode. Latched conditions stay checked. ## ## Spec ref: D-030 (testability), #503, Sprint 10 Completion Proof. const ChecklistEvaluator = preload("res://scripts/checklist/checklist_evaluator.gd") const BG_COLOR := Color(0.05, 0.05, 0.08, 0.45) const MET_COLOR := Color("#6bc9a6") # Friendly green — condition met const UNMET_COLOR := Color("#8890a0") # Dim grey — condition pending const HEADER_COLOR := Color("#c8d0e0") # Insert text color — header/summary const COMPLETE_COLOR := Color("#e8c547") # Amber — all conditions met const FONT_SIZE := 11 const LINE_HEIGHT := 16 const PADDING := Vector2(8, 6) const MAX_DESC_CHARS := 52 # Truncate long descriptions var _evaluator = null # ChecklistEvaluator instance var _last_room_id: Variant = null var _cached_font: Font = null # Cached to avoid per-frame theme lookup func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE visible = false _evaluator = ChecklistEvaluator.new() _cached_font = get_theme_default_font() func update_from_state() -> void: if not GameState.gauntlet_mode: if visible: visible = false return if not visible: visible = true var room_id: Variant = GameState.room_id if room_id == null: if _evaluator.is_loaded(): _evaluator.reset() _last_room_id = null queue_redraw() return # Load checklist on room change if room_id != _last_room_id: _evaluator.load_room(str(room_id)) _last_room_id = room_id # Evaluate conditions against current snapshot _evaluator.evaluate() queue_redraw() func _draw() -> void: if _evaluator == null or not _evaluator.is_loaded(): return var font: Font = _cached_font if _cached_font else get_theme_default_font() var results: Array = _evaluator.get_results() if results.is_empty(): return var met_count: int = _evaluator.get_met_count() var total_count: int = _evaluator.get_total_count() var all_complete: bool = _evaluator.is_complete() # Header line: "CHECK: 5/8" var header_text := "CHECK: %d/%d" % [met_count, total_count] var header_color: Color = COMPLETE_COLOR if all_complete else HEADER_COLOR # Calculate box height: header + one line per condition + padding var line_count: int = 1 + results.size() var box_height: float = PADDING.y * 2 + line_count * LINE_HEIGHT # Calculate box width from longest line var max_width: float = font.get_string_size(header_text, HORIZONTAL_ALIGNMENT_LEFT, -1, FONT_SIZE).x for r in results: var desc: String = r.get("description", r.get("id", "")) if desc.length() > MAX_DESC_CHARS: desc = desc.substr(0, MAX_DESC_CHARS - 1) + "..." var prefix: String = "[x] " if r.get("met", false) else "[ ] " var line_width: float = font.get_string_size(prefix + desc, HORIZONTAL_ALIGNMENT_LEFT, -1, FONT_SIZE).x if line_width > max_width: max_width = line_width var box_width: float = max_width + PADDING.x * 2 # Background draw_rect(Rect2(Vector2.ZERO, Vector2(box_width, box_height)), BG_COLOR) # Header var y: float = PADDING.y + FONT_SIZE draw_string(font, Vector2(PADDING.x, y), header_text, HORIZONTAL_ALIGNMENT_LEFT, -1, FONT_SIZE, header_color) # Condition lines for r in results: y += LINE_HEIGHT var is_met: bool = r.get("met", false) var prefix: String = "[x] " if is_met else "[ ] " var desc: String = r.get("description", r.get("id", "")) if desc.length() > MAX_DESC_CHARS: desc = desc.substr(0, MAX_DESC_CHARS - 1) + "..." var color: Color = MET_COLOR if is_met else UNMET_COLOR draw_string(font, Vector2(PADDING.x, y), prefix + desc, HORIZONTAL_ALIGNMENT_LEFT, -1, FONT_SIZE, color) # -- Public API --------------------------------------------------------------- func get_evaluator(): return _evaluator func get_met_count() -> int: if _evaluator == null: return 0 return _evaluator.get_met_count() func get_total_count() -> int: if _evaluator == null: return 0 return _evaluator.get_total_count() func is_complete() -> bool: if _evaluator == null: return false return _evaluator.is_complete()