diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index c6c7048b12..b406cec7d9 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -1,9 +1,4 @@ import Scene from "@/components/Scene.tsx" -import MirabufSceneObject from "./mirabuf/MirabufSceneObject.ts" -import MirabufCachingService, { MiraType } from "./mirabuf/MirabufLoader.ts" -import { mirabuf } from "./proto/mirabuf" -import MirabufParser, { ParseErrorSeverity } from "./mirabuf/MirabufParser.ts" -import MirabufInstance from "./mirabuf/MirabufInstance.ts" import { AnimatePresence } from "framer-motion" import { ReactElement, useEffect } from "react" import { ModalControlProvider, useModalManager } from "@/ui/ModalContext" @@ -56,8 +51,8 @@ import ImportLocalMirabufModal from "@/modals/mirabuf/ImportLocalMirabufModal.ts import APS from "./aps/APS.ts" import ImportMirabufPanel from "@/ui/panels/mirabuf/ImportMirabufPanel.tsx" import Skybox from "./ui/components/Skybox.tsx" +import ChooseInputSchemePanel from "./ui/panels/configuring/ChooseInputSchemePanel.tsx" import ProgressNotifications from "./ui/components/ProgressNotification.tsx" -import { ProgressHandle } from "./ui/components/ProgressNotificationData.ts" import ConfigureRobotModal from "./ui/modals/configuring/ConfigureRobotModal.tsx" import ResetAllInputsModal from "./ui/modals/configuring/ResetAllInputsModal.tsx" import ZoneConfigPanel from "./ui/panels/configuring/scoring/ZoneConfigPanel.tsx" @@ -66,8 +61,8 @@ import SceneOverlay from "./ui/components/SceneOverlay.tsx" import WPILibWSWorker from "@/systems/simulation/wpilib_brain/WPILibWSWorker.ts?worker" import WSViewPanel from "./ui/panels/WSViewPanel.tsx" import Lazy from "./util/Lazy.ts" - -const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" +import NewInputSchemeModal from "./ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx" +import AssignNewSchemeModal from "./ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx" const worker = new Lazy(() => new WPILibWSWorker()) @@ -102,48 +97,6 @@ function Synthesis() { worker.getValue() - let mira_path = DEFAULT_MIRA_PATH - - if (urlParams.has("mira")) { - mira_path = `test_mira/${urlParams.get("mira")!}` - console.debug(`Selected Mirabuf File: ${mira_path}`) - } - - const setup = async () => { - const setupProgress = new ProgressHandle("Spawning Default Robot") - setupProgress.Update("Checking cache...", 0.1) - - const info = await MirabufCachingService.CacheRemote(mira_path, MiraType.ROBOT) - .catch(_ => MirabufCachingService.CacheRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT)) - .catch(console.error) - - const miraAssembly = await MirabufCachingService.Get(info!.id, MiraType.ROBOT) - - setupProgress.Update("Parsing assembly...", 0.5) - - await (async () => { - if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { - return - } - - const parser = new MirabufParser(miraAssembly) - if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { - console.error(`Assembly Parser produced significant errors for '${miraAssembly.info!.name!}'`) - setupProgress.Fail("Failed to parse assembly") - return - } - - setupProgress.Update("Creating scene object...", 0.9) - - const mirabufSceneObject = new MirabufSceneObject(new MirabufInstance(parser), miraAssembly.info!.name!) - World.SceneRenderer.RegisterSceneObject(mirabufSceneObject) - - setupProgress.Done() - })() - } - - setup() - let mainLoopHandle = 0 const mainLoop = () => { mainLoopHandle = requestAnimationFrame(mainLoop) @@ -230,6 +183,8 @@ const initialModals = [ , , , + , + , , , , @@ -266,6 +221,7 @@ const initialPanels: ReactElement[] = [ , , , + , , ] diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 4c4cdd1414..875afbba53 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -105,7 +105,7 @@ class MirabufSceneObject extends SceneObject { // creating nametag for robots if (this.miraType === MiraType.ROBOT) { - this._nameTag = new SceneOverlayTag("Ernie") + this._nameTag = new SceneOverlayTag(() => (this._brain ? this._brain.inputSchemeName : "Not Configured")) } } diff --git a/fission/src/systems/input/DefaultInputs.ts b/fission/src/systems/input/DefaultInputs.ts index ef436a0b48..cb88a0f834 100644 --- a/fission/src/systems/input/DefaultInputs.ts +++ b/fission/src/systems/input/DefaultInputs.ts @@ -1,137 +1,327 @@ -import { AxisInput, Input, EmptyModifierState } from "./InputSystem" - -export type InputScheme = { - schemeName: string - usesGamepad: boolean - inputs: Input[] -} +import { InputScheme } from "./InputSchemeManager" +import { AxisInput, ButtonInput, EmptyModifierState } from "./InputSystem" class DefaultInputs { - private static wasd: InputScheme = { - schemeName: "WASD", - usesGamepad: false, - inputs: [ - new AxisInput("arcadeDrive", "KeyW", "KeyS"), - new AxisInput("arcadeTurn", "KeyD", "KeyA"), - - new AxisInput("joint 1", "Digit1", "Digit1", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: false, - alt: false, - shift: true, - meta: false, - }), - new AxisInput("joint 2", "Digit2", "Digit2", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: false, - alt: false, - shift: true, - meta: false, - }), - new AxisInput("joint 3", "Digit3", "Digit3", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: false, - alt: false, - shift: true, - meta: false, - }), - new AxisInput("joint 4", "Digit4", "Digit4", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: false, - alt: false, - shift: true, - meta: false, - }), - new AxisInput("joint 5", "Digit5", "Digit5", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: false, - alt: false, - shift: true, - meta: false, - }), - ], + static ernie = () => { + return { + schemeName: "Ernie", + descriptiveName: "WASD", + customized: false, + usesGamepad: false, + inputs: [ + new AxisInput("arcadeDrive", "KeyW", "KeyS"), + new AxisInput("arcadeTurn", "KeyD", "KeyA"), + + new ButtonInput("intake", "KeyE"), + new ButtonInput("eject", "KeyQ"), + + new AxisInput("joint 1", "Digit1", "Digit1", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 2", "Digit2", "Digit2", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 3", "Digit3", "Digit3", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 4", "Digit4", "Digit4", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 5", "Digit5", "Digit5", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 6"), + new AxisInput("joint 7"), + new AxisInput("joint 8"), + new AxisInput("joint 9"), + new AxisInput("joint 10"), + ], + } } - private static arrowKeys: InputScheme = { - schemeName: "Arrow Keys", - usesGamepad: false, - inputs: [ - new AxisInput("arcadeDrive", "ArrowUp", "ArrowDown"), - new AxisInput("arcadeTurn", "ArrowRight", "ArrowLeft"), - - new AxisInput("joint 1", "Slash", "Slash", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: true, - alt: false, - shift: false, - meta: false, - }), - new AxisInput("joint 2", "Period", "Period", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: true, - alt: false, - shift: false, - meta: false, - }), - new AxisInput("joint 3", "Comma", "Comma", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: true, - alt: false, - shift: false, - meta: false, - }), - new AxisInput("joint 4", "KeyM", "KeyM", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: true, - alt: false, - shift: false, - meta: false, - }), - new AxisInput("joint 5", "KeyN", "true", -1, false, false, -1, -1, false, EmptyModifierState, { - ctrl: false, - alt: false, - shift: false, - meta: false, - }), - ], + public static luna = () => { + return { + schemeName: "Luna", + descriptiveName: "Arrow Keys", + customized: false, + usesGamepad: false, + inputs: [ + new AxisInput("arcadeDrive", "ArrowUp", "ArrowDown"), + new AxisInput("arcadeTurn", "ArrowRight", "ArrowLeft"), + + new ButtonInput("intake", "Semicolon"), + new ButtonInput("eject", "KeyL"), + + new AxisInput("joint 1", "Slash", "Slash", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 2", "Period", "Period", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 3", "Comma", "Comma", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 4", "KeyM", "KeyM", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 5", "KeyN", "true", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: false, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 6"), + new AxisInput("joint 7"), + new AxisInput("joint 8"), + new AxisInput("joint 9"), + new AxisInput("joint 10"), + ], + } } - private static fullController: InputScheme = { - schemeName: "Full Controller", - usesGamepad: true, - inputs: [ - new AxisInput("arcadeDrive", "", "", 1, true), - new AxisInput("arcadeTurn", "", "", 2, false), - - new AxisInput("joint 1", "", "", -1, false, true, 3, 0), - new AxisInput("joint 2", "", "", -1, false, true, 1, 2), - new AxisInput("joint 3", "", "", -1, false, true, 4, 5), - new AxisInput("joint 3", "", "", -1, false, true, 15, 14), - new AxisInput("joint 3", "", "", -1, false, true, 12, 13), - ], + public static jax = () => { + return { + schemeName: "Jax", + descriptiveName: "Full Controller", + customized: false, + usesGamepad: true, + inputs: [ + new AxisInput("arcadeDrive", "", "", 1, true), + new AxisInput("arcadeTurn", "", "", 2, false), + + new ButtonInput("intake", "", 4), + new ButtonInput("eject", "", 5), + + new AxisInput("joint 1", "", "", -1, false, true, 3, 0), + new AxisInput("joint 2", "", "", -1, false, true, 1, 2), + new AxisInput("joint 3", "", "", -1, false, true, 15, 14), + new AxisInput("joint 4", "", "", -1, false, true, 12, 13), + new AxisInput("joint 5"), + new AxisInput("joint 6"), + new AxisInput("joint 7"), + new AxisInput("joint 8"), + new AxisInput("joint 9"), + new AxisInput("joint 10"), + ], + } } - private static leftStick: InputScheme = { - schemeName: "Left Stick", - usesGamepad: true, - inputs: [ - new AxisInput("arcadeDrive", "", "", 1, true), - new AxisInput("arcadeTurn", "", "", 0, false), + public static hunter = () => { + return { + schemeName: "Hunter", + descriptiveName: "Left Stick", + customized: false, + usesGamepad: true, + inputs: [ + new AxisInput("arcadeDrive", "", "", 1, true), + new AxisInput("arcadeTurn", "", "", 0, false), + + new ButtonInput("intake", "", 4), + new ButtonInput("eject", "", 5), + + new AxisInput("joint 2", "", "", -1, false, true, 15, 14), + new AxisInput("joint 1", "", "", -1, false, true, 12, 13), + new AxisInput("joint 3"), + new AxisInput("joint 4"), + new AxisInput("joint 5"), + new AxisInput("joint 6"), + new AxisInput("joint 7"), + new AxisInput("joint 8"), + new AxisInput("joint 9"), + new AxisInput("joint 10"), + ], + } + } + + public static carmela = () => { + return { + schemeName: "Carmela", + descriptiveName: "Right Stick", + customized: false, + usesGamepad: true, + inputs: [ + new AxisInput("arcadeDrive", "", "", 3, true), + new AxisInput("arcadeTurn", "", "", 2, false), + + new ButtonInput("intake", "", 4), + new ButtonInput("eject", "", 5), + + new AxisInput("joint 1", "", "", -1, false, true, 3, 0), + new AxisInput("joint 2", "", "", -1, false, true, 1, 2), + new AxisInput("joint 3"), + new AxisInput("joint 4"), + new AxisInput("joint 5"), + new AxisInput("joint 6"), + new AxisInput("joint 7"), + new AxisInput("joint 8"), + new AxisInput("joint 9"), + new AxisInput("joint 10"), + ], + } + } - new AxisInput("joint 2", "", "", -1, false, true, 15, 14), - new AxisInput("joint 1", "", "", -1, false, true, 12, 13), - ], + public static get defaultInputCopies() { + return [ + DefaultInputs.ernie(), + DefaultInputs.luna(), + DefaultInputs.jax(), + DefaultInputs.hunter(), + DefaultInputs.carmela(), + ] } - private static rightStick: InputScheme = { - schemeName: "Right Stick", - usesGamepad: true, - inputs: [ - new AxisInput("arcadeDrive", "", "", 3, true), - new AxisInput("arcadeTurn", "", "", 2, false), + public static get newBlankScheme(): InputScheme { + return { + schemeName: "", + descriptiveName: "", + customized: true, + usesGamepad: false, + inputs: [ + new AxisInput("arcadeDrive"), + new AxisInput("arcadeTurn"), - new AxisInput("joint 1", "", "", -1, false, true, 3, 0), - new AxisInput("joint 2", "", "", -1, false, true, 1, 2), - ], + new AxisInput("joint 1"), + new AxisInput("joint 2"), + new AxisInput("joint 3"), + new AxisInput("joint 4"), + new AxisInput("joint 5"), + new AxisInput("joint 6"), + new AxisInput("joint 7"), + new AxisInput("joint 8"), + new AxisInput("joint 9"), + new AxisInput("joint 10"), + ], + } } - public static ALL_INPUT_SCHEMES: InputScheme[] = [ - this.wasd, - this.arrowKeys, - this.fullController, - this.leftStick, - this.rightStick, + public static NAMES: string[] = [ + "Kennedy", + "Duke", + "Bria", + "Creed", + "Angie", + "Moises", + "Hattie", + "Quinton", + "Luisa", + "Ocean", + "Marlowe", + "Jimmy", + "Brielle", + "Forest", + "Katherine", + "Cade", + "Kori", + "Myles", + "Valeria", + "Braylon", + "Gracelyn", + "Killian", + "Holland", + "Jake", + "Jovie", + "William", + "Makenzie", + "Eden", + "Mabel", + "Ian", + "Leilany", + "Jayson", + "Kylie", + "Cal", + "Juliet", + "Emory", + "Eden", + "Nathanael", + "Eloise", + "Darian", + "Shelby", + "Neil", + "Scarlett", + "Ace", + "Florence", + "Alessandro", + "Sariyah", + "Joey", + "Aubrie", + "Edward", + "Octavia", + "Bode", + "Aaliyah", + "Francis", + "Camilla", + "Wilson", + "Elaina", + "Kayson", + "Kara", + "Rey", + "Madison", + "Emir", + "Alaya", + "Finley", + "Jayleen", + "Joseph", + "Arianna", + "Samson", + "Ezra", + "Amias", + "Ellen", + "Zion", + "Harley", + "Abraham", + "Elaine", + "Conner", + "Jolene", + "Kylan", + "Aislinn", + "Omar", + "Skyla", + "Shepard", + "Jaylin", + "Osiris", + "Lilyana", + "Noe", + "Crystal", + "Jeffrey", + "Emily", + "Rayan", + "Elise", + "Forrest", + "Aarya", + "Beckett", + "Jacqueline", + "Kyle", + "Kailey", + "Hank", + "Alanna", + "Marco", ] } diff --git a/fission/src/systems/input/InputSchemeManager.ts b/fission/src/systems/input/InputSchemeManager.ts new file mode 100644 index 0000000000..76c433c962 --- /dev/null +++ b/fission/src/systems/input/InputSchemeManager.ts @@ -0,0 +1,162 @@ +import { Random } from "@/util/Random" +import PreferencesSystem from "../preferences/PreferencesSystem" +import DefaultInputs from "./DefaultInputs" +import InputSystem, { AxisInput, ButtonInput, Input } from "./InputSystem" + +export type InputScheme = { + schemeName: string + descriptiveName: string + customized: boolean + usesGamepad: boolean + inputs: Input[] +} + +class InputSchemeManager { + // References to the current custom schemes to avoid parsing every time they are requested + private static _customSchemes: InputScheme[] | undefined + + /** Fetches custom input schemes from preferences manager */ + public static get customInputSchemes(): InputScheme[] { + if (this._customSchemes) return this._customSchemes + + // Load schemes from preferences and parse into objects + this._customSchemes = PreferencesSystem.getGlobalPreference("InputSchemes") + this._customSchemes.forEach(scheme => this.parseScheme(scheme)) + + return this._customSchemes + } + + /** Registers a new custom scheme */ + public static addCustomScheme(scheme: InputScheme) { + this.customInputSchemes.push(scheme) + } + + /** Parses a schemes inputs into working Input instances */ + private static parseScheme(rawInputs: InputScheme) { + for (let i = 0; i < rawInputs.inputs.length; i++) { + const rawInput = rawInputs.inputs[i] + let parsedInput: Input + + if ((rawInput as ButtonInput).keyCode != undefined) { + const rawButton = rawInput as ButtonInput + + parsedInput = new ButtonInput( + rawButton.inputName, + rawButton.keyCode, + rawButton.gamepadButton, + rawButton.keyModifiers + ) + } else { + const rawAxis = rawInput as AxisInput + + parsedInput = new AxisInput( + rawAxis.inputName, + rawAxis.posKeyCode, + rawAxis.negKeyCode, + rawAxis.gamepadAxisNumber, + rawAxis.joystickInverted, + rawAxis.useGamepadButtons, + rawAxis.posGamepadButton, + rawAxis.negGamepadButton, + rawAxis.posKeyModifiers, + rawAxis.negKeyModifiers + ) + } + + rawInputs.inputs[i] = parsedInput + } + } + + public static defaultInputSchemes: InputScheme[] = DefaultInputs.defaultInputCopies + + public static resetDefaultSchemes() { + this.defaultInputSchemes = DefaultInputs.defaultInputCopies + this._customSchemes = undefined + } + + /** Creates an array of every input scheme that is either a default or customized by the user. Custom themes will appear on top. */ + public static get allInputSchemes(): InputScheme[] { + // Start with custom input schemes + const allSchemes: InputScheme[] = [] + + this.customInputSchemes.forEach(s => allSchemes.push(s)) + + // Add default schemes if they have not been customized + this.defaultInputSchemes.forEach(defaultScheme => { + if ( + allSchemes.some(s => { + return s.schemeName === defaultScheme.schemeName + }) + ) + return + + allSchemes.push(defaultScheme) + }) + + return allSchemes + } + + /** Creates an array of every input scheme that is not currently in use by a robot */ + public static get availableInputSchemes(): InputScheme[] { + const allSchemes = this.allInputSchemes + + // Remove schemes that are in use + const schemesInUse = Array.from(InputSystem.brainIndexSchemeMap.values()) + return allSchemes.filter(scheme => !schemesInUse.includes(scheme)) + } + + /** @returns a random available robot name */ + public static get randomAvailableName(): string { + const usedNames = this.availableInputSchemes.map(s => s.schemeName) + + const randomName = () => { + const index = Math.floor(Random() * DefaultInputs.NAMES.length) + return DefaultInputs.NAMES[index] + } + + let name = randomName() + while (usedNames.includes(name)) name = randomName() + + return name + } + + /** Save all schemes that have been customized to local storage via preferences */ + public static saveSchemes() { + const customizedSchemes = Array.from(InputSystem.brainIndexSchemeMap.values()).filter(s => { + return s.customized + }) + + // Save default schemes that have been customized if a customized version does not already exist + this.defaultInputSchemes.forEach(s => { + if (!s.customized) return + + if ( + customizedSchemes.some(c => { + if (c.schemeName === s.schemeName) return true + }) + ) + return + + customizedSchemes.push(s) + }) + + PreferencesSystem.setGlobalPreference("InputSchemes", customizedSchemes) + PreferencesSystem.savePreferences() + } + + /** Returns a copy of a scheme without references to the original in any way */ + public static copyScheme(scheme: InputScheme): InputScheme { + const copiedInputs: Input[] = [] + scheme.inputs.forEach(i => copiedInputs.push(i.getCopy())) + + return { + schemeName: scheme.schemeName, + descriptiveName: scheme.descriptiveName, + customized: scheme.customized, + usesGamepad: scheme.usesGamepad, + inputs: copiedInputs, + } + } +} + +export default InputSchemeManager diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index 442c330a89..3a396f8968 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -1,6 +1,5 @@ import WorldSystem from "../WorldSystem" -import PreferencesSystem from "../preferences/PreferencesSystem" -import { InputScheme } from "./DefaultInputs" +import { InputScheme } from "./InputSchemeManager" export type ModifierState = { alt: boolean @@ -13,11 +12,9 @@ export const EmptyModifierState: ModifierState = { ctrl: false, alt: false, shif // Represents any input abstract class Input { public inputName: string - public isGlobal: boolean - constructor(inputName: string, isGlobal: boolean) { + constructor(inputName: string) { this.inputName = inputName - this.isGlobal = isGlobal } // Returns the current value of the input. Range depends on input type @@ -34,14 +31,8 @@ class ButtonInput extends Input { public gamepadButton: number - public constructor( - inputName: string, - keyCode?: string, - gamepadButton?: number, - isGlobal?: boolean, - keyModifiers?: ModifierState - ) { - super(inputName, isGlobal ?? false) + public constructor(inputName: string, keyCode?: string, gamepadButton?: number, keyModifiers?: ModifierState) { + super(inputName) this.keyCode = keyCode ?? "" this.keyModifiers = keyModifiers ?? EmptyModifierState this.gamepadButton = gamepadButton ?? -1 @@ -59,7 +50,7 @@ class ButtonInput extends Input { } getCopy(): Input { - return new ButtonInput(this.inputName, this.keyCode, this.gamepadButton, this.isGlobal, this.keyModifiers) + return new ButtonInput(this.inputName, this.keyCode, this.gamepadButton, this.keyModifiers) } } @@ -85,11 +76,10 @@ class AxisInput extends Input { useGamepadButtons?: boolean, posGamepadButton?: number, negGamepadButton?: number, - isGlobal?: boolean, posKeyModifiers?: ModifierState, negKeyModifiers?: ModifierState ) { - super(inputName, isGlobal ?? false) + super(inputName) this.posKeyCode = posKeyCode ?? "" this.posKeyModifiers = posKeyModifiers ?? EmptyModifierState @@ -136,7 +126,6 @@ class AxisInput extends Input { this.useGamepadButtons, this.posGamepadButton, this.negGamepadButton, - this.isGlobal, this.posKeyModifiers, this.negKeyModifiers ) @@ -155,6 +144,9 @@ class InputSystem extends WorldSystem { // The scheme most recently selected in the controls modal public static selectedScheme: InputScheme | undefined + // Maps a brain index to a certain input scheme + public static brainIndexSchemeMap: Map = new Map() + constructor() { super() @@ -241,8 +233,8 @@ class InputSystem extends WorldSystem { } // If an input exists, return it's value - public static getInput(inputName: string, assemblyName: string, assemblyIndex: number): number { - const targetScheme = PreferencesSystem.getRobotPreferences(assemblyName).inputsSchemes[assemblyIndex] + public static getInput(inputName: string, brainIndex: number): number { + const targetScheme = InputSystem.brainIndexSchemeMap.get(brainIndex) const targetInput = targetScheme?.inputs.find(input => input.inputName == inputName) as Input diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 9f088d7d97..e9e72ddbfb 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -1,5 +1,5 @@ +import { InputScheme } from "../input/InputSchemeManager" import { Vector3Tuple } from "three" -import { InputScheme } from "../input/DefaultInputs" export type GlobalPreference = | "ScreenMode" @@ -10,6 +10,7 @@ export type GlobalPreference = | "ReportAnalytics" | "UseMetric" | "RenderScoringZones" + | "InputSchemes" | "RenderSceneTags" export const RobotPreferencesKey: string = "Robots" @@ -24,6 +25,7 @@ export const DefaultGlobalPreferences: { [key: string]: unknown } = { ReportAnalytics: false, UseMetric: false, RenderScoringZones: true, + InputSchemes: [], RenderSceneTags: true, } @@ -56,7 +58,6 @@ export type ScoringZonePreferences = { persistentPoints: boolean deltaTransformation: number[] - //scale: [number, number, number] } export type FieldPreferences = { diff --git a/fission/src/systems/preferences/PreferencesSystem.ts b/fission/src/systems/preferences/PreferencesSystem.ts index 97f02363c1..cb54795104 100644 --- a/fission/src/systems/preferences/PreferencesSystem.ts +++ b/fission/src/systems/preferences/PreferencesSystem.ts @@ -25,7 +25,7 @@ class PreferencesSystem { private static _localStorageKey = "Preferences" /** Event dispatched when any global preference is updated */ - public static addEventListener(callback: (e: PreferenceEvent) => unknown) { + public static addEventListener(callback: (e: PreferenceEvent) => void) { window.addEventListener("preferenceChanged", callback as EventListener) } diff --git a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts index 374f5684d9..26be5467fb 100644 --- a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts +++ b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts @@ -6,8 +6,7 @@ import InputSystem from "@/systems/input/InputSystem" class ArcadeDriveBehavior extends Behavior { private leftWheels: WheelDriver[] private rightWheels: WheelDriver[] - private _assemblyName: string - private _assemblyIndex: number + private _brainIndex: number private _driveSpeed = 30 private _turnSpeed = 30 @@ -17,15 +16,13 @@ class ArcadeDriveBehavior extends Behavior { rightWheels: WheelDriver[], leftStimuli: WheelRotationStimulus[], rightStimuli: WheelRotationStimulus[], - assemblyName: string, - assemblyIndex: number + brainIndex: number ) { super(leftWheels.concat(rightWheels), leftStimuli.concat(rightStimuli)) this.leftWheels = leftWheels this.rightWheels = rightWheels - this._assemblyName = assemblyName - this._assemblyIndex = assemblyIndex + this._brainIndex = brainIndex } // Sets the drivetrains target linear and rotational velocity @@ -38,8 +35,8 @@ class ArcadeDriveBehavior extends Behavior { } public Update(_: number): void { - const driveInput = InputSystem.getInput("arcadeDrive", this._assemblyName, this._assemblyIndex) - const turnInput = InputSystem.getInput("arcadeTurn", this._assemblyName, this._assemblyIndex) + const driveInput = InputSystem.getInput("arcadeDrive", this._brainIndex) + const turnInput = InputSystem.getInput("arcadeTurn", this._brainIndex) this.DriveSpeeds(driveInput * this._driveSpeed, turnInput * this._turnSpeed) } diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index fc9e9bbd5d..c1251e1096 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -6,24 +6,16 @@ import InputSystem from "@/systems/input/InputSystem" class GenericArmBehavior extends Behavior { private _hingeDriver: HingeDriver private _inputName: string - private _assemblyName: string - private _assemblyIndex: number + private _brainIndex: number private _rotationalSpeed = 6 - constructor( - hingeDriver: HingeDriver, - hingeStimulus: HingeStimulus, - jointIndex: number, - assemblyName: string, - assemblyIndex: number - ) { + constructor(hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, jointIndex: number, brainIndex: number) { super([hingeDriver], [hingeStimulus]) this._hingeDriver = hingeDriver this._inputName = "joint " + jointIndex - this._assemblyName = assemblyName - this._assemblyIndex = assemblyIndex + this._brainIndex = brainIndex } // Sets the arms target rotational velocity @@ -32,9 +24,7 @@ class GenericArmBehavior extends Behavior { } public Update(_: number): void { - this.rotateArm( - InputSystem.getInput(this._inputName, this._assemblyName, this._assemblyIndex) * this._rotationalSpeed - ) + this.rotateArm(InputSystem.getInput(this._inputName, this._brainIndex) * this._rotationalSpeed) } } diff --git a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts index f1c01bde56..eb337b5d9e 100644 --- a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts @@ -6,24 +6,16 @@ import InputSystem from "@/systems/input/InputSystem" class GenericElevatorBehavior extends Behavior { private _sliderDriver: SliderDriver private _inputName: string - private _assemblyName: string - private _assemblyIndex: number + private _brainIndex: number private _linearSpeed = 2.5 - constructor( - sliderDriver: SliderDriver, - sliderStimulus: SliderStimulus, - jointIndex: number, - assemblyName: string, - assemblyIndex: number - ) { + constructor(sliderDriver: SliderDriver, sliderStimulus: SliderStimulus, jointIndex: number, brainIndex: number) { super([sliderDriver], [sliderStimulus]) this._sliderDriver = sliderDriver this._inputName = "joint " + jointIndex - this._assemblyName = assemblyName - this._assemblyIndex = assemblyIndex + this._brainIndex = brainIndex } // Changes the elevators target position @@ -32,9 +24,7 @@ class GenericElevatorBehavior extends Behavior { } public Update(_: number): void { - this.moveElevator( - InputSystem.getInput(this._inputName, this._assemblyName, this._assemblyIndex) * this._linearSpeed - ) + this.moveElevator(InputSystem.getInput(this._inputName, this._brainIndex) * this._linearSpeed) } } diff --git a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts index a44411acaf..feb87d70bf 100644 --- a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts +++ b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts @@ -14,31 +14,26 @@ import GenericArmBehavior from "../behavior/GenericArmBehavior" import SliderDriver from "../driver/SliderDriver" import SliderStimulus from "../stimulus/SliderStimulus" import GenericElevatorBehavior from "../behavior/GenericElevatorBehavior" -import { AxisInput, ButtonInput, Input } from "@/systems/input/InputSystem" -import DefaultInputs, { InputScheme } from "@/systems/input/DefaultInputs" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import InputSystem from "@/systems/input/InputSystem" class SynthesisBrain extends Brain { - private _behaviors: Behavior[] = [] - private _simLayer: SimulationLayer - - // Tracks how many joins have been made for unique controls - private _currentJointIndex = 1 - - private _assemblyName: string - private _assemblyIndex: number = 0 + public static brainIndexMap = new Map() - // Tracks the number of each specific mira file spawned - public static numberRobotsSpawned: { [key: string]: number } = {} + public get inputSchemeName(): string { + const scheme = InputSystem.brainIndexSchemeMap.get(this._brainIndex) + if (scheme == undefined) return "Not Configured" - // A list of all the robots spawned including their assembly index - public static robotsSpawned: string[] = [] + return scheme.schemeName + } - // The total number of robots spawned - private static _currentRobotIndex: number = 0 + private _behaviors: Behavior[] = [] + private _simLayer: SimulationLayer + private _assemblyName: string + private _brainIndex: number - // A list of all the fields spawned - public static fieldsSpawned: string[] = [] + // Tracks how many joins have been made with unique controls + private _currentJointIndex = 1 public constructor(mechanism: Mechanism, assemblyName: string) { super(mechanism) @@ -46,30 +41,22 @@ class SynthesisBrain extends Brain { this._simLayer = World.SimulationSystem.GetSimulationLayer(mechanism)! this._assemblyName = assemblyName + this._brainIndex = SynthesisBrain.brainIndexMap.size + SynthesisBrain.brainIndexMap.set(this._brainIndex, this) + if (!this._simLayer) { - console.log("SimulationLayer is undefined") + console.error("SimulationLayer is undefined") return } // Only adds controls to mechanisms that are controllable (ignores fields) if (mechanism.controllable) { - if (SynthesisBrain.numberRobotsSpawned[assemblyName] == undefined) - SynthesisBrain.numberRobotsSpawned[assemblyName] = 0 - else SynthesisBrain.numberRobotsSpawned[assemblyName]++ - - this._assemblyIndex = SynthesisBrain.numberRobotsSpawned[assemblyName] - SynthesisBrain.robotsSpawned.push(this.getNumberedAssemblyName()) - this.configureArcadeDriveBehavior() this.configureArmBehaviors() this.configureElevatorBehaviors() - this.configureInputs() } else { this.configureField() - SynthesisBrain.fieldsSpawned.push(assemblyName) } - - SynthesisBrain._currentRobotIndex++ } public Enable(): void {} @@ -83,12 +70,11 @@ class SynthesisBrain extends Brain { } public clearControls(): void { - const index = SynthesisBrain.robotsSpawned.indexOf(this.getNumberedAssemblyName()) - SynthesisBrain.robotsSpawned.splice(index, 1) + InputSystem.brainIndexSchemeMap.delete(this._brainIndex) } // Creates an instance of ArcadeDriveBehavior and automatically configures it - public configureArcadeDriveBehavior() { + private configureArcadeDriveBehavior() { const wheelDrivers: WheelDriver[] = this._simLayer.drivers.filter( driver => driver instanceof WheelDriver ) as WheelDriver[] @@ -128,19 +114,12 @@ class SynthesisBrain extends Brain { } this._behaviors.push( - new ArcadeDriveBehavior( - leftWheels, - rightWheels, - leftStimuli, - rightStimuli, - this._assemblyName, - this._assemblyIndex - ) + new ArcadeDriveBehavior(leftWheels, rightWheels, leftStimuli, rightStimuli, this._brainIndex) ) } // Creates instances of ArmBehavior and automatically configures them - public configureArmBehaviors() { + private configureArmBehaviors() { const hingeDrivers: HingeDriver[] = this._simLayer.drivers.filter( driver => driver instanceof HingeDriver ) as HingeDriver[] @@ -150,20 +129,14 @@ class SynthesisBrain extends Brain { for (let i = 0; i < hingeDrivers.length; i++) { this._behaviors.push( - new GenericArmBehavior( - hingeDrivers[i], - hingeStimuli[i], - this._currentJointIndex, - this._assemblyName, - this._assemblyIndex - ) + new GenericArmBehavior(hingeDrivers[i], hingeStimuli[i], this._currentJointIndex, this._brainIndex) ) this._currentJointIndex++ } } // Creates instances of ElevatorBehavior and automatically configures them - public configureElevatorBehaviors() { + private configureElevatorBehaviors() { const sliderDrivers: SliderDriver[] = this._simLayer.drivers.filter( driver => driver instanceof SliderDriver ) as SliderDriver[] @@ -177,97 +150,18 @@ class SynthesisBrain extends Brain { sliderDrivers[i], sliderStimuli[i], this._currentJointIndex, - this._assemblyName, - this._assemblyIndex + this._brainIndex ) ) this._currentJointIndex++ } } - private configureInputs() { - // Check for existing inputs - const robotConfig = PreferencesSystem.getRobotPreferences(this._assemblyName) - if (robotConfig.inputsSchemes[this._assemblyIndex] != undefined) { - SynthesisBrain.parseInputs(robotConfig.inputsSchemes[this._assemblyIndex]) - return - } - - // Configure with default inputs - - const scheme = DefaultInputs.ALL_INPUT_SCHEMES[SynthesisBrain._currentRobotIndex] - - robotConfig.inputsSchemes[this._assemblyIndex] = { - schemeName: this._assemblyName, - usesGamepad: scheme?.usesGamepad ?? false, - inputs: [], - } - const inputList = robotConfig.inputsSchemes[this._assemblyIndex].inputs - - if (scheme) { - const arcadeDrive = scheme.inputs.find(i => i.inputName === "arcadeDrive") - if (arcadeDrive) inputList.push(arcadeDrive.getCopy()) - else inputList.push(new AxisInput("arcadeDrive")) - - const arcadeTurn = scheme.inputs.find(i => i.inputName === "arcadeTurn") - if (arcadeTurn) inputList.push(arcadeTurn.getCopy()) - else inputList.push(new AxisInput("arcadeTurn")) - - for (let i = 1; i < this._currentJointIndex; i++) { - const controlPreset = scheme.inputs.find(input => input.inputName == "joint " + i) - - if (controlPreset) inputList.push(controlPreset.getCopy()) - else inputList.push(new AxisInput("joint " + i)) - } - } - } - private configureField() { PreferencesSystem.getFieldPreferences(this._assemblyName) /** Put any field configuration here */ } - - private getNumberedAssemblyName(): string { - return `[${this._assemblyIndex}] ${this._assemblyName}` - } - - private static parseInputs(rawInputs: InputScheme) { - for (let i = 0; i < rawInputs.inputs.length; i++) { - const rawInput = rawInputs.inputs[i] - let parsedInput: Input - - if ((rawInput as ButtonInput).keyCode != undefined) { - const rawButton = rawInput as ButtonInput - - parsedInput = new ButtonInput( - rawButton.inputName, - rawButton.keyCode, - rawButton.gamepadButton, - rawButton.isGlobal, - rawButton.keyModifiers - ) - } else { - const rawAxis = rawInput as AxisInput - - parsedInput = new AxisInput( - rawAxis.inputName, - rawAxis.posKeyCode, - rawAxis.negKeyCode, - rawAxis.gamepadAxisNumber, - rawAxis.joystickInverted, - rawAxis.useGamepadButtons, - rawAxis.posGamepadButton, - rawAxis.negGamepadButton, - rawAxis.isGlobal, - rawAxis.posKeyModifiers, - rawAxis.negKeyModifiers - ) - } - - rawInputs.inputs[i] = parsedInput - } - } } export default SynthesisBrain diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 5b88f3efbb..fa7016bcc4 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -181,7 +181,7 @@ const MainHUD: React.FC = () => { openPanel("scoring-zones") }} /> - } onClick={() => openModal("drivetrain")} /> + {/* } onClick={() => openModal("drivetrain")} /> */} } diff --git a/fission/src/ui/components/SceneOverlay.tsx b/fission/src/ui/components/SceneOverlay.tsx index 01e4ce605b..679b507bc4 100644 --- a/fission/src/ui/components/SceneOverlay.tsx +++ b/fission/src/ui/components/SceneOverlay.tsx @@ -33,7 +33,7 @@ function SceneOverlay() { transform: "translate(-50%, -100%)", }} > - + )) }, []) diff --git a/fission/src/ui/components/SceneOverlayEvents.ts b/fission/src/ui/components/SceneOverlayEvents.ts index feb6ef093e..521c4059e8 100644 --- a/fission/src/ui/components/SceneOverlayEvents.ts +++ b/fission/src/ui/components/SceneOverlayEvents.ts @@ -24,7 +24,7 @@ export const enum SceneOverlayEventKey { */ export class SceneOverlayTag { private _id: number - public text: string + public text: () => string public position: PixelSpaceCoord // Screen Space public get id() { @@ -32,7 +32,7 @@ export class SceneOverlayTag { } /** Create a new tag */ - public constructor(text: string, position?: PixelSpaceCoord) { + public constructor(text: () => string, position?: PixelSpaceCoord) { this._id = nextTagId++ this.text = text diff --git a/fission/src/ui/modals/configuring/ChangeInputsModal.tsx b/fission/src/ui/modals/configuring/ChangeInputsModal.tsx index 0c3125607f..7a45282847 100644 --- a/fission/src/ui/modals/configuring/ChangeInputsModal.tsx +++ b/fission/src/ui/modals/configuring/ChangeInputsModal.tsx @@ -7,11 +7,17 @@ import LabeledButton, { LabelPlacement } from "../../components/LabeledButton" import InputSystem, { AxisInput, ButtonInput, ModifierState, EmptyModifierState } from "@/systems/input/InputSystem" import Dropdown from "@/ui/components/Dropdown" import Checkbox from "@/ui/components/Checkbox" -import DefaultInputs, { InputScheme } from "@/systems/input/DefaultInputs" +import InputSchemeManager, { InputScheme } from "@/systems/input/InputSchemeManager" import Button from "@/ui/components/Button" import { useModalControlContext } from "@/ui/ModalContext" -import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" +import { Box, Divider, styled } from "@mui/material" +import { AiOutlinePlus } from "react-icons/ai" + +const AddIcon = + +const DividerStyled = styled(Divider)({ + borderColor: "white", +}) // capitalize first letter const transformKeyName = (keyCode: string, keyModifiers: ModifierState) => { @@ -106,16 +112,14 @@ const ChangeInputsModal: React.FC = ({ modalId }) => { const [modifierState, setModifierState] = useState(EmptyModifierState) const [chosenGamepadAxis, setChosenGamepadAxis] = useState(-1) - const [chosenResetScheme, setChosenResetScheme] = useState("WASD") const [useButtons, setUseButtons] = useState({}) // If there is a robot spawned, set it as the selected robot - if (selectedScheme == null && Object.keys(PreferencesSystem.getAllRobotPreferences()).length > 0) { + if (selectedScheme == null && InputSchemeManager.allInputSchemes.length > 0) { setTimeout(() => { - if (!InputSystem.selectedScheme) - InputSystem.selectedScheme = Object.values( - PreferencesSystem.getAllRobotPreferences() - )[0].inputsSchemes[0] + if (!InputSystem.selectedScheme) { + InputSystem.selectedScheme = InputSchemeManager.allInputSchemes[0] + } setUseButtons({}) setSelectedScheme(InputSystem.selectedScheme) @@ -137,7 +141,7 @@ const ChangeInputsModal: React.FC = ({ modalId }) => { } // Assign keyboard inputs when a key is pressed - if (!useGamepad && selectedInput && chosenKey) { + if (!useGamepad && selectedInput && chosenKey && selectedScheme) { if (selectedInput.startsWith("pos")) { const input = selectedScheme?.inputs.find( input => input.inputName == selectedInput.substring(3) @@ -156,12 +160,14 @@ const ChangeInputsModal: React.FC = ({ modalId }) => { input.keyModifiers = modifierState } + selectedScheme.customized = true + setChosenKey("") setSelectedInput("") setModifierState(EmptyModifierState) } // Assign gamepad button inputs when a button is pressed - else if (useGamepad && selectedInput && chosenButton != -1) { + else if (selectedScheme && useGamepad && selectedInput && chosenButton != -1) { if (selectedInput.startsWith("pos")) { const input = selectedScheme?.inputs.find( input => input.inputName == selectedInput.substring(3) @@ -178,15 +184,19 @@ const ChangeInputsModal: React.FC = ({ modalId }) => { input.gamepadButton = chosenButton } + selectedScheme.customized = true + setChosenButton(-1) setSelectedInput("") } // Assign gamepad axis inputs when a gamepad axis is selected - if (useGamepad && selectedInput && chosenGamepadAxis != -1) { + if (useGamepad && selectedInput && chosenGamepadAxis != -1 && selectedScheme) { const selected = selectedScheme?.inputs.find(input => input.inputName == selectedInput) as AxisInput selected.gamepadAxisNumber = chosenGamepadAxis - 1 + selectedScheme.customized = true + setChosenGamepadAxis(-1) setSelectedInput("") } @@ -343,173 +353,139 @@ const ChangeInputsModal: React.FC = ({ modalId }) => { icon={} modalId={modalId} onAccept={() => { - PreferencesSystem.savePreferences() + InputSchemeManager.saveSchemes() InputSystem.selectedScheme = undefined }} > - {Object.keys(PreferencesSystem.getAllRobotPreferences()).length > 0 ? ( - <> - -
- - { - const roboName = value.substring(4) - const controlSchemeIndex: number = +value.charAt(1) - - const newScheme = - PreferencesSystem.getAllRobotPreferences()[roboName].inputsSchemes[ - controlSchemeIndex - ] - if (newScheme == selectedScheme) return - - setSelectedScheme(undefined) - InputSystem.selectedScheme = newScheme - }} - /> - {selectedScheme ? ( - <> - { - setUseGamepad(val) - if (selectedScheme) selectedScheme.usesGamepad = val - }} - /> - - {/* */} -
- scheme.schemeName - )} - onSelect={value => { - setChosenResetScheme(value) - }} - /> -
-
-
{ - setChosenKey(selectedInput ? e.code : "") - setModifierState({ - ctrl: e.ctrlKey, - alt: e.altKey, - shift: e.shiftKey, - meta: e.metaKey, - }) - }} - > - {selectedScheme ? ( - <> - - {selectedScheme.inputs.map(c => { - if (!useGamepad) { - // Keyboard button - if (c instanceof ButtonInput) { - return KeyboardButtonSelection(c) - } - // Keyboard Axis - else if (c instanceof AxisInput) { - return KeyboardAxisSelection(c) - } - } else { - // Joystick Button - if (c instanceof ButtonInput) { - return JoystickButtonSelection(c) - } - - // Gamepad axis - else if (c instanceof AxisInput) { - return ( -
- {useButtons[c.inputName] - ? GamepadButtonAxisSelection(c) - : // Gamepad joystick axis - JoystickAxisSelection(c)} - - {/* // Button to switch between two buttons and a joystick axis */} - { - setUseButtons(prevState => ({ - ...prevState, - [c.inputName]: val, - })) - c.useGamepadButtons = val - }} - /> - {/* // Button to invert the joystick axis */} - { - c.joystickInverted = val - }} - /> -
- ) - } - } - })} -
+ ) : ( )} -
-
- - ) : ( - - )} + + +
{ + setChosenKey(selectedInput ? e.code : "") + setModifierState({ + ctrl: e.ctrlKey, + alt: e.altKey, + shift: e.shiftKey, + meta: e.metaKey, + }) + }} + > + {selectedScheme ? ( + <> + + {selectedScheme.inputs.map(c => { + if (!useGamepad) { + // Keyboard button + if (c instanceof ButtonInput) { + return KeyboardButtonSelection(c) + } + // Keyboard Axis + else if (c instanceof AxisInput) { + return KeyboardAxisSelection(c) + } + } else { + // Joystick Button + if (c instanceof ButtonInput) { + return JoystickButtonSelection(c) + } + + // Gamepad axis + else if (c instanceof AxisInput) { + return ( +
+ {useButtons[c.inputName] + ? GamepadButtonAxisSelection(c) + : // Gamepad joystick axis + JoystickAxisSelection(c)} + + {/* // Button to switch between two buttons and a joystick axis */} + { + setUseButtons(prevState => ({ + ...prevState, + [c.inputName]: val, + })) + c.useGamepadButtons = val + }} + /> + {/* // Button to invert the joystick axis */} + { + c.joystickInverted = val + }} + /> +
+ ) + } + } + })} +
+ + ) : ( + + )} +
+ + ) } diff --git a/fission/src/ui/modals/configuring/ResetAllInputsModal.tsx b/fission/src/ui/modals/configuring/ResetAllInputsModal.tsx index 689cb04cde..b058bd9d9c 100644 --- a/fission/src/ui/modals/configuring/ResetAllInputsModal.tsx +++ b/fission/src/ui/modals/configuring/ResetAllInputsModal.tsx @@ -2,40 +2,41 @@ import React from "react" import Modal, { ModalPropsImpl } from "@/components/Modal" import { GrFormClose } from "react-icons/gr" import { useModalControlContext } from "@/ui/ModalContext" -import DefaultInputs from "@/systems/input/DefaultInputs" +import InputSystem from "@/systems/input/InputSystem" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import World from "@/systems/World" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import { MiraType } from "@/mirabuf/MirabufLoader" +import InputSchemeManager from "@/systems/input/InputSchemeManager" const ResetAllInputsModal: React.FC = ({ modalId }) => { const { openModal } = useModalControlContext() return ( } modalId={modalId} onAccept={() => { - const roboPrefs = PreferencesSystem.getAllRobotPreferences() - - // TODO: This will be improved to make more sense to a user in the "named inputs" PR - Object.values(roboPrefs).forEach(roboPref => { - roboPref.inputsSchemes.forEach(currentScheme => { - const resetScheme = DefaultInputs.ALL_INPUT_SCHEMES[0] - if (!currentScheme || !resetScheme) return - - resetScheme.inputs.forEach(newInput => { - const currentInput = currentScheme.inputs.find(i => i.inputName == newInput.inputName) - - if (currentInput) { - const inputIndex = currentScheme.inputs.indexOf(currentInput) - - currentScheme.inputs[inputIndex] = newInput.getCopy() - } - }) - currentScheme.usesGamepad = resetScheme.usesGamepad + // Wipe global input scheme prefs + PreferencesSystem.setGlobalPreference("InputSchemes", []) + InputSystem.brainIndexSchemeMap.clear() + InputSchemeManager.resetDefaultSchemes() + PreferencesSystem.savePreferences() + + // Remove all robot assemblies + const assemblies = [...World.SceneRenderer.sceneObjects.entries()] + .filter(x => { + const y = + x[1] instanceof MirabufSceneObject && + (x[1] as MirabufSceneObject).miraType == MiraType.ROBOT + return y }) - }) + .map(x => x[0]) - openModal("change-inputs") + assemblies.forEach(a => { + World.SceneRenderer.RemoveSceneObject(a) + }) }} onCancel={() => { openModal("change-inputs") diff --git a/fission/src/ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx b/fission/src/ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx new file mode 100644 index 0000000000..b0e11b76df --- /dev/null +++ b/fission/src/ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx @@ -0,0 +1,41 @@ +import React, { useState } from "react" +import Input from "@/components/Input" +import Modal, { ModalPropsImpl } from "@/components/Modal" +import { GrFormClose } from "react-icons/gr" +import { useModalControlContext } from "@/ui/ModalContext" +import InputSchemeManager from "@/systems/input/InputSchemeManager" +import InputSystem from "@/systems/input/InputSystem" +import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" + +const AssignNewSchemeModal: React.FC = ({ modalId }) => { + const { openModal } = useModalControlContext() + + const [name, setName] = useState(InputSchemeManager.randomAvailableName) + + return ( + } + modalId={modalId} + onAccept={() => { + const scheme = InputSystem.brainIndexSchemeMap.get(SynthesisBrain.brainIndexMap.size - 1) + + if (scheme == undefined) return + + scheme.schemeName = name + + InputSystem.selectedScheme = scheme + InputSchemeManager.addCustomScheme(scheme) + + InputSchemeManager.saveSchemes() + + openModal("change-inputs") + }} + cancelEnabled={false} + > + + + ) +} + +export default AssignNewSchemeModal diff --git a/fission/src/ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx b/fission/src/ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx new file mode 100644 index 0000000000..a9d773631f --- /dev/null +++ b/fission/src/ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx @@ -0,0 +1,37 @@ +import React, { useState } from "react" +import Input from "@/components/Input" +import Modal, { ModalPropsImpl } from "@/components/Modal" +import { GrFormClose } from "react-icons/gr" +import { useModalControlContext } from "@/ui/ModalContext" +import InputSchemeManager from "@/systems/input/InputSchemeManager" +import InputSystem from "@/systems/input/InputSystem" +import DefaultInputs from "@/systems/input/DefaultInputs" + +const NewInputSchemeModal: React.FC = ({ modalId }) => { + const { openModal } = useModalControlContext() + + const [name, setName] = useState(InputSchemeManager.randomAvailableName) + + return ( + } + modalId={modalId} + onAccept={() => { + const scheme = DefaultInputs.newBlankScheme + scheme.schemeName = name + + InputSchemeManager.addCustomScheme(scheme) + InputSchemeManager.saveSchemes() + + InputSystem.selectedScheme = scheme + openModal("change-inputs") + }} + cancelEnabled={false} + > + + + ) +} + +export default NewInputSchemeModal diff --git a/fission/src/ui/panels/configuring/ChooseInputSchemePanel.tsx b/fission/src/ui/panels/configuring/ChooseInputSchemePanel.tsx new file mode 100644 index 0000000000..c6cfdceab3 --- /dev/null +++ b/fission/src/ui/panels/configuring/ChooseInputSchemePanel.tsx @@ -0,0 +1,167 @@ +import Panel, { PanelPropsImpl } from "@/components/Panel" +import DefaultInputs from "@/systems/input/DefaultInputs" +import InputSchemeManager, { InputScheme } from "@/systems/input/InputSchemeManager" +import InputSystem from "@/systems/input/InputSystem" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" +import Button from "@/ui/components/Button" +import Label, { LabelSize } from "@/ui/components/Label" +import { useModalControlContext } from "@/ui/ModalContext" +import { usePanelControlContext } from "@/ui/PanelContext" +import { Box, Divider, styled } from "@mui/material" +import { useEffect, useReducer } from "react" +import { AiOutlinePlus } from "react-icons/ai" +import { IoCheckmark, IoPencil, IoTrashBin } from "react-icons/io5" + +const ChooseInputSchemePanel: React.FC = ({ panelId }) => { + const { closePanel } = usePanelControlContext() + const { openModal } = useModalControlContext() + + const [_, update] = useReducer(x => !x, false) + + const AddIcon = + const DeleteIcon = + const SelectIcon = + const EditIcon = + + const LabelStyled = styled(Label)({ + fontWeight: 700, + margin: "0pt", + }) + + const DividerStyled = styled(Divider)({ + borderColor: "white", + }) + + useEffect(() => { + closePanel("import-mirabuf") + + /** If the panel is closed before a scheme is selected, defaults to the top of the list */ + return () => { + const brainIndex = SynthesisBrain.brainIndexMap.size - 1 + + if (InputSystem.brainIndexSchemeMap.has(brainIndex)) return + + const scheme = InputSchemeManager.availableInputSchemes[0] + + InputSystem.brainIndexSchemeMap.set(brainIndex, scheme) + InputSystem.selectedScheme = scheme + + openModal("change-inputs") + } + }, []) + + return ( + + {/** A scroll view with buttons to select default and custom input schemes */} +
+ {/** The label and divider at the top of the scroll view */} + + {`${InputSchemeManager.availableInputSchemes.length} Input Schemes`} + + + + {/** Creates list items with buttons */} + {InputSchemeManager.availableInputSchemes.map(scheme => { + return ( + + + {`${scheme.schemeName} | ${scheme.customized ? "Custom" : scheme.descriptiveName}`} + + + {/** Select button */} +
+ {/** New scheme with a randomly assigned name button */} +