class_name ProductionInputAdapter extends Node signal pause_requested signal rebind_result_available(result: RebindResult) signal sonar_scan_requested signal sonar_view_toggle_requested const VehicleCommand = preload("res://game/application/vehicle/vehicle_command.gd") const CameraLookCommand = preload("res://game/application/vehicle/camera_look_command.gd") const InputBindingSnapshot = preload( "res://game/application/input/input_binding_snapshot.gd" ) const RebindResult = preload("res://game/application/input/rebind_result.gd") const ACTION_FORWARD := &"vehicle_forward" const ACTION_BACKWARD := &"vehicle_backward" const ACTION_LEFT := &"vehicle_left" const ACTION_RIGHT := &"vehicle_right" const ACTION_ASCEND := &"vehicle_ascend" const ACTION_DESCEND := &"vehicle_descend" const ACTION_HIGH_THRUST := &"vehicle_high_thrust" const ACTION_EJECT := &"dive_eject" const ACTION_RECENTER_CAMERA := &"camera_recenter" const ACTION_SONAR_SCAN := &"sonar_scan" const ACTION_SONAR_VIEW_TOGGLE := &"sonar_view_toggle" const ACTION_PAUSE := &"pause_game" const BINDABLE_ACTIONS: Array[StringName] = [ ACTION_FORWARD, ACTION_BACKWARD, ACTION_LEFT, ACTION_RIGHT, ACTION_ASCEND, ACTION_DESCEND, ACTION_HIGH_THRUST, ACTION_EJECT, ACTION_RECENTER_CAMERA, ACTION_SONAR_SCAN, ACTION_SONAR_VIEW_TOGGLE, ] const ACTION_LABELS := { ACTION_FORWARD: "Avancer", ACTION_BACKWARD: "Reculer", ACTION_LEFT: "Gouverner à gauche", ACTION_RIGHT: "Gouverner à droite", ACTION_ASCEND: "Monter", ACTION_DESCEND: "Descendre", ACTION_HIGH_THRUST: "Forte poussée", ACTION_EJECT: "Éjection maintenue", ACTION_RECENTER_CAMERA: "Recentrer le regard", ACTION_SONAR_SCAN: "Émettre un relevé sonar", ACTION_SONAR_VIEW_TOGGLE: "Agrandir le sonar", } const DEFAULT_KEYS := { ACTION_FORWARD: KEY_W, ACTION_BACKWARD: KEY_S, ACTION_LEFT: KEY_A, ACTION_RIGHT: KEY_D, ACTION_ASCEND: KEY_SPACE, ACTION_DESCEND: KEY_CTRL, ACTION_HIGH_THRUST: KEY_SHIFT, ACTION_EJECT: KEY_X, ACTION_RECENTER_CAMERA: KEY_R, ACTION_SONAR_SCAN: KEY_F, ACTION_SONAR_VIEW_TOGGLE: KEY_TAB, ACTION_PAUSE: KEY_ESCAPE, } @export var mouse_sensitivity_radians_per_pixel := 0.0025 var _gameplay_enabled := false var _look_delta := Vector2.ZERO var _rebind_action_id: StringName = &"" var _pending_key_event: InputEventKey var _pending_conflicting_action_id: StringName = &"" func _ready() -> void: process_mode = Node.PROCESS_MODE_ALWAYS _ensure_default_actions() func set_gameplay_enabled(enabled: bool) -> void: _gameplay_enabled = enabled _look_delta = Vector2.ZERO if enabled: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED else: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE func sample_vehicle_command(sequence: int) -> VehicleCommand: if not _gameplay_enabled or get_tree().paused: return VehicleCommand.neutral(sequence) return VehicleCommand.new( sequence, Input.get_axis(ACTION_BACKWARD, ACTION_FORWARD), Input.get_axis(ACTION_LEFT, ACTION_RIGHT), Input.get_axis(ACTION_DESCEND, ACTION_ASCEND), Input.is_action_pressed(ACTION_HIGH_THRUST) ) func take_camera_look_command() -> CameraLookCommand: var command := CameraLookCommand.new( -_look_delta.x * mouse_sensitivity_radians_per_pixel, -_look_delta.y * mouse_sensitivity_radians_per_pixel, _gameplay_enabled and Input.is_action_just_pressed(ACTION_RECENTER_CAMERA) ) _look_delta = Vector2.ZERO return command func ejection_active() -> bool: return ( _gameplay_enabled and not get_tree().paused and Input.is_action_pressed(ACTION_EJECT) ) func binding_snapshots() -> Array[InputBindingSnapshot]: var snapshots: Array[InputBindingSnapshot] = [] for action_id: StringName in BINDABLE_ACTIONS: snapshots.append( InputBindingSnapshot.new( action_id, String(ACTION_LABELS[action_id]), _binding_text(action_id) ) ) return snapshots func begin_rebind(action_id: StringName) -> RebindResult: if action_id not in BINDABLE_ACTIONS: return RebindResult.new(false, action_id, "", &"", "Action inconnue") _rebind_action_id = action_id _pending_key_event = null _pending_conflicting_action_id = &"" return RebindResult.new(false, action_id, "", &"", "Appuyez sur une touche") func resolve_pending_conflict(swap_bindings: bool) -> RebindResult: if _pending_key_event == null or _rebind_action_id.is_empty(): return RebindResult.new(false, &"", "", &"", "Aucun conflit en attente") if not swap_bindings: var cancelled_action := _rebind_action_id _clear_pending_rebind() return RebindResult.new(false, cancelled_action, "", &"", "Remappage annulé") var target_action := _rebind_action_id var conflicting_action := _pending_conflicting_action_id var previous_target_event := _first_key_event(target_action) _set_single_key_binding(target_action, _pending_key_event) if not conflicting_action.is_empty(): if previous_target_event != null: _set_single_key_binding(conflicting_action, previous_target_event) else: InputMap.action_erase_events(conflicting_action) var result := RebindResult.new( true, target_action, _binding_text(target_action), conflicting_action, "Touches permutées" ) _clear_pending_rebind() return result func restore_default_bindings() -> void: for action_id: StringName in BINDABLE_ACTIONS: _set_single_key_binding(action_id, _key_event(int(DEFAULT_KEYS[action_id]))) func _unhandled_input(event: InputEvent) -> void: if not _rebind_action_id.is_empty(): _handle_rebind_input(event) return if event.is_action_pressed(ACTION_PAUSE): pause_requested.emit() get_viewport().set_input_as_handled() return if _gameplay_enabled and not get_tree().paused: if event.is_action_pressed(ACTION_SONAR_SCAN): sonar_scan_requested.emit() get_viewport().set_input_as_handled() return if event.is_action_pressed(ACTION_SONAR_VIEW_TOGGLE): sonar_view_toggle_requested.emit() get_viewport().set_input_as_handled() return if ( _gameplay_enabled and not get_tree().paused and event is InputEventMouseMotion ): _look_delta += (event as InputEventMouseMotion).relative func _handle_rebind_input(event: InputEvent) -> void: if not (event is InputEventKey): return var key_event := event as InputEventKey if not key_event.pressed or key_event.echo: return get_viewport().set_input_as_handled() if key_event.physical_keycode == KEY_ESCAPE: var cancelled_action := _rebind_action_id _clear_pending_rebind() rebind_result_available.emit( RebindResult.new(false, cancelled_action, "", &"", "Remappage annulé") ) return var normalized_event := _key_event(key_event.physical_keycode) var conflicting_action := _find_conflicting_action( _rebind_action_id, normalized_event.physical_keycode ) if not conflicting_action.is_empty(): _pending_key_event = normalized_event _pending_conflicting_action_id = conflicting_action rebind_result_available.emit( RebindResult.new( false, _rebind_action_id, normalized_event.as_text_physical_keycode(), conflicting_action, "Cette touche est déjà utilisée" ) ) return _set_single_key_binding(_rebind_action_id, normalized_event) var result := RebindResult.new( true, _rebind_action_id, _binding_text(_rebind_action_id), &"", "Commande mise à jour" ) _clear_pending_rebind() rebind_result_available.emit(result) func _ensure_default_actions() -> void: for action_id: StringName in DEFAULT_KEYS: if not InputMap.has_action(action_id): InputMap.add_action(action_id) if InputMap.action_get_events(action_id).is_empty(): InputMap.action_add_event(action_id, _key_event(int(DEFAULT_KEYS[action_id]))) func _find_conflicting_action( target_action: StringName, physical_keycode: Key ) -> StringName: for action_id: StringName in BINDABLE_ACTIONS: if action_id == target_action: continue var event := _first_key_event(action_id) if event != null and event.physical_keycode == physical_keycode: return action_id return &"" func _binding_text(action_id: StringName) -> String: var event := _first_key_event(action_id) return event.as_text_physical_keycode() if event != null else "Non assignée" func _first_key_event(action_id: StringName) -> InputEventKey: for event: InputEvent in InputMap.action_get_events(action_id): if event is InputEventKey: return (event as InputEventKey).duplicate() return null func _set_single_key_binding(action_id: StringName, event: InputEventKey) -> void: InputMap.action_erase_events(action_id) InputMap.action_add_event(action_id, event) func _key_event(physical_keycode: Key) -> InputEventKey: var event := InputEventKey.new() event.physical_keycode = physical_keycode return event func _clear_pending_rebind() -> void: _rebind_action_id = &"" _pending_key_event = null _pending_conflicting_action_id = &""