// Input/Components/AC_InputDevice.ts import type { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; import { ActorComponent } from '#root/UE/ActorComponent.ts'; import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts'; import type { Float } from '#root/UE/Float.ts'; import { InputDeviceSubsystem } from '#root/UE/InputDeviceSubsystem.ts'; import type { Integer } from '#root/UE/Integer.ts'; import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; /** * Input Device Detection Component * Minimal wrapper around Unreal Engine's native InputDeviceSubsystem * Provides simple binary classification: Gamepad vs Everything Else */ export class AC_InputDevice extends ActorComponent { // ════════════════════════════════════════════════════════════════════════════════════════ // FUNCTIONS // ════════════════════════════════════════════════════════════════════════════════════════ /** * Get current active input device type * @returns Current device type (cached from delegate events) * @category Device Queries * @pure true */ public GetCurrentInputDevice(): EHardwareDevicePrimaryType { return this.CurrentDevice; } /** * Check if current device is keyboard/mouse * @returns True if keyboard is active * @category Device Queries * @pure true */ public IsKeyboard(): boolean { return this.CurrentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse; } /** * Check if current device is gamepad/controller * @returns True if gamepad is active * @category Device Queries * @pure true */ public IsGamepad(): boolean { return this.CurrentDevice === EHardwareDevicePrimaryType.Gamepad; } /** * Initialize device detection system with delegate registration * @param ToastComponentRef - Toast system for debug notifications * @param DebugHUDComponentRef - Optional debug HUD for displaying device info * @category System Setup */ public InitializeDeviceDetection( ToastComponentRef: AC_ToastSystem, DebugHUDComponentRef: AC_DebugHUD ): void { this.ToastComponent = ToastComponentRef; this.DebugHUDComponent = DebugHUDComponentRef; this.RegisterHardwareDeviceDelegate(); this.DetectInitialDevice(); this.IsInitialized = true; if (SystemLibrary.IsValid(this.DebugHUDComponent)) { this.DebugHUDComponent.AddDebugPage( this.DebugPageID, 'Input Device Info', 60 ); } if (SystemLibrary.IsValid(this.ToastComponent)) { this.ToastComponent.ShowToast( `Device Detection Initialized: ${this.CurrentDevice}`, E_MessageType.Success ); } } /** * Register OnInputHardwareDeviceChanged delegate * Core of the event-driven approach * @category System Setup */ private RegisterHardwareDeviceDelegate(): void { InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent( this.OnInputHardwareDeviceChanged.bind(this) ); } /** * Detect initial device on system startup * Fallback for getting device before first hardware change event * @category System Setup */ private DetectInitialDevice(): void { this.CurrentDevice = InputDeviceSubsystem.GetMostRecentlyUsedHardwareDevice().PrimaryDeviceType; } /** * Handle hardware device change events * Called automatically when input hardware changes * @param UserId - User ID (usually 0 for single-player) * @param DeviceId - Device name string * @category Event Handling */ private OnInputHardwareDeviceChanged( UserId?: Integer, DeviceId?: Integer ): void { this.ProcessDeviceChange( InputDeviceSubsystem.GetInputDeviceHardwareIdentifier(DeviceId ?? 0) .PrimaryDeviceType ); } /** * Process device change with debouncing * Prevents rapid switching and manages device state * @param NewDevice - Newly detected device type * @category Device Management */ private ProcessDeviceChange(NewDevice: EHardwareDevicePrimaryType): void { if (NewDevice !== this.CurrentDevice && this.CanProcessDeviceChange()) { this.CurrentDevice = NewDevice; this.LastDeviceChangeTime = SystemLibrary.GetGameTimeInSeconds(); if (SystemLibrary.IsValid(this.ToastComponent)) { this.ToastComponent.ShowToast( `Input switched to ${NewDevice}`, E_MessageType.Info ); } } } /** * Check if device change can be processed (debouncing) * Prevents rapid device switching within cooldown period * @returns True if enough time has passed since last change * @category Debouncing * @pure true */ private CanProcessDeviceChange(): boolean { const HasCooldownExpired = (): boolean => SystemLibrary.GetGameTimeInSeconds() - this.LastDeviceChangeTime >= this.DeviceChangeCooldown; return HasCooldownExpired(); } /** * Update debug HUD with current device info * @category Debug */ public UpdateDebugPage(): void { if (SystemLibrary.IsValid(this.DebugHUDComponent)) { if ( this.DebugHUDComponent.ShouldUpdatePage( this.DebugPageID, SystemLibrary.GetGameTimeInSeconds() ) ) { this.DebugHUDComponent.UpdatePageContent( this.DebugPageID, `Hardware Device Identifier: ${this.GetCurrentInputDevice()}` + `Initialized: ${this.IsInitialized}` + `Last Change: ${this.LastDeviceChangeTime}` ); } } } // ════════════════════════════════════════════════════════════════════════════════════════ // VARIABLES // ════════════════════════════════════════════════════════════════════════════════════════ /** * Reference to toast system for debug notifications * @category Components */ private ToastComponent: AC_ToastSystem | null = null; /** * Reference to debug HUD component for displaying camera info * Optional, used for debugging purposes * @category Components */ public DebugHUDComponent: AC_DebugHUD | null = null; /** * Current active input device type * Updated by hardware device change events * @category Device State */ private CurrentDevice: EHardwareDevicePrimaryType = EHardwareDevicePrimaryType.Unspecified; /** * System initialization status * @category System State */ public IsInitialized: boolean = false; /** * Last device change timestamp for debouncing * @category Debouncing */ public LastDeviceChangeTime: Float = 0; /** * Minimum time between device changes (prevents flickering) * Recommended: 300-500ms for most games * @category Debouncing * @instanceEditable true */ private DeviceChangeCooldown: Float = 0.3; /** * Debug page identifier for organizing debug output * Used by debug HUD to categorize information * @category Debug * @instanceEditable true */ public readonly DebugPageID: string = 'InputDeviceInfo'; }