package funkin.system; import flixel.input.FlxInput; import flixel.input.actions.FlxAction; import flixel.input.actions.FlxActionInput; import flixel.input.actions.FlxActionInputDigital; import flixel.input.actions.FlxActionManager; import flixel.input.actions.FlxActionSet; import flixel.input.gamepad.FlxGamepadButton; import flixel.input.gamepad.FlxGamepadInputID; import flixel.input.keyboard.FlxKey; enum abstract Action(String) to String from String { var UP = "up"; var LEFT = "left"; var RIGHT = "right"; var DOWN = "down"; var UP_P = "up-press"; var LEFT_P = "left-press"; var RIGHT_P = "right-press"; var DOWN_P = "down-press"; var UP_R = "up-release"; var LEFT_R = "left-release"; var RIGHT_R = "right-release"; var DOWN_R = "down-release"; var NOTE_UP = "note-up"; var NOTE_LEFT = "note-left"; var NOTE_RIGHT = "note-right"; var NOTE_DOWN = "note-down"; var NOTE_UP_P = "note-up-press"; var NOTE_LEFT_P = "note-left-press"; var NOTE_RIGHT_P = "note-right-press"; var NOTE_DOWN_P = "note-down-press"; var NOTE_UP_R = "note-up-release"; var NOTE_LEFT_R = "note-left-release"; var NOTE_RIGHT_R = "note-right-release"; var NOTE_DOWN_R = "note-down-release"; var ACCEPT = "accept"; var BACK = "back"; var PAUSE = "pause"; var RESET = "reset"; var CHEAT = "cheat"; var SWITCHMOD = "switchmod"; } enum Device { Keys; Gamepad(id:Int); } /** * Since, in many cases multiple actions should use similar keys, we don't want the * rebinding UI to list every action. ActionBinders are what the user perceives as * an input so, for instance, they can't set jump-press and jump-release to different keys. */ enum Control { UP; LEFT; RIGHT; DOWN; NOTE_UP; NOTE_LEFT; NOTE_RIGHT; NOTE_DOWN; RESET; ACCEPT; BACK; PAUSE; CHEAT; SWITCHMOD; } enum KeyboardScheme { Solo; Duo(first:Bool); None; Custom; } /** * A list of actions that a player would invoke via some input device. * Uses FlxActions to funnel various inputs to a single action. */ @:noCustomClass class Controls extends FlxActionSet { var _up = new FlxActionDigital(Action.UP); var _left = new FlxActionDigital(Action.LEFT); var _right = new FlxActionDigital(Action.RIGHT); var _down = new FlxActionDigital(Action.DOWN); var _upP = new FlxActionDigital(Action.UP_P); var _leftP = new FlxActionDigital(Action.LEFT_P); var _rightP = new FlxActionDigital(Action.RIGHT_P); var _downP = new FlxActionDigital(Action.DOWN_P); var _upR = new FlxActionDigital(Action.UP_R); var _leftR = new FlxActionDigital(Action.LEFT_R); var _rightR = new FlxActionDigital(Action.RIGHT_R); var _downR = new FlxActionDigital(Action.DOWN_R); var _noteUp = new FlxActionDigital(Action.NOTE_UP); var _noteLeft = new FlxActionDigital(Action.NOTE_LEFT); var _noteRight = new FlxActionDigital(Action.NOTE_RIGHT); var _noteDown = new FlxActionDigital(Action.NOTE_DOWN); var _noteUpP = new FlxActionDigital(Action.NOTE_UP_P); var _noteLeftP = new FlxActionDigital(Action.NOTE_LEFT_P); var _noteRightP = new FlxActionDigital(Action.NOTE_RIGHT_P); var _noteDownP = new FlxActionDigital(Action.NOTE_DOWN_P); var _noteUpR = new FlxActionDigital(Action.NOTE_UP_R); var _noteLeftR = new FlxActionDigital(Action.NOTE_LEFT_R); var _noteRightR = new FlxActionDigital(Action.NOTE_RIGHT_R); var _noteDownR = new FlxActionDigital(Action.NOTE_DOWN_R); var _accept = new FlxActionDigital(Action.ACCEPT); var _back = new FlxActionDigital(Action.BACK); var _pause = new FlxActionDigital(Action.PAUSE); var _reset = new FlxActionDigital(Action.RESET); var _cheat = new FlxActionDigital(Action.CHEAT); var _switchMod = new FlxActionDigital(Action.SWITCHMOD); #if (haxe >= "4.0.0") var byName:Map = []; #else var byName:Map = new Map(); #end public var gamepadsAdded:Array = []; public var keyboardScheme = KeyboardScheme.None; public var UP(get, never):Bool; inline function get_UP() return _up.check(); public var LEFT(get, never):Bool; inline function get_LEFT() return _left.check(); public var RIGHT(get, never):Bool; inline function get_RIGHT() return _right.check(); public var DOWN(get, never):Bool; inline function get_DOWN() return _down.check(); public var UP_P(get, never):Bool; inline function get_UP_P() return _upP.check(); public var LEFT_P(get, never):Bool; inline function get_LEFT_P() return _leftP.check(); public var RIGHT_P(get, never):Bool; inline function get_RIGHT_P() return _rightP.check(); public var DOWN_P(get, never):Bool; inline function get_DOWN_P() return _downP.check(); public var UP_R(get, never):Bool; inline function get_UP_R() return _upR.check(); public var LEFT_R(get, never):Bool; inline function get_LEFT_R() return _leftR.check(); public var RIGHT_R(get, never):Bool; inline function get_RIGHT_R() return _rightR.check(); public var DOWN_R(get, never):Bool; inline function get_DOWN_R() return _downR.check(); public var NOTE_UP(get, never):Bool; inline function get_NOTE_UP() return _noteUp.check(); public var NOTE_LEFT(get, never):Bool; inline function get_NOTE_LEFT() return _noteLeft.check(); public var NOTE_RIGHT(get, never):Bool; inline function get_NOTE_RIGHT() return _noteRight.check(); public var NOTE_DOWN(get, never):Bool; inline function get_NOTE_DOWN() return _noteDown.check(); public var NOTE_UP_P(get, never):Bool; inline function get_NOTE_UP_P() return _noteUpP.check(); public var NOTE_LEFT_P(get, never):Bool; inline function get_NOTE_LEFT_P() return _noteLeftP.check(); public var NOTE_RIGHT_P(get, never):Bool; inline function get_NOTE_RIGHT_P() return _noteRightP.check(); public var NOTE_DOWN_P(get, never):Bool; inline function get_NOTE_DOWN_P() return _noteDownP.check(); public var NOTE_UP_R(get, never):Bool; inline function get_NOTE_UP_R() return _noteUpR.check(); public var NOTE_LEFT_R(get, never):Bool; inline function get_NOTE_LEFT_R() return _noteLeftR.check(); public var NOTE_RIGHT_R(get, never):Bool; inline function get_NOTE_RIGHT_R() return _noteRightR.check(); public var NOTE_DOWN_R(get, never):Bool; inline function get_NOTE_DOWN_R() return _noteDownR.check(); public var ACCEPT(get, never):Bool; inline function get_ACCEPT() return _accept.check(); public var BACK(get, never):Bool; inline function get_BACK() return _back.check(); public var PAUSE(get, never):Bool; inline function get_PAUSE() return _pause.check(); public var RESET(get, never):Bool; inline function get_RESET() return _reset.check(); public var CHEAT(get, never):Bool; inline function get_CHEAT() return _cheat.check(); public var SWITCHMOD(get, never):Bool; inline function get_SWITCHMOD() return _switchMod.check(); public function new(name, scheme = None) { super(name); add(_up); add(_left); add(_right); add(_down); add(_upP); add(_leftP); add(_rightP); add(_downP); add(_upR); add(_leftR); add(_rightR); add(_downR); add(_noteUp); add(_noteLeft); add(_noteRight); add(_noteDown); add(_noteUpP); add(_noteLeftP); add(_noteRightP); add(_noteDownP); add(_noteUpR); add(_noteLeftR); add(_noteRightR); add(_noteDownR); add(_accept); add(_back); add(_pause); add(_reset); add(_cheat); add(_switchMod); for (action in digitalActions) byName[action.name] = action; setKeyboardScheme(scheme, false); } override function update() { super.update(); } // inline public function checkByName(name:Action):Bool { #if debug if (!byName.exists(name)) throw 'Invalid name: $name'; #end return byName[name].check(); } public function getDialogueName(action:FlxActionDigital):String { var input = action.inputs[0]; return switch input.device { case KEYBOARD: return '[${(input.inputID : FlxKey)}]'; case GAMEPAD: return '(${(input.inputID : FlxGamepadInputID)})'; case device: throw 'unhandled device: $device'; } } public function getDialogueNameFromToken(token:String):String { return getDialogueName(getActionFromControl(Control.createByName(token.toUpperCase()))); } function getActionFromControl(control:Control):FlxActionDigital { return switch (control) { case UP: _up; case DOWN: _down; case LEFT: _left; case RIGHT: _right; case NOTE_UP: _noteUp; case NOTE_DOWN: _noteDown; case NOTE_LEFT: _noteLeft; case NOTE_RIGHT: _noteRight; case ACCEPT: _accept; case BACK: _back; case PAUSE: _pause; case RESET: _reset; case CHEAT: _cheat; case SWITCHMOD: _switchMod; } } static function init():Void { var actions = new FlxActionManager(); FlxG.inputs.add(actions); } /** * Calls a function passing each action bound by the specified control * @param control * @param func * @return ->Void) */ function forEachBound(control:Control, func:FlxActionDigital->FlxInputState->Void) { switch (control) { case NOTE_UP: func(_noteUp, PRESSED); func(_noteUpP, JUST_PRESSED); func(_noteUpR, JUST_RELEASED); case NOTE_LEFT: func(_noteLeft, PRESSED); func(_noteLeftP, JUST_PRESSED); func(_noteLeftR, JUST_RELEASED); case NOTE_RIGHT: func(_noteRight, PRESSED); func(_noteRightP, JUST_PRESSED); func(_noteRightR, JUST_RELEASED); case NOTE_DOWN: func(_noteDown, PRESSED); func(_noteDownP, JUST_PRESSED); func(_noteDownR, JUST_RELEASED); case UP: func(_up, PRESSED); func(_upP, JUST_PRESSED); func(_upR, JUST_RELEASED); case LEFT: func(_left, PRESSED); func(_leftP, JUST_PRESSED); func(_leftR, JUST_RELEASED); case RIGHT: func(_right, PRESSED); func(_rightP, JUST_PRESSED); func(_rightR, JUST_RELEASED); case DOWN: func(_down, PRESSED); func(_downP, JUST_PRESSED); func(_downR, JUST_RELEASED); case ACCEPT: func(_accept, JUST_PRESSED); case BACK: func(_back, JUST_PRESSED); case PAUSE: func(_pause, JUST_PRESSED); case RESET: func(_reset, JUST_PRESSED); case CHEAT: func(_cheat, JUST_PRESSED); case SWITCHMOD: func(_switchMod, JUST_PRESSED); } } public function replaceBinding(control:Control, device:Device, ?toAdd:Int, ?toRemove:Int) { if (toAdd == toRemove) return; switch (device) { case Keys: if (toRemove != null) unbindKeys(control, [toRemove]); if (toAdd != null) bindKeys(control, [toAdd]); case Gamepad(id): if (toRemove != null) unbindButtons(control, id, [toRemove]); if (toAdd != null) bindButtons(control, id, [toAdd]); } } public function copyFrom(controls:Controls, ?device:Device) { #if (haxe >= "4.0.0") for (name => action in controls.byName) { for (input in action.inputs) { if (device == null || isDevice(input, device)) byName[name].add(cast input); } } #else for (name in controls.byName.keys()) { var action = controls.byName[name]; for (input in action.inputs) { if (device == null || isDevice(input, device)) byName[name].add(cast input); } } #end switch (device) { case null: // add all #if (haxe >= "4.0.0") for (gamepad in controls.gamepadsAdded) if (!gamepadsAdded.contains(gamepad)) gamepadsAdded.push(gamepad); #else for (gamepad in controls.gamepadsAdded) if (gamepadsAdded.indexOf(gamepad) == -1) gamepadsAdded.push(gamepad); #end mergeKeyboardScheme(controls.keyboardScheme); case Gamepad(id): gamepadsAdded.push(id); case Keys: mergeKeyboardScheme(controls.keyboardScheme); } } inline public function copyTo(controls:Controls, ?device:Device) { controls.copyFrom(this, device); } function mergeKeyboardScheme(scheme:KeyboardScheme):Void { if (scheme != None) { switch (keyboardScheme) { case None: keyboardScheme = scheme; default: keyboardScheme = Custom; } } } /** * Sets all actions that pertain to the binder to trigger when the supplied keys are used. * If binder is a literal you can inline this */ public function bindKeys(control:Control, keys:Array) { #if (haxe >= "4.0.0") inline forEachBound(control, (action, state) -> addKeys(action, keys, state)); #else forEachBound(control, function(action, state) addKeys(action, keys, state)); #end } /** * Sets all actions that pertain to the binder to trigger when the supplied keys are used. * If binder is a literal you can inline this */ public function unbindKeys(control:Control, keys:Array) { #if (haxe >= "4.0.0") inline forEachBound(control, (action, _) -> removeKeys(action, keys)); #else forEachBound(control, function(action, _) removeKeys(action, keys)); #end } inline static function addKeys(action:FlxActionDigital, keys:Array, state:FlxInputState) { for (key in keys) action.addKey(key, state); } static function removeKeys(action:FlxActionDigital, keys:Array) { var i = action.inputs.length; while (i-- > 0) { var input = action.inputs[i]; if (input.device == KEYBOARD && keys.indexOf(cast input.inputID) != -1) action.remove(input); } } public function setKeyboardScheme(scheme:KeyboardScheme, reset = true) { if (reset) removeKeyboard(); keyboardScheme = scheme; switch (scheme) { case Solo: inline bindKeys(Control.UP, Options.SOLO_UP); inline bindKeys(Control.DOWN, Options.SOLO_DOWN); inline bindKeys(Control.LEFT, Options.SOLO_LEFT); inline bindKeys(Control.RIGHT, Options.SOLO_RIGHT); inline bindKeys(Control.NOTE_UP, Options.SOLO_NOTE_UP); inline bindKeys(Control.NOTE_DOWN, Options.SOLO_NOTE_DOWN); inline bindKeys(Control.NOTE_LEFT, Options.SOLO_NOTE_LEFT); inline bindKeys(Control.NOTE_RIGHT, Options.SOLO_NOTE_RIGHT); inline bindKeys(Control.ACCEPT, Options.SOLO_ACCEPT); inline bindKeys(Control.BACK, Options.SOLO_BACK); inline bindKeys(Control.PAUSE, Options.SOLO_PAUSE); inline bindKeys(Control.RESET, Options.SOLO_RESET); inline bindKeys(Control.SWITCHMOD, Options.SOLO_SWITCHMOD); case Duo(true): inline bindKeys(Control.UP, Options.P1_UP); inline bindKeys(Control.DOWN, Options.P1_DOWN); inline bindKeys(Control.LEFT, Options.P1_LEFT); inline bindKeys(Control.RIGHT, Options.P1_RIGHT); inline bindKeys(Control.NOTE_UP, Options.P1_NOTE_UP); inline bindKeys(Control.NOTE_DOWN, Options.P1_NOTE_DOWN); inline bindKeys(Control.NOTE_LEFT, Options.P1_NOTE_LEFT); inline bindKeys(Control.NOTE_RIGHT, Options.P1_NOTE_RIGHT); inline bindKeys(Control.ACCEPT, Options.P1_ACCEPT); inline bindKeys(Control.BACK, Options.P1_BACK); inline bindKeys(Control.PAUSE, Options.P1_PAUSE); inline bindKeys(Control.RESET, Options.P1_RESET); inline bindKeys(Control.SWITCHMOD, Options.P1_SWITCHMOD); case Duo(false): inline bindKeys(Control.UP, Options.P2_UP); inline bindKeys(Control.DOWN, Options.P2_DOWN); inline bindKeys(Control.LEFT, Options.P2_LEFT); inline bindKeys(Control.RIGHT, Options.P2_RIGHT); inline bindKeys(Control.NOTE_UP, Options.P2_NOTE_UP); inline bindKeys(Control.NOTE_DOWN, Options.P2_NOTE_DOWN); inline bindKeys(Control.NOTE_LEFT, Options.P2_NOTE_LEFT); inline bindKeys(Control.NOTE_RIGHT, Options.P2_NOTE_RIGHT); inline bindKeys(Control.ACCEPT, Options.P2_ACCEPT); inline bindKeys(Control.BACK, Options.P2_BACK); inline bindKeys(Control.PAUSE, Options.P2_PAUSE); inline bindKeys(Control.RESET, Options.P2_RESET); inline bindKeys(Control.SWITCHMOD, Options.P2_SWITCHMOD); case None: // nothing case Custom: // nothing } } function removeKeyboard() { for (action in this.digitalActions) { var i = action.inputs.length; while (i-- > 0) { var input = action.inputs[i]; if (input.device == KEYBOARD) action.remove(input); } } } public function addGamepad(id:Int, ?buttonMap:Map>):Void { gamepadsAdded.push(id); #if (haxe >= "4.0.0") for (control => buttons in buttonMap) inline bindButtons(control, id, buttons); #else for (control in buttonMap.keys()) bindButtons(control, id, buttonMap[control]); #end } inline function addGamepadLiteral(id:Int, ?buttonMap:Map>):Void { gamepadsAdded.push(id); #if (haxe >= "4.0.0") for (control => buttons in buttonMap) inline bindButtons(control, id, buttons); #else for (control in buttonMap.keys()) bindButtons(control, id, buttonMap[control]); #end } public function removeGamepad(deviceID:Int = FlxInputDeviceID.ALL):Void { for (action in this.digitalActions) { var i = action.inputs.length; while (i-- > 0) { var input = action.inputs[i]; if (input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID)) action.remove(input); } } gamepadsAdded.remove(deviceID); } public function addDefaultGamepad(id):Void { #if !switch addGamepadLiteral(id, [ Control.ACCEPT => [A], Control.BACK => [B], Control.UP => [DPAD_UP, LEFT_STICK_DIGITAL_UP], Control.DOWN => [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN], Control.LEFT => [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT], Control.RIGHT => [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT], Control.PAUSE => [START], Control.RESET => [Y] ]); #else addGamepadLiteral(id, [ //Swap A and B for switch Control.ACCEPT => [B], Control.BACK => [A], Control.UP => [DPAD_UP, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP], Control.DOWN => [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN], Control.LEFT => [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT], Control.RIGHT => [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT], Control.PAUSE => [START], //Swap Y and X for switch Control.RESET => [Y], Control.CHEAT => [X] ]); #end } /** * Sets all actions that pertain to the binder to trigger when the supplied keys are used. * If binder is a literal you can inline this */ public function bindButtons(control:Control, id, buttons) { #if (haxe >= "4.0.0") inline forEachBound(control, (action, state) -> addButtons(action, buttons, state, id)); #else forEachBound(control, function(action, state) addButtons(action, buttons, state, id)); #end } /** * Sets all actions that pertain to the binder to trigger when the supplied keys are used. * If binder is a literal you can inline this */ public function unbindButtons(control:Control, gamepadID:Int, buttons) { #if (haxe >= "4.0.0") inline forEachBound(control, (action, _) -> removeButtons(action, gamepadID, buttons)); #else forEachBound(control, function(action, _) removeButtons(action, gamepadID, buttons)); #end } inline static function addButtons(action:FlxActionDigital, buttons:Array, state, id) { for (button in buttons) action.addGamepad(button, state, id); } static function removeButtons(action:FlxActionDigital, gamepadID:Int, buttons:Array) { var i = action.inputs.length; while (i-- > 0) { var input = action.inputs[i]; if (isGamepad(input, gamepadID) && buttons.indexOf(cast input.inputID) != -1) action.remove(input); } } public function getInputsFor(control:Control, device:Device, ?list:Array):Array { if (list == null) list = []; switch (device) { case Keys: for (input in getActionFromControl(control).inputs) { if (input.device == KEYBOARD) list.push(input.inputID); } case Gamepad(id): for (input in getActionFromControl(control).inputs) { if (input.deviceID == id) list.push(input.inputID); } } return list; } public function removeDevice(device:Device) { switch (device) { case Keys: setKeyboardScheme(None); case Gamepad(id): removeGamepad(id); } } static function isDevice(input:FlxActionInput, device:Device) { return switch device { case Keys: input.device == KEYBOARD; case Gamepad(id): isGamepad(input, id); } } inline static function isGamepad(input:FlxActionInput, deviceID:Int) { return input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID); } }