Quality Gate / Quality Gate (pull_request) Successful in 9m4s
Instance twelve Blender-scripted glTF models in the cockpit scene and rebuild the cabin around them: gauge panel, sonar, switch panel, alert box, speaker, porthole, hull wall, caged lamps, gas bottle, handrails and side consoles. Presentation only; no domain, application or world rule changes beyond enabling glow in the test environment. - Needles, alert lamps and sonar screen are driven by the existing snapshots; the alert box now carries SYS/ATTN/ALRM. - Depth dial reads percent of certified depth. - Sonar display restyled as a turquoise holographic relief with a shader grid and height scale; SubViewport matches the glass (960x604). - Cabin lighting retuned: warm pooled lamp light, cool porthole rim, lamp shadows, dial backlight. - Procedural stand-ins removed (bulkhead, tanks, consoles, levers, indicator columns, placeholder pipe run, dial face PNGs). - Tests adapted where nodes disappeared; one assertion removed (pipe_segment_count) until the pipe kit is placed. - Model .glb files are committed without LFS; textures Godot extracts next to them at import are ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
151 lines
4.6 KiB
GDScript
151 lines
4.6 KiB
GDScript
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
|