From 3714a59fca702ad32056ce5c7ccbd89a682d5ebe Mon Sep 17 00:00:00 2001 From: tobspr Date: Tue, 19 May 2020 09:14:40 +0200 Subject: [PATCH] Add customizable keybindings & watermark --- res/ui/icons/edit_key.png | Bin 0 -> 634 bytes res/ui/icons/main_menu_settings.png | Bin 0 -> 2186 bytes res/ui/icons/reset_key.png | Bin 0 -> 1541 bytes src/css/common.scss | 2 +- src/css/main.scss | 2 + src/css/states/about.scss | 5 + src/css/states/keybindings.scss | 54 +++++++ src/css/states/main_menu.scss | 4 +- src/css/states/settings.scss | 14 +- src/css/textual_game_state.scss | 30 +++- src/js/application.js | 11 +- src/js/core/config.js | 2 +- src/js/game/camera.js | 19 +-- src/js/game/hud/base_hud_part.js | 8 +- src/js/game/hud/hud.js | 10 +- src/js/game/hud/parts/building_placer.js | 21 ++- src/js/game/hud/parts/buildings_toolbar.js | 5 +- src/js/game/hud/parts/game_menu.js | 5 +- src/js/game/hud/parts/keybinding_overlay.js | 16 +- src/js/game/hud/parts/mass_selector.js | 13 +- src/js/game/hud/parts/settings_menu.js | 7 +- src/js/game/hud/parts/shop.js | 6 +- src/js/game/hud/parts/statistics.js | 6 +- src/js/game/hud/parts/watermark.js | 27 ++++ src/js/game/key_action_mapper.js | 92 ++++++----- src/js/profile/application_settings.js | 38 ++++- src/js/states/about.js | 35 ++++ src/js/states/keybindings.js | 171 ++++++++++++++++++++ src/js/states/settings.js | 16 +- translations/base-en.yaml | 91 +++++++++-- 30 files changed, 581 insertions(+), 129 deletions(-) create mode 100644 res/ui/icons/edit_key.png create mode 100644 res/ui/icons/main_menu_settings.png create mode 100644 res/ui/icons/reset_key.png create mode 100644 src/css/states/about.scss create mode 100644 src/css/states/keybindings.scss create mode 100644 src/js/game/hud/parts/watermark.js create mode 100644 src/js/states/about.js create mode 100644 src/js/states/keybindings.js diff --git a/res/ui/icons/edit_key.png b/res/ui/icons/edit_key.png new file mode 100644 index 0000000000000000000000000000000000000000..544342d5f52d5cb88968969ca95ef4cb51605337 GIT binary patch literal 634 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 literal 0 HcmV?d00001 diff --git a/res/ui/icons/reset_key.png b/res/ui/icons/reset_key.png new file mode 100644 index 0000000000000000000000000000000000000000..ee457b5dd60f70c87297a22fce547a51bc3f1550 GIT binary patch literal 1541 zcmZ`%Yfuwc6utoh0l`tBVkx#13rYiVvzyn300{&M@(Kx2d=v>71tP-42q+XmtpWu> zQAFhBvs6)(msmR%L@TIZA7jx`ELBj7k18OPhqPyd<4k{a=kC4de)s#%ckbEEmIwP; zS=d=1gsl8!QU$DH>SY+iTy`PzIV>jYB|#E|&gajVRMTPa5G_*#A+*&Qp^TjfjRTa? zkI-fep@B$*gvAKiCYF8|Dnf|XBM%Jo1p-NuY#tBi3J8MF;qVDUKu|F?;~aqjAyq0B zm&+wMf_DQ>2?3wa2RS9=lJAdz4@D|a=9>*)RsaFOoKHPhGAj=ajJN@&9ns{$N3b9^I0q*sBqjM zq$E@z7K4PuaUKkChXJ7Bjv#-h>UuQ*k*cWaoNvE${@*LCr>M$4{D zO*Ht@R@}Wo`Yk$buZno{%Jk^&85cY)uAVKBzr9@;tCU7%kJ&tbnLdBky|S<8tpAJX zVa>hd(pj0?Z}P<3qc&rbc`iio%vw?({Oz^Hztcr=y#q6gwPylx-RA3ukIXxnHuPiA zi=qHaS>EGY?Xz308q0SMJ6GA=X=@pGNcn!`{0EGcdlastOXonPw5R|-DaxYHm%+w7 zQMO_eLNl*YFYN$x=0^aU>HULz&3ft9rdG_~e%Y3TkdddqR1y~6`r_czHP&qV-8J-k z^p%1|a&gPiA`6BrG*hul-jEUVRc(%O;*QFyK(pXdSF@5I?i^DUuC8OHrCcA?q;DyA zKKOHpjm9h0u8?F+iM^u~x}|X?*RZnkeS6I#s~f8~7}u8+YR$%b`)nDH_FQok2eZTM zMbf?i9C@7B{myEo^%i+p=LJ0CYReag#CizmZ=Nb0*Ly z#e^7Mcc>t$@7Ig6XQG?uDkoCctX{b%Y1>Fz`$Wy&*c4jSpw8)4oV|6OWs$!_!GSLQ zjz9CV)KhNVJ?RS_iZx}8*`uNdmj~pc$&_e{O0!Rh=5L8iz)7+?b=Xf zSJWr=&v>DteR&;c5*XcM-9xR6kK03rlG4gX`o|l_Qk(xiruBL>W^6Mw*e%nxwZhNk zr1uTiuT|?c!f2fa7KCFs=7w?I*kl;S6S4^*hhSou5W|*pGpqk2h)-A>yKc+>3!G!f wSs?h3a!@9$(kq#a*#Qap`vyzTdPi>m7gzBY9{>OV literal 0 HcmV?d00001 diff --git a/src/css/common.scss b/src/css/common.scss index 55af39dd..34d6f5b5 100644 --- a/src/css/common.scss +++ b/src/css/common.scss @@ -523,7 +523,7 @@ canvas { } .checkbox { - $bgColor: darken($mainBgColor, 0); + $bgColor: darken($mainBgColor, 3); background-color: $bgColor; @include S(width, 35px); @include S(height, 17px); diff --git a/src/css/main.scss b/src/css/main.scss index ca97440f..c5fe6305 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -23,7 +23,9 @@ @import "states/preload"; @import "states/main_menu"; @import "states/ingame"; +@import "states/keybindings"; @import "states/settings"; +@import "states/about"; @import "ingame_hud/buildings_toolbar"; @import "ingame_hud/building_placer"; diff --git a/src/css/states/about.scss b/src/css/states/about.scss new file mode 100644 index 00000000..1875804e --- /dev/null +++ b/src/css/states/about.scss @@ -0,0 +1,5 @@ +#state_AboutState { + .content { + @include PlainText; + } +} diff --git a/src/css/states/keybindings.scss b/src/css/states/keybindings.scss new file mode 100644 index 00000000..f5ee3170 --- /dev/null +++ b/src/css/states/keybindings.scss @@ -0,0 +1,54 @@ +#state_KeybindingsState { + .content { + .topEntries { + display: grid; + grid-template-columns: 1fr auto; + @include S(grid-gap, 5px); + @include S(margin-bottom, 10px); + } + + .hint { + display: block; + background: #eee; + @include S(padding, 4px); + @include PlainText; + } + + .category { + .entry { + display: grid; + @include S(margin-top, 2px); + @include S(padding-top, 2px); + @include S(grid-gap, 4px); + grid-template-columns: 1fr #{D(100px)} auto auto; + border-bottom: #{D(1px)} dotted #eee; + color: #888c8f; + .mapping { + color: $colorBlueBright; + text-align: center; + } + + button { + @include S(height, 15px); + @include S(width, 15px); + @include IncreasedClickArea(0px); + background: transparent center center / 40% no-repeat; + opacity: 0.9; + &.editKeybinding { + background-image: uiResource("icons/edit_key.png"); + } + + &.resetKeybinding { + background-image: uiResource("icons/reset_key.png"); + } + + &.disabled { + pointer-events: none; + cursor: default; + opacity: 0.1 !important; + } + } + } + } + } +} diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index 13157dc0..d933bbde 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -9,13 +9,13 @@ .settingsButton { position: absolute; - @include S(bottom, 30px); + @include S(top, 30px); @include S(right, 30px); @include S(width, 35px); @include S(height, 35px); pointer-events: all; cursor: pointer; - background: uiResource("icons/settings.png") center center / contain no-repeat; + background: uiResource("icons/main_menu_settings.png") center center / contain no-repeat; transition: opacity 0.12s ease-in-out; &:hover { opacity: 0.9; diff --git a/src/css/states/settings.scss b/src/css/states/settings.scss index 0c9ca862..61deac4e 100644 --- a/src/css/states/settings.scss +++ b/src/css/states/settings.scss @@ -1,13 +1,5 @@ #state_SettingsState { .content { - .categoryLabel { - display: block; - text-transform: uppercase; - @include S(margin-top, 15px); - @include S(margin-bottom, 15px); - @include Heading; - } - .versionbar { @include S(margin-top, 20px); @include SuperSmallText; @@ -21,9 +13,13 @@ } } + button.about { + background-color: $colorGreenBright; + } + .setting { @include S(padding, 10px); - background: #eee; + background: #eeeff5; @include S(border-radius, $globalBorderRadius); @include S(margin-bottom, 5px); diff --git a/src/css/textual_game_state.scss b/src/css/textual_game_state.scss index 617b7c26..deac7faf 100644 --- a/src/css/textual_game_state.scss +++ b/src/css/textual_game_state.scss @@ -38,13 +38,27 @@ @include S(margin-bottom, 20px); } - .content { - background: #fff; - @include S(border-radius, $globalBorderRadius); - @include S(padding, 10px); - max-height: calc(80vh - #{D(60px)}); - overflow-y: auto; - box-sizing: border-box; - pointer-events: all; + > .container { + > .content { + background: #fff; + @include S(border-radius, $globalBorderRadius); + @include S(padding, 10px); + height: calc(80vh - #{D(60px)}); + overflow-y: auto; + box-sizing: border-box; + pointer-events: all; + + a { + color: $colorBlueBright; + } + + .categoryLabel { + display: block; + text-transform: uppercase; + @include S(margin-top, 15px); + @include S(margin-bottom, 15px); + @include Heading; + } + } } } diff --git a/src/js/application.js b/src/js/application.js index 1832939a..7a1941ea 100644 --- a/src/js/application.js +++ b/src/js/application.js @@ -32,6 +32,8 @@ import { queryParamOptions } from "./core/query_parameters"; import { NoGameAnalytics } from "./platform/browser/no_game_analytics"; import { StorageImplBrowserIndexedDB } from "./platform/browser/storage_indexed_db"; import { SettingsState } from "./states/settings"; +import { KeybindingsState } from "./states/keybindings"; +import { AboutState } from "./states/about"; const logger = createLogger("application"); @@ -143,7 +145,14 @@ export class Application { */ registerStates() { /** @type {Array} */ - const states = [PreloadState, MainMenuState, InGameState, SettingsState]; + const states = [ + PreloadState, + MainMenuState, + InGameState, + SettingsState, + KeybindingsState, + AboutState, + ]; for (let i = 0; i < states.length; ++i) { this.stateMgr.register(states[i]); diff --git a/src/js/core/config.js b/src/js/core/config.js index 93e2b04a..e88962e9 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -71,7 +71,7 @@ export const globalConfig = { debug: { /* dev:start */ - // fastGameEnter: true, + fastGameEnter: true, noArtificialDelays: true, // disableSavegameWrite: true, // showEntityBounds: true, diff --git a/src/js/game/camera.js b/src/js/game/camera.js index c0326fbd..671f3b6e 100644 --- a/src/js/game/camera.js +++ b/src/js/game/camera.js @@ -17,6 +17,7 @@ import { clamp } from "../core/utils"; import { mixVector, Vector } from "../core/vector"; import { BasicSerializableObject, types } from "../savegame/serialization"; import { GameRoot } from "./root"; +import { KEYMAPPINGS } from "./key_action_mapper"; const logger = createLogger("camera"); @@ -330,12 +331,12 @@ export class Camera extends BasicSerializableObject { */ bindKeys() { const mapper = this.root.gameState.keyActionMapper; - mapper.getBinding("map_move_up").add(() => (this.keyboardForce.y = -1)); - mapper.getBinding("map_move_down").add(() => (this.keyboardForce.y = 1)); - mapper.getBinding("map_move_right").add(() => (this.keyboardForce.x = 1)); - mapper.getBinding("map_move_left").add(() => (this.keyboardForce.x = -1)); + mapper.getBinding(KEYMAPPINGS.ingame.mapMoveUp).add(() => (this.keyboardForce.y = -1)); + mapper.getBinding(KEYMAPPINGS.ingame.mapMoveDown).add(() => (this.keyboardForce.y = 1)); + mapper.getBinding(KEYMAPPINGS.ingame.mapMoveRight).add(() => (this.keyboardForce.x = 1)); + mapper.getBinding(KEYMAPPINGS.ingame.mapMoveLeft).add(() => (this.keyboardForce.x = -1)); - mapper.getBinding("center_map").add(() => this.centerOnMap()); + mapper.getBinding(KEYMAPPINGS.ingame.centerMap).add(() => this.centerOnMap()); } centerOnMap() { @@ -867,19 +868,19 @@ export class Camera extends BasicSerializableObject { let forceY = 0; const actionMapper = this.root.gameState.keyActionMapper; - if (actionMapper.getBinding("map_move_up").currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveUp).currentlyDown) { forceY -= 1; } - if (actionMapper.getBinding("map_move_down").currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveDown).currentlyDown) { forceY += 1; } - if (actionMapper.getBinding("map_move_left").currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveLeft).currentlyDown) { forceX -= 1; } - if (actionMapper.getBinding("map_move_right").currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveRight).currentlyDown) { forceX += 1; } diff --git a/src/js/game/hud/base_hud_part.js b/src/js/game/hud/base_hud_part.js index cd6aff17..a700a6b3 100644 --- a/src/js/game/hud/base_hud_part.js +++ b/src/js/game/hud/base_hud_part.js @@ -154,10 +154,10 @@ export class BaseHUDPart { */ forwardMapMovementKeybindings(sourceMapper) { sourceMapper.forward(this.root.gameState.keyActionMapper, [ - "map_move_up", - "map_move_right", - "map_move_down", - "map_move_left", + "mapMoveUp", + "mapMoveRight", + "mapMoveDown", + "mapMoveLeft", ]); } } diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 6551016e..4c2a53a6 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -23,6 +23,8 @@ import { HUDNotifications, enumNotificationType } from "./parts/notifications"; import { HUDSettingsMenu } from "./parts/settings_menu"; import { HUDDebugInfo } from "./parts/debug_info"; import { HUDEntityDebugger } from "./parts/entity_debugger"; +import { KEYMAPPINGS } from "../key_action_mapper"; +import { HUDWatermark } from "./parts/watermark"; export class GameHUD { /** @@ -76,6 +78,10 @@ export class GameHUD { this.parts.entityDebugger = new HUDEntityDebugger(this.root); } + if (!G_IS_STANDALONE && G_IS_RELEASE) { + this.parts.watermark = new HUDWatermark(this.root); + } + const frag = document.createDocumentFragment(); for (const key in this.parts) { this.parts[key].createElements(frag); @@ -88,7 +94,7 @@ export class GameHUD { } this.internalInitSignalConnections(); - this.root.gameState.keyActionMapper.getBinding("toggle_hud").add(this.toggleUi, this); + this.root.gameState.keyActionMapper.getBinding(KEYMAPPINGS.ingame.toggleHud).add(this.toggleUi, this); } /** @@ -186,7 +192,7 @@ export class GameHUD { * @param {DrawParameters} parameters */ drawOverlays(parameters) { - const partsOrder = []; + const partsOrder = ["watermark"]; for (let i = 0; i < partsOrder.length; ++i) { if (this.parts[partsOrder[i]]) { diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index 4ef09727..8c83f17d 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -19,6 +19,7 @@ import { defaultBuildingVariant, MetaBuilding } from "../../meta_building"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { T } from "../../../translations"; +import { KEYMAPPINGS } from "../../key_action_mapper"; export class HUDBuildingPlacer extends BaseHUDPart { initialize() { @@ -30,11 +31,13 @@ export class HUDBuildingPlacer extends BaseHUDPart { this.fakeEntity = null; const keyActionMapper = this.root.gameState.keyActionMapper; - keyActionMapper.getBinding("building_abort_placement").add(this.abortPlacement, this); - keyActionMapper.getBinding("back").add(this.abortPlacement, this); + keyActionMapper + .getBinding(KEYMAPPINGS.placement.abortBuildingPlacement) + .add(this.abortPlacement, this); + keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this); - keyActionMapper.getBinding("rotate_while_placing").add(this.tryRotate, this); - keyActionMapper.getBinding("cycle_variants").add(this.cycleVariants, this); + keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this); + keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this); this.domAttach = new DynamicDomAttach(this.root, this.element, {}); @@ -281,7 +284,9 @@ export class HUDBuildingPlacer extends BaseHUDPart { this.buildingInfoElements.label.innerHTML = T.buildings[metaBuilding.id].name; this.buildingInfoElements.descText.innerHTML = T.buildings[metaBuilding.id].description; - const binding = this.root.gameState.keyActionMapper.getBinding("building_" + metaBuilding.getId()); + const binding = this.root.gameState.keyActionMapper.getBinding( + KEYMAPPINGS.buildings[metaBuilding.getId()] + ); this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace( "", "" + binding.getKeyCodeString() + "" @@ -322,7 +327,9 @@ export class HUDBuildingPlacer extends BaseHUDPart { T.ingame.buildingPlacement.cycleBuildingVariants.replace( "", "" + - this.root.gameState.keyActionMapper.getBinding("cycle_variants").getKeyCodeString() + + this.root.gameState.keyActionMapper + .getBinding(KEYMAPPINGS.placement.cycleBuildingVariants) + .getKeyCodeString() + "" ) ); @@ -452,7 +459,7 @@ export class HUDBuildingPlacer extends BaseHUDPart { ) { // Succesfully placed - if (metaBuilding.getFlipOrientationAfterPlacement()) { + if (metaBuilding.getFlipOrientationAfterPlacement() && !this.root.app.inputMgr.ctrlIsDown) { this.currentBaseRotation = (180 + this.currentBaseRotation) % 360; } diff --git a/src/js/game/hud/parts/buildings_toolbar.js b/src/js/game/hud/parts/buildings_toolbar.js index 011e245e..f7a21a1b 100644 --- a/src/js/game/hud/parts/buildings_toolbar.js +++ b/src/js/game/hud/parts/buildings_toolbar.js @@ -14,6 +14,7 @@ import { MetaTrashBuilding } from "../../buildings/trash"; import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt"; import { MetaBuilding } from "../../meta_building"; import { BaseHUDPart } from "../base_hud_part"; +import { KEYMAPPINGS } from "../../key_action_mapper"; const toolbarBuildings = [ MetaBeltBaseBuilding, @@ -65,7 +66,7 @@ export class HUDBuildingsToolbar extends BaseHUDPart { for (let i = 0; i < toolbarBuildings.length; ++i) { const metaBuilding = gMetaBuildingRegistry.findByClass(toolbarBuildings[i]); - const binding = actionMapper.getBinding("building_" + metaBuilding.getId()); + const binding = actionMapper.getBinding(KEYMAPPINGS.buildings[metaBuilding.getId()]); const itemContainer = makeDiv(items, null, ["building"]); itemContainer.setAttribute("data-icon", "building_icons/" + metaBuilding.getId() + ".png"); @@ -91,7 +92,7 @@ export class HUDBuildingsToolbar extends BaseHUDPart { ); this.lastSelectedIndex = 0; - actionMapper.getBinding("cycle_buildings").add(this.cycleBuildings, this); + actionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildings).add(this.cycleBuildings, this); } update() { diff --git a/src/js/game/hud/parts/game_menu.js b/src/js/game/hud/parts/game_menu.js index e7772a39..5877a5bd 100644 --- a/src/js/game/hud/parts/game_menu.js +++ b/src/js/game/hud/parts/game_menu.js @@ -3,6 +3,7 @@ import { makeDiv, randomInt } from "../../../core/utils"; import { SOUNDS } from "../../../platform/sound"; import { enumNotificationType } from "./notifications"; import { T } from "../../../translations"; +import { KEYMAPPINGS } from "../../key_action_mapper"; export class HUDGameMenu extends BaseHUDPart { initialize() {} @@ -14,7 +15,7 @@ export class HUDGameMenu extends BaseHUDPart { id: "shop", label: "Upgrades", handler: () => this.root.hud.parts.shop.show(), - keybinding: "menu_open_shop", + keybinding: KEYMAPPINGS.ingame.menuOpenShop, badge: () => this.root.hubGoals.getAvailableUpgradeCount(), notification: /** @type {[string, enumNotificationType]} */ ([ T.ingame.notifications.newUpgrade, @@ -25,7 +26,7 @@ export class HUDGameMenu extends BaseHUDPart { id: "stats", label: "Stats", handler: () => this.root.hud.parts.statistics.show(), - keybinding: "menu_open_stats", + keybinding: KEYMAPPINGS.ingame.menuOpenStats, }, ]; diff --git a/src/js/game/hud/parts/keybinding_overlay.js b/src/js/game/hud/parts/keybinding_overlay.js index 20bbb5b4..a1a941dc 100644 --- a/src/js/game/hud/parts/keybinding_overlay.js +++ b/src/js/game/hud/parts/keybinding_overlay.js @@ -1,6 +1,6 @@ import { BaseHUDPart } from "../base_hud_part"; import { makeDiv } from "../../../core/utils"; -import { getStringForKeyCode } from "../../key_action_mapper"; +import { getStringForKeyCode, KEYMAPPINGS } from "../../key_action_mapper"; import { TrackedState } from "../../../core/tracked_state"; import { queryParamOptions } from "../../../core/query_parameters"; import { T } from "../../../translations"; @@ -32,16 +32,16 @@ export class HUDKeybindingOverlay extends BaseHUDPart { [], `
- ${getKeycode("center_map")} + ${getKeycode(KEYMAPPINGS.ingame.centerMap)}
- ${getKeycode("map_move_up")} - ${getKeycode("map_move_left")} - ${getKeycode("map_move_down")} - ${getKeycode("map_move_right")} + ${getKeycode(KEYMAPPINGS.ingame.mapMoveUp)} + ${getKeycode(KEYMAPPINGS.ingame.mapMoveLeft)} + ${getKeycode(KEYMAPPINGS.ingame.mapMoveDown)} + ${getKeycode(KEYMAPPINGS.ingame.mapMoveRight)}
@@ -55,12 +55,12 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
- ${getKeycode("building_abort_placement")} + ${getKeycode(KEYMAPPINGS.placement.abortBuildingPlacement)}
- ${getKeycode("rotate_while_placing")} + ${getKeycode(KEYMAPPINGS.placement.rotateWhilePlacing)}
diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 8ad27acc..3dca5678 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -10,15 +10,18 @@ import { DynamicDomAttach } from "../dynamic_dom_attach"; import { createLogger } from "../../../core/logging"; import { enumMouseButton } from "../../camera"; import { T } from "../../../translations"; +import { KEYMAPPINGS } from "../../key_action_mapper"; const logger = createLogger("hud/mass_selector"); export class HUDMassSelector extends BaseHUDPart { createElements(parent) { const removalKeybinding = this.root.gameState.keyActionMapper - .getBinding("confirm_mass_delete") + .getBinding(KEYMAPPINGS.massSelect.confirmMassDelete) + .getKeyCodeString(); + const abortKeybinding = this.root.gameState.keyActionMapper + .getBinding(KEYMAPPINGS.general.back) .getKeyCodeString(); - const abortKeybinding = this.root.gameState.keyActionMapper.getBinding("back").getKeyCodeString(); this.element = makeDiv( parent, @@ -43,8 +46,10 @@ export class HUDMassSelector extends BaseHUDPart { this.root.camera.movePreHandler.add(this.onMouseMove, this); this.root.camera.upPostHandler.add(this.onMouseUp, this); - this.root.gameState.keyActionMapper.getBinding("back").add(this.onBack, this); - this.root.gameState.keyActionMapper.getBinding("confirm_mass_delete").add(this.confirmDelete, this); + this.root.gameState.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.onBack, this); + this.root.gameState.keyActionMapper + .getBinding(KEYMAPPINGS.massSelect.confirmMassDelete) + .add(this.confirmDelete, this); this.domAttach = new DynamicDomAttach(this.root, this.element); } diff --git a/src/js/game/hud/parts/settings_menu.js b/src/js/game/hud/parts/settings_menu.js index 9ea8abad..4acc04b1 100644 --- a/src/js/game/hud/parts/settings_menu.js +++ b/src/js/game/hud/parts/settings_menu.js @@ -2,7 +2,7 @@ import { BaseHUDPart } from "../base_hud_part"; import { makeDiv, formatSeconds, formatBigNumberFull } from "../../../core/utils"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { InputReceiver } from "../../../core/input_receiver"; -import { KeyActionMapper } from "../../key_action_mapper"; +import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; import { T } from "../../../translations"; import { StaticMapEntityComponent } from "../../components/static_map_entity"; import { ItemProcessorComponent } from "../../components/item_processor"; @@ -72,7 +72,7 @@ export class HUDSettingsMenu extends BaseHUDPart { } initialize() { - this.root.gameState.keyActionMapper.getBinding("back").add(this.show, this); + this.root.gameState.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.show, this); this.domAttach = new DynamicDomAttach(this.root, this.background, { attachClass: "visible", @@ -80,8 +80,7 @@ export class HUDSettingsMenu extends BaseHUDPart { this.inputReciever = new InputReceiver("settingsmenu"); this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); - - this.keyActionMapper.getBinding("back").add(this.close, this); + this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); this.close(); } diff --git a/src/js/game/hud/parts/shop.js b/src/js/game/hud/parts/shop.js index 04c053cb..0445e604 100644 --- a/src/js/game/hud/parts/shop.js +++ b/src/js/game/hud/parts/shop.js @@ -3,7 +3,7 @@ import { ClickDetector } from "../../../core/click_detector"; import { InputReceiver } from "../../../core/input_receiver"; import { formatBigNumber, makeDiv } from "../../../core/utils"; import { T } from "../../../translations"; -import { KeyActionMapper } from "../../key_action_mapper"; +import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; import { UPGRADES } from "../../upgrades"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; @@ -168,8 +168,8 @@ export class HUDShop extends BaseHUDPart { this.inputReciever = new InputReceiver("shop"); this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); - this.keyActionMapper.getBinding("back").add(this.close, this); - this.keyActionMapper.getBinding("menu_open_shop").add(this.close, this); + this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); + this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuOpenShop).add(this.close, this); this.close(); diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js index f8cdd0a8..fc19b3fd 100644 --- a/src/js/game/hud/parts/statistics.js +++ b/src/js/game/hud/parts/statistics.js @@ -1,7 +1,7 @@ import { Math_min } from "../../../core/builtins"; import { InputReceiver } from "../../../core/input_receiver"; import { makeButton, makeDiv, removeAllChildren, capitalizeFirstLetter } from "../../../core/utils"; -import { KeyActionMapper } from "../../key_action_mapper"; +import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; import { enumAnalyticsDataSource } from "../../production_analytics"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; @@ -81,8 +81,8 @@ export class HUDStatistics extends BaseHUDPart { this.inputReciever = new InputReceiver("statistics"); this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); - this.keyActionMapper.getBinding("back").add(this.close, this); - this.keyActionMapper.getBinding("menu_open_stats").add(this.close, this); + this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); + this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuOpenStats).add(this.close, this); /** @type {Object.} */ this.activeHandles = {}; diff --git a/src/js/game/hud/parts/watermark.js b/src/js/game/hud/parts/watermark.js new file mode 100644 index 00000000..2ba1a6e3 --- /dev/null +++ b/src/js/game/hud/parts/watermark.js @@ -0,0 +1,27 @@ +import { BaseHUDPart } from "../base_hud_part"; +import { DrawParameters } from "../../../core/draw_parameters"; + +export class HUDWatermark extends BaseHUDPart { + createElements() {} + + initialize() {} + + /** + * + * @param {DrawParameters} parameters + */ + drawOverlays(parameters) { + const w = this.root.gameWidth; + + parameters.context.fillStyle = "#f77"; + parameters.context.font = "50px GameFont"; + parameters.context.textAlign = "center"; + parameters.context.fillText("DEMO VERSION", w / 2, 100); + + parameters.context.fillStyle = "#aaaca9"; + parameters.context.font = "20px GameFont"; + parameters.context.fillText("Get shapez.io on steam for the full experience!", w / 2, 140); + + parameters.context.textAlign = "left"; + } +} diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index d0a9913e..6aff0e88 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -7,55 +7,72 @@ import { Application } from "../application"; import { Signal, STOP_PROPAGATION } from "../core/signal"; import { IS_MOBILE } from "../core/config"; import { T } from "../translations"; +import { JSON_stringify } from "../core/builtins"; function key(str) { return str.toUpperCase().charCodeAt(0); } -// TODO: Configurable -export const defaultKeybindings = { +export const KEYMAPPINGS = { general: { confirm: { keyCode: 13 }, // enter back: { keyCode: 27, builtin: true }, // escape }, ingame: { - map_move_up: { keyCode: key("W") }, - map_move_right: { keyCode: key("D") }, - map_move_down: { keyCode: key("S") }, - map_move_left: { keyCode: key("A") }, + mapMoveUp: { keyCode: key("W") }, + mapMoveRight: { keyCode: key("D") }, + mapMoveDown: { keyCode: key("S") }, + mapMoveLeft: { keyCode: key("A") }, - center_map: { keyCode: 32 }, + centerMap: { keyCode: 32 }, - menu_open_shop: { keyCode: key("F") }, - menu_open_stats: { keyCode: key("G") }, + menuOpenShop: { keyCode: key("F") }, + menuOpenStats: { keyCode: key("G") }, - confirm_mass_delete: { keyCode: key("X") }, // DEL - toggle_hud: { keyCode: 113 }, // F2 + toggleHud: { keyCode: 113 }, // F2 }, - toolbar: { - building_belt: { keyCode: key("1") }, - building_splitter: { keyCode: key("2") }, - building_underground_belt: { keyCode: key("3") }, - building_miner: { keyCode: key("4") }, - building_cutter: { keyCode: key("5") }, - building_rotater: { keyCode: key("6") }, - building_stacker: { keyCode: key("7") }, - building_mixer: { keyCode: key("8") }, - building_painter: { keyCode: key("9") }, - building_trash: { keyCode: key("0") }, - - building_abort_placement: { keyCode: key("Q") }, + buildings: { + belt: { keyCode: key("1") }, + splitter: { 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") }, + }, - rotate_while_placing: { keyCode: key("R") }, + placement: { + abortBuildingPlacement: { keyCode: key("Q") }, + rotateWhilePlacing: { keyCode: key("R") }, + cycleBuildingVariants: { keyCode: key("T") }, + cycleBuildings: { keyCode: 9 }, // TAB + }, - cycle_variants: { keyCode: key("T") }, + massSelect: { + massSelectStart: { keyCode: 17, builtin: true }, // CTRL + massSelectSelectMultiple: { keyCode: 16, builtin: true }, // SHIFT + confirmMassDelete: { keyCode: key("X") }, + }, - cycle_buildings: { keyCode: 9 }, + placementModifiers: { + placementDisableAutoOrientation: { keyCode: 17, builtin: true }, // CTRL + placeMultiple: { keyCode: 16, builtin: true }, // SHIFT + placeInverse: { keyCode: 18, builtin: true }, // ALT }, }; +// Assign ids +for (const categoryId in KEYMAPPINGS) { + for (const mappingId in KEYMAPPINGS[categoryId]) { + KEYMAPPINGS[categoryId][mappingId].id = mappingId; + } +} + /** * Returns a keycode -> string * @param {number} code @@ -271,14 +288,14 @@ export class KeyActionMapper { /** @type {Object.} */ this.keybindings = {}; - // const overrides = root.app.settings.getKeybindingOverrides(); + const overrides = root.app.settings.getKeybindingOverrides(); - for (const category in defaultKeybindings) { - for (const key in defaultKeybindings[category]) { - let payload = Object.assign({}, defaultKeybindings[category][key]); - // if (overrides[key]) { - // payload.keyCode = overrides[key]; - // } + for (const category in KEYMAPPINGS) { + for (const key in KEYMAPPINGS[category]) { + let payload = Object.assign({}, KEYMAPPINGS[category][key]); + if (overrides[key]) { + payload.keyCode = overrides[key]; + } this.keybindings[key] = new Keybinding(this.root.app, payload); } @@ -380,10 +397,13 @@ export class KeyActionMapper { /** * Returns a given keybinding - * @param {string} id + * @param {{ keyCode: number }} binding * @returns {Keybinding} */ - getBinding(id) { + getBinding(binding) { + // @ts-ignore + const id = binding.id; + assert(id, "Not a valid keybinding: " + JSON_stringify(binding)); assert(this.keybindings[id], "Keybinding " + id + " not known!"); return this.keybindings[id]; } diff --git a/src/js/profile/application_settings.js b/src/js/profile/application_settings.js index 1b784063..95222f35 100644 --- a/src/js/profile/application_settings.js +++ b/src/js/profile/application_settings.js @@ -130,6 +130,11 @@ class SettingsStorage { this.musicMuted = false; this.theme = "light"; this.refreshRate = "60"; + + /** + * @type {Object.} + */ + this.keybindingOverrides = {}; } } @@ -201,6 +206,10 @@ export class ApplicationSettings extends ReadWriteProxy { return this.getAllSettings().fullscreen; } + getKeybindingOverrides() { + return this.getAllSettings().keybindingOverrides; + } + // Setters /** @@ -224,6 +233,33 @@ export class ApplicationSettings extends ReadWriteProxy { assertAlways(false, "Unknown setting: " + key); } + /** + * Sets a new keybinding override + * @param {string} keybindingId + * @param {number} keyCode + */ + updateKeybindingOverride(keybindingId, keyCode) { + assert(Number.isInteger(keyCode), "Not a valid key code: " + keyCode); + this.getAllSettings().keybindingOverrides[keybindingId] = keyCode; + return this.writeAsync(); + } + + /** + * Resets a given keybinding override + * @param {string} id + */ + resetKeybindingOverride(id) { + delete this.getAllSettings().keybindingOverrides[id]; + return this.writeAsync(); + } + /** + * Resets all keybinding overrides + */ + resetKeybindingOverrides() { + this.getAllSettings().keybindingOverrides = {}; + return this.writeAsync(); + } + // RW Proxy impl verify(data) { if (!data.settings) { @@ -252,7 +288,7 @@ export class ApplicationSettings extends ReadWriteProxy { } getCurrentVersion() { - return 4; + return 5; } migrate(data) { diff --git a/src/js/states/about.js b/src/js/states/about.js new file mode 100644 index 00000000..b112335c --- /dev/null +++ b/src/js/states/about.js @@ -0,0 +1,35 @@ +import { TextualGameState } from "../core/textual_game_state"; +import { SOUNDS } from "../platform/sound"; +import { T } from "../translations"; +import { KEYMAPPINGS, getStringForKeyCode } from "../game/key_action_mapper"; +import { Dialog } from "../core/modal_dialog_elements"; + +export class AboutState extends TextualGameState { + constructor() { + super("AboutState"); + } + + getStateHeaderTitle() { + return T.about.title; + } + + getMainContentHTML() { + return ` + This game is open source and developed by Tobias Springer (this is me). +

+ If you want to contribute, check out shapez.io on github. +

+ This game wouldn't have been possible without the great discord community arround my games - You should really join the discord server! +

+ The soundtrack was made by Peppsen - He's awesome. +

+ Finally, huge thanks to my best friend Niklas - Without our factorio sessions this game would never have existed. + `; + } + + onEnter() {} + + getDefaultPreviousState() { + return "SettingsState"; + } +} diff --git a/src/js/states/keybindings.js b/src/js/states/keybindings.js new file mode 100644 index 00000000..f7997850 --- /dev/null +++ b/src/js/states/keybindings.js @@ -0,0 +1,171 @@ +import { TextualGameState } from "../core/textual_game_state"; +import { SOUNDS } from "../platform/sound"; +import { T } from "../translations"; +import { KEYMAPPINGS, getStringForKeyCode } from "../game/key_action_mapper"; +import { Dialog } from "../core/modal_dialog_elements"; + +export class KeybindingsState extends TextualGameState { + constructor() { + super("KeybindingsState"); + } + + getStateHeaderTitle() { + return T.keybindings.title; + } + + getMainContentHTML() { + return ` + +
+ ${T.keybindings.hint} + + +
+ +
+ +
+ `; + } + + onEnter() { + const keybindingsElem = this.htmlElement.querySelector(".keybindings"); + + this.trackClicks(this.htmlElement.querySelector(".resetBindings"), this.resetBindings); + + for (const category in KEYMAPPINGS) { + const categoryDiv = document.createElement("div"); + categoryDiv.classList.add("category"); + keybindingsElem.appendChild(categoryDiv); + + const labelDiv = document.createElement("strong"); + labelDiv.innerText = T.keybindings.categoryLabels[category]; + labelDiv.classList.add("categoryLabel"); + categoryDiv.appendChild(labelDiv); + + for (const keybindingId in KEYMAPPINGS[category]) { + const mapped = KEYMAPPINGS[category][keybindingId]; + + const elem = document.createElement("div"); + elem.classList.add("entry"); + elem.setAttribute("data-keybinding", keybindingId); + categoryDiv.appendChild(elem); + + const title = document.createElement("span"); + title.classList.add("title"); + title.innerText = T.keybindings.mappings[keybindingId]; + elem.appendChild(title); + + const mappingDiv = document.createElement("span"); + mappingDiv.classList.add("mapping"); + elem.appendChild(mappingDiv); + + const editBtn = document.createElement("button"); + editBtn.classList.add("styledButton", "editKeybinding"); + + const resetBtn = document.createElement("button"); + resetBtn.classList.add("styledButton", "resetKeybinding"); + + if (mapped.builtin) { + editBtn.classList.add("disabled"); + resetBtn.classList.add("disabled"); + } else { + this.trackClicks(editBtn, () => this.editKeybinding(keybindingId)); + this.trackClicks(resetBtn, () => this.resetKeybinding(keybindingId)); + } + elem.appendChild(editBtn); + elem.appendChild(resetBtn); + } + } + this.updateKeybindings(); + } + + editKeybinding(id) { + const dialog = new Dialog({ + app: this.app, + title: T.dialogs.editKeybinding.title, + contentHTML: T.dialogs.editKeybinding.desc, + buttons: ["cancel:good"], + type: "info", + }); + + dialog.inputReciever.keydown.add(({ keyCode, shift, alt, event }) => { + if (keyCode === 27) { + this.dialogs.closeDialog(dialog); + return; + } + + if (event) { + event.preventDefault(); + } + + if ( + // Enter + keyCode === 13 || + // TAB + keyCode === 9 + ) { + // Ignore builtins + return; + } + + this.app.settings.updateKeybindingOverride(id, keyCode); + + this.dialogs.closeDialog(dialog); + this.updateKeybindings(); + }); + + dialog.inputReciever.backButton.add(() => {}); + + this.dialogs.internalShowDialog(dialog); + this.app.sound.playUiSound(SOUNDS.dialogOk); + } + + updateKeybindings() { + const overrides = this.app.settings.getKeybindingOverrides(); + for (const category in KEYMAPPINGS) { + for (const keybindingId in KEYMAPPINGS[category]) { + const mapped = KEYMAPPINGS[category][keybindingId]; + + const container = this.htmlElement.querySelector("[data-keybinding='" + keybindingId + "']"); + assert(container, "Container for keybinding not found: " + keybindingId); + + let keyCode = mapped.keyCode; + if (overrides[keybindingId]) { + keyCode = overrides[keybindingId]; + } + + const mappingDiv = container.querySelector(".mapping"); + mappingDiv.innerHTML = getStringForKeyCode(keyCode); + mappingDiv.classList.toggle("changed", !!overrides[keybindingId]); + + const resetBtn = container.querySelector("button.resetKeybinding"); + resetBtn.classList.toggle("disabled", mapped.builtin || !overrides[keybindingId]); + } + } + } + + resetKeybinding(id) { + this.app.settings.resetKeybindingOverride(id); + this.updateKeybindings(); + } + + resetBindings() { + const { reset } = this.dialogs.showWarning( + T.dialogs.resetKeybindingsConfirmation.title, + T.dialogs.resetKeybindingsConfirmation.desc, + ["cancel:good", "reset:bad"] + ); + + reset.add(() => { + this.app.settings.resetKeybindingOverrides(); + this.updateKeybindings(); + + this.dialogs.showInfo(T.dialogs.keybindingsResetOk.title, T.dialogs.keybindingsResetOk.desc); + }); + } + + getDefaultPreviousState() { + return "SettingsState"; + } +} diff --git a/src/js/states/settings.js b/src/js/states/settings.js index 35d7687c..82aa83d5 100644 --- a/src/js/states/settings.js +++ b/src/js/states/settings.js @@ -23,9 +23,7 @@ export class SettingsState extends TextualGameState { ` : "" } - - - + @@ -33,7 +31,6 @@ export class SettingsState extends TextualGameState { ${this.getSettingsHtml()}
${T.global.loading} ...
-
@@ -82,10 +79,7 @@ export class SettingsState extends TextualGameState { onEnter(payload) { this.renderBuildText(); - this.trackClicks(this.htmlElement.querySelector(".copyright"), this.onCopyrightClicked, { - preventDefault: false, - }); - this.trackClicks(this.htmlElement.querySelector(".changelog"), this.onChangelogClicked, { + this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, { preventDefault: false, }); @@ -113,8 +107,8 @@ export class SettingsState extends TextualGameState { }); } - onCopyrightClicked() { - // this.moveToStateAddGoBack("CopyrightState"); + onAboutClicked() { + this.moveToStateAddGoBack("AboutState"); } onChangelogClicked() { @@ -122,6 +116,6 @@ export class SettingsState extends TextualGameState { } onKeybindingsClicked() { - // this.moveToStateAddGoBack("KeybindingsState"); + this.moveToStateAddGoBack("KeybindingsState"); } } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index a5b6aaf9..f5e8773e 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -82,6 +82,7 @@ dialogs: cancel: Cancel later: Later restart: Restart + reset: Reset importSavegameError: title: Import Error @@ -113,6 +114,18 @@ dialogs: text: >- You need to restart the game to apply the settings. + editKeybinding: + title: Change Keybinding + desc: Press the key you want to assign, or escape to cancel. + + resetKeybindingsConfirmation: + title: Reset keybindings + desc: This will reset all keybindings to their default values. Please confirm. + + keybindingsResetOk: + title: Keybindings reset + desc: The keybindings have been reset to their respective defaults! + ingame: # This is shown in the top left corner and displays useful keybindings in # every situation @@ -226,43 +239,43 @@ shopUpgrades: # Buildings and their name / description buildings: belt: - name: Belt + name: &belt Belt description: Transports items, hold and drag to place multiple. miner: # Internal name for the Extractor - name: Extractor + name: &miner Extractor description: Place over a shape or color to extract it. underground_belt: # Internal name for the Tunnel - name: Tunnel + name: &underground_belt Tunnel description: Allows to tunnel resources under buildings and belts. splitter: # Internal name for the Balancer - name: Balancer + name: &splitter Balancer description: Multifunctional - Evenly distributes all inputs onto all outputs. cutter: - name: Cut Half + name: &cutter Cut Half description: Cuts shapes from top to bottom and outputs both halfs. If you use only one part, be sure to destroy the other part or it will stall! rotater: - name: Rotate + name: &rotater Rotate description: Rotates shapes clockwise by 90 degrees. stacker: # Internal name for the Combiner - name: Combine + name: &stacker Combine description: Combines both items. If they can not be merged, the right item is placed above the left item. mixer: - name: Mix Colors + name: &mixer Mix Colors description: Mixes two colors using additive blending. painter: - name: Dye + name: &painter Dye description: Colors the whole shape on the left input with the color from the right input. trash: # Internal name for the destroyer - name: Destroyed + name: &trash Destroyed description: Accepts inputs from all sides and destroys them. Forever. storyRewards: @@ -329,4 +342,60 @@ settings: refreshRate: title: Simulation Target description: >- - If you have a 144hz monitor, change the refresh rate here so the game will properly simulate at higher refresh rates. + If you have a 144hz monitor, change the refresh rate here so the game will properly simulate at higher refresh rates. This might actually decrease the FPS if your computer is too slow. + +keybindings: + title: Keybindings + hint: >- + Tip: Be sure to make use of CTRL, SHIFT and ALT! They enable different placement options. + + resetKeybindings: Reset Keyinbindings + + categoryLabels: + general: Appplication + ingame: Game + placement: Placement + massSelect: Mass Delete + buildings: Building Shortcuts + placementModifiers: Placement Modifiers + + mappings: + confirm: Confirm + back: Back + mapMoveUp: Move Up + mapMoveRight: Move Right + mapMoveDown: Move Down + mapMoveLeft: Move Left + + centerMap: Center Map + + menuOpenShop: Upgrades + menuOpenStats: Statistics + + toggleHud: Toggle HUD + belt: *belt + splitter: *splitter + underground_belt: *underground_belt + miner: *miner + cutter: *cutter + rotater: *rotater + stacker: *stacker + mixer: *mixer + painter: *painter + trash: *trash + + abortBuildingPlacement: Abort Placement + rotateWhilePlacing: Rotate + cycleBuildingVariants: Cycle Variants + confirmMassDelete: Confirm Mass Delete + cycleBuildings: Cycle Buildings + + massSelectStart: Hold and drag to start + massSelectSelectMultiple: Select multiple areas + + placementDisableAutoOrientation: Disable automatic orientation + placeMultiple: Stay in placement mode + placeInverse: Invert automatic orientation + +about: + title: About this Game