diff --git a/website-client/src/components/Macros/MacroRecorder.ts b/website-client/src/components/Macros/MacroRecorder.ts new file mode 100644 index 0000000..a619a6b --- /dev/null +++ b/website-client/src/components/Macros/MacroRecorder.ts @@ -0,0 +1,29 @@ +export default class MacroRecorder { + isRecording = false + lastCommandTime = Date.now() + currentRecording: { command: string, controllerState: any }[] = [] + + start() { + this.currentRecording = [] + this.lastCommandTime = Date.now() + this.isRecording = true + } + + stop() { + this.isRecording = false + console.debug("Recorded macro:") + console.debug(this.currentRecording) + } + + add(command: string, controllerState: any) { + if (this.isRecording) { + const commandTime = Date.now() + this.currentRecording.push({ + command: `wait ${commandTime - this.lastCommandTime}`, + controllerState + }) + this.currentRecording.push({ command, controllerState }) + this.lastCommandTime = commandTime + } + } +} diff --git a/website-client/src/components/Macros/Macros.tsx b/website-client/src/components/Macros/Macros.tsx new file mode 100644 index 0000000..0405143 --- /dev/null +++ b/website-client/src/components/Macros/Macros.tsx @@ -0,0 +1,91 @@ +import { createStyles, withStyles } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import Grid from '@material-ui/core/Grid'; +import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { SendCommand } from '../../key-binding/KeyBinding'; +import { ControllerState } from '../Controller/ControllerState'; +import MacroRecorder from './MacroRecorder'; + +const styles = () => createStyles({ +}) + +class Macros extends React.Component<{ + macroRecorder: MacroRecorder, + sendCommand: SendCommand, +}, any> { + constructor(props: any) { + super(props) + + this.state = { + isRecording: false, + macroExists: false, + } + + this.playLastRecordedMacro = this.playLastRecordedMacro.bind(this) + this.startRecording = this.startRecording.bind(this) + this.stopRecording = this.stopRecording.bind(this) + } + + startRecording(): void { + this.setState({ isRecording: true }) + this.props.macroRecorder.start() + } + + stopRecording(): void { + this.props.macroRecorder.stop() + this.setState({ isRecording: false, macroExists: true, }) + } + + async sleep(sleepMillis: number) { + return new Promise(resolve => setTimeout(resolve, sleepMillis)); + } + + async playLastRecordedMacro(): Promise { + for (const c of this.props.macroRecorder.currentRecording) { + const { command, controllerState } = c + const m = /wait (\d+)/.exec(command) + if (m) { + const sleepMillis = parseInt(m[1]) + if (sleepMillis > 100) { + const s = Date.now() + await this.sleep(sleepMillis) + } + } else { + this.props.sendCommand(command, controllerState) + } + } + } + + render(): React.ReactNode { + return
+ Macros + + + + + +
+ } +} + +export default withStyles(styles)(Macros) diff --git a/website-client/src/components/PlayGame.tsx b/website-client/src/components/PlayGame.tsx index dd6f5a5..14436a3 100644 --- a/website-client/src/components/PlayGame.tsx +++ b/website-client/src/components/PlayGame.tsx @@ -13,6 +13,8 @@ import GamepadBinding from '../key-binding/GamepadBinding' import KeyboardBinding from '../key-binding/KeyboardBinding' import Controller from './Controller/Controller' import { ControllerState } from './Controller/ControllerState' +import MacroRecorder from './Macros/MacroRecorder' +import Macros from './Macros/Macros' // Can take a Theme as input. const styles = () => createStyles({ @@ -45,6 +47,8 @@ const styles = () => createStyles({ 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 { + macroRecorder = new MacroRecorder() + constructor(props: Readonly) { super(props) @@ -215,6 +219,11 @@ class PlayGame extends React.Component { this.setState({ controllerState, }) + // TODO Find a more compact way to store controller state changes. + // Maybe they shouldn't be stored at all and we can just re-parse the command. + // That would save weird logic in other places but still keep redundancies trying to make a compact command but they rebuilding it. + // Although the rebuilding can be limited to be doing just when saving a macro. + this.macroRecorder.add(command, JSON.parse(JSON.stringify(controllerState))) } private toggleSendMode() { @@ -311,6 +320,7 @@ class PlayGame extends React.Component { mixerChannel: this.state.mixerChannel, }} /> +
URL Parameters for this page