From 5931345c71a7e3ddd0d71a9ac17cb64b76af3480 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 5 Oct 2023 02:21:01 -0400 Subject: [PATCH 1/2] Rewrite precise inputs to work on gamepad --- hmm.json | 6 +- source/funkin/Controls.hx | 10 + source/funkin/PlayerSettings.hx | 7 +- source/funkin/input/PreciseInputManager.hx | 211 +++++++++++++++++++-- source/funkin/play/PlayState.hx | 98 ---------- source/funkin/ui/ControlsMenu.hx | 51 ++++- source/funkin/ui/StickerSubState.hx | 2 +- source/funkin/util/FlxGamepadUtil.hx | 44 +++++ 8 files changed, 309 insertions(+), 120 deletions(-) create mode 100644 source/funkin/util/FlxGamepadUtil.hx diff --git a/hmm.json b/hmm.json index 47460facf..34e9efb02 100644 --- a/hmm.json +++ b/hmm.json @@ -104,7 +104,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "f195121ebec688b417e38ab115185c8d93c349d3", + "ref": "737b86f121cdc90358d59e2e527934f267c94a2c", "url": "https://github.com/EliteMasterEric/lime" }, { @@ -139,7 +139,7 @@ "name": "openfl", "type": "git", "dir": null, - "ref": "d33d489a137ff8fdece4994cf1302f0b6334ed08", + "ref": "f229d76361c7e31025a048fe7909847f75bb5d5e", "url": "https://github.com/EliteMasterEric/openfl" }, { @@ -160,4 +160,4 @@ "version": "0.11.0" } ] -} \ No newline at end of file +} diff --git a/source/funkin/Controls.hx b/source/funkin/Controls.hx index 81055fb34..9372c4dc6 100644 --- a/source/funkin/Controls.hx +++ b/source/funkin/Controls.hx @@ -1,5 +1,7 @@ + package funkin; +import flixel.input.gamepad.FlxGamepad; import flixel.util.FlxDirectionFlags; import flixel.FlxObject; import flixel.input.FlxInput; @@ -832,6 +834,14 @@ class Controls extends FlxActionSet fromSaveData(padData, Gamepad(id)); } + public function getGamepadIds():Array { + return gamepadsAdded; + } + + public function getGamepads():Array { + return [for (id in gamepadsAdded) FlxG.gamepads.getByID(id)]; + } + inline function addGamepadLiteral(id:Int, ?buttonMap:Map>):Void { gamepadsAdded.push(id); diff --git a/source/funkin/PlayerSettings.hx b/source/funkin/PlayerSettings.hx index a4d8a3b5c..e97cfe384 100644 --- a/source/funkin/PlayerSettings.hx +++ b/source/funkin/PlayerSettings.hx @@ -77,6 +77,11 @@ class PlayerSettings this.id = id; this.controls = new Controls('player$id', None); + addKeyboard(); + } + + function addKeyboard():Void + { var useDefault = true; if (Save.get().hasControls(id, Keys)) { @@ -96,7 +101,6 @@ class PlayerSettings controls.setKeyboardScheme(Solo); } - // Apply loaded settings. PreciseInputManager.instance.initializeKeys(controls); } @@ -124,6 +128,7 @@ class PlayerSettings trace("Loading gamepad control scheme"); controls.addDefaultGamepad(gamepad.id); } + PreciseInputManager.instance.initializeButtons(controls, gamepad); } /** diff --git a/source/funkin/input/PreciseInputManager.hx b/source/funkin/input/PreciseInputManager.hx index 6217b2fe7..897f738fb 100644 --- a/source/funkin/input/PreciseInputManager.hx +++ b/source/funkin/input/PreciseInputManager.hx @@ -1,18 +1,25 @@ package funkin.input; -import openfl.ui.Keyboard; -import funkin.play.notes.NoteDirection; -import flixel.input.keyboard.FlxKeyboard.FlxKeyInput; -import openfl.events.KeyboardEvent; import flixel.FlxG; +import flixel.input.FlxInput; import flixel.input.FlxInput.FlxInputState; import flixel.input.FlxKeyManager; +import flixel.input.gamepad.FlxGamepad; +import flixel.input.gamepad.FlxGamepadInputID; import flixel.input.keyboard.FlxKey; +import flixel.input.keyboard.FlxKeyboard.FlxKeyInput; import flixel.input.keyboard.FlxKeyList; import flixel.util.FlxSignal.FlxTypedSignal; +import funkin.play.notes.NoteDirection; +import funkin.util.FlxGamepadUtil; import haxe.Int64; +import lime.ui.Gamepad as LimeGamepad; +import lime.ui.GamepadAxis as LimeGamepadAxis; +import lime.ui.GamepadButton as LimeGamepadButton; import lime.ui.KeyCode; import lime.ui.KeyModifier; +import openfl.events.KeyboardEvent; +import openfl.ui.Keyboard; /** * A precise input manager that: @@ -43,6 +50,20 @@ class PreciseInputManager extends FlxKeyManager */ var _keyListDir:Map; + /** + * A FlxGamepadID->Array, with FlxGamepadInputID being the counterpart to FlxKey. + */ + var _buttonList:Map>; + + var _buttonListArray:Array>; + + var _buttonListMap:Map>>; + + /** + * A FlxGamepadID->FlxGamepadInputID->NoteDirection, with FlxGamepadInputID being the counterpart to FlxKey. + */ + var _buttonListDir:Map>; + /** * The timestamp at which a given note direction was last pressed. */ @@ -53,17 +74,32 @@ class PreciseInputManager extends FlxKeyManager */ var _dirReleaseTimestamps:Map; + var _deviceBinds:MapInt64->Void, + onButtonUp:LimeGamepadButton->Int64->Void + }>; + public function new() { super(PreciseInputList.new); + _deviceBinds = []; + _keyList = []; - _dirPressTimestamps = new Map(); - _dirReleaseTimestamps = new Map(); + // _keyListMap + // _keyListArray _keyListDir = new Map(); - FlxG.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); - FlxG.stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp); + _buttonList = []; + _buttonListMap = []; + _buttonListArray = []; + _buttonListDir = new Map>(); + + _dirPressTimestamps = new Map(); + _dirReleaseTimestamps = new Map(); + + // Keyboard FlxG.stage.application.window.onKeyDownPrecise.add(handleKeyDown); FlxG.stage.application.window.onKeyUpPrecise.add(handleKeyUp); @@ -84,6 +120,17 @@ class PreciseInputManager extends FlxKeyManager }; } + public static function getButtonsForDirection(controls:Controls, noteDirection:NoteDirection) + { + return switch (noteDirection) + { + case NoteDirection.LEFT: controls.getButtonsForAction(NOTE_LEFT); + case NoteDirection.DOWN: controls.getButtonsForAction(NOTE_DOWN); + case NoteDirection.UP: controls.getButtonsForAction(NOTE_UP); + case NoteDirection.RIGHT: controls.getButtonsForAction(NOTE_RIGHT); + }; + } + /** * Convert from int to Int64. */ @@ -138,6 +185,43 @@ class PreciseInputManager extends FlxKeyManager } } + public function initializeButtons(controls:Controls, gamepad:FlxGamepad):Void + { + clearButtons(); + + var limeGamepad = FlxGamepadUtil.getLimeGamepad(gamepad); + var callbacks = + { + onButtonDown: handleButtonDown.bind(gamepad), + onButtonUp: handleButtonUp.bind(gamepad) + }; + limeGamepad.onButtonDownPrecise.add(callbacks.onButtonDown); + limeGamepad.onButtonUpPrecise.add(callbacks.onButtonUp); + + for (noteDirection in DIRECTIONS) + { + var buttons = getButtonsForDirection(controls, noteDirection); + for (button in buttons) + { + var input = new FlxInput(button); + + var buttonListEntry = _buttonList.get(gamepad.id); + if (buttonListEntry == null) _buttonList.set(gamepad.id, buttonListEntry = []); + buttonListEntry.push(button); + + _buttonListArray.push(input); + + var buttonListMapEntry = _buttonListMap.get(gamepad.id); + if (buttonListMapEntry == null) _buttonListMap.set(gamepad.id, buttonListMapEntry = new Map>()); + buttonListMapEntry.set(button, input); + + var buttonListDirEntry = _buttonListDir.get(gamepad.id); + if (buttonListDirEntry == null) _buttonListDir.set(gamepad.id, buttonListDirEntry = new Map()); + buttonListDirEntry.set(button, noteDirection); + } + } + } + /** * Get the time, in nanoseconds, since the given note direction was last pressed. * @param noteDirection The note direction to check. @@ -165,11 +249,41 @@ class PreciseInputManager extends FlxKeyManager return _keyListMap.get(key); } + public function getInputByButton(gamepad:FlxGamepad, button:FlxGamepadInputID):FlxInput + { + return _buttonListMap.get(gamepad.id).get(button); + } + public function getDirectionForKey(key:FlxKey):NoteDirection { return _keyListDir.get(key); } + public function getDirectionForButton(gamepad:FlxGamepad, button:FlxGamepadInputID):NoteDirection + { + return _buttonListDir.get(gamepad.id).get(button); + } + + function getButton(gamepad:FlxGamepad, button:FlxGamepadInputID):FlxInput + { + return _buttonListMap.get(gamepad.id).get(button); + } + + function updateButtonStates(gamepad:FlxGamepad, button:FlxGamepadInputID, down:Bool):Void + { + var input = getButton(gamepad, button); + if (input == null) return; + + if (down) + { + input.press(); + } + else + { + input.release(); + } + } + function handleKeyDown(keyCode:KeyCode, _:KeyModifier, timestamp:Int64):Void { var key:FlxKey = convertKeyCode(keyCode); @@ -181,7 +295,7 @@ class PreciseInputManager extends FlxKeyManager updateKeyStates(key, true); - if (getInputByKey(key) ?.justPressed ?? false) + if (getInputByKey(key)?.justPressed ?? false) { onInputPressed.dispatch( { @@ -198,12 +312,12 @@ class PreciseInputManager extends FlxKeyManager if (_keyList.indexOf(key) == -1) return; // TODO: Remove this line with SDL3 when timestamps change meaning. - // This is because SDL3's timestamps are measured in nanoseconds, not milliseconds. + // This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds. timestamp *= Constants.NS_PER_MS; updateKeyStates(key, false); - if (getInputByKey(key) ?.justReleased ?? false) + if (getInputByKey(key)?.justReleased ?? false) { onInputReleased.dispatch( { @@ -214,6 +328,54 @@ class PreciseInputManager extends FlxKeyManager } } + function handleButtonDown(gamepad:FlxGamepad, button:LimeGamepadButton, timestamp:Int64):Void + { + var buttonId:FlxGamepadInputID = FlxGamepadUtil.getInputID(gamepad, button); + + var buttonListEntry = _buttonList.get(gamepad.id); + if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return; + + // TODO: Remove this line with SDL3 when timestamps change meaning. + // This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds. + timestamp *= Constants.NS_PER_MS; + + updateButtonStates(gamepad, buttonId, true); + + if (getInputByButton(gamepad, buttonId)?.justPressed ?? false) + { + onInputPressed.dispatch( + { + noteDirection: getDirectionForButton(gamepad, buttonId), + timestamp: timestamp + }); + _dirPressTimestamps.set(getDirectionForButton(gamepad, buttonId), timestamp); + } + } + + function handleButtonUp(gamepad:FlxGamepad, button:LimeGamepadButton, timestamp:Int64):Void + { + var buttonId:FlxGamepadInputID = FlxGamepadUtil.getInputID(gamepad, button); + + var buttonListEntry = _buttonList.get(gamepad.id); + if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return; + + // TODO: Remove this line with SDL3 when timestamps change meaning. + // This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds. + timestamp *= Constants.NS_PER_MS; + + updateButtonStates(gamepad, buttonId, false); + + if (getInputByButton(gamepad, buttonId)?.justReleased ?? false) + { + onInputReleased.dispatch( + { + noteDirection: getDirectionForButton(gamepad, buttonId), + timestamp: timestamp + }); + _dirReleaseTimestamps.set(getDirectionForButton(gamepad, buttonId), timestamp); + } + } + static function convertKeyCode(input:KeyCode):FlxKey { @:privateAccess @@ -228,6 +390,31 @@ class PreciseInputManager extends FlxKeyManager _keyListMap.clear(); _keyListDir.clear(); } + + function clearButtons():Void + { + _buttonListArray = []; + _buttonListDir.clear(); + + for (gamepad in _deviceBinds.keys()) + { + var callbacks = _deviceBinds.get(gamepad); + var limeGamepad = FlxGamepadUtil.getLimeGamepad(gamepad); + limeGamepad.onButtonDownPrecise.remove(callbacks.onButtonDown); + limeGamepad.onButtonUpPrecise.remove(callbacks.onButtonUp); + } + _deviceBinds.clear(); + } + + public override function destroy():Void + { + // Keyboard + FlxG.stage.application.window.onKeyDownPrecise.remove(handleKeyDown); + FlxG.stage.application.window.onKeyUpPrecise.remove(handleKeyUp); + + clearKeys(); + clearButtons(); + } } class PreciseInputList extends FlxKeyList @@ -264,7 +451,7 @@ class PreciseInputList extends FlxKeyList { for (key in getKeysForDir(noteDir)) { - if (check(_preciseInputManager.getInputByKey(key) ?.ID)) return true; + if (check(_preciseInputManager.getInputByKey(key)?.ID)) return true; } return false; } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 26aef9b3d..ce3000645 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -909,7 +909,6 @@ class PlayState extends MusicBeatSubState } // Handle keybinds. - // if (!isInCutscene && !disableKeys) keyShit(true); processInputQueue(); if (!isInCutscene && !disableKeys) debugKeyShit(); if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed); @@ -2014,103 +2013,6 @@ class PlayState extends MusicBeatSubState } } - /** - * Handle player inputs. - */ - function keyShit(test:Bool):Void - { - // control arrays, order L D R U - var holdArray:Array = [controls.NOTE_LEFT, controls.NOTE_DOWN, controls.NOTE_UP, controls.NOTE_RIGHT]; - var pressArray:Array = [ - controls.NOTE_LEFT_P, - controls.NOTE_DOWN_P, - controls.NOTE_UP_P, - controls.NOTE_RIGHT_P - ]; - var releaseArray:Array = [ - controls.NOTE_LEFT_R, - controls.NOTE_DOWN_R, - controls.NOTE_UP_R, - controls.NOTE_RIGHT_R - ]; - - // if (pressArray.contains(true)) - // { - // var lol:Array = cast pressArray; - // inputSpitter.push(Std.int(Conductor.songPosition) + ' ' + lol.join(' ')); - // } - - // HOLDS, check for sustain notes - if (holdArray.contains(true) && generatedMusic) - { - /* - activeNotes.forEachAlive(function(daNote:Note) { - if (daNote.isSustainNote && daNote.canBeHit && daNote.mustPress && holdArray[daNote.data.noteData]) goodNoteHit(daNote); - }); - */ - } - - // PRESSES, check for note hits - if (pressArray.contains(true) && generatedMusic) - { - Haptic.vibrate(100, 100); - - if (currentStage != null && currentStage.getBoyfriend() != null) - { - currentStage.getBoyfriend().holdTimer = 0; - } - - var possibleNotes:Array = []; // notes that can be hit - var directionList:Array = []; // directions that can be hit - var dumbNotes:Array = []; // notes to kill later - - for (note in dumbNotes) - { - FlxG.log.add('killing dumb ass note at ' + note.noteData.time); - note.kill(); - // activeNotes.remove(note, true); - note.destroy(); - } - - possibleNotes.sort((a, b) -> Std.int(a.noteData.time - b.noteData.time)); - - if (perfectMode) - { - goodNoteHit(possibleNotes[0], null); - } - else if (possibleNotes.length > 0) - { - for (shit in 0...pressArray.length) - { // if a direction is hit that shouldn't be - if (pressArray[shit] && !directionList.contains(shit)) ghostNoteMiss(shit); - } - for (coolNote in possibleNotes) - { - if (pressArray[coolNote.noteData.getDirection()]) goodNoteHit(coolNote, null); - } - } - else - { - // HNGGG I really want to add an option for ghost tapping - // L + ratio - for (shit in 0...pressArray.length) - if (pressArray[shit]) ghostNoteMiss(shit, false); - } - } - - if (currentStage == null) return; - - for (keyId => isPressed in pressArray) - { - if (playerStrumline == null) continue; - - var dir:NoteDirection = Strumline.DIRECTIONS[keyId]; - - if (isPressed && !playerStrumline.isConfirm(dir)) playerStrumline.playPress(dir); - if (!holdArray[keyId]) playerStrumline.playStatic(dir); - } - } - function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void { var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, Highscore.tallies.combo + 1, true); diff --git a/source/funkin/ui/ControlsMenu.hx b/source/funkin/ui/ControlsMenu.hx index 0d9db5b34..8197424ee 100644 --- a/source/funkin/ui/ControlsMenu.hx +++ b/source/funkin/ui/ControlsMenu.hx @@ -163,7 +163,17 @@ class ControlsMenu extends funkin.ui.OptionsState.Page function onSelect():Void { - keyUsedToEnterPrompt = FlxG.keys.firstJustPressed(); + switch (currentDevice) + { + case Keys: + { + keyUsedToEnterPrompt = FlxG.keys.firstJustPressed(); + } + case Gamepad(id): + { + buttonUsedToEnterPrompt = FlxG.gamepads.getByID(id).firstJustPressedID(); + } + } controlGrid.enabled = false; canExit = false; @@ -204,6 +214,7 @@ class ControlsMenu extends funkin.ui.OptionsState.Page } var keyUsedToEnterPrompt:Null = null; + var buttonUsedToEnterPrompt:Null = null; override function update(elapsed:Float):Void { @@ -246,19 +257,49 @@ class ControlsMenu extends funkin.ui.OptionsState.Page case Gamepad(id): { var button = FlxG.gamepads.getByID(id).firstJustReleasedID(); - if (button != NONE && button != keyUsedToEnterPrompt) + if (button != NONE && button != buttonUsedToEnterPrompt) { if (button != BACK) onInputSelect(button); closePrompt(); } + + var key = FlxG.keys.firstJustReleased(); + if (key != NONE && key != keyUsedToEnterPrompt) + { + if (key == ESCAPE) + { + closePrompt(); + } + else if (key == BACKSPACE) + { + onInputSelect(NONE); + closePrompt(); + } + } } } } - var keyJustReleased:Int = FlxG.keys.firstJustReleased(); - if (keyJustReleased != NONE && keyJustReleased == keyUsedToEnterPrompt) + switch (currentDevice) { - keyUsedToEnterPrompt = null; + case Keys: + { + var keyJustReleased:Int = FlxG.keys.firstJustReleased(); + if (keyJustReleased != NONE && keyJustReleased == keyUsedToEnterPrompt) + { + keyUsedToEnterPrompt = null; + } + buttonUsedToEnterPrompt = null; + } + case Gamepad(id): + { + var buttonJustReleased:Int = FlxG.gamepads.getByID(id).firstJustReleasedID(); + if (buttonJustReleased != NONE && buttonJustReleased == buttonUsedToEnterPrompt) + { + buttonUsedToEnterPrompt = null; + } + keyUsedToEnterPrompt = null; + } } } diff --git a/source/funkin/ui/StickerSubState.hx b/source/funkin/ui/StickerSubState.hx index 067f50c31..1d9edab0e 100644 --- a/source/funkin/ui/StickerSubState.hx +++ b/source/funkin/ui/StickerSubState.hx @@ -67,7 +67,7 @@ class StickerSubState extends MusicBeatSubState new FlxTimer().start(sticker.timing, _ -> { sticker.visible = false; - if (ind == grpStickers.members.length - 1) + if (grpStickers == null || ind == grpStickers.members.length - 1) { switchingState = false; close(); diff --git a/source/funkin/util/FlxGamepadUtil.hx b/source/funkin/util/FlxGamepadUtil.hx new file mode 100644 index 000000000..d768b42c4 --- /dev/null +++ b/source/funkin/util/FlxGamepadUtil.hx @@ -0,0 +1,44 @@ +package funkin.util; + +import flixel.input.gamepad.FlxGamepad; +import flixel.input.gamepad.FlxGamepadInputID; +import lime.ui.Gamepad as LimeGamepad; +import lime.ui.GamepadAxis as LimeGamepadAxis; +import lime.ui.GamepadButton as LimeGamepadButton; + +class FlxGamepadUtil +{ + public static function getInputID(gamepad:FlxGamepad, button:LimeGamepadButton):FlxGamepadInputID + { + #if FLX_GAMEINPUT_API + // FLX_GAMEINPUT_API internally assigns 6 axes to IDs 0-5, which LimeGamepadButton doesn't account for, so we need to offset the ID by 6. + final OFFSET:Int = 6; + #else + final OFFSET:Int = 0; + #end + + var result:FlxGamepadInputID = gamepad.mapping.getID(button + OFFSET); + if (result == NONE) return NONE; + return result; + } + + public static function getLimeGamepad(input:FlxGamepad):Null + { + #if FLX_GAMEINPUT_API @:privateAccess + return input._device.getLimeGamepad(); + #else + return null; + #end + } + + @:privateAccess + public static function getFlxGamepadByLimeGamepad(gamepad:LimeGamepad):FlxGamepad + { + // Why is this so elaborate? + @:privateAccess + var gameInputDevice:openfl.ui.GameInputDevice = openfl.ui.GameInput.__getDevice(gamepad); + @:privateAccess + var gamepadIndex:Int = FlxG.gamepads.findGamepadIndex(gameInputDevice); + return FlxG.gamepads.getByID(gamepadIndex); + } +} From 13e2fa9a0bfc48f20470df04ac9aa5aa72ea4ce6 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 16 Oct 2023 21:34:27 -0400 Subject: [PATCH 2/2] assets fix --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index a62e7e50d..8104d43e5 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a62e7e50d59c14d256c75da651b79dea77e1620e +Subproject commit 8104d43e584a1f25e574438d7b21a7e671358969