class_name VerticalSliceRoot extends Node const DiveDifficultyProfile = preload("res://game/domain/rules/dive_difficulty_profile.gd") const DiveUpgradeEffects = preload("res://game/domain/rules/dive_upgrade_effects.gd") const DiveAccessibilityAdjustments = preload( "res://game/domain/rules/dive_accessibility_adjustments.gd" ) const DiveRulesResolver = preload("res://game/domain/rules/dive_rules_resolver.gd") const DiveSession = preload("res://game/domain/dive/dive_session.gd") const DiveTickRunner = preload("res://game/application/dive/dive_tick_runner.gd") const DiveUpdate = preload("res://game/domain/dive/dive_update.gd") const CollisionObservation = preload("res://game/domain/dive/collision_observation.gd") const EjectionIntent = preload("res://game/domain/dive/ejection_intent.gd") const ReturnRouteSample = preload("res://game/domain/dive/return_route_sample.gd") const VehicleCommand = preload("res://game/application/vehicle/vehicle_command.gd") const VehicleCollisionSample = preload( "res://game/application/vehicle/vehicle_collision_sample.gd" ) const PilotUseCase = preload("res://game/application/vehicle/pilot_use_case.gd") const SonarService = preload("res://game/application/sonar/sonar_service.gd") const SubmarineActor = preload("res://game/world/submarine/submarine_actor.gd") const SubmarineMotionAdapter = preload( "res://game/world/submarine/submarine_motion_adapter.gd" ) const SubmarineTelemetryAdapter = preload( "res://game/world/submarine/submarine_telemetry_adapter.gd" ) const ReferenceBathymetryAcquisition = preload( "res://game/world/sonar/reference_bathymetry_acquisition.gd" ) const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd") const DiveSite = preload("res://game/world/sites/dive_site.gd") const PlayableBoundsRule = preload("res://game/application/sites/playable_bounds_rule.gd") const PlayableBoundsStatus = preload( "res://game/application/sites/playable_bounds_status.gd" ) const ProductionInputAdapter = preload( "res://game/infrastructure/input/production_input_adapter.gd" ) const CockpitCameraController = preload( "res://game/presentation/cockpit/cockpit_camera_controller.gd" ) const CockpitInstrumentPanel = preload( "res://game/presentation/cockpit/cockpit_instrument_panel.gd" ) const CockpitHud = preload("res://game/presentation/cockpit/cockpit_hud.gd") const SonarDisplay = preload("res://game/presentation/sonar/sonar_display.gd") const MainMenuScreen = preload("res://game/presentation/main_menu/main_menu_screen.gd") const PauseMenu = preload("res://game/presentation/pause/pause_menu.gd") const ControlsSettingsScreen = preload( "res://game/presentation/settings/controls_settings_screen.gd" ) const ConfirmationDialogScreen = preload( "res://game/presentation/confirmation/confirmation_dialog.gd" ) const RebindResult = preload("res://game/application/input/rebind_result.gd") const MAIN_MENU_SCENE = preload("res://game/presentation/main_menu/main_menu_screen.tscn") const DIVE_WORLD_SCENE_PATH := "res://game/composition/vertical_slice/dive_world.tscn" const DEFAULT_SITE_PATH := "res://content/sites/zone1_vigilant.tres" const COCKPIT_HUD_SCENE = preload("res://game/presentation/cockpit/cockpit_hud.tscn") const SONAR_DISPLAY_SCENE = preload( "res://game/presentation/sonar/sonar_display.tscn" ) const PAUSE_MENU_SCENE = preload("res://game/presentation/pause/pause_menu.tscn") const CONTROLS_SETTINGS_SCENE = preload( "res://game/presentation/settings/controls_settings_screen.tscn" ) const CONFIRMATION_SCENE = preload( "res://game/presentation/confirmation/confirmation_dialog.tscn" ) const DIVE_SESSION_ID := &"vertical_slice_vehicle_test" const CONFIRM_ABANDON := &"abandon_dive" const CONFIRM_MAIN_MENU := &"return_main_menu" const CONFIRM_QUIT := &"quit_game" @onready var _world_content: Node3D = %WorldContent @onready var _content: Control = %Content @onready var _overlay_content: Control = %OverlayContent @onready var _input_adapter: ProductionInputAdapter = %ProductionInputAdapter var _runner: DiveTickRunner var _pilot_use_case: PilotUseCase var _telemetry_adapter: SubmarineTelemetryAdapter var _actor: SubmarineActor var _camera_controller: CockpitCameraController var _instrument_panel: CockpitInstrumentPanel var _hud: CockpitHud var _sonar_service: SonarService var _sonar_display: SonarDisplay var _latest_update: DiveUpdate var _active_world: Node3D var _selected_site_path := DEFAULT_SITE_PATH var _dive_site: DiveSite var _bounds_rule: PlayableBoundsRule var _bounds_status: PlayableBoundsStatus var _active_screen: Control var _active_overlay: Control var _vehicle_command_sequence := 0 var _pending_collision_samples: Array[VehicleCollisionSample] = [] var _dive_start_position := Vector3.ZERO var _dive_start_depth_m := 0.0 var _confirmed_ejection_pending := false var _dive_terminal := false func _ready() -> void: _input_adapter.pause_requested.connect(_on_pause_requested) _input_adapter.rebind_result_available.connect(_on_rebind_result_available) _input_adapter.sonar_scan_requested.connect(_on_sonar_scan_requested) _input_adapter.sonar_view_toggle_requested.connect(_on_sonar_view_toggle_requested) _show_main_menu() func _start_dive() -> void: var rules = DiveRulesResolver.new().resolve( DiveDifficultyProfile.standard(), DiveUpgradeEffects.new(), DiveAccessibilityAdjustments.new() ) if rules == null: return var session := DiveSession.new(DIVE_SESSION_ID, rules) if not session.is_ready(): return var definition := load(_selected_site_path) as DiveSiteDefinition if definition == null: push_error("dive site definition %s cannot be loaded" % _selected_site_path) return var definition_error := definition.validation_error() if not definition_error.is_empty(): push_error("dive site %s is invalid: %s" % [_selected_site_path, definition_error]) return var world_scene := load(DIVE_WORLD_SCENE_PATH) as PackedScene if world_scene == null: return var world := world_scene.instantiate() as Node3D _replace_active_world(world) _dive_site = world.get_node("%DiveSite") as DiveSite var site_error := _dive_site.configure(definition) if not site_error.is_empty(): push_error("dive site %s cannot be built: %s" % [_selected_site_path, site_error]) _show_main_menu() return _actor = world.get_node("%SubmarineActor") as SubmarineActor _actor.global_transform = _dive_site.spawn_transform() _bounds_rule = PlayableBoundsRule.new(definition.playable_bounds_m) _bounds_status = _bounds_rule.evaluate(_actor.global_position) _camera_controller = world.get_node("%CockpitCameraRig") as CockpitCameraController _instrument_panel = world.get_node("%CockpitInterior") as CockpitInstrumentPanel _hud = COCKPIT_HUD_SCENE.instantiate() as CockpitHud _replace_active_screen(_hud) _sonar_display = SONAR_DISPLAY_SCENE.instantiate() as SonarDisplay _hud.add_child(_sonar_display) _instrument_panel.attach_sonar_texture(_sonar_display.sonar_texture()) var motion_adapter := SubmarineMotionAdapter.new(_actor) _pilot_use_case = PilotUseCase.new(motion_adapter) _telemetry_adapter = SubmarineTelemetryAdapter.new(_actor) var sonar_acquisition := ReferenceBathymetryAcquisition.new( _actor.get_world_3d(), _actor ) _sonar_service = SonarService.new(sonar_acquisition, DIVE_SESSION_ID) _runner = DiveTickRunner.new(session) _latest_update = _runner.initial_update() _vehicle_command_sequence = 0 _pending_collision_samples.clear() _confirmed_ejection_pending = false _dive_terminal = false _dive_start_position = _actor.global_position _dive_start_depth_m = _actor.current_depth_m() _actor.collision_sampled.connect(_on_collision_sampled) var initial_telemetry = _telemetry_adapter.sample( DIVE_SESSION_ID, _runner.next_sequence() ) _hud.present(_latest_update, initial_telemetry) _hud.present_bounds(_bounds_status) _instrument_panel.present(_latest_update, initial_telemetry) _camera_controller.present_telemetry(initial_telemetry) _input_adapter.set_gameplay_enabled(true) set_physics_process(true) func _physics_process(delta: float) -> void: if _runner == null or _actor == null or _dive_terminal: return var command: VehicleCommand = _input_adapter.sample_vehicle_command( _vehicle_command_sequence ) if _latest_update.snapshot.battery.remaining_kwh <= 0.0: command = VehicleCommand.neutral(_vehicle_command_sequence) var pilot_result = _pilot_use_case.execute(command) if pilot_result.accepted: _vehicle_command_sequence += 1 _camera_controller.apply_look(_input_adapter.take_camera_look_command()) var telemetry = _telemetry_adapter.sample(DIVE_SESSION_ID, _runner.next_sequence()) _camera_controller.present_telemetry(telemetry) var collisions := _consume_collision_observations(_runner.next_sequence()) var ejection := EjectionIntent.new( _input_adapter.ejection_active(), _confirmed_ejection_pending ) _confirmed_ejection_pending = false var return_route := ReturnRouteSample.new( true, _actor.global_position.distance_to(_dive_start_position), maxf(_actor.current_depth_m() - _dive_start_depth_m, 0.0) ) _latest_update = _runner.advance( telemetry, delta, collisions, [], ejection, return_route ) _hud.present(_latest_update, telemetry) _bounds_status = _bounds_rule.evaluate(_actor.global_position) _hud.present_bounds(_bounds_status) _instrument_panel.present(_latest_update, telemetry) _sonar_service.advance(delta) _present_sonar() if _latest_update.snapshot.is_terminal(): _finish_dive_motion() func latest_update() -> DiveUpdate: return _latest_update func active_screen() -> Control: return _active_screen func active_world() -> Node3D: return _active_world func submarine_actor() -> SubmarineActor: return _actor func camera_controller() -> CockpitCameraController: return _camera_controller func cockpit_instrument_panel() -> CockpitInstrumentPanel: return _instrument_panel func input_adapter() -> ProductionInputAdapter: return _input_adapter func active_overlay() -> Control: return _active_overlay func sonar_service() -> SonarService: return _sonar_service func sonar_display() -> SonarDisplay: return _sonar_display func select_dive_site(definition_path: String) -> void: _selected_site_path = definition_path func dive_site() -> DiveSite: return _dive_site func latest_bounds_status() -> PlayableBoundsStatus: return _bounds_status func _finish_dive_motion() -> void: _dive_terminal = true _input_adapter.set_gameplay_enabled(false) var neutral: VehicleCommand = VehicleCommand.neutral(_vehicle_command_sequence) if _pilot_use_case.execute(neutral).accepted: _vehicle_command_sequence += 1 func _consume_collision_observations( tick_sequence: int ) -> Array[CollisionObservation]: var observations: Array[CollisionObservation] = [] for sample: VehicleCollisionSample in _pending_collision_samples: observations.append( CollisionObservation.new( sample.sample_id, sample.sequence, tick_sequence, sample.normal_impact_speed_mps ) ) _pending_collision_samples.clear() return observations func _on_collision_sampled(sample: VehicleCollisionSample) -> void: _pending_collision_samples.append(sample) func _on_sonar_scan_requested() -> void: if _sonar_service == null or _actor == null or _dive_terminal: return var result = _sonar_service.request_scan(_sonar_sensor_transform()) if result.accepted: _present_sonar() func _on_sonar_view_toggle_requested() -> void: if _sonar_display != null and not _dive_terminal: _sonar_display.toggle_expanded() func _present_sonar() -> void: if _sonar_service == null or _sonar_display == null: return _sonar_display.present( _sonar_service.current_scan(), _sonar_service.previous_scan(), _sonar_service.visible_row_count(), _sonar_service.previous_scan_opacity() ) func _sonar_sensor_transform() -> Transform3D: var world_basis := _actor.global_basis var contract_basis := Basis( world_basis.x, world_basis.y, -world_basis.z ) var sensor_origin := _actor.global_transform * Vector3(0.0, -0.75, -2.2) return Transform3D(contract_basis, sensor_origin) func _on_pause_requested() -> void: if _runner == null: return if not get_tree().paused: _open_pause_menu() elif _active_overlay is ConfirmationDialogScreen: _show_pause_menu_overlay() elif _active_overlay is ControlsSettingsScreen: _show_pause_menu_overlay() else: _resume_dive() func _open_pause_menu() -> void: get_tree().paused = true _input_adapter.set_gameplay_enabled(false) _show_pause_menu_overlay() func _show_pause_menu_overlay() -> void: var menu := PAUSE_MENU_SCENE.instantiate() as PauseMenu _replace_overlay(menu) menu.resume_requested.connect(_resume_dive) menu.controls_requested.connect(_show_controls_overlay) menu.abandon_requested.connect(_confirm_abandon) menu.main_menu_requested.connect(_confirm_main_menu) menu.quit_requested.connect(_confirm_quit) func _resume_dive() -> void: _replace_overlay(null) get_tree().paused = false _input_adapter.set_gameplay_enabled(not _dive_terminal) func _show_controls_overlay() -> void: var screen := CONTROLS_SETTINGS_SCENE.instantiate() as ControlsSettingsScreen _replace_overlay(screen) screen.present_bindings(_input_adapter.binding_snapshots()) screen.rebind_requested.connect(_on_rebind_requested) screen.restore_defaults_requested.connect(_on_restore_defaults_requested) screen.conflict_resolution_requested.connect(_on_conflict_resolution_requested) screen.back_requested.connect(_show_pause_menu_overlay) func _on_rebind_requested(action_id: StringName) -> void: var result: RebindResult = _input_adapter.begin_rebind(action_id) var screen := _active_overlay as ControlsSettingsScreen if screen != null: screen.present_rebind_result(result) screen.show_waiting_for_key() func _on_rebind_result_available(result: RebindResult) -> void: var screen := _active_overlay as ControlsSettingsScreen if screen == null: return screen.present_rebind_result(result) if result.accepted: screen.present_bindings(_input_adapter.binding_snapshots()) func _on_conflict_resolution_requested(swap_bindings: bool) -> void: var result: RebindResult = _input_adapter.resolve_pending_conflict(swap_bindings) var screen := _active_overlay as ControlsSettingsScreen if screen != null: screen.present_rebind_result(result) screen.present_bindings(_input_adapter.binding_snapshots()) func _on_restore_defaults_requested() -> void: _input_adapter.restore_default_bindings() var screen := _active_overlay as ControlsSettingsScreen if screen != null: screen.present_bindings(_input_adapter.binding_snapshots()) func _confirm_abandon() -> void: _show_confirmation( CONFIRM_ABANDON, "Abandonner la plongée ?", "Vous serez éjecté puis évacué. Le sous-marin et sa cargaison non sécurisée resteront au fond. Une franchise sera appliquée.", "S'ÉJECTER ET ABANDONNER" ) func _confirm_main_menu() -> void: _show_confirmation( CONFIRM_MAIN_MENU, "Retourner au menu principal ?", "La plongée actuelle sera interrompue et aucune progression sous-marine ne sera sauvegardée.", "RETOURNER AU MENU" ) func _confirm_quit() -> void: _show_confirmation( CONFIRM_QUIT, "Quitter le jeu ?", "La plongée actuelle sera interrompue.", "QUITTER LE JEU" ) func _show_confirmation( action_id: StringName, title: String, message: String, confirm_label: String ) -> void: var dialog := CONFIRMATION_SCENE.instantiate() as ConfirmationDialogScreen _replace_overlay(dialog) dialog.configure(action_id, title, message, confirm_label) dialog.cancelled.connect(_show_pause_menu_overlay) dialog.confirmed.connect(_on_confirmation_accepted) func _on_confirmation_accepted(action_id: StringName) -> void: match action_id: CONFIRM_ABANDON: _confirmed_ejection_pending = true _replace_overlay(null) get_tree().paused = false _input_adapter.set_gameplay_enabled(true) CONFIRM_MAIN_MENU: _show_main_menu() CONFIRM_QUIT: _quit_game() func _show_main_menu() -> void: get_tree().paused = false _input_adapter.set_gameplay_enabled(false) _runner = null _pilot_use_case = null _telemetry_adapter = null _actor = null _dive_site = null _bounds_rule = null _bounds_status = null _camera_controller = null _instrument_panel = null _hud = null _sonar_service = null _sonar_display = null _latest_update = null _pending_collision_samples.clear() _dive_terminal = false set_physics_process(false) _replace_overlay(null) _replace_active_world(null) var screen := MAIN_MENU_SCENE.instantiate() as MainMenuScreen _replace_active_screen(screen) screen.new_game_requested.connect(_start_dive) screen.quit_requested.connect(_quit_game) func _replace_active_world(next_world: Node3D) -> void: if _active_world != null: _world_content.remove_child(_active_world) _active_world.queue_free() _active_world = next_world if _active_world != null: _world_content.add_child(_active_world) func _replace_active_screen(next_screen: Control) -> void: if _active_screen != null: _content.remove_child(_active_screen) _active_screen.queue_free() _active_screen = next_screen if _active_screen != null: _content.add_child(_active_screen) func _replace_overlay(next_overlay: Control) -> void: if _active_overlay != null: _overlay_content.remove_child(_active_overlay) _active_overlay.queue_free() _active_overlay = next_overlay if _active_overlay != null: _overlay_content.add_child(_active_overlay) func _quit_game() -> void: get_tree().paused = false get_tree().quit()