299 lines
8.6 KiB
GDScript
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 = &""
|