Implement production vertical slice foundation
This commit is contained in:
1 parent
9d4a89f355
commit
a18e82eb44
104 files changed
+10595
-268
No files matched your search
@@ -0,0 +1,20 @@
|
||||
class_name DiagnosticTelemetrySource
|
||||
extends RefCounted
|
||||
|
||||
const VehicleTelemetry = preload("res://game/application/dive/vehicle_telemetry.gd")
|
||||
|
||||
|
||||
func sample(session_id: StringName, sequence: int) -> VehicleTelemetry:
|
||||
return VehicleTelemetry.new(
|
||||
session_id,
|
||||
sequence,
|
||||
Vector3(0.0, -24.0, 0.0),
|
||||
Quaternion.IDENTITY,
|
||||
Vector3(0.0, 0.0, -1.5),
|
||||
Vector3.ZERO,
|
||||
24.0,
|
||||
0.25,
|
||||
0.0,
|
||||
0.0,
|
||||
false
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
[gd_scene load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://game/world/sites/vehicle_test_environment.tscn" id="1_environment"]
|
||||
[ext_resource type="PackedScene" path="res://game/world/submarine/submarine_actor.tscn" id="2_submarine"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_camera_rig.tscn" id="3_camera"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_interior.tscn" id="4_cockpit"]
|
||||
|
||||
[node name="VehicleTestWorld" type="Node3D"]
|
||||
|
||||
[node name="Environment" parent="." instance=ExtResource("1_environment")]
|
||||
|
||||
[node name="SubmarineActor" parent="." instance=ExtResource("2_submarine")]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(0, -12, 12)
|
||||
|
||||
[node name="CockpitInterior" parent="SubmarineActor" instance=ExtResource("4_cockpit")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="CockpitCameraRig" parent="SubmarineActor" instance=ExtResource("3_camera")]
|
||||
unique_name_in_owner = true
|
||||
@@ -0,0 +1,426 @@
|
||||
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 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 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 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 VEHICLE_TEST_WORLD_SCENE_PATH := (
|
||||
"res://game/composition/vertical_slice/vehicle_test_world.tscn"
|
||||
)
|
||||
const COCKPIT_HUD_SCENE = preload("res://game/presentation/cockpit/cockpit_hud.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 _latest_update: DiveUpdate
|
||||
var _active_world: Node3D
|
||||
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)
|
||||
_show_main_menu()
|
||||
|
||||
|
||||
func _start_vehicle_test() -> 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 world_scene := load(VEHICLE_TEST_WORLD_SCENE_PATH) as PackedScene
|
||||
if world_scene == null:
|
||||
return
|
||||
var world := world_scene.instantiate() as Node3D
|
||||
_replace_active_world(world)
|
||||
_actor = world.get_node("%SubmarineActor") as SubmarineActor
|
||||
_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)
|
||||
|
||||
var motion_adapter := SubmarineMotionAdapter.new(_actor)
|
||||
_pilot_use_case = PilotUseCase.new(motion_adapter)
|
||||
_telemetry_adapter = SubmarineTelemetryAdapter.new(_actor)
|
||||
_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)
|
||||
_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)
|
||||
_instrument_panel.present(_latest_update, telemetry)
|
||||
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 _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_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
|
||||
_camera_controller = null
|
||||
_instrument_panel = null
|
||||
_hud = 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_vehicle_test)
|
||||
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()
|
||||
@@ -0,0 +1,35 @@
|
||||
[gd_scene load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://game/composition/vertical_slice/vertical_slice_root.gd" id="1_root"]
|
||||
[ext_resource type="Script" path="res://game/infrastructure/input/production_input_adapter.gd" id="2_input"]
|
||||
|
||||
[node name="VerticalSliceRoot" type="Node"]
|
||||
script = ExtResource("1_root")
|
||||
|
||||
[node name="WorldContent" type="Node3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="Content" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="OverlayContent" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
process_mode = 3
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="ProductionInputAdapter" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
process_mode = 3
|
||||
script = ExtResource("2_input")
|
||||
Reference in new issue
Block a user