From 923091e6aa2661bee172ecbd465d9fe6441e8808 Mon Sep 17 00:00:00 2001 From: "Justin D. Harris" Date: Mon, 8 Jun 2020 22:28:50 -0400 Subject: [PATCH] client: show what controller buttons are being pressed (#19) README: Add another example link. Client * Add explanation to enable insecure content. * show controller and sticks/buttons light up when being used * keyboard: Show button being pressed. * Put video in between controllers. --- README.md | 4 +- .../src/components/Button/Button.module.css | 21 +++ .../src/components/Button/Button.tsx | 14 ++ .../Controller/Controller.module.css | 97 +++++++++++ .../src/components/Controller/Controller.tsx | 135 +++++++++++++++ .../components/Controller/ControllerState.ts | 42 +++++ .../Controller/Joystick/Joystick.module.css | 21 +++ .../Controller/Joystick/Joystick.tsx | 24 +++ .../src/components/Diamond/Blank.tsx | 11 ++ .../src/components/Diamond/Diamond.module.css | 6 + .../src/components/Diamond/Diamond.tsx | 29 ++++ website-client/src/components/PlayGame.tsx | 157 ++++++++---------- website-client/src/components/VideoStream.tsx | 45 +++++ .../src/key-binding/GamepadBinding.ts | 46 +++-- website-client/src/key-binding/KeyBinding.ts | 14 +- .../src/key-binding/KeyboardBinding.ts | 39 ++++- website-client/src/key-binding/actions.ts | 39 ++++- 17 files changed, 625 insertions(+), 119 deletions(-) create mode 100644 website-client/src/components/Button/Button.module.css create mode 100644 website-client/src/components/Button/Button.tsx create mode 100644 website-client/src/components/Controller/Controller.module.css create mode 100644 website-client/src/components/Controller/Controller.tsx create mode 100644 website-client/src/components/Controller/ControllerState.ts create mode 100644 website-client/src/components/Controller/Joystick/Joystick.module.css create mode 100644 website-client/src/components/Controller/Joystick/Joystick.tsx create mode 100644 website-client/src/components/Diamond/Blank.tsx create mode 100644 website-client/src/components/Diamond/Diamond.module.css create mode 100644 website-client/src/components/Diamond/Diamond.tsx create mode 100644 website-client/src/components/VideoStream.tsx diff --git a/README.md b/README.md index 1573bda..249de9c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,9 @@ You <===> Website <=====> Server <--Bluetooth--> Switch '------------------------------ Streaming Server ``` -Example [video](https://www.youtube.com/watch?v=EIofCEfQA1E). +Example [video](https://youtu.be/EIofCEfQA1E) of someone playing my Switch from another city. + +Example [video](https://youtu.be/TJlWK2HU8Do) of me using an Xbox controller (that does not have Bluetooth) to play my Switch. # Status One keyboard layout or gaming controller layout is supported to map input to the control sticks and the buttons on a Nintendo Switch controller. diff --git a/website-client/src/components/Button/Button.module.css b/website-client/src/components/Button/Button.module.css new file mode 100644 index 0000000..c8fde17 --- /dev/null +++ b/website-client/src/components/Button/Button.module.css @@ -0,0 +1,21 @@ +.Button { + background-color: #111; + color: white; + display: flex; + border-radius: 50%; + height: 4rem; + width: 4rem; + justify-content: center; + align-items: center; + text-transform: capitalize; + user-select: none; +} + +.Button h1 { + text-align: center; +} + +.Pressed { + background-color: #999; + color: #111; +} \ No newline at end of file diff --git a/website-client/src/components/Button/Button.tsx b/website-client/src/components/Button/Button.tsx new file mode 100644 index 0000000..2b97d9a --- /dev/null +++ b/website-client/src/components/Button/Button.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import classes from './Button.module.css' + +const Button: React.FunctionComponent = (props: any) => { + let classList = classes.Button + if (props.button.pressed) classList += " " + classes.Pressed + return ( +
+

{props.button.symbol}

+
+ ) +} + +export default Button diff --git a/website-client/src/components/Controller/Controller.module.css b/website-client/src/components/Controller/Controller.module.css new file mode 100644 index 0000000..900b41a --- /dev/null +++ b/website-client/src/components/Controller/Controller.module.css @@ -0,0 +1,97 @@ +/* TODO Use lower-case for the names. */ + +.Left { + display: flex; + align-items: center; +} + +.Middle { + /* Some of the settings were used before the video was in between the controls. */ + /* display: flex; */ + /* flex-direction: column; */ + /* flex-grow: 1; */ + min-width: 23rem; +} + +.Right { + +} + +.LargeButton { + background-color: #111; + margin: 1rem; + border-radius: 10%; + display: flex; + justify-content: center; +} + +.LargeButton p { + color: white; + font-weight: 700; + font-size: 30px; + margin: 0; + user-select: none; +} + +.SmallButton { + background-color: #111; + margin: 0; + border-radius: 10%; + display: flex; + justify-content: center; + flex-grow: 1; +} + +.SmallButton p { + color: white; + font-weight: 700; + font-size: 20px; + margin: 0; + user-select: none; +} + +.Row { + display: flex; + padding: 0 1rem 2rem +} + +.Symbol { + margin: 0; +} + +.Symbol p { + margin: 0 1rem; + color: #111; + font-weight: 900; + font-size: 32px; + user-select: none; +} + +.Pressed { + background-color: #999; + color: #111; +} + +.PressedAlt p { + color: #999; +} + +.capture { + position: relative; + top: -40px; + margin-left: 140px; +} + +.capture p { + font-size: 70px; +} + +.home { + position: relative; + top: -40px; + margin-left: 40px; +} + +.home p { + font-size: 70px; +} \ No newline at end of file diff --git a/website-client/src/components/Controller/Controller.tsx b/website-client/src/components/Controller/Controller.tsx new file mode 100644 index 0000000..3fa713c --- /dev/null +++ b/website-client/src/components/Controller/Controller.tsx @@ -0,0 +1,135 @@ +import { createStyles, withStyles } from '@material-ui/core' +import React from 'react' +import Diamond from '../Diamond/Diamond' +import VideoStream from '../VideoStream' +import cssClasses from './Controller.module.css' +import { ControllerState } from './ControllerState' +import Joystick from './Joystick/Joystick' + +const styles = () => createStyles({ + controller: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + }, +}) + +class Controller extends React.Component { + render(): React.ReactNode { + const { classes } = this.props + const controllerState: ControllerState = this.props.controllerState || new ControllerState() + + return ( +
+
+
+
+

L

+
+
+
+

ZL

+
+
+ {/* Slightly wider than a typical minus. */} +

+
+
+
+ + +
+

+
+
+
+ +
+
+
+
+

R

+
+
+
+

+

+
+
+

ZR

+
+
+
+ + +
+

+
+
+
+ ) + } +} + +export default withStyles(styles)(Controller) diff --git a/website-client/src/components/Controller/ControllerState.ts b/website-client/src/components/Controller/ControllerState.ts new file mode 100644 index 0000000..156e39d --- /dev/null +++ b/website-client/src/components/Controller/ControllerState.ts @@ -0,0 +1,42 @@ +class ButtonState { + public isPressed = false +} + +class StickState extends ButtonState { + /** -1 is left, +1 is right */ + public horizontalValue = 0 + + /** -1 is up, +1 is down */ + public verticalValue = 0 +} + +// Member names in here match actions. +class ControllerState { + // Arrows + arrowLeft = new ButtonState() + arrowRight = new ButtonState() + arrowUp = new ButtonState() + arrowDown = new ButtonState() + + a = new ButtonState() + b = new ButtonState() + x = new ButtonState() + y = new ButtonState() + + l = new ButtonState() + zl = new ButtonState() + + r = new ButtonState() + zr = new ButtonState() + + minus = new ButtonState() + plus = new ButtonState() + + capture = new ButtonState() + home = new ButtonState() + + leftStick = new StickState() + rightStick = new StickState() +} + +export { ControllerState, ButtonState } diff --git a/website-client/src/components/Controller/Joystick/Joystick.module.css b/website-client/src/components/Controller/Joystick/Joystick.module.css new file mode 100644 index 0000000..36fdce4 --- /dev/null +++ b/website-client/src/components/Controller/Joystick/Joystick.module.css @@ -0,0 +1,21 @@ +.joystickHolder { + height: 6rem; + width: 6rem; + background-color: #111; + border-radius: 50%; + margin: 1rem auto; +} + +.joystick { + height: 5rem; + width: 5rem; + background-color: #222; + border-radius: 50%; + margin: 1rem auto; + position: relative; + top: 0.5rem; +} + +.pressed { + background-color: #999; +} \ No newline at end of file diff --git a/website-client/src/components/Controller/Joystick/Joystick.tsx b/website-client/src/components/Controller/Joystick/Joystick.tsx new file mode 100644 index 0000000..acd2056 --- /dev/null +++ b/website-client/src/components/Controller/Joystick/Joystick.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import classes from './Joystick.module.css' + +const Joystick: React.FunctionComponent = (props: any) => { + let joystickHolderClassList = classes.joystickHolder + let joystickClassList = classes.joystick + const movedThreshold = 0.15 + if (props.pressed) { + joystickHolderClassList += " " + classes.pressed + } + if (Math.abs(props.x) > movedThreshold || Math.abs(props.y) > movedThreshold) { + joystickClassList += " " + classes.pressed + } + + const styles = { + transform: `translate(${props.x * 15}px, ${props.y * 15}px)`, + } + return
+
+
+
+} + +export default Joystick \ No newline at end of file diff --git a/website-client/src/components/Diamond/Blank.tsx b/website-client/src/components/Diamond/Blank.tsx new file mode 100644 index 0000000..59eb9dc --- /dev/null +++ b/website-client/src/components/Diamond/Blank.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const Blank: React.FunctionComponent = () => { + const style = { + width: "4rem", + height: "4em", + } + return
+} + +export default Blank \ No newline at end of file diff --git a/website-client/src/components/Diamond/Diamond.module.css b/website-client/src/components/Diamond/Diamond.module.css new file mode 100644 index 0000000..bc7733e --- /dev/null +++ b/website-client/src/components/Diamond/Diamond.module.css @@ -0,0 +1,6 @@ +.Diamond { + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} \ No newline at end of file diff --git a/website-client/src/components/Diamond/Diamond.tsx b/website-client/src/components/Diamond/Diamond.tsx new file mode 100644 index 0000000..1282700 --- /dev/null +++ b/website-client/src/components/Diamond/Diamond.tsx @@ -0,0 +1,29 @@ + +import React from 'react' +import classes from './Diamond.module.css' +import Button from '../Button/Button' +import Blank from './Blank' + +const Diamond: React.FunctionComponent = (props: any) => { + return ( +
+
+ +
+
+
+
+ +
+
+ ) +} + +export default Diamond diff --git a/website-client/src/components/PlayGame.tsx b/website-client/src/components/PlayGame.tsx index dd84fd3..dd6f5a5 100644 --- a/website-client/src/components/PlayGame.tsx +++ b/website-client/src/components/PlayGame.tsx @@ -11,9 +11,18 @@ import React from 'react' import io from 'socket.io-client' import GamepadBinding from '../key-binding/GamepadBinding' import KeyboardBinding from '../key-binding/KeyboardBinding' +import Controller from './Controller/Controller' +import { ControllerState } from './Controller/ControllerState' // Can take a Theme as input. const styles = () => createStyles({ + connectionSection: { + minHeight: 120, + }, + serverAddressInput: { + width: 250, + maxWidth: '100%' + }, controller: { marginTop: '10px', }, @@ -22,25 +31,19 @@ const styles = () => createStyles({ }, rightButtons: { }, - mixerDiv: { - paddingTop: '10px', - textAlign: 'center', - }, - mixerIframe: { - border: '0px', - overflow: 'hidden', - /* Normally the Switch has 1080p: 1920x1080 */ - /* Use low resolution for now since streaming will mainly be low quality for low latency. */ - width: '960px', - height: '540px', + inputMethodSelect: { + paddingTop: 20, + width: 400, maxWidth: '100%', - maxHeight: '100%', }, - inputMethodSelect: { - paddingTop: '10px', + urlParamsInfo: { + paddingTop: 20, + paddingBottom: 10, }, }) +const setupMixedContent = " You may have to enable \"mixed content\" or \"insecure content\" for this connection in your browser's settings if the server your friend is hosting does not have SSL (a link that starts with https). Warning! This is insecure." + class PlayGame extends React.Component { constructor(props: Readonly) { super(props) @@ -51,7 +54,6 @@ class PlayGame extends React.Component { this.handleGamepadConnected = this.handleGamepadConnected.bind(this) this.handleGamepadDisconnected = this.handleGamepadDisconnected.bind(this) this.onDisconnect = this.onDisconnect.bind(this) - this.renderVideo = this.renderVideo.bind(this) this.sendCommand = this.sendCommand.bind(this) this.toggleConnect = this.toggleConnect.bind(this) this.toggleSendMode = this.toggleSendMode.bind(this) @@ -101,14 +103,16 @@ class PlayGame extends React.Component { if (connectNow) { this.toggleConnect() } - this.checkSendMode() + if (isInSendMode) { + this.checkSendMode() + } }) window.addEventListener('gamepadconnected', this.handleGamepadConnected) window.addEventListener('gamepaddisconnected', this.handleGamepadDisconnected) } - private handleGamepadConnected(e: any | GamepadEvent): void { + private handleGamepadConnected(e: any | GamepadEventInit): void { console.debug("Gamepad connected at index %d: %s. %d buttons, %d axes.", e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length) @@ -125,7 +129,7 @@ class PlayGame extends React.Component { }) } - private handleGamepadDisconnected(e: any | GamepadEvent): void { + private handleGamepadDisconnected(e: any | GamepadEventInit): void { console.debug("Gamepad disconnected at index %d: %s.", e.gamepad.index, e.gamepad.id) if (this.state.inputMethod && this.state.inputMethod.index === e.gamepad.index) { @@ -181,8 +185,8 @@ class PlayGame extends React.Component { this.onDisconnect() return } - const address = this.state.serverAddress - const status = `Attempting to connect to "${address}"...` + const address: string = this.state.serverAddress + const status = `Attempting to connect to "${address}"...${!address.startsWith('https://') ? setupMixedContent : ""}` this.updateConnectionStatus(status) const socket = io(address) this.setState({ @@ -204,18 +208,12 @@ class PlayGame extends React.Component { }) } - private sendCommand(command: string) { - if (!command || !this.state.socket || !this.state.isInSendMode) { - console.debug("Not connected but would send:\n%s", command) - return + private sendCommand(command: string, controllerState: ControllerState) { + if (command && this.state.socket && this.state.isInSendMode) { + this.state.socket.emit('p', command) } this.setState({ - status: `Status: emitting ${command}` - }) - this.state.socket.emit('p', command, (data: string) => { - this.setState({ - status: `Status: responded: ${data}` - }) + controllerState, }) } @@ -247,47 +245,42 @@ class PlayGame extends React.Component { const { classes } = this.props return ( -
- - - - - - {this.state.connectionStatus} - - - - - {this.state.sendModeStatus} - {this.state.status} - - - - To use a controller, either select it from the list below or - connect it to your device and then press any button on it. - - option.getName()} - onChange={this.handleInputMethodSelection} - renderInput={(params) => } - /> - + + + + + + {this.state.connectionStatus} + -
- {this.renderVideo()} - {/* TODO Use Controller from https://github.com/nuiofrd/switch-remoteplay/tree/master/switch-rp-client/src */} + + + {this.state.sendModeStatus} + {this.state.status} + + + + + To use a controller, either select it from the list below or + connect it to this device and then press any button on it. + + option.getName()} + onChange={this.handleInputMethodSelection} + renderInput={(params) => } + />
{this.state.inputMethod.getName() === 'Keyboard' && @@ -313,12 +306,12 @@ class PlayGame extends React.Component {
} - - Nintendo Switch Controller +
-
+
URL Parameters for this page a: The server address of the host that will send commands to the Switch. @@ -337,20 +330,6 @@ class PlayGame extends React.Component {
) } - - private renderVideo(): React.ReactNode { - if (this.state.mixerChannel) { - // Mixer docs: https://mixer.com/dashboard/channel/customize - // The Mixer stream seems to start muted, and there does seem to be a way that works to unmute it by default. - const { classes } = this.props - return (
- -
) - } else { - return undefined - } - } } export default withStyles(styles)(PlayGame) diff --git a/website-client/src/components/VideoStream.tsx b/website-client/src/components/VideoStream.tsx new file mode 100644 index 0000000..7ff6420 --- /dev/null +++ b/website-client/src/components/VideoStream.tsx @@ -0,0 +1,45 @@ +import { createStyles, withStyles } from '@material-ui/core' +import React from 'react' + +// Normally the Switch has 1080p: 1920x1080 +// This is just for the dimensions on the page, the full screen option can be selected. +// A low resolution is fine anyway since streaming will mainly be low quality for low latency. +const height = 440 +const width = Math.floor(height * 1920 / 1080) + +// Can take a Theme as input. +const styles = () => createStyles({ + mixerDiv: { + paddingTop: '10px', + textAlign: 'center', + }, + mixerIframe: { + border: '0px', + overflow: 'hidden', + width: `${width}px`, + height: `${height}px`, + maxWidth: '100%', + maxHeight: '100%', + }, +}) + +class VideoStream extends React.Component { + render(): React.ReactNode { + const { mixerChannel } = this.props + if (mixerChannel) { + // Mixer docs: https://mixer.com/dashboard/channel/customize + // The Mixer stream seems to start muted, and there does seem to be a way that works to unmute it by default. + const { classes } = this.props + return (
+ +
) + } else { + return (
) + } + } +} + +export default withStyles(styles)(VideoStream) diff --git a/website-client/src/key-binding/GamepadBinding.ts b/website-client/src/key-binding/GamepadBinding.ts index b1f6dfd..757484f 100644 --- a/website-client/src/key-binding/GamepadBinding.ts +++ b/website-client/src/key-binding/GamepadBinding.ts @@ -18,8 +18,8 @@ export default class GamepadBinding extends KeyBinding { actions.zr, actions.minus, actions.plus, - actions.l_stick, - actions.r_stick, + actions.leftStick, + actions.rightStick, actions.arrowUp, actions.arrowDown, actions.arrowLeft, @@ -29,10 +29,10 @@ export default class GamepadBinding extends KeyBinding { axisDeltaThreshold = 0.01 axisToAction = [ - 's l h ', - 's l v ', - 's r h ', - 's r v ', + { name: 'leftStick', dirName: 'horizontalValue', command: 's l h ', }, + { name: 'leftStick', dirName: 'verticalValue', command: 's l v ', }, + { name: 'rightStick', dirName: 'horizontalValue', command: 's r h ', }, + { name: 'rightStick', dirName: 'verticalValue', command: 's r v ', }, ] gamepad: Gamepad @@ -46,16 +46,22 @@ export default class GamepadBinding extends KeyBinding { } loop(): void { - const gamepads = navigator.getGamepads ? navigator.getGamepads() : ((navigator as any).webkitGetGamepads ? (navigator as any).webkitGetGamepads : []) - if (this.gamepad.index < gamepads.length) { - const gamepad = gamepads[this.gamepad.index] - const command = this.getCommand(gamepad) - if (command) { - this.sendCommand(command) + try { + const gamepads = navigator.getGamepads ? navigator.getGamepads() : ((navigator as any).webkitGetGamepads ? (navigator as any).webkitGetGamepads : []) + if (this.gamepad.index < gamepads.length) { + const gamepad = gamepads[this.gamepad.index] + const command = this.getCommand(gamepad) + if (command) { + this.sendCommand(command, this.controllerState) + } + this.gamepad = gamepad } - this.gamepad = gamepad + this.animationRequest = requestAnimationFrame(this.loop) + } catch (err) { + // Sometimes a gamepad disconnecting can cause an error. + console.warn(err) + this.stop() } - this.animationRequest = requestAnimationFrame(this.loop) } getCommand(gamepad: Gamepad): string { @@ -64,13 +70,17 @@ export default class GamepadBinding extends KeyBinding { const button = gamepad.buttons[i] const isPressed = button.pressed || button.value > this.buttonValueThreshold if (isPressed !== this.gamepad.buttons[i].pressed) { - commands.push(isPressed ? this.buttonToAction[i].down : this.buttonToAction[i].up) + const action = this.buttonToAction[i] + commands.push(isPressed ? action.down : action.up); + (this.controllerState as any)[action.name].isPressed = isPressed } } for (let i = 0; i < gamepad.axes.length && i < this.axisToAction.length; ++i) { const axisValue = gamepad.axes[i] if (Math.abs(axisValue - this.gamepad.axes[i]) > this.axisDeltaThreshold) { - commands.push(this.axisToAction[i] + axisValue.toFixed(2)) + const action = this.axisToAction[i] + commands.push(action.command + axisValue.toFixed(2)); + (this.controllerState as any)[action.name][action.dirName] = axisValue } } return commands.join('&') @@ -81,12 +91,12 @@ export default class GamepadBinding extends KeyBinding { } start(): void { - console.debug(`${this.getName()}: Starting`) + super.start() this.loop() } stop(): void { - console.debug(`${this.getName()}: Stopping`) + super.stop() if (this.animationRequest !== undefined) { cancelAnimationFrame(this.animationRequest) } diff --git a/website-client/src/key-binding/KeyBinding.ts b/website-client/src/key-binding/KeyBinding.ts index dde0b95..0205b6b 100644 --- a/website-client/src/key-binding/KeyBinding.ts +++ b/website-client/src/key-binding/KeyBinding.ts @@ -1,9 +1,12 @@ +import { ControllerState } from '../components/Controller/ControllerState' + export interface SendCommand { - (command: string): void + (command: string, controllerState: ControllerState): void } export abstract class KeyBinding { sendCommand: SendCommand + controllerState = new ControllerState() constructor(sendCommand: SendCommand) { this.sendCommand = sendCommand @@ -11,6 +14,11 @@ export abstract class KeyBinding { abstract getName(): string - abstract start(): void - abstract stop(): void + public start(): void { + console.debug(`${this.getName()}: Starting`) + } + + public stop(): void { + console.debug(`${this.getName()}: Stopping`) + } } diff --git a/website-client/src/key-binding/KeyboardBinding.ts b/website-client/src/key-binding/KeyboardBinding.ts index 4c2f393..4f3788f 100644 --- a/website-client/src/key-binding/KeyboardBinding.ts +++ b/website-client/src/key-binding/KeyboardBinding.ts @@ -57,7 +57,7 @@ export default class KeyboardBinding extends KeyBinding { } start: () => void = () => { - console.debug(`${this.getName()}: Starting`) + super.start() document.addEventListener('keydown', this.handleKeyDown) document.addEventListener('keyup', this.handleKeyUp) @@ -79,7 +79,7 @@ export default class KeyboardBinding extends KeyBinding { } stop(): void { - console.debug(`${this.getName()}: Stopping`) + super.stop() document.removeEventListener('keydown', this.handleKeyDown) document.removeEventListener('keyup', this.handleKeyUp) } @@ -122,7 +122,7 @@ export default class KeyboardBinding extends KeyBinding { // } private handleKey(e: KeyboardEvent | MouseEvent, keyName: string, keyDirection: string) { - let keyMapping: any = this.keyMap + let keyMapping = this.keyMap if (keyName in keyMapping) { keyMapping[keyName].isDown = keyDirection === 'down' @@ -132,9 +132,36 @@ export default class KeyboardBinding extends KeyBinding { keyMapping = this.shiftKeyMap } - const command = keyMapping[keyName] - if (command) { - this.sendCommand(command[keyDirection]) + const action = keyMapping[keyName] + if (action) { + const controllerState = this.controllerState as any; + + if (action.dirName) { + const stick = controllerState[action.name] + if (stick) { + switch (action.dirName) { + case 'left': + stick.horizontalValue = keyDirection === 'down' ? -1 : 0 + break + case 'right': + stick.horizontalValue = keyDirection === 'down' ? +1 : 0 + break + case 'up': + stick.verticalValue = keyDirection === 'down' ? -1 : 0 + break + case 'down': + stick.verticalValue = keyDirection === 'down' ? +1 : 0 + break + } + } + } else { + const button = controllerState[action.name] + + if (button) { + button.isPressed = keyDirection === 'down' + } + } + this.sendCommand(action[keyDirection], this.controllerState) e.preventDefault() } else if (e.type === 'keydown') { console.debug(`Pressed ${(e as KeyboardEvent).code}.`) diff --git a/website-client/src/key-binding/actions.ts b/website-client/src/key-binding/actions.ts index b1be3f9..99f5931 100644 --- a/website-client/src/key-binding/actions.ts +++ b/website-client/src/key-binding/actions.ts @@ -2,117 +2,152 @@ const maxStick = 'max' const minStick = 'min' const centerStick = 'center' +// Keys in here match ControllerState. export default { // Left Stick leftStickFullLeft: { + name: 'leftStick', + dirName: 'left', down: `s l h ${minStick}`, up: `s l h ${centerStick}`, }, leftStickFullRight: { + name: 'leftStick', + dirName: 'right', down: `s l h ${maxStick}`, up: `s l h ${centerStick}`, }, leftStickFullUp: { + name: 'leftStick', + dirName: 'up', down: `s l v ${maxStick}`, up: `s l v ${centerStick}`, }, leftStickFullDown: { + name: 'leftStick', + dirName: 'down', down: `s l v ${minStick}`, up: `s l v ${centerStick}`, }, - l_stick: { + leftStick: { + name: 'leftStick', down: 'l_stick d', up: 'l_stick u', }, // Right Stick rightStickFullLeft: { + name: 'rightStick', + dirName: 'left', down: `s r h ${minStick}`, up: `s r h ${centerStick}`, }, rightStickFullRight: { + name: 'rightStick', + dirName: 'right', down: `s r h ${maxStick}`, up: `s r h ${centerStick}`, }, rightStickFullUp: { + name: 'rightStick', + dirName: 'up', down: `s r v ${maxStick}`, up: `s r v ${centerStick}`, }, rightStickFullDown: { + name: 'rightStick', + dirName: 'down', down: `s r v ${minStick}`, up: `s r v ${centerStick}`, }, - r_stick: { + rightStick: { + name: 'rightStick', down: 'r_stick d', up: 'r_stick u', }, // Arrows arrowLeft: { + name: 'arrowLeft', down: 'left d', up: 'left u', }, arrowRight: { + name: 'arrowRight', down: 'right d', up: 'right u', }, arrowUp: { + name: 'arrowUp', down: 'up d', up: 'up u', }, arrowDown: { + name: 'arrowDown', down: 'down d', up: 'down u', }, b: { + name: 'b', down: 'b d', up: 'b u', }, x: { + name: 'x', down: 'x d', up: 'x u', }, y: { + name: 'y', down: 'y d', up: 'y u', }, a: { + name: 'a', down: 'a d', up: 'a u', }, l: { + name: 'l', down: 'l d', up: 'l u', }, zl: { + name: 'zl', down: 'zl d', up: 'zl u', }, r: { + name: 'r', down: 'r d', up: 'r u', }, zr: { + name: 'zr', down: 'zr d', up: 'zr u', }, minus: { + name: 'minus', down: 'minus d', up: 'minus u', }, plus: { + name: 'plus', down: 'plus d', up: 'plus u', }, capture: { + name: 'capture', down: 'capture d', up: 'capture u', }, home: { + name: 'home', down: 'home d', up: 'home u', },