From 8a60acc6e39233d0d1efdcaa004845ccd3adba98 Mon Sep 17 00:00:00 2001 From: tobspr Date: Fri, 14 Jan 2022 19:01:38 +0100 Subject: [PATCH] Minor Keybindings refactor, add support for keybindings to mods, add support for dialogs to mods --- src/css/states/mods.scss | 7 +- src/css/states/settings.scss | 4 - src/js/core/input_distributor.js | 1 + src/js/core/modal_dialog_elements.js | 3 +- src/js/game/key_action_mapper.js | 259 ++++++++++++++++---------- src/js/mods/demo_mod.nobuild/index.js | 14 ++ src/js/mods/mod.js | 4 + src/js/mods/mod_interface.js | 68 ++++++- src/js/mods/mod_signals.js | 6 +- src/js/mods/modloader.js | 9 + src/js/states/ingame.js | 7 + src/js/states/keybindings.js | 20 +- translations/base-en.yaml | 1 + 13 files changed, 286 insertions(+), 117 deletions(-) diff --git a/src/css/states/mods.scss b/src/css/states/mods.scss index b0d0cb9b..54d8422b 100644 --- a/src/css/states/mods.scss +++ b/src/css/states/mods.scss @@ -89,10 +89,10 @@ @include S(border-radius, $globalBorderRadius); background: $accentColorBright; @include S(margin-bottom, 4px); - @include S(padding, 7px); - @include S(grid-gap, 5px); + @include S(padding, 7px, 10px); + @include S(grid-gap, 15px); display: grid; - grid-template-columns: 1fr D(100px) D(100px) D(100px); + grid-template-columns: 1fr D(100px) D(80px) D(50px); .checkbox { align-self: center; @@ -120,6 +120,7 @@ .author { display: flex; flex-direction: column; + align-self: center; strong { text-transform: uppercase; color: $accentColorDark; diff --git a/src/css/states/settings.scss b/src/css/states/settings.scss index 75ef6b85..15187cda 100644 --- a/src/css/states/settings.scss +++ b/src/css/states/settings.scss @@ -105,10 +105,6 @@ } } - button.privacy { - @include S(margin-top, 4px); - } - .versionbar { @include S(margin-top, 10px); diff --git a/src/js/core/input_distributor.js b/src/js/core/input_distributor.js index 03ad8e0c..debdba6c 100644 --- a/src/js/core/input_distributor.js +++ b/src/js/core/input_distributor.js @@ -211,6 +211,7 @@ export class InputDistributor { keyCode: keyCode, shift: event.shiftKey, alt: event.altKey, + ctrl: event.ctrlKey, initial: isInitial, event, }) === STOP_PROPAGATION diff --git a/src/js/core/modal_dialog_elements.js b/src/js/core/modal_dialog_elements.js index ee552aa9..5af7d9d1 100644 --- a/src/js/core/modal_dialog_elements.js +++ b/src/js/core/modal_dialog_elements.js @@ -90,8 +90,9 @@ export class Dialog { * @param {number} param0.keyCode * @param {boolean} param0.shift * @param {boolean} param0.alt + * @param {boolean} param0.ctrl */ - handleKeydown({ keyCode, shift, alt }) { + handleKeydown({ keyCode, shift, alt, ctrl }) { if (keyCode === kbEnter && this.enterHandler) { this.internalButtonHandler(this.enterHandler); return STOP_PROPAGATION; diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 090b8b83..3378368a 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -7,118 +7,152 @@ import { Application } from "../application"; import { Signal, STOP_PROPAGATION } from "../core/signal"; import { IS_MOBILE } from "../core/config"; import { T } from "../translations"; -function key(str) { + +export function keyToKeyCode(str) { return str.toUpperCase().charCodeAt(0); } -const KEYCODE_UP_ARROW = 38; -const KEYCODE_DOWN_ARROW = 40; -const KEYCODE_LEFT_ARROW = 37; -const KEYCODE_RIGHT_ARROW = 39; +export const KEYCODES = { + Tab: 9, + Enter: 13, + + Shift: 16, + Ctrl: 17, + Alt: 18, + + Escape: 27, + + Space: 32, + + ArrowLeft: 37, + ArrowUp: 38, + ArrowRight: 39, + ArrowDown: 40, + + Delete: 46, + + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + + Plus: 187, + Minus: 189, +}; export const KEYMAPPINGS = { + // Make sure mods come first so they can override everything + mods: {}, + general: { - confirm: { keyCode: 13 }, // enter - back: { keyCode: 27, builtin: true }, // escape + confirm: { keyCode: KEYCODES.Enter }, + back: { keyCode: KEYCODES.Escape, builtin: true }, }, ingame: { - menuOpenShop: { keyCode: key("F") }, - menuOpenStats: { keyCode: key("G") }, - menuClose: { keyCode: key("Q") }, + menuOpenShop: { keyCode: keyToKeyCode("F") }, + menuOpenStats: { keyCode: keyToKeyCode("G") }, + menuClose: { keyCode: keyToKeyCode("Q") }, - toggleHud: { keyCode: 113 }, // F2 - exportScreenshot: { keyCode: 114 }, // F3PS - toggleFPSInfo: { keyCode: 115 }, // F4 + toggleHud: { keyCode: KEYCODES.F2 }, + exportScreenshot: { keyCode: KEYCODES.F3 }, + toggleFPSInfo: { keyCode: KEYCODES.F4 }, - switchLayers: { keyCode: key("E") }, + switchLayers: { keyCode: keyToKeyCode("E") }, - showShapeTooltip: { keyCode: 18 }, // ALT + showShapeTooltip: { keyCode: KEYCODES.Alt }, }, navigation: { - mapMoveUp: { keyCode: key("W") }, - mapMoveRight: { keyCode: key("D") }, - mapMoveDown: { keyCode: key("S") }, - mapMoveLeft: { keyCode: key("A") }, - mapMoveFaster: { keyCode: 16 }, //shift + mapMoveUp: { keyCode: keyToKeyCode("W") }, + mapMoveRight: { keyCode: keyToKeyCode("D") }, + mapMoveDown: { keyCode: keyToKeyCode("S") }, + mapMoveLeft: { keyCode: keyToKeyCode("A") }, + mapMoveFaster: { keyCode: KEYCODES.Shift }, - centerMap: { keyCode: 32 }, // SPACE - mapZoomIn: { keyCode: 187, repeated: true }, // "+" - mapZoomOut: { keyCode: 189, repeated: true }, // "-" - - createMarker: { keyCode: key("M") }, + centerMap: { keyCode: KEYCODES.Space }, + mapZoomIn: { keyCode: KEYCODES.Plus, repeated: true }, + mapZoomOut: { keyCode: KEYCODES.Minus, repeated: true }, + createMarker: { keyCode: keyToKeyCode("M") }, }, buildings: { // Puzzle buildings - constant_producer: { keyCode: key("H") }, - goal_acceptor: { keyCode: key("N") }, - block: { keyCode: key("4") }, + constant_producer: { keyCode: keyToKeyCode("H") }, + goal_acceptor: { keyCode: keyToKeyCode("N") }, + block: { keyCode: keyToKeyCode("4") }, // Primary Toolbar - belt: { keyCode: key("1") }, - balancer: { keyCode: key("2") }, - underground_belt: { keyCode: key("3") }, - miner: { keyCode: key("4") }, - cutter: { keyCode: key("5") }, - rotater: { keyCode: key("6") }, - stacker: { keyCode: key("7") }, - mixer: { keyCode: key("8") }, - painter: { keyCode: key("9") }, - trash: { keyCode: key("0") }, + belt: { keyCode: keyToKeyCode("1") }, + balancer: { keyCode: keyToKeyCode("2") }, + underground_belt: { keyCode: keyToKeyCode("3") }, + miner: { keyCode: keyToKeyCode("4") }, + cutter: { keyCode: keyToKeyCode("5") }, + rotater: { keyCode: keyToKeyCode("6") }, + stacker: { keyCode: keyToKeyCode("7") }, + mixer: { keyCode: keyToKeyCode("8") }, + painter: { keyCode: keyToKeyCode("9") }, + trash: { keyCode: keyToKeyCode("0") }, // Sandbox - item_producer: { keyCode: key("L") }, + item_producer: { keyCode: keyToKeyCode("L") }, // Secondary toolbar - storage: { keyCode: key("Y") }, - reader: { keyCode: key("U") }, - lever: { keyCode: key("I") }, - filter: { keyCode: key("O") }, - display: { keyCode: key("P") }, + storage: { keyCode: keyToKeyCode("Y") }, + reader: { keyCode: keyToKeyCode("U") }, + lever: { keyCode: keyToKeyCode("I") }, + filter: { keyCode: keyToKeyCode("O") }, + display: { keyCode: keyToKeyCode("P") }, // Wires toolbar - wire: { keyCode: key("1") }, - wire_tunnel: { keyCode: key("2") }, - constant_signal: { keyCode: key("3") }, - logic_gate: { keyCode: key("4") }, - virtual_processor: { keyCode: key("5") }, - analyzer: { keyCode: key("6") }, - comparator: { keyCode: key("7") }, - transistor: { keyCode: key("8") }, + wire: { keyCode: keyToKeyCode("1") }, + wire_tunnel: { keyCode: keyToKeyCode("2") }, + constant_signal: { keyCode: keyToKeyCode("3") }, + logic_gate: { keyCode: keyToKeyCode("4") }, + virtual_processor: { keyCode: keyToKeyCode("5") }, + analyzer: { keyCode: keyToKeyCode("6") }, + comparator: { keyCode: keyToKeyCode("7") }, + transistor: { keyCode: keyToKeyCode("8") }, }, placement: { - pipette: { keyCode: key("Q") }, - rotateWhilePlacing: { keyCode: key("R") }, - rotateInverseModifier: { keyCode: 16 }, // SHIFT - rotateToUp: { keyCode: KEYCODE_UP_ARROW }, - rotateToDown: { keyCode: KEYCODE_DOWN_ARROW }, - rotateToRight: { keyCode: KEYCODE_RIGHT_ARROW }, - rotateToLeft: { keyCode: KEYCODE_LEFT_ARROW }, - cycleBuildingVariants: { keyCode: key("T") }, - cycleBuildings: { keyCode: 9 }, // TAB - switchDirectionLockSide: { keyCode: key("R") }, + pipette: { keyCode: keyToKeyCode("Q") }, + rotateWhilePlacing: { keyCode: keyToKeyCode("R") }, + rotateInverseModifier: { keyCode: KEYCODES.Shift }, + rotateToUp: { keyCode: KEYCODES.ArrowUp }, + rotateToDown: { keyCode: KEYCODES.ArrowDown }, + rotateToRight: { keyCode: KEYCODES.ArrowRight }, + rotateToLeft: { keyCode: KEYCODES.ArrowLeft }, + cycleBuildingVariants: { keyCode: keyToKeyCode("T") }, + cycleBuildings: { keyCode: KEYCODES.Tab }, + switchDirectionLockSide: { keyCode: keyToKeyCode("R") }, - copyWireValue: { keyCode: key("Z") }, + copyWireValue: { keyCode: keyToKeyCode("Z") }, }, massSelect: { - massSelectStart: { keyCode: 17 }, // CTRL - massSelectSelectMultiple: { keyCode: 16 }, // SHIFT - massSelectCopy: { keyCode: key("C") }, - massSelectCut: { keyCode: key("X") }, - massSelectClear: { keyCode: key("B") }, - confirmMassDelete: { keyCode: 46 }, // DEL - pasteLastBlueprint: { keyCode: key("V") }, + massSelectStart: { keyCode: KEYCODES.Ctrl }, + massSelectSelectMultiple: { keyCode: KEYCODES.Shift }, + massSelectCopy: { keyCode: keyToKeyCode("C") }, + massSelectCut: { keyCode: keyToKeyCode("X") }, + massSelectClear: { keyCode: keyToKeyCode("B") }, + confirmMassDelete: { keyCode: KEYCODES.Delete }, + pasteLastBlueprint: { keyCode: keyToKeyCode("V") }, }, placementModifiers: { - lockBeltDirection: { keyCode: 16 }, // SHIFT - placementDisableAutoOrientation: { keyCode: 17 }, // CTRL - placeMultiple: { keyCode: 16 }, // SHIFT - placeInverse: { keyCode: 18 }, // ALT + lockBeltDirection: { keyCode: KEYCODES.Shift }, + placementDisableAutoOrientation: { keyCode: KEYCODES.Ctrl }, + placeMultiple: { keyCode: KEYCODES.Shift }, + placeInverse: { keyCode: KEYCODES.Alt }, }, }; @@ -153,23 +187,23 @@ export function getStringForKeyCode(code) { return "MB5"; case 8: return "⌫"; - case 9: + case KEYCODES.Tab: return T.global.keys.tab; - case 13: + case KEYCODES.Enter: return "⏎"; - case 16: + case KEYCODES.Shift: return "⇪"; - case 17: + case KEYCODES.Ctrl: return T.global.keys.control; - case 18: + case KEYCODES.Alt: return T.global.keys.alt; case 19: return "PAUSE"; case 20: return "CAPS"; - case 27: + case KEYCODES.Escape: return T.global.keys.escape; - case 32: + case KEYCODES.Space: return T.global.keys.space; case 33: return "PGUP"; @@ -179,13 +213,13 @@ export function getStringForKeyCode(code) { return "END"; case 36: return "HOME"; - case KEYCODE_LEFT_ARROW: + case KEYCODES.ArrowLeft: return "⬅"; - case KEYCODE_UP_ARROW: + case KEYCODES.ArrowUp: return "⬆"; - case KEYCODE_RIGHT_ARROW: + case KEYCODES.ArrowRight: return "➡"; - case KEYCODE_DOWN_ARROW: + case KEYCODES.ArrowDown: return "⬇"; case 44: return "PRNT"; @@ -225,29 +259,29 @@ export function getStringForKeyCode(code) { return "."; case 111: return "/"; - case 112: + case KEYCODES.F1: return "F1"; - case 113: + case KEYCODES.F2: return "F2"; - case 114: + case KEYCODES.F3: return "F3"; - case 115: + case KEYCODES.F4: return "F4"; - case 116: + case KEYCODES.F5: return "F5"; - case 117: + case KEYCODES.F6: return "F6"; - case 118: + case KEYCODES.F7: return "F7"; - case 119: + case KEYCODES.F8: return "F8"; - case 120: + case KEYCODES.F9: return "F9"; - case 121: + case KEYCODES.F10: return "F10"; - case 122: + case KEYCODES.F11: return "F11"; - case 123: + case KEYCODES.F12: return "F12"; case 144: @@ -296,8 +330,9 @@ export class Keybinding { * @param {number} param0.keyCode * @param {boolean=} param0.builtin * @param {boolean=} param0.repeated + * @param {{ shift?: boolean; alt?: boolean; ctrl?: boolean; }=} param0.modifiers */ - constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) { + constructor(keyMapper, app, { keyCode, builtin = false, repeated = false, modifiers = {} }) { assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode); this.keyMapper = keyMapper; this.app = app; @@ -305,6 +340,8 @@ export class Keybinding { this.builtin = builtin; this.repeated = repeated; + this.modifiers = modifiers; + this.signal = new Signal(); this.toggled = new Signal(); } @@ -395,7 +432,6 @@ export class KeyActionMapper { if (overrides[key]) { payload.keyCode = overrides[key]; } - this.keybindings[key] = new Keybinding(this, this.root.app, payload); if (G_IS_DEV) { @@ -459,9 +495,10 @@ export class KeyActionMapper { * @param {number} param0.keyCode * @param {boolean} param0.shift * @param {boolean} param0.alt + * @param {boolean} param0.ctrl * @param {boolean=} param0.initial */ - handleKeydown({ keyCode, shift, alt, initial }) { + handleKeydown({ keyCode, shift, alt, ctrl, initial }) { let stop = false; // Find mapping @@ -469,6 +506,18 @@ export class KeyActionMapper { /** @type {Keybinding} */ const binding = this.keybindings[key]; if (binding.keyCode === keyCode && (initial || binding.repeated)) { + if (binding.modifiers.shift && !shift) { + continue; + } + + if (binding.modifiers.ctrl && !ctrl) { + continue; + } + + if (binding.modifiers.alt && !alt) { + continue; + } + /** @type {Signal} */ const signal = this.keybindings[key].signal; if (signal.dispatch() === STOP_PROPAGATION) { @@ -505,4 +554,14 @@ export class KeyActionMapper { assert(this.keybindings[id], "Keybinding " + id + " not known!"); return this.keybindings[id]; } + + /** + * Returns a given keybinding + * @param {string} id + * @returns {Keybinding} + */ + getBindingById(id) { + assert(this.keybindings[id], "Keybinding " + id + " not known!"); + return this.keybindings[id]; + } } diff --git a/src/js/mods/demo_mod.nobuild/index.js b/src/js/mods/demo_mod.nobuild/index.js index 083d9ca6..35cd8532 100644 --- a/src/js/mods/demo_mod.nobuild/index.js +++ b/src/js/mods/demo_mod.nobuild/index.js @@ -194,6 +194,20 @@ registerMod(shapez => { element.primaryBuildings.push(MetaDemoModBuilding); } }); + + // Register keybinding + this.modInterface.registerIngameKeybinding({ + id: "demo_mod_binding", + keyCode: shapez.keyToKeyCode("F"), + translation: "mymod: Do something (always with SHIFT)", + modifiers: { + shift: true, + }, + handler: root => { + this.dialogs.showInfo("Mod Message", "It worked!"); + return shapez.STOP_PROPAGATION; + }, + }); } }; }); diff --git a/src/js/mods/mod.js b/src/js/mods/mod.js index 4e14bfe2..0bfe3693 100644 --- a/src/js/mods/mod.js +++ b/src/js/mods/mod.js @@ -29,4 +29,8 @@ export class Mod { } init() {} + + get dialogs() { + return this.modInterface.dialogs; + } } diff --git a/src/js/mods/mod_interface.js b/src/js/mods/mod_interface.js index 3ab05af3..f5af7ef5 100644 --- a/src/js/mods/mod_interface.js +++ b/src/js/mods/mod_interface.js @@ -6,7 +6,6 @@ import { MetaBuilding } from "../game/meta_building"; /* typehints:end */ import { defaultBuildingVariant } from "../game/meta_building"; -import { createLogger } from "../core/logging"; import { AtlasSprite, SpriteAtlasLink } from "../core/sprites"; import { enumShortcodeToSubShape, @@ -22,8 +21,8 @@ import { gComponentRegistry, gMetaBuildingRegistry } from "../core/global_regist import { MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS } from "../game/map_chunk"; import { MODS_ADDITIONAL_SYSTEMS } from "../game/game_system_manager"; import { MOD_CHUNK_DRAW_HOOKS } from "../game/map_chunk_view"; - -const LOG = createLogger("mod-interface"); +import { KEYMAPPINGS } from "../game/key_action_mapper"; +import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs"; export class ModInterface { /** @@ -32,9 +31,6 @@ export class ModInterface { */ constructor(modLoader) { this.modLoader = modLoader; - - /** @type {Map} */ - this.lazySprites = new Map(); } registerCss(cssString) { @@ -249,6 +245,66 @@ export class ModInterface { } } + /** + * + * @param {Object} param0 + * @param {string} param0.id + * @param {number} param0.keyCode + * @param {string} param0.translation + * @param {boolean=} param0.repeated + * @param {((GameRoot) => void)=} param0.handler + * @param {{shift?: boolean; alt?: boolean; ctrl?: boolean}=} param0.modifiers + * @param {boolean=} param0.builtin + */ + registerIngameKeybinding({ + id, + keyCode, + translation, + modifiers = {}, + repeated = false, + builtin = false, + handler = null, + }) { + if (!KEYMAPPINGS.mods) { + KEYMAPPINGS.mods = {}; + } + const binding = (KEYMAPPINGS.mods[id] = { + keyCode, + id, + repeated, + modifiers, + builtin, + }); + this.registerTranslations("en", { + keybindings: { + mappings: { + [id]: translation, + }, + }, + }); + + if (handler) { + this.modLoader.signals.gameStarted.add(root => { + root.keyMapper.getBindingById(id).addToTop(handler.bind(null, root)); + }); + } + + return binding; + } + + /** + * @returns {HUDModalDialogs} + */ + get dialogs() { + const state = this.modLoader.app.stateMgr.currentState; + // @ts-ignore + if (state.dialogs) { + // @ts-ignore + return state.dialogs; + } + throw new Error("Tried to access dialogs but current state doesn't support it"); + } + setBuildingToolbarIcon(buildingId, iconBase64) { this.registerCss(` [data-icon="building_icons/${buildingId}.png"] .icon { diff --git a/src/js/mods/mod_signals.js b/src/js/mods/mod_signals.js index 7701e10f..9b893e66 100644 --- a/src/js/mods/mod_signals.js +++ b/src/js/mods/mod_signals.js @@ -1,8 +1,10 @@ -import { Signal } from "../core/signal"; /* typehints:start */ import { BaseHUDPart } from "../game/hud/base_hud_part"; +import { GameRoot } from "../game/root"; /* typehints:end */ +import { Signal } from "../core/signal"; + // Single file to avoid circular deps export const MOD_SIGNALS = { @@ -15,4 +17,6 @@ export const MOD_SIGNALS = { hudElementInitialized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()), hudElementFinalized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()), + + gameStarted: /** @type {TypedSignal<[GameRoot]>} */ (new Signal()), }; diff --git a/src/js/mods/modloader.js b/src/js/mods/modloader.js index c8e57f0b..f9926eff 100644 --- a/src/js/mods/modloader.js +++ b/src/js/mods/modloader.js @@ -1,3 +1,7 @@ +/* typehints:start */ +import { Application } from "../application"; +/* typehints:end */ + import { globalConfig } from "../core/config"; import { createLogger } from "../core/logging"; import { getIPCRenderer } from "../core/utils"; @@ -11,6 +15,11 @@ export class ModLoader { constructor() { LOG.log("modloader created"); + /** + * @type {Application} + */ + this.app = undefined; + /** @type {Mod[]} */ this.mods = []; diff --git a/src/js/states/ingame.js b/src/js/states/ingame.js index 0dd6c72a..1d16c4f4 100644 --- a/src/js/states/ingame.js +++ b/src/js/states/ingame.js @@ -9,6 +9,7 @@ import { Savegame } from "../savegame/savegame"; import { GameCore } from "../game/core"; import { MUSIC } from "../platform/sound"; import { enumGameModeIds } from "../game/game_mode"; +import { MOD_SIGNALS } from "../mods/mod_signals"; const logger = createLogger("state/ingame"); @@ -82,6 +83,10 @@ export class InGameState extends GameState { this.currentSavePromise = null; } + get dialogs() { + return this.core.root.hud.parts.dialogs; + } + /** * Switches the game into another sub-state * @param {string} stage @@ -318,6 +323,8 @@ export class InGameState extends GameState { // Initial resize, might have changed during loading (this is possible) this.core.resize(this.app.screenWidth, this.app.screenHeight); + + MOD_SIGNALS.gameStarted.dispatch(this.core.root); } } diff --git a/src/js/states/keybindings.js b/src/js/states/keybindings.js index a01629f1..e6721bf8 100644 --- a/src/js/states/keybindings.js +++ b/src/js/states/keybindings.js @@ -19,7 +19,7 @@ export class KeybindingsState extends TextualGameState {
${T.keybindings.hint} - +
@@ -34,6 +34,10 @@ export class KeybindingsState extends TextualGameState { this.trackClicks(this.htmlElement.querySelector(".resetBindings"), this.resetBindings); for (const category in KEYMAPPINGS) { + if (Object.keys(KEYMAPPINGS[category]).length === 0) { + continue; + } + const categoryDiv = document.createElement("div"); categoryDiv.classList.add("category"); keybindingsElem.appendChild(categoryDiv); @@ -138,7 +142,19 @@ export class KeybindingsState extends TextualGameState { } const mappingDiv = container.querySelector(".mapping"); - mappingDiv.innerHTML = getStringForKeyCode(keyCode); + let modifiers = ""; + + if (mapped.modifiers && mapped.modifiers.shift) { + modifiers += "⇪ "; + } + if (mapped.modifiers && mapped.modifiers.alt) { + modifiers += T.global.keys.alt + " "; + } + if (mapped.modifiers && mapped.modifiers.ctrl) { + modifiers += T.global.keys.control + " "; + } + + mappingDiv.innerHTML = modifiers + getStringForKeyCode(keyCode); mappingDiv.classList.toggle("changed", !!overrides[keybindingId]); const resetBtn = container.querySelector("button.resetKeybinding"); diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 14dd610e..2f76c00f 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -1328,6 +1328,7 @@ keybindings: massSelect: Mass Select buildings: Building Shortcuts placementModifiers: Placement Modifiers + mods: Provided by Mods mappings: confirm: Confirm