class_name CockpitDetailing extends Node3D const DiveUpdate = preload("res://game/domain/dive/dive_update.gd") const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd") const VehicleTelemetry = preload("res://game/application/dive/vehicle_telemetry.gd") const CockpitAnalogGauges = preload("res://game/presentation/cockpit/cockpit_analog_gauges.gd") const CockpitDashboardDetailing = preload( "res://game/presentation/cockpit/cockpit_dashboard_detailing.gd" ) const CockpitMaterialPalette = preload("res://game/presentation/cockpit/cockpit_material_palette.gd") const CockpitMeshFactory = preload("res://game/presentation/cockpit/cockpit_mesh_factory.gd") var _materials: CockpitMaterialPalette var _painted_steel: Material var _dark_steel: Material var _brass: Material var _copper: Material var _analog_gauges: CockpitAnalogGauges var _dashboard_detailing: CockpitDashboardDetailing var _warm_practical: OmniLight3D var _emergency_light: OmniLight3D func _ready() -> void: _create_materials() _apply_materials_to_existing_cockpit() _build_pressure_hull_ribs() _build_life_support_pipes() _build_overhead_console() _build_panel_hardware() _build_dashboard_instrumentation() _build_practical_lights() func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void: if update == null or update.snapshot == null or telemetry == null: return var snapshot = update.snapshot _analog_gauges.present( snapshot.oxygen.ratio, snapshot.battery.ratio, snapshot.hull.ratio, clampf( snapshot.depth.current_m / maxf(snapshot.depth.certified_m, 0.001), 0.0, 1.0 ), snapshot.alerts.maximum ) var alert_level: DiveAlertLevel.Value = snapshot.alerts.maximum _emergency_light.visible = alert_level >= DiveAlertLevel.Value.CRITICAL _warm_practical.light_energy = ( 0.28 if alert_level >= DiveAlertLevel.Value.CRITICAL else 0.72 ) func pressure_hull_rib_count() -> int: var ribs := get_node_or_null("PressureHullRibs") return 0 if ribs == null else ribs.get_child_count() func pipe_segment_count() -> int: var pipes := get_node_or_null("LifeSupportPipes") return 0 if pipes == null else pipes.get_child_count() func battery_needle_degrees() -> float: return 0.0 if _analog_gauges == null else _analog_gauges.battery_needle_degrees() func textures_are_loaded() -> bool: return ( _materials != null and _materials.overhead_steel.albedo_texture != null and _materials.gauge_face.albedo_texture != null ) func dashboard_control_count() -> int: return 0 if _dashboard_detailing == null else _dashboard_detailing.control_count() func gauges_are_dashboard_mounted() -> bool: return _analog_gauges != null and _analog_gauges.get_parent() == get_parent().get_node("Dashboard") func _create_materials() -> void: _materials = CockpitMaterialPalette.new() _painted_steel = _materials.painted_steel _dark_steel = _materials.dark_steel _brass = _materials.brass _copper = _materials.copper func _apply_materials_to_existing_cockpit() -> void: _apply_material_to_paths( _painted_steel, [ "CabinShell/LeftWall", "CabinShell/RightWall", "LeftConsole", "RightConsole", ] ) _apply_material_to_paths( _dark_steel, [ "CabinShell/RearBulkhead", "PortholeAssembly/LeftBulkhead", "PortholeAssembly/RightBulkhead", "PortholeAssembly/BottomBulkhead", "ThrottleLever", "VerticalLever", ] ) _apply_material_to_paths( _materials.overhead_steel, [ "CabinShell/Ceiling", "PortholeAssembly/TopBulkhead", "Dashboard/MainPanel", ] ) _apply_material_to_paths( _brass, [ "PortholeAssembly/WindowFrame", "Dashboard/ResourcesFrame", "Dashboard/NavigationFrame", "Dashboard/IntegrityFrame", "ThrottleKnob", "VerticalKnob", ] ) func _apply_material_to_paths(material: Material, paths: Array[String]) -> void: var cockpit := get_parent() for path: String in paths: var mesh := cockpit.get_node_or_null(path) as MeshInstance3D if mesh != null: mesh.material_override = material func _build_pressure_hull_ribs() -> void: var root := Node3D.new() root.name = "PressureHullRibs" add_child(root) for rib_index in 2: var rib := MeshInstance3D.new() rib.name = "CircularRib%d" % (rib_index + 1) var torus := TorusMesh.new() torus.inner_radius = 1.00 torus.outer_radius = 1.075 torus.rings = 48 torus.ring_segments = 10 rib.mesh = torus rib.material_override = _dark_steel rib.position = Vector3(0.0, 0.22, -0.90 + rib_index * 1.35) rib.rotation_degrees.x = 90.0 root.add_child(rib) for side: float in [-1.0, 1.0]: CockpitMeshFactory.add_box( "AngledShoulder", root, Vector3(side * 0.88, 0.84, -0.36), Vector3(0.11, 0.62, 1.52), _painted_steel, Vector3(0.0, 0.0, side * -18.0) ) CockpitMeshFactory.add_box( "LowerStringer", root, Vector3(side * 0.91, -0.38, -0.18), Vector3(0.10, 0.12, 1.70), _dark_steel ) func _build_life_support_pipes() -> void: var root := Node3D.new() root.name = "LifeSupportPipes" add_child(root) for side: float in [-1.0, 1.0]: var x: float = side * 0.83 CockpitMeshFactory.add_cylinder( "VerticalPipe", root, Vector3(x, 0.33, -1.12), 0.027, 1.18, _copper ) CockpitMeshFactory.add_cylinder( "ReturnPipe", root, Vector3(side * 0.73, 0.47, -1.09), 0.019, 0.92, _brass ) for clamp_y: float in [-0.04, 0.35, 0.72]: var clamp := MeshInstance3D.new() clamp.name = "PipeClamp" var torus := TorusMesh.new() torus.inner_radius = 0.027 torus.outer_radius = 0.041 torus.rings = 16 torus.ring_segments = 6 clamp.mesh = torus clamp.material_override = _dark_steel clamp.position = Vector3(x, clamp_y, -1.12) root.add_child(clamp) CockpitMeshFactory.add_sphere( "PipeJunction", root, Vector3(x, 0.91, -1.12), 0.052, _copper ) CockpitMeshFactory.add_cylinder( "OverheadCrossPipe", root, Vector3(0.0, 0.93, -1.12), 0.031, 1.82, _copper, Vector3(0.0, 0.0, 90.0) ) func _build_overhead_console() -> void: var root := Node3D.new() root.name = "OverheadConsole" add_child(root) CockpitMeshFactory.add_box( "BreakerHousing", root, Vector3(0.0, 0.94, -1.06), Vector3(0.30, 0.09, 0.12), _materials.overhead_steel, Vector3(-8.0, 0.0, 0.0) ) CockpitMeshFactory.add_box( "WarmLightDiffuser", root, Vector3(0.0, 0.895, -0.985), Vector3(0.13, 0.010, 0.026), _materials.warm_emission, Vector3(-8.0, 0.0, 0.0) ) for guard_index in 5: var guard_x := -0.05 + guard_index * 0.025 CockpitMeshFactory.add_box( "LampGuard", root, Vector3(guard_x, 0.896, -0.968), Vector3(0.005, 0.045, 0.008), _dark_steel, Vector3(-8.0, 0.0, 0.0) ) for switch_index in 5: var x := -0.10 + switch_index * 0.05 CockpitMeshFactory.add_cylinder( "BreakerSwitch", root, Vector3(x, 0.965, -0.985), 0.004, 0.018, _materials.rubber, Vector3(90.0, 0.0, 0.0) ) func _build_panel_hardware() -> void: var root := Node3D.new() root.name = "PanelHardware" add_child(root) for x: float in [-0.73, -0.49, -0.24, 0.24, 0.49, 0.73]: for y: float in [-0.18, 0.22]: CockpitMeshFactory.add_cylinder( "PanelFastener", root, Vector3(x, y, -1.105), 0.014, 0.018, _dark_steel, Vector3(90.0, 0.0, 0.0) ) for side: float in [-1.0, 1.0]: CockpitMeshFactory.add_box( "ServiceBox", root, Vector3(side * 0.89, 0.08, -1.10), Vector3(0.18, 0.34, 0.19), _painted_steel, Vector3(0.0, side * -12.0, 0.0) ) var label := Label3D.new() label.name = "ServicePlate" label.text = "O₂" if side < 0.0 else "ELEC" label.font_size = 16 label.pixel_size = 0.0009 label.modulate = Color(0.72, 0.69, 0.56) label.position = Vector3(side * 0.89, 0.10, -0.99) root.add_child(label) func _build_dashboard_instrumentation() -> void: var dashboard := get_parent().get_node_or_null("Dashboard") as Node3D if dashboard == null: return _dashboard_detailing = CockpitDashboardDetailing.new(_materials) dashboard.add_child(_dashboard_detailing) _dashboard_detailing.apply_screen_materials(dashboard) _dashboard_detailing.hide_placeholder_indicators(get_parent()) _analog_gauges = CockpitAnalogGauges.new(_materials) dashboard.add_child(_analog_gauges) func _build_practical_lights() -> void: _warm_practical = OmniLight3D.new() _warm_practical.name = "WarmPracticalLight" _warm_practical.position = Vector3(0.0, 0.82, -0.50) _warm_practical.light_color = Color(1.0, 0.52, 0.22) _warm_practical.light_energy = 0.58 _warm_practical.omni_range = 2.2 _warm_practical.shadow_enabled = false add_child(_warm_practical) var cool_fill := OmniLight3D.new() cool_fill.name = "CoolRearFill" cool_fill.position = Vector3(0.0, 0.42, 0.35) cool_fill.light_color = Color(0.24, 0.46, 0.47) cool_fill.light_energy = 0.48 cool_fill.omni_range = 3.0 cool_fill.shadow_enabled = false add_child(cool_fill) _emergency_light = OmniLight3D.new() _emergency_light.name = "EmergencyLight" _emergency_light.position = Vector3(-0.58, 0.44, -0.83) _emergency_light.light_color = Color(1.0, 0.04, 0.015) _emergency_light.light_energy = 1.4 _emergency_light.omni_range = 2.4 _emergency_light.visible = false add_child(_emergency_light)