import { getRecordEntries, PartialRecord } from "../Types/Record"; import { ComplexPage, SimplePage } from "../ui/Enums"; import { EventEmitter } from "./EventEmitter"; import { KEY } from "./KeyboardEventKey"; export enum ScriptEditorAction { Save = "ScriptEditor-Save", GoToTerminal = "ScriptEditor-GoToTerminal", Run = "ScriptEditor-Run", } export const SpoilerKeyBindingTypes = [ SimplePage.StaneksGift, SimplePage.Sleeves, SimplePage.Grafting, SimplePage.Bladeburner, SimplePage.Corporation, SimplePage.Gang, ] as const; export const GoToPageKeyBindingTypes = [ SimplePage.Terminal, ComplexPage.ScriptEditor, SimplePage.ActiveScripts, SimplePage.CreateProgram, SimplePage.Stats, SimplePage.Factions, SimplePage.Augmentations, SimplePage.Hacknet, SimplePage.City, SimplePage.Travel, SimplePage.Job, SimplePage.StockMarket, SimplePage.Go, SimplePage.DarkNet, SimplePage.Milestones, ComplexPage.Documentation, SimplePage.Achievements, ComplexPage.Options, ...SpoilerKeyBindingTypes, ] as const; export const ScriptEditorActionBindingTypes = [ScriptEditorAction.Save, ScriptEditorAction.GoToTerminal]; export const KeyBindingTypes = [...GoToPageKeyBindingTypes, ...ScriptEditorActionBindingTypes] as const; export type GoToPageKeyBindingType = (typeof GoToPageKeyBindingTypes)[number]; export type ScriptEditorActionBindingType = (typeof ScriptEditorActionBindingTypes)[number]; export type KeyBindingType = (typeof KeyBindingTypes)[number]; export type KeyCombination = { control: boolean; alt: boolean; shift: boolean; meta: boolean; key: string; }; export type PlayerDefinedKeyBindingsType = PartialRecord< KeyBindingType, [KeyCombination | null, KeyCombination | null] >; export const DefaultKeyBindings: Record = { [SimplePage.Terminal]: [ { control: false, alt: true, shift: false, meta: false, key: "T", }, null, ], [ComplexPage.ScriptEditor]: [ { control: false, alt: true, shift: false, meta: false, key: "E", }, null, ], [SimplePage.ActiveScripts]: [ { control: false, alt: true, shift: false, meta: false, key: "S", }, null, ], [SimplePage.CreateProgram]: [ { control: false, alt: true, shift: false, meta: false, key: "P", }, null, ], [SimplePage.StaneksGift]: [null, null], [SimplePage.Stats]: [ { control: false, alt: true, shift: false, meta: false, key: "C", }, null, ], [SimplePage.Factions]: [ { control: false, alt: true, shift: false, meta: false, key: "F", }, null, ], [SimplePage.Augmentations]: [ { control: false, alt: true, shift: false, meta: false, key: "A", }, null, ], [SimplePage.Hacknet]: [ { control: false, alt: true, shift: false, meta: false, key: "H", }, null, ], [SimplePage.Sleeves]: [null, null], [SimplePage.Grafting]: [null, null], [SimplePage.City]: [ { control: false, alt: true, shift: false, meta: false, key: "W", }, null, ], [SimplePage.Travel]: [ { control: false, alt: true, shift: false, meta: false, key: "R", }, null, ], [SimplePage.Job]: [ { control: false, alt: true, shift: false, meta: false, key: "J", }, null, ], [SimplePage.StockMarket]: [null, null], [SimplePage.Bladeburner]: [ { control: false, alt: true, shift: false, meta: false, key: "B", }, null, ], [SimplePage.Corporation]: [null, null], [SimplePage.Gang]: [ { control: false, alt: true, shift: false, meta: false, key: "G", }, null, ], [SimplePage.Go]: [null, null], [SimplePage.DarkNet]: [null, null], [SimplePage.Milestones]: [null, null], [ComplexPage.Documentation]: [ { control: false, alt: true, shift: false, meta: false, key: "U", }, null, ], [SimplePage.Achievements]: [null, null], [ComplexPage.Options]: [ { control: false, alt: true, shift: false, meta: false, key: "O", }, null, ], [ScriptEditorAction.Save]: [ { control: true, alt: false, shift: false, meta: false, key: "S", }, { control: false, alt: false, shift: false, meta: true, key: "S", }, ], [ScriptEditorAction.GoToTerminal]: [ { control: true, alt: false, shift: false, meta: false, key: "B", }, { control: false, alt: false, shift: false, meta: true, key: "B", }, ], [ScriptEditorAction.Run]: [ { control: true, alt: false, shift: false, meta: false, key: "Q", }, null, ], }; // This is the set of key bindings merged from DefaultKeyBindings and Settings.KeyBindings. export const CurrentKeyBindings = structuredClone(DefaultKeyBindings); /** * In order to avoid a circular dependency, do not use Settings.KeyBindings directly in this function. We need to pass * it as a parameter. */ export function mergePlayerDefinedKeyBindings(bindings: PlayerDefinedKeyBindingsType): void { for (const [action, keyCombinations] of getRecordEntries(bindings)) { CurrentKeyBindings[action][0] = keyCombinations[0]; CurrentKeyBindings[action][1] = keyCombinations[1]; } } export function areDifferentKeyCombinations(combination1: KeyCombination, combination2: KeyCombination) { return ( combination1.control !== combination2.control || combination1.alt !== combination2.alt || combination1.shift !== combination2.shift || combination1.meta !== combination2.meta || combination1.key !== combination2.key ); } export function parseKeyCombinationToString(keyCombination: KeyCombination | null): string { if (!keyCombination) { return ""; } let result = ""; if (keyCombination.control) { result += "Ctrl + "; } if (keyCombination.alt) { if (window.navigator.userAgent.includes("Mac")) { result += "Option + "; } else { result += "Alt + "; } } if (keyCombination.shift) { result += "Shift + "; } if (keyCombination.meta) { if (window.navigator.userAgent.includes("Mac")) { result += "⌘ + "; } else { // Most non-Apple keyboards print a form of Windows icon on the key cap of the "meta" key. result += "⊞ + "; } } if (keyCombination.key === KEY.SPACE) { result += "Space"; } else { result += keyCombination.key; } return result; } export function parseKeyCombinationsToString(keyCombinations: (KeyCombination | null)[]): string { let result = ""; for (const keyCombination of keyCombinations) { if (!keyCombination) { continue; } result += ` or ${parseKeyCombinationToString(keyCombination)}`; } if (result.startsWith(" or ")) { return result.substring(4); } return result; } export function getKeyCombination( keyBindings: typeof DefaultKeyBindings, keyBindingType: KeyBindingType, isPrimary: boolean, ): KeyCombination | null { return keyBindings[keyBindingType][isPrimary ? 0 : 1]; } export function convertKeyboardEventToKeyCombination(event: KeyboardEvent): KeyCombination { return { control: event.ctrlKey, alt: event.altKey, shift: event.shiftKey, meta: event.metaKey, /** * Use uppercase to avoid the problem of Caps Lock key. For example, if the player presses Alt+t when Caps Lock is * on, event.key will be "T", not "t". */ key: event.key.toUpperCase(), }; } export function determineKeyBindingTypes( keyBindings: typeof DefaultKeyBindings, keyCombination: KeyCombination, ): Set { const result = new Set(); for (const [keyBindingType, combinations] of getRecordEntries(keyBindings)) { for (const combination of combinations) { if ( !combination || combination.control !== keyCombination.control || combination.alt !== keyCombination.alt || combination.shift !== keyCombination.shift || combination.meta !== keyCombination.meta || combination.key !== keyCombination.key ) { continue; } result.add(keyBindingType); } } return result; } export function isKeyCombinationPressed( keyCombination: KeyCombination, requiredCombination: { control?: boolean; alt?: boolean; shift?: boolean; meta?: boolean; key: string; }, ): boolean { for (const key of ["control", "alt", "shift", "meta"] as const) { if (requiredCombination[key] === undefined) { requiredCombination[key] = false; } } return ( requiredCombination.control === keyCombination.control && requiredCombination.alt === keyCombination.alt && requiredCombination.shift === keyCombination.shift && requiredCombination.meta === keyCombination.meta && requiredCombination.key === keyCombination.key ); } /** * This function can be called in situations that the parameter is a string, not just KeyBindingType. */ export function isSpoilerKeyBindingType(keyBindingType: string): boolean { return SpoilerKeyBindingTypes.some((value) => value === keyBindingType); } export enum KeyBindingEventType { StartSettingUp, StopSettingUp, } export const KeyBindingEvents = new EventEmitter<[KeyBindingEventType]>();