/** * INPUT REMAPPING SYSTEM * Allows players to customize keyboard and controller bindings * Supports multiple control profiles and one-handed layouts */ class InputRemappingSystem { constructor(scene) { this.scene = scene; this.enabled = true; // Default key bindings this.defaultBindings = { // Movement 'move_up': ['W', 'UP'], 'move_down': ['S', 'DOWN'], 'move_left': ['A', 'LEFT'], 'move_right': ['D', 'RIGHT'], // Actions 'interact': ['E', 'SPACE'], 'attack': ['MOUSE_LEFT', 'J'], 'use_tool': ['MOUSE_LEFT', 'J'], 'cancel': ['ESC', 'X'], 'confirm': ['ENTER', 'E'], // Inventory & UI 'inventory': ['I', 'TAB'], 'crafting': ['C'], 'map': ['M'], 'quest_log': ['Q'], 'pause': ['ESC', 'P'], // Tools 'tool_1': ['1'], 'tool_2': ['2'], 'tool_3': ['3'], 'tool_4': ['4'], 'tool_5': ['5'], // Quick actions 'quick_heal': ['H'], 'quick_eat': ['F'], 'sprint': ['SHIFT'], 'crouch': ['CTRL'], // Camera 'zoom_in': ['PLUS', 'MOUSE_WHEEL_UP'], 'zoom_out': ['MINUS', 'MOUSE_WHEEL_DOWN'], 'camera_reset': ['R'] }; // Controller bindings (Xbox/PlayStation layout) this.controllerBindings = { 'move': 'LEFT_STICK', 'camera': 'RIGHT_STICK', 'interact': 'A', // Xbox A / PS Cross 'attack': 'X', // Xbox X / PS Square 'cancel': 'B', // Xbox B / PS Circle 'inventory': 'Y', // Xbox Y / PS Triangle 'sprint': 'LB', 'use_tool': 'RT', 'quick_menu': 'LT', 'pause': 'START', 'map': 'SELECT' }; // Control profiles this.profiles = { 'default': JSON.parse(JSON.stringify(this.defaultBindings)), 'wasd': JSON.parse(JSON.stringify(this.defaultBindings)), 'arrows': this.createArrowsProfile(), 'left-handed': this.createLeftHandedProfile(), 'right-handed': this.createRightHandedProfile(), 'custom-1': JSON.parse(JSON.stringify(this.defaultBindings)), 'custom-2': JSON.parse(JSON.stringify(this.defaultBindings)), 'custom-3': JSON.parse(JSON.stringify(this.defaultBindings)) }; // Current active profile this.activeProfile = 'default'; this.currentBindings = this.profiles[this.activeProfile]; // Rebinding state this.isRebinding = false; this.rebindingAction = null; this.rebindingCallback = null; // Input buffer for detecting key presses this.inputBuffer = []; this.maxBufferSize = 10; this.loadSettings(); this.init(); console.log('✅ Input Remapping System initialized'); } init() { // Set up input listeners this.setupInputListeners(); } setupInputListeners() { // Keyboard input this.scene.input.keyboard.on('keydown', (event) => { if (this.isRebinding) { this.handleRebindInput(event.key.toUpperCase()); } }); // Mouse input this.scene.input.on('pointerdown', (pointer) => { if (this.isRebinding) { const button = pointer.leftButtonDown() ? 'MOUSE_LEFT' : pointer.rightButtonDown() ? 'MOUSE_RIGHT' : pointer.middleButtonDown() ? 'MOUSE_MIDDLE' : null; if (button) { this.handleRebindInput(button); } } }); // Mouse wheel this.scene.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => { if (this.isRebinding) { const input = deltaY < 0 ? 'MOUSE_WHEEL_UP' : 'MOUSE_WHEEL_DOWN'; this.handleRebindInput(input); } }); } /** * Create arrows-based profile (arrow keys for movement) */ createArrowsProfile() { const profile = JSON.parse(JSON.stringify(this.defaultBindings)); profile.move_up = ['UP', 'W']; profile.move_down = ['DOWN', 'S']; profile.move_left = ['LEFT', 'A']; profile.move_right = ['RIGHT', 'D']; return profile; } /** * Create left-handed profile (numpad for movement) */ createLeftHandedProfile() { return { // Movement on numpad 'move_up': ['NUMPAD_8', 'I'], 'move_down': ['NUMPAD_5', 'K'], 'move_left': ['NUMPAD_4', 'J'], 'move_right': ['NUMPAD_6', 'L'], // Actions on left side 'interact': ['Q', 'SPACE'], 'attack': ['MOUSE_LEFT', 'W'], 'use_tool': ['MOUSE_LEFT', 'W'], 'cancel': ['ESC', 'E'], 'confirm': ['ENTER', 'Q'], // Inventory & UI 'inventory': ['TAB', 'U'], 'crafting': ['R'], 'map': ['T'], 'quest_log': ['Y'], 'pause': ['ESC', 'P'], // Tools (right side for easy access) 'tool_1': ['7'], 'tool_2': ['8'], 'tool_3': ['9'], 'tool_4': ['0'], 'tool_5': ['MINUS'], // Quick actions 'quick_heal': ['A'], 'quick_eat': ['S'], 'sprint': ['SHIFT'], 'crouch': ['CTRL'], // Camera 'zoom_in': ['PLUS', 'MOUSE_WHEEL_UP'], 'zoom_out': ['EQUALS', 'MOUSE_WHEEL_DOWN'], 'camera_reset': ['BACKSPACE'] }; } /** * Create right-handed profile (standard WASD) */ createRightHandedProfile() { return JSON.parse(JSON.stringify(this.defaultBindings)); } /** * Check if an action is pressed */ isActionPressed(action) { const bindings = this.currentBindings[action]; if (!bindings) return false; for (const key of bindings) { if (this.isKeyPressed(key)) { return true; } } return false; } /** * Check if a specific key is pressed */ isKeyPressed(key) { if (key.startsWith('MOUSE_')) { const pointer = this.scene.input.activePointer; if (key === 'MOUSE_LEFT') return pointer.leftButtonDown(); if (key === 'MOUSE_RIGHT') return pointer.rightButtonDown(); if (key === 'MOUSE_MIDDLE') return pointer.middleButtonDown(); return false; } const keyObj = this.scene.input.keyboard.addKey(key, false); return keyObj && keyObj.isDown; } /** * Check if an action was just pressed (single frame) */ isActionJustPressed(action) { const bindings = this.currentBindings[action]; if (!bindings) return false; for (const key of bindings) { if (this.isKeyJustPressed(key)) { return true; } } return false; } /** * Check if a key was just pressed */ isKeyJustPressed(key) { if (key.startsWith('MOUSE_')) { const pointer = this.scene.input.activePointer; if (key === 'MOUSE_LEFT') return pointer.leftButtonDown() && pointer.getDuration() < 100; if (key === 'MOUSE_RIGHT') return pointer.rightButtonDown() && pointer.getDuration() < 100; if (key === 'MOUSE_MIDDLE') return pointer.middleButtonDown() && pointer.getDuration() < 100; return false; } const keyObj = this.scene.input.keyboard.addKey(key, false); return keyObj && Phaser.Input.Keyboard.JustDown(keyObj); } /** * Start rebinding an action */ startRebinding(action, callback) { if (!this.currentBindings[action]) { console.error(`Action "${action}" does not exist`); return; } this.isRebinding = true; this.rebindingAction = action; this.rebindingCallback = callback; console.log(`🎮 Rebinding action: ${action}. Press any key...`); } /** * Handle rebind input */ handleRebindInput(input) { if (!this.isRebinding) return; // Ignore ESC (cancel rebinding) if (input === 'ESC' || input === 'ESCAPE') { this.cancelRebinding(); return; } // Set new binding (replace first binding) this.currentBindings[this.rebindingAction][0] = input; console.log(`✅ Action "${this.rebindingAction}" rebound to: ${input}`); // Call callback if provided if (this.rebindingCallback) { this.rebindingCallback(this.rebindingAction, input); } this.isRebinding = false; this.rebindingAction = null; this.rebindingCallback = null; this.saveSettings(); } /** * Cancel rebinding */ cancelRebinding() { console.log('❌ Rebinding cancelled'); this.isRebinding = false; this.rebindingAction = null; this.rebindingCallback = null; } /** * Reset action to default binding */ resetAction(action) { if (!this.defaultBindings[action]) { console.error(`Action "${action}" does not exist`); return; } this.currentBindings[action] = JSON.parse(JSON.stringify(this.defaultBindings[action])); this.saveSettings(); console.log(`🔄 Action "${action}" reset to default`); } /** * Reset all bindings to default */ resetAllBindings() { this.currentBindings = JSON.parse(JSON.stringify(this.defaultBindings)); this.profiles[this.activeProfile] = this.currentBindings; this.saveSettings(); console.log('🔄 All bindings reset to default'); } /** * Switch to a different profile */ switchProfile(profileName) { if (!this.profiles[profileName]) { console.error(`Profile "${profileName}" does not exist`); return; } this.activeProfile = profileName; this.currentBindings = this.profiles[profileName]; this.saveSettings(); console.log(`🎮 Switched to profile: ${profileName}`); } /** * Save current bindings to a custom profile */ saveToProfile(profileName) { if (!profileName.startsWith('custom-')) { console.error('Can only save to custom profiles (custom-1, custom-2, custom-3)'); return; } this.profiles[profileName] = JSON.parse(JSON.stringify(this.currentBindings)); this.saveSettings(); console.log(`💾 Bindings saved to profile: ${profileName}`); } /** * Get binding display name */ getBindingDisplay(action) { const bindings = this.currentBindings[action]; if (!bindings || bindings.length === 0) return 'Not bound'; return bindings.map(key => this.formatKeyName(key)).join(' / '); } /** * Format key name for display */ formatKeyName(key) { const keyMap = { 'MOUSE_LEFT': 'Left Click', 'MOUSE_RIGHT': 'Right Click', 'MOUSE_MIDDLE': 'Middle Click', 'MOUSE_WHEEL_UP': 'Scroll Up', 'MOUSE_WHEEL_DOWN': 'Scroll Down', 'SPACE': 'Space', 'ENTER': 'Enter', 'ESC': 'Escape', 'SHIFT': 'Shift', 'CTRL': 'Ctrl', 'ALT': 'Alt', 'TAB': 'Tab', 'BACKSPACE': 'Backspace', 'UP': '↑', 'DOWN': '↓', 'LEFT': '←', 'RIGHT': '→', 'PLUS': '+', 'MINUS': '-', 'EQUALS': '=' }; return keyMap[key] || key; } /** * Get all available profiles */ getProfiles() { return Object.keys(this.profiles); } /** * Get current profile name */ getCurrentProfile() { return this.activeProfile; } /** * Export bindings as JSON */ exportBindings() { const data = { activeProfile: this.activeProfile, profiles: this.profiles }; return JSON.stringify(data, null, 2); } /** * Import bindings from JSON */ importBindings(jsonString) { try { const data = JSON.parse(jsonString); this.activeProfile = data.activeProfile || 'default'; this.profiles = data.profiles || this.profiles; this.currentBindings = this.profiles[this.activeProfile]; this.saveSettings(); console.log('✅ Bindings imported successfully'); return true; } catch (error) { console.error('❌ Failed to import bindings:', error); return false; } } /** * Save settings to localStorage */ saveSettings() { const data = { activeProfile: this.activeProfile, profiles: this.profiles }; localStorage.setItem('novafarma_input_bindings', JSON.stringify(data)); } /** * Load settings from localStorage */ loadSettings() { const saved = localStorage.getItem('novafarma_input_bindings'); if (saved) { try { const data = JSON.parse(saved); this.activeProfile = data.activeProfile || 'default'; this.profiles = { ...this.profiles, ...data.profiles }; this.currentBindings = this.profiles[this.activeProfile]; console.log('✅ Input bindings loaded from localStorage'); } catch (error) { console.error('❌ Failed to load input bindings:', error); } } } /** * Get controller button name */ getControllerButtonName(button) { const buttonMap = { 'A': 'A (Cross)', 'B': 'B (Circle)', 'X': 'X (Square)', 'Y': 'Y (Triangle)', 'LB': 'LB (L1)', 'RB': 'RB (R1)', 'LT': 'LT (L2)', 'RT': 'RT (R2)', 'START': 'Start', 'SELECT': 'Select (Share)', 'LEFT_STICK': 'Left Stick', 'RIGHT_STICK': 'Right Stick' }; return buttonMap[button] || button; } /** * Check if controller is connected */ isControllerConnected() { return this.scene.input.gamepad && this.scene.input.gamepad.total > 0; } /** * Get connected controller info */ getControllerInfo() { if (!this.isControllerConnected()) return null; const pad = this.scene.input.gamepad.getPad(0); return { id: pad.id, index: pad.index, buttons: pad.buttons.length, axes: pad.axes.length }; } destroy() { this.saveSettings(); console.log('🎮 Input Remapping System destroyed'); } }