diff --git a/src/battle-scene.ts b/src/battle-scene.ts
index 46f7b4ae0a2..504fa1af191 100644
--- a/src/battle-scene.ts
+++ b/src/battle-scene.ts
@@ -1,4 +1,4 @@
-import Phaser, { Time } from 'phaser';
+import Phaser from 'phaser';
 import UI, { Mode } from './ui/ui';
 import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from './phases';
 import Pokemon, { PlayerPokemon, EnemyPokemon } from './field/pokemon';
@@ -54,13 +54,14 @@ import CharSprite from './ui/char-sprite';
 import DamageNumberHandler from './field/damage-number-handler';
 import PokemonInfoContainer from './ui/pokemon-info-container';
 import { biomeDepths } from './data/biomes';
-import { initTouchControls } from './touch-controls';
 import { UiTheme } from './enums/ui-theme';
 import { SceneBase } from './scene-base';
 import CandyBar from './ui/candy-bar';
 import { Variant, variantData } from './data/variant';
 import { Localizable } from './plugins/i18n';
 import { STARTING_WAVE_OVERRIDE, OPP_SPECIES_OVERRIDE, SEED_OVERRIDE, STARTING_BIOME_OVERRIDE } from './overrides';
+import {InputsController} from "./inputs-controller";
+import {UiInputs} from "./ui-inputs";
 
 export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
 
@@ -69,33 +70,12 @@ const DEBUG_RNG = false;
 export const startingWave = STARTING_WAVE_OVERRIDE || 1;
 
 const expSpriteKeys: string[] = [];
-const repeatInputDelayMillis = 250;
 
 export let starterColors: StarterColors;
 interface StarterColors {
 	[key: string]: [string, string]
 }
 
-export enum Button {
-	UP,
-	DOWN,
-	LEFT,
-	RIGHT,
-	SUBMIT,
-	ACTION,
-	CANCEL,
-	MENU,
-	STATS,
-	CYCLE_SHINY,
-	CYCLE_FORM,
-	CYCLE_GENDER,
-	CYCLE_ABILITY,
-	CYCLE_NATURE,
-	CYCLE_VARIANT,
-	SPEED_UP,
-	SLOW_DOWN
-}
-
 export interface PokeballCounts {
 	[pb: string]: integer;
 }
@@ -104,6 +84,8 @@ export type AnySound = Phaser.Sound.WebAudioSound | Phaser.Sound.HTML5AudioSound
 
 export default class BattleScene extends SceneBase {
 	public rexUI: UIPlugin;
+	public inputController: InputsController;
+	public uiInputs: UiInputs;
 
 	public sessionPlayTime: integer = null;
 	public masterVolume: number = 0.5;
@@ -124,7 +106,8 @@ export default class BattleScene extends SceneBase {
 	public gamepadSupport: boolean = true;
 	public enableTouchControls: boolean = false;
 	public enableVibration: boolean = false;
-	
+	public abSwapped: boolean = false;
+
 	public disableMenu: boolean = false;
 
 	public gameData: GameData;
@@ -189,34 +172,6 @@ export default class BattleScene extends SceneBase {
 	private bgmResumeTimer: Phaser.Time.TimerEvent;
 	private bgmCache: Set<string> = new Set();
 	private playTimeTimer: Phaser.Time.TimerEvent;
-	
-	private buttonKeys: Phaser.Input.Keyboard.Key[][];
-	private lastProcessedButtonPressTimes: Map<Button, number> = new Map();
-	// movementButtonLock ensures only a single movement key is firing repeated inputs
-	// (i.e. by holding down a button) at a time
-	private movementButtonLock: Button;
-
-  // using a dualshock controller as a map
-	private gamepadKeyConfig = {
-		[Button.UP]: 12, // up
-		[Button.DOWN]: 13, // down
-		[Button.LEFT]: 14, // left
-		[Button.RIGHT]: 15, // right
-		[Button.SUBMIT]: 17, // touchpad
-		[Button.ACTION]: 0, // X
-		[Button.CANCEL]: 1, // O
-		[Button.MENU]: 9, // options
-		[Button.STATS]: 8, // share
-		[Button.CYCLE_SHINY]: 5, // RB
-		[Button.CYCLE_FORM]: 4, // LB
-		[Button.CYCLE_GENDER]: 6, // LT
-		[Button.CYCLE_ABILITY]: 7, // RT
-		[Button.CYCLE_NATURE]: 2, // square
-		[Button.CYCLE_VARIANT]: 3, // triangle
-		[Button.SPEED_UP]: 10, // L3
-		[Button.SLOW_DOWN]: 11 // R3
-	};
-	public gamepadButtonStates: boolean[] = new Array(17).fill(false);
 
 	public rngCounter: integer = 0;
 	public rngSeedOverride: string = '';
@@ -235,15 +190,6 @@ export default class BattleScene extends SceneBase {
 		this.nextCommandPhaseQueue = [];
 	}
 
-	/**
-	 * Conditionally swaps the ACTION and CANCEL button
-	 * @param standard When true, uses the default values
-	 */
-	setGamepadConfirm(standard: boolean) {
-		this.gamepadKeyConfig[Button.ACTION] = standard ? 0 : 1;
-		this.gamepadKeyConfig[Button.CANCEL] = standard ? 1 : 0;
-	}
-
 	loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) {
 		if (experimental === undefined)
 			experimental = this.experimentalSprites;
@@ -269,7 +215,7 @@ export default class BattleScene extends SceneBase {
 				return ret;
 			};
 		}
-		
+
 		populateAnims();
 
 		await this.initVariantData();
@@ -277,13 +223,13 @@ export default class BattleScene extends SceneBase {
 
 	create() {
 		initGameSpeed.apply(this);
+		this.inputController = new InputsController(this);
+		this.uiInputs = new UiInputs(this, this.inputController);
 
 		this.gameData = new GameData(this);
 
 		addUiThemeOverrides(this);
 
-		this.setupControls();
-
 		this.load.setBaseURL();
 
 		this.spritePipeline = new SpritePipeline(this.game);
@@ -296,7 +242,7 @@ export default class BattleScene extends SceneBase {
 	}
 
 	update() {
-		this.checkInput();
+		this.inputController.update();
 		this.ui?.update();
 	}
 
@@ -615,42 +561,6 @@ export default class BattleScene extends SceneBase {
 		return true;
 	}
 
-	setupControls() {
-		const keyCodes = Phaser.Input.Keyboard.KeyCodes;
-		const keyConfig = {
-			[Button.UP]: [keyCodes.UP, keyCodes.W],
-			[Button.DOWN]: [keyCodes.DOWN, keyCodes.S],
-			[Button.LEFT]: [keyCodes.LEFT, keyCodes.A],
-			[Button.RIGHT]: [keyCodes.RIGHT, keyCodes.D],
-			[Button.SUBMIT]: [keyCodes.ENTER],
-			[Button.ACTION]: [keyCodes.SPACE, keyCodes.ENTER, keyCodes.Z],
-			[Button.CANCEL]: [keyCodes.BACKSPACE, keyCodes.X],
-			[Button.MENU]: [keyCodes.ESC, keyCodes.M],
-			[Button.STATS]: [keyCodes.SHIFT, keyCodes.C],
-			[Button.CYCLE_SHINY]: [keyCodes.R],
-			[Button.CYCLE_FORM]: [keyCodes.F],
-			[Button.CYCLE_GENDER]: [keyCodes.G],
-			[Button.CYCLE_ABILITY]: [keyCodes.E],
-			[Button.CYCLE_NATURE]: [keyCodes.N],
-			[Button.CYCLE_VARIANT]: [keyCodes.V],
-			[Button.SPEED_UP]: [keyCodes.PLUS],
-			[Button.SLOW_DOWN]: [keyCodes.MINUS]
-		};
-		const mobileKeyConfig = {};
-		this.buttonKeys = [];
-		for (let b of Utils.getEnumValues(Button)) {
-			const keys: Phaser.Input.Keyboard.Key[] = [];
-			if (keyConfig.hasOwnProperty(b)) {
-				for (let k of keyConfig[b])
-					keys.push(this.input.keyboard.addKey(k, false));
-				mobileKeyConfig[Button[b]] = keys[0];
-			}
-			this.buttonKeys[b] = keys;
-		}
-
-		initTouchControls(mobileKeyConfig);
-	}
-
 	getParty(): PlayerPokemon[] {
 		return this.party;
 	}
@@ -1352,177 +1262,6 @@ export default class BattleScene extends SceneBase {
 		return biomes[Utils.randSeedInt(biomes.length)];
 	}
 
-	checkInput(): boolean {
-		let inputSuccess = false;
-		let vibrationLength = 0;
-		if (this.buttonJustPressed(Button.UP) || this.repeatInputDurationJustPassed(Button.UP)) {
-			inputSuccess = this.ui.processInput(Button.UP);
-			vibrationLength = 5;
-			this.setLastProcessedMovementTime(Button.UP)
-		} else if (this.buttonJustPressed(Button.DOWN) || this.repeatInputDurationJustPassed(Button.DOWN)) {
-			inputSuccess = this.ui.processInput(Button.DOWN);
-			vibrationLength = 5;
-			this.setLastProcessedMovementTime(Button.DOWN)
-		} else if (this.buttonJustPressed(Button.LEFT) || this.repeatInputDurationJustPassed(Button.LEFT)) {
-			inputSuccess = this.ui.processInput(Button.LEFT);
-			vibrationLength = 5;
-			this.setLastProcessedMovementTime(Button.LEFT)
-		} else if (this.buttonJustPressed(Button.RIGHT) || this.repeatInputDurationJustPassed(Button.RIGHT)) {
-			inputSuccess = this.ui.processInput(Button.RIGHT);
-			vibrationLength = 5;
-			this.setLastProcessedMovementTime(Button.RIGHT)
-		} else if (this.buttonJustPressed(Button.SUBMIT) || this.repeatInputDurationJustPassed(Button.SUBMIT)) {
-			inputSuccess = this.ui.processInput(Button.SUBMIT) || this.ui.processInput(Button.ACTION);
-			this.setLastProcessedMovementTime(Button.SUBMIT);
-		} else if (this.buttonJustPressed(Button.ACTION) || this.repeatInputDurationJustPassed(Button.ACTION)) {
-			inputSuccess = this.ui.processInput(Button.ACTION);
-			this.setLastProcessedMovementTime(Button.ACTION);
-		} else if (this.buttonJustPressed(Button.CANCEL)|| this.repeatInputDurationJustPassed(Button.CANCEL)) {
-			inputSuccess = this.ui.processInput(Button.CANCEL);
-			this.setLastProcessedMovementTime(Button.CANCEL);
-		} else if (this.buttonJustPressed(Button.MENU)) {
-			if (this.disableMenu)
-				return;
-			switch (this.ui?.getMode()) {
-				case Mode.MESSAGE:
-					if (!(this.ui.getHandler() as MessageUiHandler).pendingPrompt)
-						return;
-				case Mode.TITLE:
-				case Mode.COMMAND:
-				case Mode.FIGHT:
-				case Mode.BALL:
-				case Mode.TARGET_SELECT:
-				case Mode.SAVE_SLOT:
-				case Mode.PARTY:
-				case Mode.SUMMARY:
-				case Mode.STARTER_SELECT:
-				case Mode.CONFIRM:
-				case Mode.OPTION_SELECT:
-					this.ui.setOverlayMode(Mode.MENU);
-					inputSuccess = true;
-					break;
-				case Mode.MENU:
-				case Mode.SETTINGS:
-				case Mode.ACHIEVEMENTS:
-					this.ui.revertMode();
-					this.playSound('select');
-					inputSuccess = true;
-					break;
-				default:
-					return;
-			}
-		} else if (this.ui?.getHandler() instanceof StarterSelectUiHandler) {
-			if (this.buttonJustPressed(Button.CYCLE_SHINY)) {
-				inputSuccess = this.ui.processInput(Button.CYCLE_SHINY);
-        this.setLastProcessedMovementTime(Button.CYCLE_SHINY);
-			} else if (this.buttonJustPressed(Button.CYCLE_FORM)) {
-				inputSuccess = this.ui.processInput(Button.CYCLE_FORM);
-        this.setLastProcessedMovementTime(Button.CYCLE_FORM);
-			} else if (this.buttonJustPressed(Button.CYCLE_GENDER)) {
-				inputSuccess = this.ui.processInput(Button.CYCLE_GENDER);
-        this.setLastProcessedMovementTime(Button.CYCLE_GENDER);
-			} else if (this.buttonJustPressed(Button.CYCLE_ABILITY)) {
-				inputSuccess = this.ui.processInput(Button.CYCLE_ABILITY);
-        this.setLastProcessedMovementTime(Button.CYCLE_ABILITY);
-			} else if (this.buttonJustPressed(Button.CYCLE_NATURE)) {
-				inputSuccess = this.ui.processInput(Button.CYCLE_NATURE);
-        this.setLastProcessedMovementTime(Button.CYCLE_NATURE);
-			} else if (this.buttonJustPressed(Button.CYCLE_VARIANT)) {
-				inputSuccess = this.ui.processInput(Button.CYCLE_VARIANT);
-				this.setLastProcessedMovementTime(Button.CYCLE_VARIANT);
-			} else
-				return;
-		}	else if (this.buttonJustPressed(Button.SPEED_UP)) {
-			if (this.gameSpeed < 5) {
-				this.gameData.saveSetting(Setting.Game_Speed, settingOptions[Setting.Game_Speed].indexOf(`${this.gameSpeed}x`) + 1);
-				if (this.ui?.getMode() === Mode.SETTINGS)
-					(this.ui.getHandler() as SettingsUiHandler).show([]);
-			}
-		} else if (this.buttonJustPressed(Button.SLOW_DOWN)) {
-			if (this.gameSpeed > 1) {
-				this.gameData.saveSetting(Setting.Game_Speed, Math.max(settingOptions[Setting.Game_Speed].indexOf(`${this.gameSpeed}x`) - 1, 0));
-				if (this.ui?.getMode() === Mode.SETTINGS)
-					(this.ui.getHandler() as SettingsUiHandler).show([]);
-			}
-		} else {
-			let pressed = false;
-			if (this.ui && (this.buttonJustReleased(Button.STATS) || (pressed = this.buttonJustPressed(Button.STATS)))) {
-				for (let p of this.getField().filter(p => p?.isActive(true)))
-					p.toggleStats(pressed);
-				if (pressed)
-					this.setLastProcessedMovementTime(Button.STATS);
-			} else
-				return;
-		}
-		if (inputSuccess && this.enableVibration && typeof navigator.vibrate !== 'undefined')
-			navigator.vibrate(vibrationLength || 10);		
-	}
-
-  /**
-   * gamepadButtonJustDown returns true if @param button has just been pressed down
-   * or not. It will only return true once, until the key is released and pressed down
-   * again. 
-   */
-	gamepadButtonJustDown(button: Phaser.Input.Gamepad.Button): boolean {
-		if (!button || !this.gamepadSupport)
-			return false;
-
-		let ret = false;
-		if (button.pressed) {
-			if (!this.gamepadButtonStates[button.index])
-				ret = true;
-			this.gamepadButtonStates[button.index] = true;
-		} else
-			this.gamepadButtonStates[button.index] = false;
-
-		return ret;
-  }
-
-	buttonJustPressed(button: Button): boolean {
-		const gamepad = this.input.gamepad?.gamepads[0];
-		return this.buttonKeys[button].some(k => Phaser.Input.Keyboard.JustDown(k)) || this.gamepadButtonJustDown(gamepad?.buttons[this.gamepadKeyConfig[button]]);
-	}
-
-	/**
-   * gamepadButtonJustUp returns true if @param button has just been released
-   * or not. It will only return true once, until the key is released and pressed down
-   * again.
-   */
-	gamepadButtonJustUp(button: Phaser.Input.Gamepad.Button): boolean {
-		if (!button || !this.gamepadSupport)
-			return false;
-
-		return !this.gamepadButtonStates[button.index];
-  }
-
-	buttonJustReleased(button: Button): boolean {
-		const gamepad = this.input.gamepad?.gamepads[0];
-		return this.buttonKeys[button].some(k => Phaser.Input.Keyboard.JustUp(k)) || this.gamepadButtonJustUp(gamepad?.buttons[this.gamepadKeyConfig[button]]);
-	}
-
-	/**
-	 * repeatInputDurationJustPassed returns true if @param button has been held down long
-	 * enough to fire a repeated input. A button must claim the movementButtonLock before
-	 * firing a repeated input - this is to prevent multiple buttons from firing repeatedly.
-	 */
-	repeatInputDurationJustPassed(button: Button): boolean {
-		if (this.movementButtonLock !== null && this.movementButtonLock !== button) {
-			return false;
-		}
-		if (this.buttonKeys[button].every(k => k.isUp) && this.gamepadButtonStates.every(b => b == false)) {
-			this.movementButtonLock = null;
-			return false;
-		}
-		if (this.time.now - this.lastProcessedButtonPressTimes.get(button) >= repeatInputDelayMillis) {
-			return true;
-		}
-	}
-
-	setLastProcessedMovementTime(button: Button) {
-		this.lastProcessedButtonPressTimes.set(button, this.time.now);
-		this.movementButtonLock = button;
-	}
-
 	isBgmPlaying(): boolean {
 		return this.bgm && this.bgm.isPlaying;
 	}
diff --git a/src/configs/pad_dualshock.ts b/src/configs/pad_dualshock.ts
new file mode 100644
index 00000000000..4f66ff8c00a
--- /dev/null
+++ b/src/configs/pad_dualshock.ts
@@ -0,0 +1,29 @@
+/**
+ * Dualshock mapping
+ */
+const pad_dualshock = {
+    padID: 'Dualshock',
+    padType: 'Sony',
+    gamepadMapping: {
+        RC_S: 0,
+        RC_E: 1,
+        RC_W: 2,
+        RC_N: 3,
+        START: 9, // Options
+        SELECT: 8, // Share
+        LB: 4,
+        RB: 5,
+        LT: 6,
+        RT: 7,
+        LS: 10,
+        RS: 11,
+        LC_N: 12,
+        LC_S: 13,
+        LC_W: 14,
+        LC_E: 15,
+        MENU: 16,
+        TOUCH: 17
+    },
+};
+
+export default pad_dualshock;
diff --git a/src/configs/pad_generic.ts b/src/configs/pad_generic.ts
new file mode 100644
index 00000000000..19b5d3df16e
--- /dev/null
+++ b/src/configs/pad_generic.ts
@@ -0,0 +1,27 @@
+/**
+ * Generic pad mapping
+ */
+const pad_generic = {
+    padID: 'Generic',
+    padType: 'generic',
+    gamepadMapping: {
+        RC_S: 0,
+        RC_E: 1,
+        RC_W: 2,
+        RC_N: 3,
+        START: 9,
+        SELECT: 8,
+        LB: 4,
+        RB: 5,
+        LT: 6,
+        RT: 7,
+        LS: 10,
+        RS: 11,
+        LC_N: 12,
+        LC_S: 13,
+        LC_W: 14,
+        LC_E: 15
+    },
+};
+
+export default pad_generic;
diff --git a/src/configs/pad_unlicensedSNES.ts b/src/configs/pad_unlicensedSNES.ts
new file mode 100644
index 00000000000..ba8ee538d30
--- /dev/null
+++ b/src/configs/pad_unlicensedSNES.ts
@@ -0,0 +1,23 @@
+/**
+ * 081f-e401 - UnlicensedSNES
+ */
+const pad_unlicensedSNES = {
+    padID: '081f-e401',
+    padType: 'snes',
+    gamepadMapping : {
+        RC_S: 2,
+        RC_E: 1,
+        RC_W: 3,
+        RC_N: 0,
+        START: 9,
+        SELECT: 8,
+        LB: 4,
+        RB: 5,
+        LC_N: 12,
+        LC_S: 13,
+        LC_W: 14,
+        LC_E: 15
+    }
+};
+
+export default pad_unlicensedSNES;
diff --git a/src/configs/pad_xbox360.ts b/src/configs/pad_xbox360.ts
new file mode 100644
index 00000000000..e44ebb54b64
--- /dev/null
+++ b/src/configs/pad_xbox360.ts
@@ -0,0 +1,28 @@
+/**
+ * Generic pad mapping
+ */
+const pad_xbox360 = {
+    padID: 'Xbox 360 controller (XInput STANDARD GAMEPAD)',
+    padType: 'xbox',
+    gamepadMapping: {
+        RC_S: 0,
+        RC_E: 1,
+        RC_W: 2,
+        RC_N: 3,
+        START: 9,
+        SELECT: 8,
+        LB: 4,
+        RB: 5,
+        LT: 6,
+        RT: 7,
+        LS: 10,
+        RS: 11,
+        LC_N: 12,
+        LC_S: 13,
+        LC_W: 14,
+        LC_E: 15,
+        MENU: 16
+    },
+};
+
+export default pad_xbox360;
diff --git a/src/enums/buttons.ts b/src/enums/buttons.ts
new file mode 100644
index 00000000000..034c5a2af83
--- /dev/null
+++ b/src/enums/buttons.ts
@@ -0,0 +1,19 @@
+export enum Button {
+	UP,
+	DOWN,
+	LEFT,
+	RIGHT,
+	SUBMIT,
+	ACTION,
+	CANCEL,
+	MENU,
+	STATS,
+	CYCLE_SHINY,
+	CYCLE_FORM,
+	CYCLE_GENDER,
+	CYCLE_ABILITY,
+	CYCLE_NATURE,
+	CYCLE_VARIANT,
+	SPEED_UP,
+	SLOW_DOWN
+}
diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts
new file mode 100644
index 00000000000..a68fcd8057d
--- /dev/null
+++ b/src/inputs-controller.ts
@@ -0,0 +1,255 @@
+import Phaser, {Time} from "phaser";
+import * as Utils from "./utils";
+import {initTouchControls} from './touch-controls';
+import pad_generic from "./configs/pad_generic";
+import pad_unlicensedSNES from "./configs/pad_unlicensedSNES";
+import pad_xbox360 from "./configs/pad_xbox360";
+import pad_dualshock from "./configs/pad_dualshock";
+import {Button} from "./enums/buttons";
+
+export interface GamepadMapping {
+    [key: string]: number;
+}
+
+export interface GamepadConfig {
+    padID: string;
+    padType: string;
+    gamepadMapping: GamepadMapping;
+}
+
+export interface ActionGamepadMapping {
+    [key: string]: Button;
+}
+
+const repeatInputDelayMillis = 250;
+
+export class InputsController {
+    private buttonKeys: Phaser.Input.Keyboard.Key[][];
+    private gamepads: Array<string> = new Array();
+    private scene: Phaser.Scene;
+
+    // buttonLock ensures only a single movement key is firing repeated inputs
+    // (i.e. by holding down a button) at a time
+    private buttonLock: Button;
+    private interactions: Map<Button, Map<string, boolean>> = new Map();
+    private time: Time;
+    private player: Map<String, GamepadMapping> = new Map();
+
+    constructor(scene: Phaser.Scene) {
+        this.scene = scene;
+        this.time = this.scene.time;
+        this.buttonKeys = [];
+
+        for (const b of Utils.getEnumValues(Button)) {
+            this.interactions[b] = {
+                pressTime: false,
+                isPressed: false,
+            }
+        }
+        // We don't want the menu key to be repeated
+        delete this.interactions[Button.MENU];
+        delete this.interactions[Button.STATS];
+        this.init();
+    }
+
+    init(): void {
+        this.events = new Phaser.Events.EventEmitter();
+
+        if (typeof this.scene.input.gamepad !== 'undefined') {
+            this.scene.input.gamepad.on('connected', function (thisGamepad) {
+                this.refreshGamepads();
+                this.setupGamepad(thisGamepad);
+            }, this);
+
+            // Check to see if the gamepad has already been setup by the browser
+            this.scene.input.gamepad.refreshPads();
+            if (this.scene.input.gamepad.total) {
+                this.refreshGamepads();
+                for (const thisGamepad of this.gamepads) {
+                    this.scene.input.gamepad.emit('connected', thisGamepad);
+                }
+            }
+
+            this.scene.input.gamepad.on('down', this.gamepadButtonDown, this);
+            this.scene.input.gamepad.on('up', this.gamepadButtonUp, this);
+        }
+
+        // Keyboard
+        this.setupKeyboardControls();
+    }
+
+    update(): void {
+        for (const b of Utils.getEnumValues(Button)) {
+            if (!this.interactions.hasOwnProperty(b)) continue;
+            if (this.repeatInputDurationJustPassed(b)) {
+                this.events.emit('input_down', {
+                    controller_type: 'repeated',
+                    button: b,
+                });
+                this.setLastProcessedMovementTime(b);
+            }
+        }
+    }
+
+    setupGamepad(thisGamepad: Phaser.Input.Gamepad.Gamepad): void {
+        let gamepadID = thisGamepad.id.toLowerCase();
+        const mappedPad = this.mapGamepad(gamepadID);
+        this.player['mapping'] = mappedPad.gamepadMapping;
+    }
+
+    refreshGamepads(): void {
+        // Sometimes, gamepads are undefined. For some reason.
+        this.gamepads = this.scene.input.gamepad.gamepads.filter(function (el) {
+            return el != null;
+        });
+
+        for (const [index, thisGamepad] of this.gamepads.entries()) {
+            thisGamepad.index = index; // Overwrite the gamepad index, in case we had undefined gamepads earlier
+        }
+    }
+
+    getActionGamepadMapping(): ActionGamepadMapping {
+        const gamepadMapping = {};
+        gamepadMapping[this.player.mapping.LC_N] = Button.UP;
+        gamepadMapping[this.player.mapping.LC_S] = Button.DOWN;
+        gamepadMapping[this.player.mapping.LC_W] = Button.LEFT;
+        gamepadMapping[this.player.mapping.LC_E] = Button.RIGHT;
+        gamepadMapping[this.player.mapping.TOUCH] = Button.SUBMIT;
+        gamepadMapping[this.player.mapping.RC_S] = this.scene.abSwapped ? Button.CANCEL : Button.ACTION;
+        gamepadMapping[this.player.mapping.RC_E] = this.scene.abSwapped ? Button.ACTION : Button.CANCEL;
+        gamepadMapping[this.player.mapping.SELECT] = Button.STATS;
+        gamepadMapping[this.player.mapping.START] = Button.MENU;
+        gamepadMapping[this.player.mapping.RB] = Button.CYCLE_SHINY;
+        gamepadMapping[this.player.mapping.LB] = Button.CYCLE_FORM;
+        gamepadMapping[this.player.mapping.LT] = Button.CYCLE_GENDER;
+        gamepadMapping[this.player.mapping.RT] = Button.CYCLE_ABILITY;
+        gamepadMapping[this.player.mapping.RC_W] = Button.CYCLE_NATURE;
+        gamepadMapping[this.player.mapping.RC_N] = Button.CYCLE_VARIANT;
+        gamepadMapping[this.player.mapping.LS] = Button.SPEED_UP;
+        gamepadMapping[this.player.mapping.RS] = Button.SLOW_DOWN;
+
+        return gamepadMapping;
+    }
+
+    gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void {
+        const actionMapping = this.getActionGamepadMapping();
+        const buttonDown = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index];
+        if (buttonDown !== undefined) {
+            this.events.emit('input_down', {
+                controller_type: 'gamepad',
+                button: buttonDown,
+            });
+            this.setLastProcessedMovementTime(buttonDown);
+        }
+    }
+
+    gamepadButtonUp(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void {
+        const actionMapping = this.getActionGamepadMapping();
+        const buttonUp = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index];
+        if (buttonUp !== undefined) {
+            this.events.emit('input_up', {
+                controller_type: 'gamepad',
+                button: buttonUp,
+            });
+            this.delLastProcessedMovementTime(buttonUp);
+        }
+    }
+
+    setupKeyboardControls(): void {
+        const keyCodes = Phaser.Input.Keyboard.KeyCodes;
+        const keyConfig = {
+            [Button.UP]: [keyCodes.UP, keyCodes.W],
+            [Button.DOWN]: [keyCodes.DOWN, keyCodes.S],
+            [Button.LEFT]: [keyCodes.LEFT, keyCodes.A],
+            [Button.RIGHT]: [keyCodes.RIGHT, keyCodes.D],
+            [Button.SUBMIT]: [keyCodes.ENTER],
+            [Button.ACTION]: [keyCodes.SPACE, this.scene.abSwapped ? keyCodes.X : keyCodes.Z],
+            [Button.CANCEL]: [keyCodes.BACKSPACE, this.scene.abSwapped ? keyCodes.Z : keyCodes.X],
+            [Button.MENU]: [keyCodes.ESC, keyCodes.M],
+            [Button.STATS]: [keyCodes.SHIFT, keyCodes.C],
+            [Button.CYCLE_SHINY]: [keyCodes.R],
+            [Button.CYCLE_FORM]: [keyCodes.F],
+            [Button.CYCLE_GENDER]: [keyCodes.G],
+            [Button.CYCLE_ABILITY]: [keyCodes.E],
+            [Button.CYCLE_NATURE]: [keyCodes.N],
+            [Button.CYCLE_VARIANT]: [keyCodes.V],
+            [Button.SPEED_UP]: [keyCodes.PLUS],
+            [Button.SLOW_DOWN]: [keyCodes.MINUS]
+        };
+        const mobileKeyConfig = {};
+        for (const b of Utils.getEnumValues(Button)) {
+            const keys: Phaser.Input.Keyboard.Key[] = [];
+            if (keyConfig.hasOwnProperty(b)) {
+                for (let k of keyConfig[b])
+                    keys.push(this.scene.input.keyboard.addKey(k, false));
+                mobileKeyConfig[Button[b]] = keys[0];
+            }
+            this.buttonKeys[b] = keys;
+        }
+
+        initTouchControls(mobileKeyConfig);
+        this.listenInputKeyboard();
+    }
+
+    listenInputKeyboard(): void {
+        this.buttonKeys.forEach((row, index) => {
+            for (const key of row) {
+                key.on('down', () => {
+                    this.events.emit('input_down', {
+                        controller_type: 'keyboard',
+                        button: index,
+                    });
+                    this.setLastProcessedMovementTime(index);
+                });
+                key.on('up', () => {
+                    this.events.emit('input_up', {
+                        controller_type: 'keyboard',
+                        button: index,
+                    });
+                    this.delLastProcessedMovementTime(index);
+                });
+            }
+        });
+    }
+
+    mapGamepad(id: string): GamepadConfig {
+        id = id.toLowerCase();
+
+        if (id.includes('081f') && id.includes('e401')) {
+            return pad_unlicensedSNES;
+        } else if (id.includes('xbox') && id.includes('360')) {
+            return pad_xbox360;
+        } else if (id.includes('054c')) {
+            return pad_dualshock;
+        }
+
+        return pad_generic;
+    }
+
+    /**
+     * repeatInputDurationJustPassed returns true if @param button has been held down long
+     * enough to fire a repeated input. A button must claim the buttonLock before
+     * firing a repeated input - this is to prevent multiple buttons from firing repeatedly.
+     */
+    repeatInputDurationJustPassed(button: Button): boolean {
+        if (this.buttonLock === null || this.buttonLock !== button) {
+            return false;
+        }
+        if (this.time.now - this.interactions[button].pressTime >= repeatInputDelayMillis) {
+            this.buttonLock = null;
+            return true;
+        }
+    }
+
+    setLastProcessedMovementTime(button: Button): void {
+        if (!this.interactions.hasOwnProperty(button)) return;
+        this.buttonLock = button;
+        this.interactions[button].pressTime = this.time.now;
+    }
+
+    delLastProcessedMovementTime(button: Button): void {
+        if (!this.interactions.hasOwnProperty(button)) return;
+        this.buttonLock = null;
+        this.interactions[button].pressTime = null;
+    }
+}
\ No newline at end of file
diff --git a/src/system/settings.ts b/src/system/settings.ts
index e5769dfcce9..3805a35bb95 100644
--- a/src/system/settings.ts
+++ b/src/system/settings.ts
@@ -152,7 +152,7 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer)
       scene.gamepadSupport = settingOptions[setting][value] !== 'Disabled';
       break;
     case Setting.Swap_A_and_B:
-      scene.setGamepadConfirm(settingOptions[setting][value] !== 'Enabled');
+      scene.abSwapped = settingOptions[setting][value] !== 'Disabled';
       break;
     case Setting.Touch_Controls:
       scene.enableTouchControls = settingOptions[setting][value] !== 'Disabled' && hasTouchscreen();
diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts
new file mode 100644
index 00000000000..38d8e7830c4
--- /dev/null
+++ b/src/ui-inputs.ts
@@ -0,0 +1,154 @@
+import Phaser from "phaser";
+import {Mode} from "./ui/ui";
+import {InputsController} from "./inputs-controller";
+import MessageUiHandler from "./ui/message-ui-handler";
+import StarterSelectUiHandler from "./ui/starter-select-ui-handler";
+import {Setting, settingOptions} from "./system/settings";
+import SettingsUiHandler from "./ui/settings-ui-handler";
+import {Button} from "./enums/buttons";
+
+export interface ActionKeys {
+    [key in Button]: () => void;
+}
+
+export class UiInputs {
+    private scene: Phaser.Scene;
+    private events: Phaser.Events;
+    private inputsController: InputsController;
+
+    constructor(scene: Phaser.Scene, inputsController: InputsController) {
+        this.scene = scene;
+        this.inputsController = inputsController;
+        this.init();
+    }
+
+    init(): void {
+        this.events = this.inputsController.events;
+        this.listenInputs();
+    }
+
+    listenInputs(): void {
+        this.events.on('input_down', (event) => {
+            const actions = this.getActionsKeyDown();
+            if (!actions.hasOwnProperty(event.button)) return;
+            actions[event.button]();
+        }, this);
+
+        this.events.on('input_up', (event) => {
+            const actions = this.getActionsKeyUp();
+            if (!actions.hasOwnProperty(event.button)) return;
+            actions[event.button]();
+        }, this);
+    }
+
+    doVibration(inputSuccess: boolean, vibrationLength: number): void {
+        if (inputSuccess && this.scene.enableVibration && typeof navigator.vibrate !== 'undefined')
+            navigator.vibrate(vibrationLength);
+    }
+
+    getActionsKeyDown(): ActionKeys {
+        const actions = {};
+        actions[Button.UP] = () => this.buttonDirection(Button.UP);
+        actions[Button.DOWN] = () => this.buttonDirection(Button.DOWN);
+        actions[Button.LEFT] = () => this.buttonDirection(Button.LEFT);
+        actions[Button.RIGHT] = () => this.buttonDirection(Button.RIGHT);
+        actions[Button.SUBMIT] = () => this.buttonTouch();
+        actions[Button.ACTION] = () => this.buttonAb(Button.ACTION);
+        actions[Button.CANCEL] = () => this.buttonAb(Button.CANCEL);
+        actions[Button.MENU] = () => this.buttonMenu();
+        actions[Button.STATS] = () => this.buttonStats(true);
+        actions[Button.CYCLE_SHINY] = () => this.buttonCycleOption(Button.CYCLE_SHINY);
+        actions[Button.CYCLE_FORM] = () => this.buttonCycleOption(Button.CYCLE_FORM);
+        actions[Button.CYCLE_GENDER] = () => this.buttonCycleOption(Button.CYCLE_GENDER);
+        actions[Button.CYCLE_ABILITY] = () => this.buttonCycleOption(Button.CYCLE_ABILITY);
+        actions[Button.CYCLE_NATURE] = () => this.buttonCycleOption(Button.CYCLE_NATURE);
+        actions[Button.CYCLE_VARIANT] = () => this.buttonCycleOption(Button.CYCLE_VARIANT);
+        actions[Button.SPEED_UP] = () => this.buttonSpeedChange();
+        actions[Button.SLOW_DOWN] = () => this.buttonSpeedChange(false);
+        return actions;
+    }
+
+    getActionsKeyUp(): ActionKeys {
+        const actions = {};
+        actions[Button.STATS] = () => this.buttonStats(false);
+        return actions;
+    }
+
+    buttonDirection(direction: Button): void {
+        const inputSuccess = this.scene.ui.processInput(direction);
+        const vibrationLength = 5;
+        this.doVibration(inputSuccess, vibrationLength);
+    }
+
+    buttonAb(button: Button): void {
+        this.scene.ui.processInput(button);
+    }
+
+    buttonTouch(): void {
+        this.scene.ui.processInput(Button.SUBMIT) || this.scene.ui.processInput(Button.ACTION);
+    }
+
+    buttonStats(pressed: boolean = true): void {
+        if (pressed) {
+            for (let p of this.scene.getField().filter(p => p?.isActive(true)))
+                p.toggleStats(true);
+        } else {
+            for (let p of this.scene.getField().filter(p => p?.isActive(true)))
+                p.toggleStats(false);
+        }
+    }
+
+    buttonMenu(): void {
+        if (this.scene.disableMenu)
+            return;
+        switch (this.scene.ui?.getMode()) {
+            case Mode.MESSAGE:
+                if (!(this.scene.ui.getHandler() as MessageUiHandler).pendingPrompt)
+                    return;
+            case Mode.TITLE:
+            case Mode.COMMAND:
+            case Mode.FIGHT:
+            case Mode.BALL:
+            case Mode.TARGET_SELECT:
+            case Mode.SAVE_SLOT:
+            case Mode.PARTY:
+            case Mode.SUMMARY:
+            case Mode.STARTER_SELECT:
+            case Mode.CONFIRM:
+            case Mode.OPTION_SELECT:
+                this.scene.ui.setOverlayMode(Mode.MENU);
+                break;
+            case Mode.MENU:
+            case Mode.SETTINGS:
+            case Mode.ACHIEVEMENTS:
+                this.scene.ui.revertMode();
+                this.scene.playSound('select');
+                break;
+            default:
+                return
+        }
+    }
+
+    buttonCycleOption(button: Button): void {
+        if (this.scene.ui?.getHandler() instanceof StarterSelectUiHandler) {
+            this.scene.ui.processInput(button);
+        }
+    }
+
+    buttonSpeedChange(up = true): void {
+        if (up) {
+            if (this.scene.gameSpeed < 5) {
+                this.scene.gameData.saveSetting(Setting.Game_Speed, settingOptions[Setting.Game_Speed].indexOf(`${this.scene.gameSpeed}x`) + 1);
+                if (this.scene.ui?.getMode() === Mode.SETTINGS)
+                    (this.scene.ui.getHandler() as SettingsUiHandler).show([]);
+            }
+            return;
+        }
+        if (this.scene.gameSpeed > 1) {
+            this.scene.gameData.saveSetting(Setting.Game_Speed, Math.max(settingOptions[Setting.Game_Speed].indexOf(`${this.scene.gameSpeed}x`) - 1, 0));
+            if (this.scene.ui?.getMode() === Mode.SETTINGS)
+                (this.scene.ui.getHandler() as SettingsUiHandler).show([]);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts
index 2f2c4face89..ffc0cabc89c 100644
--- a/src/ui/abstact-option-select-ui-handler.ts
+++ b/src/ui/abstact-option-select-ui-handler.ts
@@ -1,10 +1,11 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { TextStyle, addTextObject } from "./text";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
 import { addWindow } from "./ui-theme";
 import * as Utils from "../utils";
 import { argbFromRgba } from "@material/material-color-utilities";
+import {Button} from "../enums/buttons";
 
 export interface OptionSelectConfig {
   xOffset?: number;
diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts
index cadda64e032..561b0917177 100644
--- a/src/ui/achvs-ui-handler.ts
+++ b/src/ui/achvs-ui-handler.ts
@@ -1,9 +1,10 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { Achv, achvs } from "../system/achv";
 import MessageUiHandler from "./message-ui-handler";
 import { TextStyle, addTextObject } from "./text";
 import { Mode } from "./ui";
 import { addWindow } from "./ui-theme";
+import {Button} from "../enums/buttons";
 
 export default class AchvsUiHandler extends MessageUiHandler {
   private achvsContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/awaitable-ui-handler.ts b/src/ui/awaitable-ui-handler.ts
index e8cc979e423..532ca1115d2 100644
--- a/src/ui/awaitable-ui-handler.ts
+++ b/src/ui/awaitable-ui-handler.ts
@@ -1,6 +1,7 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
+import {Button} from "../enums/buttons";
 
 export default abstract class AwaitableUiHandler extends UiHandler {
   protected awaitingActionInput: boolean;
diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts
index f2ebdc342a4..06729151d44 100644
--- a/src/ui/ball-ui-handler.ts
+++ b/src/ui/ball-ui-handler.ts
@@ -1,11 +1,12 @@
 import { CommandPhase } from "../phases";
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { getPokeballName } from "../data/pokeball";
 import { addTextObject, TextStyle } from "./text";
 import { Command } from "./command-ui-handler";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
 import { addWindow } from "./ui-theme";
+import {Button} from "../enums/buttons";
 
 export default class BallUiHandler extends UiHandler {
   private pokeballSelectContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts
index 5e2cb56518f..d04a98ea810 100644
--- a/src/ui/battle-message-ui-handler.ts
+++ b/src/ui/battle-message-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "./text";
 import { Mode } from "./ui";
 import * as Utils from "../utils";
@@ -6,6 +6,7 @@ import MessageUiHandler from "./message-ui-handler";
 import { getStatName, Stat } from "../data/pokemon-stat";
 import { addWindow } from "./ui-theme";
 import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
+import {Button} from "../enums/buttons";
 
 export default class BattleMessageUiHandler extends MessageUiHandler {
   private levelUpStatsContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts
index b8223694b4c..a2705301563 100644
--- a/src/ui/command-ui-handler.ts
+++ b/src/ui/command-ui-handler.ts
@@ -1,10 +1,11 @@
 import { CommandPhase } from "../phases";
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { addTextObject, TextStyle } from "./text";
 import PartyUiHandler, { PartyUiMode } from "./party-ui-handler";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
 import i18next from '../plugins/i18n';
+import {Button} from "../enums/buttons";
 
 export enum Command {
   FIGHT = 0,
diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts
index a220b6f3a3d..bac980db99e 100644
--- a/src/ui/confirm-ui-handler.ts
+++ b/src/ui/confirm-ui-handler.ts
@@ -1,7 +1,8 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import AbstractOptionSelectUiHandler, { OptionSelectConfig } from "./abstact-option-select-ui-handler";
 import { Mode } from "./ui";
 import i18next from "i18next";
+import {Button} from "../enums/buttons";
 
 export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
   private switchCheck: boolean;
diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts
index f0e32dbc2e9..315dfdbd3d3 100644
--- a/src/ui/egg-gacha-ui-handler.ts
+++ b/src/ui/egg-gacha-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { Mode } from "./ui";
 import { TextStyle, addTextObject, getEggTierTextTint } from "./text";
 import MessageUiHandler from "./message-ui-handler";
@@ -9,6 +9,7 @@ import { getPokemonSpecies } from "../data/pokemon-species";
 import { addWindow } from "./ui-theme";
 import { Tutorial, handleTutorial } from "../tutorial";
 import { EggTier } from "../data/enums/egg-type";
+import {Button} from "../enums/buttons";
 
 const defaultText = 'Select a machine.';
 
diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts
index f841bafc268..ea8df429c58 100644
--- a/src/ui/egg-hatch-scene-handler.ts
+++ b/src/ui/egg-hatch-scene-handler.ts
@@ -1,7 +1,8 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { EggHatchPhase } from "../egg-hatch-phase";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
+import {Button} from "../enums/buttons";
 
 export default class EggHatchSceneHandler extends UiHandler {
   public eggHatchContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/egg-list-ui-handler.ts b/src/ui/egg-list-ui-handler.ts
index 7537b8deeb0..edeac7d71c7 100644
--- a/src/ui/egg-list-ui-handler.ts
+++ b/src/ui/egg-list-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { Mode } from "./ui";
 import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
 import { TextStyle, addTextObject } from "./text";
@@ -6,6 +6,7 @@ import MessageUiHandler from "./message-ui-handler";
 import { EGG_SEED, Egg, GachaType, getEggGachaTypeDescriptor, getEggHatchWavesMessage, getEggDescriptor } from "../data/egg";
 import * as Utils from "../utils";
 import { addWindow } from "./ui-theme";
+import {Button} from "../enums/buttons";
 
 export default class EggListUiHandler extends MessageUiHandler {
   private eggListContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/evolution-scene-handler.ts b/src/ui/evolution-scene-handler.ts
index 7e0ef063ea0..3361e9038d8 100644
--- a/src/ui/evolution-scene-handler.ts
+++ b/src/ui/evolution-scene-handler.ts
@@ -1,7 +1,8 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import MessageUiHandler from "./message-ui-handler";
 import { TextStyle, addTextObject } from "./text";
 import { Mode } from "./ui";
+import {Button} from "../enums/buttons";
 
 export default class EvolutionSceneHandler extends MessageUiHandler {
   public evolutionContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts
index 1a7a8bef597..ee7e413db5d 100644
--- a/src/ui/fight-ui-handler.ts
+++ b/src/ui/fight-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { addTextObject, TextStyle } from "./text";
 import { Type } from "../data/type";
 import { Command } from "./command-ui-handler";
@@ -8,6 +8,7 @@ import * as Utils from "../utils";
 import { CommandPhase } from "../phases";
 import { MoveCategory } from "#app/data/move.js";
 import i18next from '../plugins/i18n';
+import {Button} from "../enums/buttons";
 
 export default class FightUiHandler extends UiHandler {
   private movesContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/form-modal-ui-handler.ts b/src/ui/form-modal-ui-handler.ts
index ec5f4147c58..b2c2c118100 100644
--- a/src/ui/form-modal-ui-handler.ts
+++ b/src/ui/form-modal-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { ModalConfig, ModalUiHandler } from "./modal-ui-handler";
 import { Mode } from "./ui";
 import { TextStyle, addTextInputObject, addTextObject } from "./text";
@@ -6,6 +6,7 @@ import { WindowVariant, addWindow } from "./ui-theme";
 import InputText from "phaser3-rex-plugins/plugins/inputtext";
 import * as Utils from "../utils";
 import i18next from '../plugins/i18n';
+import {Button} from "../enums/buttons";
 
 export interface FormModalConfig extends ModalConfig {
   errorMessage?: string;
diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts
index 00e358ff8e4..0b97d322e02 100644
--- a/src/ui/game-stats-ui-handler.ts
+++ b/src/ui/game-stats-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { TextStyle, addTextObject, getTextColor } from "./text";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
@@ -6,6 +6,7 @@ import { addWindow } from "./ui-theme";
 import * as Utils from "../utils";
 import { DexAttr, GameData } from "../system/game-data";
 import { speciesStarters } from "../data/pokemon-species";
+import {Button} from "../enums/buttons";
 
 interface DisplayStat {
   label?: string;
diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts
index 03d93699e75..bf032667610 100644
--- a/src/ui/menu-ui-handler.ts
+++ b/src/ui/menu-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button, bypassLogin } from "../battle-scene";
+import BattleScene, { bypassLogin } from "../battle-scene";
 import { TextStyle, addTextObject } from "./text";
 import { Mode } from "./ui";
 import * as Utils from "../utils";
@@ -9,6 +9,7 @@ import { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui
 import { Tutorial, handleTutorial } from "../tutorial";
 import { updateUserInfo } from "../account";
 import i18next from '../plugins/i18n';
+import {Button} from "../enums/buttons";
 
 export enum MenuOptions {
   GAME_SETTINGS,
diff --git a/src/ui/modal-ui-handler.ts b/src/ui/modal-ui-handler.ts
index f193a3db54f..77a3c14bd7b 100644
--- a/src/ui/modal-ui-handler.ts
+++ b/src/ui/modal-ui-handler.ts
@@ -1,8 +1,9 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { TextStyle, addTextObject } from "./text";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
 import { WindowVariant, addWindow } from "./ui-theme";
+import {Button} from "../enums/buttons";
 
 export interface ModalConfig {
   buttonActions: Function[];
diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts
index e5252e02a8f..8af13d8f648 100644
--- a/src/ui/modifier-select-ui-handler.ts
+++ b/src/ui/modifier-select-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption } from "../modifier/modifier-type";
 import { getPokeballAtlasKey, PokeballType } from "../data/pokeball";
 import { addTextObject, getModifierTierTextTint, getTextColor, TextStyle } from "./text";
@@ -6,6 +6,7 @@ import AwaitableUiHandler from "./awaitable-ui-handler";
 import { Mode } from "./ui";
 import { LockModifierTiersModifier, PokemonHeldItemModifier } from "../modifier/modifier";
 import { handleTutorial, Tutorial } from "../tutorial";
+import {Button} from "../enums/buttons";
 
 export const SHOP_OPTIONS_ROW_LIMIT = 6;
 
diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts
index 35014fa7027..8b497655a17 100644
--- a/src/ui/party-ui-handler.ts
+++ b/src/ui/party-ui-handler.ts
@@ -1,5 +1,5 @@
 import { CommandPhase } from "../phases";
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { PlayerPokemon, PokemonMove } from "../field/pokemon";
 import { addTextObject, TextStyle } from "./text";
 import { Command } from "./command-ui-handler";
@@ -16,6 +16,7 @@ import { pokemonEvolutions } from "../data/pokemon-evolutions";
 import { addWindow } from "./ui-theme";
 import { SpeciesFormChangeItemTrigger } from "../data/pokemon-forms";
 import { getVariantTint } from "#app/data/variant";
+import {Button} from "../enums/buttons";
 
 const defaultMessage = 'Choose a Pokémon.';
 
diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts
index 69ac94d99ba..a30e21c8f57 100644
--- a/src/ui/save-slot-select-ui-handler.ts
+++ b/src/ui/save-slot-select-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { gameModes } from "../game-mode";
 import { SessionSaveData } from "../system/game-data";
 import { TextStyle, addTextObject } from "./text";
@@ -9,6 +9,7 @@ import PokemonData from "../system/pokemon-data";
 import { PokemonHeldItemModifier } from "../modifier/modifier";
 import MessageUiHandler from "./message-ui-handler";
 import i18next from "i18next";
+import {Button} from "../enums/buttons";
 
 const sessionSlotCount = 5;
 
diff --git a/src/ui/settings-ui-handler.ts b/src/ui/settings-ui-handler.ts
index 8f43b377d21..3ed83268ee6 100644
--- a/src/ui/settings-ui-handler.ts
+++ b/src/ui/settings-ui-handler.ts
@@ -1,10 +1,11 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { Setting, reloadSettings, settingDefaults, settingOptions } from "../system/settings";
 import { hasTouchscreen, isMobile } from "../touch-controls";
 import { TextStyle, addTextObject } from "./text";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
 import { addWindow } from "./ui-theme";
+import {Button} from "../enums/buttons";
 
 export default class SettingsUiHandler extends UiHandler {
   private settingsContainer: Phaser.GameObjects.Container;
diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts
index 4cfef282c81..f887fe69c58 100644
--- a/src/ui/starter-select-ui-handler.ts
+++ b/src/ui/starter-select-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button, starterColors } from "../battle-scene";
+import BattleScene, { starterColors } from "../battle-scene";
 import PokemonSpecies, { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
 import { Species } from "../data/enums/species";
 import { TextStyle, addBBCodeTextObject, addTextObject } from "./text";
@@ -28,6 +28,7 @@ import { OptionSelectItem } from "./abstact-option-select-ui-handler";
 import { pokemonPrevolutions } from "#app/data/pokemon-evolutions";
 import { Variant, getVariantTint } from "#app/data/variant";
 import i18next from "i18next";
+import {Button} from "../enums/buttons";
 
 export type StarterSelectCallback = (starters: Starter[]) => void;
 
diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts
index ee514665dc9..ac04d41be5f 100644
--- a/src/ui/summary-ui-handler.ts
+++ b/src/ui/summary-ui-handler.ts
@@ -1,4 +1,4 @@
-import BattleScene, { Button, starterColors } from "../battle-scene";
+import BattleScene, { starterColors } from "../battle-scene";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
 import * as Utils from "../utils";
@@ -19,6 +19,7 @@ import { Nature, getNatureStatMultiplier } from "../data/nature";
 import { loggedInUser } from "../account";
 import { PlayerGender } from "../system/game-data";
 import { Variant, getVariantTint } from "#app/data/variant";
+import {Button} from "../enums/buttons";
 
 enum Page {
   PROFILE,
diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts
index f8a7c9d28a3..36588dc4784 100644
--- a/src/ui/target-select-ui-handler.ts
+++ b/src/ui/target-select-ui-handler.ts
@@ -1,10 +1,11 @@
 import { BattlerIndex } from "../battle";
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { Moves } from "../data/enums/moves";
 import { Mode } from "./ui";
 import UiHandler from "./ui-handler";
 import * as Utils from "../utils";
 import { getMoveTargets } from "../data/move";
+import {Button} from "../enums/buttons";
 
 export type TargetSelectCallback = (cursor: integer) => void;
 
diff --git a/src/ui/ui-handler.ts b/src/ui/ui-handler.ts
index e3c94b76467..7fdb85d94c0 100644
--- a/src/ui/ui-handler.ts
+++ b/src/ui/ui-handler.ts
@@ -1,6 +1,7 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { TextStyle, getTextColor } from "./text";
 import UI, { Mode } from "./ui";
+import {Button} from "../enums/buttons";
 
 export default abstract class UiHandler {
   protected scene: BattleScene;
diff --git a/src/ui/ui.ts b/src/ui/ui.ts
index 6e20b2cb8b8..09deb2bdd7a 100644
--- a/src/ui/ui.ts
+++ b/src/ui/ui.ts
@@ -1,4 +1,4 @@
-import { Button, default as BattleScene } from '../battle-scene';
+import { default as BattleScene } from '../battle-scene';
 import UiHandler from './ui-handler';
 import BattleMessageUiHandler from './battle-message-ui-handler';
 import CommandUiHandler from './command-ui-handler';
@@ -35,6 +35,7 @@ import SavingIconHandler from './saving-icon-handler';
 import UnavailableModalUiHandler from './unavailable-modal-ui-handler';
 import OutdatedModalUiHandler from './outdated-modal-ui-handler';
 import SessionReloadModalUiHandler from './session-reload-modal-ui-handler';
+import {Button} from "../enums/buttons";
 
 export enum Mode {
   MESSAGE,
diff --git a/src/ui/vouchers-ui-handler.ts b/src/ui/vouchers-ui-handler.ts
index 3f41cf9ae74..e28e211ee53 100644
--- a/src/ui/vouchers-ui-handler.ts
+++ b/src/ui/vouchers-ui-handler.ts
@@ -1,9 +1,10 @@
-import BattleScene, { Button } from "../battle-scene";
+import BattleScene from "../battle-scene";
 import { Voucher, getVoucherTypeIcon, getVoucherTypeName, vouchers } from "../system/voucher";
 import MessageUiHandler from "./message-ui-handler";
 import { TextStyle, addTextObject } from "./text";
 import { Mode } from "./ui";
 import { addWindow } from "./ui-theme";
+import {Button} from "../enums/buttons";
 
 const itemRows = 4;
 const itemCols = 17;