Files
Ransom-s-wake/game/infrastructure/input/production_input_adapter.gd
T

299 lines
8.6 KiB
GDScript

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 = &""