class_name SonarHud extends CanvasLayer const SonarModelScript = preload("res://src/sonar_model.gd") const SonarReliefRendererScript = preload("res://src/sonar_relief_renderer.gd") signal expanded_changed(is_expanded: bool) var _renderer: SubViewport var _telemetry_label: Label var _compact_panel: PanelContainer var _compact_status: Label var _expanded_overlay: ColorRect var _expanded_status: Label var _help_label: Label var _expanded := false func _ready() -> void: layer = 2 _renderer = SonarReliefRendererScript.new() _renderer.name = "SonarReliefRenderer" add_child(_renderer) _build_hud() func update_telemetry(snapshot: Dictionary) -> void: _telemetry_label.text = "PROFONDEUR %.1f m\nVITESSE %.1f m/s\nPROPULSION %d%%" % [ float(snapshot.get("depth", 0.0)), float(snapshot.get("speed", 0.0)), int(round(float(snapshot.get("battery", 0.0)))), ] func update_sonar_pose(new_transform: Transform3D) -> void: _renderer.update_submarine_pose(new_transform) func show_ping_started(_max_range: float) -> void: _renderer.start_scan() _set_status( "SCAN 0% • PREMIERS RETOURS EN ATTENTE", Color("9cf7f2"), "SCAN 0%" ) func show_ping_progress( current_cells: Array[Dictionary], previous_cells: Array[Dictionary], _max_range: float, progress: float ) -> void: _renderer.update_scan(current_cells, previous_cells, progress) var metal_detected := _metal_cell_count(current_cells) > 0 var color := Color("ffad52") if metal_detected else Color("9cf7f2") var suffix := " • ANOMALIE" if metal_detected else "" var status := "SCAN %d%%%s" % [int(round(progress * 100.0)), suffix] _set_status(status, color, status) func show_ping_results( current_cells: Array[Dictionary], _previous_cells: Array[Dictionary], _max_range: float ) -> void: _renderer.finish_scan(current_cells) var metal_count := _metal_cell_count(current_cells) var status := "SCAN TERMINÉ • RELIEF RECONSTRUIT" var color := Color("9cf7f2") if metal_count > 0: status += " • ANOMALIE ORANGE" color = Color("ffad52") _set_status( status, color, "SCAN TERMINÉ • ANOMALIE" if metal_count > 0 else "SCAN TERMINÉ" ) func toggle_expanded() -> void: set_expanded(not _expanded) func set_expanded(is_expanded: bool) -> void: if _expanded == is_expanded: return _expanded = is_expanded _expanded_overlay.visible = is_expanded _compact_panel.visible = not is_expanded _help_label.visible = not is_expanded expanded_changed.emit(is_expanded) func is_expanded() -> bool: return _expanded func relief_renderer() -> SubViewport: return _renderer func _set_status(text: String, color: Color, compact_text: String = "") -> void: _compact_status.text = text if compact_text.is_empty() else compact_text _compact_status.modulate = color _expanded_status.text = text _expanded_status.modulate = color func _metal_cell_count(cells: Array[Dictionary]) -> int: var count := 0 for cell: Dictionary in cells: if int(cell["material"]) == SonarModelScript.MaterialCategory.METAL: count += 1 return count func _build_hud() -> void: var root := Control.new() root.name = "SonarHudRoot" root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(root) var objective := Label.new() objective.set_anchors_preset(Control.PRESET_CENTER_TOP) objective.position = Vector2(-390.0, 24.0) objective.size = Vector2(780.0, 38.0) objective.text = "LISEZ LE RELIEF • FRANCHISSEZ L'OUVERTURE • IDENTIFIEZ L'ANOMALIE" objective.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER objective.add_theme_font_size_override("font_size", 18) root.add_child(objective) var telemetry_panel := PanelContainer.new() telemetry_panel.position = Vector2(22.0, 82.0) telemetry_panel.size = Vector2(280.0, 118.0) root.add_child(telemetry_panel) _telemetry_label = Label.new() _telemetry_label.text = "PROFONDEUR\nVITESSE\nPROPULSION" _telemetry_label.add_theme_font_size_override("font_size", 16) telemetry_panel.add_child(_telemetry_label) var instruction_panel := PanelContainer.new() instruction_panel.position = Vector2(22.0, 214.0) instruction_panel.size = Vector2(294.0, 112.0) root.add_child(instruction_panel) var instruction := Label.new() instruction.text = ( "F ÉMETTRE UN SCAN MANUEL\n" + "Le relief apparaît du proche au lointain.\n" + "L'orange signale un retour anormal." ) instruction.modulate = Color(0.78, 0.94, 0.95, 0.94) instruction_panel.add_child(instruction) _compact_panel = PanelContainer.new() _compact_panel.set_anchors_preset(Control.PRESET_TOP_RIGHT) _compact_panel.position = Vector2(-342.0, 82.0) _compact_panel.size = Vector2(320.0, 276.0) root.add_child(_compact_panel) var compact_box := VBoxContainer.new() compact_box.add_theme_constant_override("separation", 6) _compact_panel.add_child(compact_box) var compact_title := Label.new() compact_title.text = "SONAR BATHYMÉTRIQUE • AGRANDIR" compact_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER compact_title.add_theme_font_size_override("font_size", 14) compact_box.add_child(compact_title) var compact_view := _make_sonar_texture_rect(Vector2(298.0, 190.0)) compact_view.tooltip_text = "Cliquez pour ouvrir le grand écran sonar" compact_view.mouse_filter = Control.MOUSE_FILTER_STOP compact_view.gui_input.connect(_on_compact_view_input) compact_box.add_child(compact_view) _compact_status = Label.new() _compact_status.text = "F • LANCER LE PREMIER SCAN" _compact_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER compact_box.add_child(_compact_status) var compact_reference := Label.new() compact_reference.text = "PORTÉE 95 M • SOUS-MARIN EN BAS • CAP VERS LE HAUT" compact_reference.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER compact_reference.modulate = Color(0.62, 0.82, 0.84, 0.86) compact_reference.add_theme_font_size_override("font_size", 11) compact_box.add_child(compact_reference) _expanded_overlay = ColorRect.new() _expanded_overlay.name = "ExpandedSonarOverlay" _expanded_overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _expanded_overlay.color = Color(0.0, 0.015, 0.02, 0.96) _expanded_overlay.mouse_filter = Control.MOUSE_FILTER_STOP _expanded_overlay.visible = false root.add_child(_expanded_overlay) var expanded_panel := PanelContainer.new() expanded_panel.position = Vector2(110.0, 34.0) expanded_panel.size = Vector2(1060.0, 652.0) _expanded_overlay.add_child(expanded_panel) var expanded_box := VBoxContainer.new() expanded_box.add_theme_constant_override("separation", 7) expanded_panel.add_child(expanded_box) var expanded_title := Label.new() expanded_title.text = "SONAR BATHYMÉTRIQUE • PORTÉE 95 M • CAP VERS LE HAUT" expanded_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER expanded_title.add_theme_font_size_override("font_size", 19) expanded_box.add_child(expanded_title) var expanded_view := _make_sonar_texture_rect(Vector2(1038.0, 556.0)) expanded_box.add_child(expanded_view) _expanded_status = Label.new() _expanded_status.text = "F • LANCER LE PREMIER SCAN" _expanded_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _expanded_status.add_theme_font_size_override("font_size", 16) expanded_box.add_child(_expanded_status) var legend := Label.new() legend.text = "BLEU PROFOND • VERT/JAUNE HAUTEUR • ORANGE CAISSE/ANOMALIE • NOIR INCONNU" legend.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER legend.modulate = Color(0.68, 0.88, 0.90, 0.90) expanded_box.add_child(legend) var close_button := Button.new() close_button.position = Vector2(1044.0, 46.0) close_button.size = Vector2(108.0, 36.0) close_button.text = "FERMER ×" close_button.pressed.connect(func() -> void: set_expanded(false)) _expanded_overlay.add_child(close_button) _help_label = Label.new() _help_label.set_anchors_preset(Control.PRESET_BOTTOM_WIDE) _help_label.offset_left = 20.0 _help_label.offset_top = -42.0 _help_label.offset_right = -20.0 _help_label.offset_bottom = -12.0 _help_label.text = "F scanner • Tab agrandir/fermer • W/S propulsion • A/D gouvernail • Espace/Ctrl profondeur • Échap souris" _help_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _help_label.modulate = Color(0.75, 0.90, 0.94, 0.9) root.add_child(_help_label) func _make_sonar_texture_rect(minimum_size: Vector2) -> TextureRect: var view := TextureRect.new() view.custom_minimum_size = minimum_size view.texture = _renderer.get_texture() view.expand_mode = TextureRect.EXPAND_IGNORE_SIZE view.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED return view func _on_compact_view_input(event: InputEvent) -> void: if ( event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed ): set_expanded(true)