class_name CockpitInstrumentPanel extends Node3D const DiveUpdate = preload("res://game/domain/dive/dive_update.gd") const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd") const ReturnEstimate = preload("res://game/domain/dive/return_estimate.gd") const VehicleTelemetry = preload("res://game/application/dive/vehicle_telemetry.gd") const CockpitDetailing = preload( "res://game/presentation/cockpit/cockpit_detailing.gd" ) const COLOR_NORMAL := Color(0.45, 0.89, 0.90, 1.0) const COLOR_ATTENTION := Color(1.0, 0.75, 0.20, 1.0) const COLOR_CRITICAL := Color(1.0, 0.42, 0.31, 1.0) ## 1.0 shows the sonar picture as rendered; slightly above lets its brightest ## lines bloom without clipping the surface colours. const SONAR_SCREEN_ENERGY := 1.25 @onready var _resources_label: Label3D = %ResourcesLabel @onready var _navigation_label: Label3D = %NavigationLabel @onready var _integrity_label: Label3D = %IntegrityLabel @onready var _return_label: Label3D = %ReturnLabel @onready var _status_label: Label3D = %StatusLabel @onready var _sonar_screen: MeshInstance3D = %SonarScreen @onready var _sonar_placeholder: Label3D = %SonarPlaceholder @onready var _detailing: CockpitDetailing = %CockpitDetailing var _sonar_texture: Texture2D func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void: if update == null or update.snapshot == null or telemetry == null: return var snapshot = update.snapshot _resources_label.text = ( "BAT %.1f kWh · O₂ %.0f %%\n%.0f kW · %.1f L/min" % [ snapshot.battery.remaining_kwh, snapshot.oxygen.ratio * 100.0, snapshot.battery.current_power_kw, snapshot.oxygen.consumption_l_per_min, ] ) _navigation_label.text = ( "PROF. %.1f / %.1f m\nVIT. %.2f m/s" % [ snapshot.depth.current_m, snapshot.depth.certified_m, telemetry.world_linear_velocity_mps.length(), ] ) _integrity_label.text = ( "COQUE %.0f %% · %.2f bar\nLIMITE %.2f bar" % [ snapshot.hull.ratio * 100.0, snapshot.hull.exterior_pressure_bar, snapshot.hull.certified_pressure_bar, ] ) _return_label.text = _return_text(snapshot.return_estimate) _status_label.text = "SYSTÈMES · %s" % _alert_text(snapshot.alerts.maximum) _resources_label.modulate = _alert_color( maxi(snapshot.alerts.battery, snapshot.alerts.oxygen) ) _integrity_label.modulate = _alert_color( maxi(snapshot.alerts.hull, snapshot.alerts.depth) ) _status_label.modulate = _alert_color(snapshot.alerts.maximum) _detailing.present(update, telemetry) func resources_text() -> String: return _resources_label.text func navigation_text() -> String: return _navigation_label.text func integrity_text() -> String: return _integrity_label.text func return_text() -> String: return _return_label.text func attach_sonar_texture(texture: Texture2D) -> void: _sonar_texture = texture # The glass shows the picture by emission only: black albedo, no specular, # so cabin lights add nothing to it, and the energy can lift the brightest # lines just above 1.0 for the environment glow to pick up. var material := StandardMaterial3D.new() material.albedo_color = Color.BLACK material.metallic_specular = 0.0 material.roughness = 1.0 material.emission_enabled = true material.emission = Color.WHITE material.emission_operator = BaseMaterial3D.EMISSION_OP_MULTIPLY material.emission_texture = texture material.emission_energy_multiplier = SONAR_SCREEN_ENERGY _sonar_screen.material_override = material _sonar_placeholder.visible = false func sonar_texture() -> Texture2D: return _sonar_texture func detailing() -> CockpitDetailing: return _detailing func _return_text(estimate: ReturnEstimate) -> String: match estimate.status: ReturnEstimate.Status.SAFE: return "RETOUR %.1f kWh · MARGE %.1f kWh" % [ estimate.required_kwh, estimate.margin_kwh, ] ReturnEstimate.Status.TIGHT: return "RETOUR SERRÉ · MARGE %.1f kWh" % estimate.margin_kwh ReturnEstimate.Status.INSUFFICIENT: return "RETOUR INSUFFISANT · MANQUE %.1f kWh" % absf( estimate.margin_kwh ) return "RETOUR · INCONNU" func _alert_text(level: DiveAlertLevel.Value) -> String: match level: DiveAlertLevel.Value.NORMAL: return "NORMAL" DiveAlertLevel.Value.ATTENTION: return "ATTENTION" DiveAlertLevel.Value.RETURN: return "RETOUR" DiveAlertLevel.Value.CRITICAL: return "CRITIQUE" DiveAlertLevel.Value.FAILURE: return "DÉFAILLANCE" return "INCONNU" func _alert_color(level: DiveAlertLevel.Value) -> Color: if level >= DiveAlertLevel.Value.CRITICAL: return COLOR_CRITICAL if level >= DiveAlertLevel.Value.ATTENTION: return COLOR_ATTENTION return COLOR_NORMAL