From e0facaf788ff86e49ca69586acc6b657e4ea73fa Mon Sep 17 00:00:00 2001 From: tobspr Date: Thu, 28 May 2020 14:53:11 +0200 Subject: [PATCH] Fix keys being stuck, show savegame levels in main menu --- gulp/html.js | 4 +- gulp/js.js | 6 --- gulp/webpack.config.js | 3 -- gulp/webpack.production.config.js | 2 - src/css/ingame_hud/buildings_toolbar.scss | 2 +- src/css/ingame_hud/mass_selector.scss | 2 +- src/css/states/main_menu.scss | 2 +- src/js/changelog.js | 5 +- src/js/core/config.js | 4 +- src/js/core/input_distributor.js | 21 ++++++-- src/js/game/camera.js | 8 +-- src/js/game/hud/hud.js | 1 + src/js/game/hud/parts/blueprint_placer.js | 8 ++- src/js/game/hud/parts/building_placer.js | 27 ++++++---- src/js/game/hud/parts/mass_selector.js | 22 ++++++-- src/js/game/hud/parts/pinned_shapes.js | 13 +++++ src/js/game/hud/parts/unlock_notification.js | 3 +- src/js/game/key_action_mapper.js | 25 ++++----- src/js/game/tutorial_goals.js | 53 ++++++++++++-------- src/js/game/upgrades.js | 1 + src/js/globals.d.ts | 1 - src/js/savegame/savegame.js | 11 ++-- src/js/savegame/savegame_manager.js | 12 ++++- src/js/savegame/savegame_serializer.js | 47 ++--------------- src/js/savegame/savegame_typedefs.js | 8 +-- src/js/savegame/schemas/1001.js | 4 ++ src/js/states/main_menu.js | 6 ++- translations/base-en.yaml | 12 +++++ 28 files changed, 175 insertions(+), 138 deletions(-) diff --git a/gulp/html.js b/gulp/html.js index 816b4401..94c25010 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -292,7 +292,7 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) { }); gulp.task("html.prod", () => { - return buildHtml("https://api.shapez.io", { + return buildHtml("https://analytics.shapez.io", { analytics: true, }); }); @@ -315,7 +315,7 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) { }); gulp.task("html.standalone-prod", () => { - return buildHtml("https://api.shapez.io", { + return buildHtml("https://analytics.shapez.io", { analytics: false, standalone: true, enableCachebust: false, diff --git a/gulp/js.js b/gulp/js.js index a8f39c1e..0425e469 100644 --- a/gulp/js.js +++ b/gulp/js.js @@ -46,7 +46,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) { requireUncached("./webpack.production.config.js")({ enableAssert: true, environment: "staging", - apiEndpoint: "https://api-staging.shapez.io/v1", es6: false, }) ) @@ -63,7 +62,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) { requireUncached("./webpack.production.config.js")({ enableAssert: true, environment: "staging", - apiEndpoint: "https://api-staging.shapez.io/v1", es6: true, }) ) @@ -81,7 +79,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) { requireUncached("./webpack.production.config.js")({ enableAssert: false, environment: "prod", - apiEndpoint: "https://api.shapez.io/v1", es6: false, }) ) @@ -100,7 +97,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) { enableAssert: false, environment: "prod", es6: true, - apiEndpoint: "https://api.shapez.io/v1", }) ) ) @@ -148,7 +144,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) { requireUncached("./webpack.production.config.js")({ enableAssert: true, environment: "staging", - apiEndpoint: "https://api-staging.shapez.io/v1", es6: true, standalone: true, }) @@ -165,7 +160,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) { requireUncached("./webpack.production.config.js")({ enableAssert: false, environment: "prod", - apiEndpoint: "https://api.shapez.io/v1", es6: true, standalone: true, }) diff --git a/gulp/webpack.config.js b/gulp/webpack.config.js index 026ec55e..274cc3ef 100644 --- a/gulp/webpack.config.js +++ b/gulp/webpack.config.js @@ -32,9 +32,6 @@ module.exports = ({ watch = false, standalone = false }) => { "window.assert(false, 'abstract method called of: ' + (this.name || (this.constructor && this.constructor.name)));", G_HAVE_ASSERT: "true", G_APP_ENVIRONMENT: JSON.stringify("dev"), - G_API_ENDPOINT: JSON.stringify( - lzString.compressToEncodedURIComponent("http://localhost:5005/v1") - ), G_TRACKING_ENDPOINT: JSON.stringify( lzString.compressToEncodedURIComponent("http://localhost:10005/v1") ), diff --git a/gulp/webpack.production.config.js b/gulp/webpack.production.config.js index c8dce19d..14c6598a 100644 --- a/gulp/webpack.production.config.js +++ b/gulp/webpack.production.config.js @@ -13,7 +13,6 @@ const UnusedFilesPlugin = require("unused-files-webpack-plugin").UnusedFilesWebp module.exports = ({ enableAssert = false, - apiEndpoint, environment, es6 = false, standalone = false, @@ -30,7 +29,6 @@ module.exports = ({ G_IS_STANDALONE: standalone ? "true" : "false", G_IS_BROWSER: isBrowser ? "true" : "false", G_IS_MOBILE_APP: mobileApp ? "true" : "false", - G_API_ENDPOINT: JSON.stringify(lzString.compressToEncodedURIComponent(apiEndpoint)), G_TRACKING_ENDPOINT: JSON.stringify( lzString.compressToEncodedURIComponent("https://tracking.shapez.io/v1") ), diff --git a/src/css/ingame_hud/buildings_toolbar.scss b/src/css/ingame_hud/buildings_toolbar.scss index a16acfb3..740e1f58 100644 --- a/src/css/ingame_hud/buildings_toolbar.scss +++ b/src/css/ingame_hud/buildings_toolbar.scss @@ -11,7 +11,7 @@ border-bottom-width: 0; transition: transform 0.12s ease-in-out; - background: rgba(mix(#ddd, $colorBlueBright, 80%), 0.89); + background: rgba(mix(#ddd, $colorBlueBright, 80%), 0.69); &:not(.visible) { transform: translateX(-50%) translateY(#{D(100px)}); diff --git a/src/css/ingame_hud/mass_selector.scss b/src/css/ingame_hud/mass_selector.scss index 3c41893c..5ccbf86a 100644 --- a/src/css/ingame_hud/mass_selector.scss +++ b/src/css/ingame_hud/mass_selector.scss @@ -12,7 +12,7 @@ .keybinding { vertical-align: middle; - @include S(margin, 0, 4px); + @include S(margin, 0, 1px); position: relative; top: unset; left: unset; diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index cb8b0b06..fe44f464 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -235,7 +235,7 @@ opacity: 0.5; } - .updateTime { + .level { grid-column: 1 / 2; grid-row: 1 / 2; @include PlainText; diff --git a/src/js/changelog.js b/src/js/changelog.js index 87dc357b..e4533d00 100644 --- a/src/js/changelog.js +++ b/src/js/changelog.js @@ -3,12 +3,15 @@ export const CHANGELOG = [ version: "1.1.0", date: "unreleased", entries: [ - "BLUEPRINTS!", + "BLUEPRINTS! They are unlocked at level 11.", + "Savegame levels are now shown in the main menu. For existing games, save them again to make the level show up.", "Allow holding SHIFT to rotate counter clockwise", "Allow changing all keybindings, including CTRL, ALT and SHIFT (by Dimava)", "Added confirmation when deleting more than 500 buildings at a time", "Added background to toolbar to increase contrast", "Further decrease requirements of first levels", + "Pinned shapes now are saved", + "Fix keys being stuck when opening a dialog", "Allow placing extractors anywhere again, but they don't work at all if not placed on a resource", "Fix cycling through keybindings selecting locked buildings as well (by Dimava)", "There is now a github action, checking all pull requests with eslint. (by mrHedgehog)", diff --git a/src/js/core/config.js b/src/js/core/config.js index 26105562..953af4e7 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -83,7 +83,7 @@ export const globalConfig = { debug: { /* dev:start */ - fastGameEnter: true, + // fastGameEnter: true, // noArtificialDelays: true, // disableSavegameWrite: true, // showEntityBounds: true, @@ -100,7 +100,7 @@ export const globalConfig = { // testClipping: true, // framePausesBetweenTicks: 40, // testTranslations: true, - enableEntityInspector: true, + // enableEntityInspector: true, // testAds: true, // disableMapOverview: true, disableTutorialHints: true, diff --git a/src/js/core/input_distributor.js b/src/js/core/input_distributor.js index 4ed1429c..4bd476b4 100644 --- a/src/js/core/input_distributor.js +++ b/src/js/core/input_distributor.js @@ -23,6 +23,11 @@ export class InputDistributor { /** @type {Array} */ this.filters = []; + /** + * All keys which are currently down + */ + this.keysDown = new Set(); + this.bindToEvents(); } @@ -173,6 +178,7 @@ export class InputDistributor { */ handleBlur() { this.forwardToReceiver("pageBlur", {}); + this.keysDown.clear(); } /** @@ -180,19 +186,24 @@ export class InputDistributor { */ handleKeydown(event) { if ( - // TAB - event.keyCode === 9 || - // F1 - F10 - (event.keyCode >= 112 && event.keyCode < 122) + event.keyCode === 9 || // TAB + event.keyCode === 16 || // SHIFT + event.keyCode === 17 || // CTRL + event.keyCode === 18 || // ALT + (event.keyCode >= 112 && event.keyCode < 122) // F1 - F10 ) { event.preventDefault(); } + const isInitial = !this.keysDown.has(event.keyCode); + this.keysDown.add(event.keyCode); + if ( this.forwardToReceiver("keydown", { keyCode: event.keyCode, shift: event.shiftKey, alt: event.altKey, + initial: isInitial, event, }) === STOP_PROPAGATION ) { @@ -212,6 +223,8 @@ export class InputDistributor { * @param {KeyboardEvent} event */ handleKeyup(event) { + this.keysDown.delete(event.keyCode); + this.forwardToReceiver("keyup", { keyCode: event.keyCode, shift: event.shiftKey, diff --git a/src/js/game/camera.js b/src/js/game/camera.js index f0efaba1..4bdeab91 100644 --- a/src/js/game/camera.js +++ b/src/js/game/camera.js @@ -873,19 +873,19 @@ export class Camera extends BasicSerializableObject { let forceY = 0; const actionMapper = this.root.keyMapper; - if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveUp).currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveUp).isCurrentlyPressed()) { forceY -= 1; } - if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveDown).currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveDown).isCurrentlyPressed()) { forceY += 1; } - if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveLeft).currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveLeft).isCurrentlyPressed()) { forceX -= 1; } - if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveRight).currentlyDown) { + if (actionMapper.getBinding(KEYMAPPINGS.ingame.mapMoveRight).isCurrentlyPressed()) { forceX += 1; } diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 2294b4f1..62509927 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -59,6 +59,7 @@ export class GameHUD { vignetteOverlay: new HUDVignetteOverlay(this.root), + // Must always exist pinnedShapes: new HUDPinnedShapes(this.root), notifications: new HUDNotifications(this.root), diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js index a3ff0f14..5034b2ba 100644 --- a/src/js/game/hud/parts/blueprint_placer.js +++ b/src/js/game/hud/parts/blueprint_placer.js @@ -59,7 +59,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart { const tile = worldPos.toTileSpace(); if (blueprint.tryPlace(this.root, tile)) { // This actually feels weird - // if (!this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeMultiple).currentlyDown) { + // if (!this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeMultiple).isCurrentlyPressed()) { // this.currentBlueprint.set(null); // } } @@ -84,7 +84,11 @@ export class HUDBlueprintPlacer extends BaseHUDPart { rotateBlueprint() { if (this.currentBlueprint.get()) { - if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).currentlyDown) { + if ( + this.root.keyMapper + .getBinding(KEYMAPPINGS.placement.rotateInverseModifier) + .isCurrentlyPressed() + ) { this.currentBlueprint.get().rotateCcw(); } else { this.currentBlueprint.get().rotateCw(); diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index ab3ebae4..c1179f33 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -161,9 +161,9 @@ export class HUDBuildingPlacer extends BaseHUDPart { if ( metaBuilding && metaBuilding.getRotateAutomaticallyWhilePlacing(this.currentVariant.get()) && - !this.root.keyMapper.getBinding( - KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation - ).currentlyDown + !this.root.keyMapper + .getBinding(KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation) + .isCurrentlyPressed() ) { const delta = newPos.sub(oldPos); const angleDeg = Math_degrees(delta.angle()); @@ -171,8 +171,9 @@ export class HUDBuildingPlacer extends BaseHUDPart { // Holding alt inverts the placement if ( - this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeInverse) - .currentlyDown + this.root.keyMapper + .getBinding(KEYMAPPINGS.placementModifiers.placeInverse) + .isCurrentlyPressed() ) { this.currentBaseRotation = (180 + this.currentBaseRotation) % 360; } @@ -394,7 +395,11 @@ export class HUDBuildingPlacer extends BaseHUDPart { tryRotate() { const selectedBuilding = this.currentMetaBuilding.get(); if (selectedBuilding) { - if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).currentlyDown) { + if ( + this.root.keyMapper + .getBinding(KEYMAPPINGS.placement.rotateInverseModifier) + .isCurrentlyPressed() + ) { this.currentBaseRotation = (this.currentBaseRotation + 270) % 360; } else { this.currentBaseRotation = (this.currentBaseRotation + 90) % 360; @@ -479,16 +484,18 @@ export class HUDBuildingPlacer extends BaseHUDPart { if ( metaBuilding.getFlipOrientationAfterPlacement() && - !this.root.keyMapper.getBinding( - KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation - ).currentlyDown + !this.root.keyMapper + .getBinding(KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation) + .isCurrentlyPressed() ) { this.currentBaseRotation = (180 + this.currentBaseRotation) % 360; } if ( !metaBuilding.getStayInPlacementMode() && - !this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeMultiple).currentlyDown && + !this.root.keyMapper + .getBinding(KEYMAPPINGS.placementModifiers.placeMultiple) + .isCurrentlyPressed() && !this.root.app.settings.getAllSettings().alwaysMultiplace ) { // Stop placement diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 1f628c38..2d59fb84 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -12,6 +12,7 @@ import { enumMouseButton } from "../../camera"; import { T } from "../../../translations"; import { KEYMAPPINGS } from "../../key_action_mapper"; import { THEME } from "../../theme"; +import { enumHubGoalRewards } from "../../tutorial_goals"; const logger = createLogger("hud/mass_selector"); @@ -30,9 +31,9 @@ export class HUDMassSelector extends BaseHUDPart { "ingame_HUD_MassSelector", [], T.ingame.massSelect.infoText - .replace("", removalKeybinding) - .replace("", copyKeybinding) - .replace("", abortKeybinding) + .replace("", `${removalKeybinding}`) + .replace("", `${copyKeybinding}`) + .replace("", `${abortKeybinding}`) ); } @@ -107,6 +108,13 @@ export class HUDMassSelector extends BaseHUDPart { startCopy() { if (this.selectedUids.size > 0) { + if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) { + this.root.hud.parts.dialogs.showInfo( + T.dialogs.blueprintsNotUnlocked.title, + T.dialogs.blueprintsNotUnlocked.desc + ); + return; + } this.root.hud.signals.buildingsSelectedForCopy.dispatch(Array.from(this.selectedUids)); this.selectedUids = new Set(); this.root.soundProxy.playUiClick(); @@ -121,7 +129,7 @@ export class HUDMassSelector extends BaseHUDPart { * @param {enumMouseButton} mouseButton */ onMouseDown(pos, mouseButton) { - if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectStart).currentlyDown) { + if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectStart).isCurrentlyPressed()) { return; } @@ -129,7 +137,11 @@ export class HUDMassSelector extends BaseHUDPart { return; } - if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectSelectMultiple).currentlyDown) { + if ( + !this.root.keyMapper + .getBinding(KEYMAPPINGS.massSelect.massSelectSelectMultiple) + .isCurrentlyPressed() + ) { // Start new selection this.selectedUids = new Set(); } diff --git a/src/js/game/hud/parts/pinned_shapes.js b/src/js/game/hud/parts/pinned_shapes.js index a463c602..8b7d30ff 100644 --- a/src/js/game/hud/parts/pinned_shapes.js +++ b/src/js/game/hud/parts/pinned_shapes.js @@ -9,6 +9,19 @@ export class HUDPinnedShapes extends BaseHUDPart { this.element = makeDiv(parent, "ingame_HUD_PinnedShapes", []); } + serialize() { + return { + shapes: this.pinnedShapes, + }; + } + + deserialize(data) { + if (!data || !data.shapes || !Array.isArray(data.shapes)) { + return "Invalid pinned shapes data"; + } + this.pinnedShapes = data.shapes; + } + initialize() { /** @type {Array<{ key: string, goal: number }>} */ this.pinnedShapes = []; diff --git a/src/js/game/hud/parts/unlock_notification.js b/src/js/game/hud/parts/unlock_notification.js index 570d04d9..d3a3d8a7 100644 --- a/src/js/game/hud/parts/unlock_notification.js +++ b/src/js/game/hud/parts/unlock_notification.js @@ -89,9 +89,10 @@ export class HUDUnlockNotification extends BaseHUDPart { clearTimeout(this.buttonShowTimeout); } + this.element.querySelector("button.close").classList.remove("unlocked"); this.buttonShowTimeout = setTimeout( () => this.element.querySelector("button.close").classList.add("unlocked"), - G_IS_DEV ? 1000 : 10000 + 10000 ); } diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 2c575501..e219949e 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -235,12 +235,17 @@ export class Keybinding { this.builtin = builtin; this.repeated = repeated; - this.currentlyDown = false; - this.signal = new Signal(); this.toggled = new Signal(); } + /** + * Returns whether this binding is currently pressed + */ + isCurrentlyPressed() { + return this.app.inputMgr.keysDown.has(this.keyCode); + } + /** * Adds an event listener * @param {function() : void} receiver @@ -350,7 +355,6 @@ export class KeyActionMapper { for (const key in this.keybindings) { /** @type {Keybinding} */ const binding = this.keybindings[key]; - binding.currentlyDown = false; } } @@ -360,17 +364,16 @@ export class KeyActionMapper { * @param {number} param0.keyCode * @param {boolean} param0.shift * @param {boolean} param0.alt + * @param {boolean=} param0.initial */ - handleKeydown({ keyCode, shift, alt }) { + handleKeydown({ keyCode, shift, alt, initial }) { let stop = false; // Find mapping for (const key in this.keybindings) { /** @type {Keybinding} */ const binding = this.keybindings[key]; - if (binding.keyCode === keyCode && (!binding.currentlyDown || binding.repeated)) { - binding.currentlyDown = true; - + if (binding.keyCode === keyCode && (initial || binding.repeated)) { /** @type {Signal} */ const signal = this.keybindings[key].signal; if (signal.dispatch() === STOP_PROPAGATION) { @@ -392,13 +395,7 @@ export class KeyActionMapper { * @param {boolean} param0.alt */ handleKeyup({ keyCode, shift, alt }) { - for (const key in this.keybindings) { - /** @type {Keybinding} */ - const binding = this.keybindings[key]; - if (binding.keyCode === keyCode) { - binding.currentlyDown = false; - } - } + // Empty } /** diff --git a/src/js/game/tutorial_goals.js b/src/js/game/tutorial_goals.js index 331ace0e..3e85de98 100644 --- a/src/js/game/tutorial_goals.js +++ b/src/js/game/tutorial_goals.js @@ -23,6 +23,7 @@ export const enumHubGoalRewards = { reward_painter_quad: "reward_painter_quad", reward_storage: "reward_storage", + reward_blueprints: "reward_blueprints", reward_freeplay: "reward_freeplay", no_reward: "no_reward", @@ -65,14 +66,14 @@ export const tutorialGoals = [ // Rotater { shape: "Cu----Cu", // belts t2 - required: 300, + required: 200, reward: enumHubGoalRewards.reward_tunnel, }, // 6 { shape: "Cu------", // miners t2 - required: 500, + required: 400, reward: enumHubGoalRewards.reward_painter, }, @@ -80,14 +81,14 @@ export const tutorialGoals = [ // Painter { shape: "CrCrCrCr", // unused - required: 1000, + required: 800, reward: enumHubGoalRewards.reward_rotater_ccw, }, // 8 { shape: "RbRb----", // painter t2 - required: 1500, + required: 1250, reward: enumHubGoalRewards.reward_mixer, }, @@ -95,7 +96,7 @@ export const tutorialGoals = [ // Mixing (purple) { shape: "CpCpCpCp", // belts t3 - required: 2500, + required: 1750, reward: enumHubGoalRewards.reward_splitter_compact, }, @@ -103,7 +104,7 @@ export const tutorialGoals = [ // Star shape + cyan { shape: "ScScScSc", // miners t3 - required: 5000, + required: 2250, reward: enumHubGoalRewards.reward_stacker, }, @@ -111,46 +112,54 @@ export const tutorialGoals = [ // Stacker { shape: "CgScScCg", // processors t3 - required: 6000, + required: 3000, reward: enumHubGoalRewards.reward_miner_chainable, }, // 12 + // Blueprints { - shape: "RpRpRpRp:CwCwCwCw", // painting t3 - required: 7000, - reward: enumHubGoalRewards.reward_underground_belt_tier_2, + shape: "CbCbCbRb:CwCwCwCw", + required: 4000, + reward: enumHubGoalRewards.reward_blueprints, }, // 13 { - shape: "SrSrSrSr:CyCyCyCy", // unused - required: 7850, - reward: enumHubGoalRewards.reward_storage, + shape: "RpRpRpRp:CwCwCwCw", // painting t3 + required: 9000, + reward: enumHubGoalRewards.reward_underground_belt_tier_2, }, // 14 { - shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants) - required: 8000, - reward: enumHubGoalRewards.reward_cutter_quad, + shape: "SrSrSrSr:CyCyCyCy", // unused + required: 12000, + reward: enumHubGoalRewards.reward_storage, }, // 15 { - shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants) - required: 9000, - reward: enumHubGoalRewards.reward_painter_double, + shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants) + required: 14000, + reward: enumHubGoalRewards.reward_cutter_quad, }, // 16 { - shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two varinats) - required: 10000, - reward: enumHubGoalRewards.reward_painter_quad, + shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants) + required: 17000, + reward: enumHubGoalRewards.reward_painter_double, }, // 17 + { + shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two varinats) + required: 30000, + reward: enumHubGoalRewards.reward_painter_quad, + }, + + // 18 { shape: finalGameShape, required: 50000, diff --git a/src/js/game/upgrades.js b/src/js/game/upgrades.js index 8864c5e2..b766c149 100644 --- a/src/js/game/upgrades.js +++ b/src/js/game/upgrades.js @@ -2,6 +2,7 @@ import { findNiceIntegerValue } from "../core/utils"; import { ShapeDefinition } from "./shape_definition"; export const finalGameShape = "RuCw--Cw:----Ru--"; +export const blueprintShape = "CbCbCbRb:CwCwCwCw"; export const UPGRADES = { belt: { diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts index 977e8ff3..dc2246d0 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -7,7 +7,6 @@ declare function assertAlways(condition: boolean | object | string, ...errorMess declare const abstract: void; -declare const G_API_ENDPOINT: string; declare const G_APP_ENVIRONMENT: string; declare const G_HAVE_ASSERT: boolean; declare const G_BUILD_TIME: number; diff --git a/src/js/savegame/savegame.js b/src/js/savegame/savegame.js index 8027c188..a78200f9 100644 --- a/src/js/savegame/savegame.js +++ b/src/js/savegame/savegame.js @@ -63,9 +63,7 @@ export class Savegame extends ReadWriteProxy { return { version: this.getCurrentVersion(), dump: null, - stats: { - buildingsPlaced: 0, - }, + stats: {}, lastUpdate: Date.now(), }; } @@ -79,7 +77,6 @@ export class Savegame extends ReadWriteProxy { return ExplainedResult.bad("Can not migrate savegame, too old"); } - console.log("TODO: Migrate from", data.version); if (data.version === 1000) { SavegameInterface_V1001.migrate1000to1001(data); data.version = 1001; @@ -222,6 +219,12 @@ export class Savegame extends ReadWriteProxy { saveMetadata() { this.metaDataRef.lastUpdate = new Date().getTime(); this.metaDataRef.version = this.getCurrentVersion(); + if (!this.hasGameDump()) { + this.metaDataRef.level = 0; + } else { + this.metaDataRef.level = this.currentData.dump.hubGoals.level; + } + return this.app.savegameMgr.writeAsync(); } diff --git a/src/js/savegame/savegame_manager.js b/src/js/savegame/savegame_manager.js index 6b63d721..2c20c819 100644 --- a/src/js/savegame/savegame_manager.js +++ b/src/js/savegame/savegame_manager.js @@ -19,7 +19,8 @@ export const enumLocalSavegameStatus = { * @typedef {{ * lastUpdate: number, * version: number, - * internalId: string + * internalId: string, + * level: number * }} SavegameMetadata * * @typedef {{ @@ -48,7 +49,7 @@ export class SavegameManager extends ReadWriteProxy { } getCurrentVersion() { - return 1000; + return 1001; } /** @@ -68,6 +69,13 @@ export class SavegameManager extends ReadWriteProxy { * @param {SavegamesData} data */ migrate(data) { + if (data.version < 1001) { + data.savegames.forEach(savegame => { + savegame.level = 0; + }); + data.version = 1001; + } + return ExplainedResult.good(); } diff --git a/src/js/savegame/savegame_serializer.js b/src/js/savegame/savegame_serializer.js index 5c46e07c..855405b7 100644 --- a/src/js/savegame/savegame_serializer.js +++ b/src/js/savegame/savegame_serializer.js @@ -38,6 +38,7 @@ export class SavegameSerializer { map: root.map.serialize(), entityMgr: root.entityMgr.serialize(), hubGoals: root.hubGoals.serialize(), + pinnedShapes: root.hud.parts.pinnedShapes.serialize(), }; data.entities = this.internal.serializeEntityArray(root.entityMgr.entities); @@ -118,7 +119,7 @@ export class SavegameSerializer { /** * Tries to load the savegame from a given dump - * @param {SerializedGame} savegame + * @param {import("./savegame_typedefs").SerializedGame} savegame * @param {GameRoot} root * @returns {ExplainedResult} */ @@ -135,6 +136,7 @@ export class SavegameSerializer { errorReason = errorReason || root.camera.deserialize(savegame.camera); errorReason = errorReason || root.map.deserialize(savegame.map); errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals); + errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes); errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities); // Check for errors @@ -144,47 +146,4 @@ export class SavegameSerializer { return ExplainedResult.good(); } - - /////////// MIGRATION HELPERS /////////// - - /** - * Performs a function on each component (useful to add / remove / alter properties for migration) - * @param {SerializedGame} savegame - * @param {typeof Component} componentHandle - * @param {function} modifier - */ - migration_migrateComponent(savegame, componentHandle, modifier) { - const targetId = componentHandle.getId(); - for (const entityListId in savegame.entities) { - for (let i = 0; i < savegame.entities[entityListId].length; ++i) { - const list = savegame.entities[entityListId][i]; - for (let k = 0; k < list.length; ++k) { - const entity = list[k]; - const components = entity.components; - if (components[targetId]) { - modifier(components[targetId]); - } - } - } - } - } - - /** - * Performs an operation on each object which is a PooledObject (usually Projectiles). Useful to - * perform migrations - * @param {Array} pools - * @param {string} targetClassKey - * @param {function} modifier - */ - migration_migrateGenericObjectPool(pools, targetClassKey, modifier) { - for (let i = 0; i < pools.length; ++i) { - const pool = pools[i]; - if (pool.key === targetClassKey) { - const entries = pool.data.entries; - for (const uid in entries) { - modifier(entries[uid]); - } - } - } - } } diff --git a/src/js/savegame/savegame_typedefs.js b/src/js/savegame/savegame_typedefs.js index 821306a4..1a1eff62 100644 --- a/src/js/savegame/savegame_typedefs.js +++ b/src/js/savegame/savegame_typedefs.js @@ -1,11 +1,10 @@ +import { Entity } from "../game/entity"; + /** * @typedef {{ - * buildingsPlaced: number * }} SavegameStats */ -import { Entity } from "../game/entity"; - /** * @typedef {{ * camera: any, @@ -13,6 +12,7 @@ import { Entity } from "../game/entity"; * entityMgr: any, * map: any, * hubGoals: any, + * pinnedShapes: any, * entities: Array * }} SerializedGame */ @@ -22,6 +22,6 @@ import { Entity } from "../game/entity"; * version: number, * dump: SerializedGame, * stats: SavegameStats, - * lastUpdate: number + * lastUpdate: number, * }} SavegameData */ diff --git a/src/js/savegame/schemas/1001.js b/src/js/savegame/schemas/1001.js index 7604dec4..2794993a 100644 --- a/src/js/savegame/schemas/1001.js +++ b/src/js/savegame/schemas/1001.js @@ -24,6 +24,10 @@ export class SavegameInterface_V1001 extends SavegameInterface_V1000 { return true; } + dump.pinnedShapes = { + shapes: [], + }; + const entities = dump.entities; for (let i = 0; i < entities.length; ++i) { const entity = entities[i]; diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index d95f520e..afe22067 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -283,8 +283,10 @@ export class MainMenuState extends GameState { makeDiv( elem, null, - ["updateTime"], - formatSecondsToTimeAgo((new Date().getTime() - games[i].lastUpdate) / 1000.0) + ["level"], + games[i].level + ? T.mainMenu.savegameLevel.replace("", "" + games[i].level) + : T.mainMenu.savegameLevelUnknown ); const deleteButton = document.createElement("button"); diff --git a/translations/base-en.yaml b/translations/base-en.yaml index dfc73801..0aef9b11 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -78,6 +78,9 @@ mainMenu: browserWarning: >- Sorry, but the game is known to run slow on your browser! Get the standalone version or download chrome for the full experience. + savegameLevel: Level + savegameLevelUnknown: Unknown Level + dialogs: buttons: ok: OK @@ -177,6 +180,11 @@ dialogs: desc: >- You are deleting a lot of buildings ( to be exact)! Are you sure you want to do this? + blueprintsNotUnlocked: + title: Not unlocked yet + desc: >- + Blueprints have not been unlocked yet! Complete more levels to unlock them. + ingame: # This is shown in the top left corner and displays useful keybindings in # every situation @@ -447,6 +455,10 @@ storyRewards: title: Freeplay desc: You did it! You unlocked the free-play mode! This means that shapes are now randomly generated! (No worries, more content is planned for the standalone!) + reward_blueprints: + title: Blueprints + desc: You can now copy and paste parts of your factory! Select an area (Hold ctrl, then drag), then press 'C' to copy it.

Pasting it is not free, you need to produce blueprint shapes to afford it! (Those you just delivered). + # Special reward, which is shown when there is no reward actually no_reward: title: Next level