diff --git a/src/js/core/config.js b/src/js/core/config.js index cb25890e..53510f17 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -1,5 +1,3 @@ -import { queryParamOptions } from "./query_parameters"; - export const IS_DEBUG = G_IS_DEV && typeof window !== "undefined" && @@ -9,6 +7,8 @@ export const IS_DEBUG = export const SUPPORT_TOUCH = false; +export const IS_MAC = navigator.platform.toLowerCase().indexOf("mac") >= 0; + const smoothCanvas = true; export const THIRDPARTY_URLS = { diff --git a/src/js/core/restriction_manager.js b/src/js/core/restriction_manager.js index 615e80ab..6daa4026 100644 --- a/src/js/core/restriction_manager.js +++ b/src/js/core/restriction_manager.js @@ -1,4 +1,5 @@ import { Application } from "../application"; +import { IS_MAC } from "./config"; import { ExplainedResult } from "./explained_result"; import { queryParamOptions } from "./query_parameters"; import { ReadWriteProxy } from "./read_write_proxy"; @@ -9,6 +10,8 @@ export class RestrictionManager extends ReadWriteProxy { */ constructor(app) { super(app, "restriction-flags.bin"); + + this.currentData = this.getDefaultData(); } // -- RW Proxy Impl @@ -24,6 +27,7 @@ export class RestrictionManager extends ReadWriteProxy { */ getDefaultData() { return { + version: this.getCurrentVersion(), savegameV1119Imported: false, }; } @@ -42,17 +46,53 @@ export class RestrictionManager extends ReadWriteProxy { return ExplainedResult.good(); } + initialize() { + return this.readAsync().then(() => { + if (this.currentData.savegameV1119Imported) { + console.warn("Levelunlock is granted to current user due to past savegame"); + } + }); + } + // -- End RW Proxy Impl + /** + * Checks if there are any savegames from the 1.1.19 version + */ + onHasLegacySavegamesChanged(has119Savegames = false) { + if (has119Savegames && !this.currentData.savegameV1119Imported) { + this.currentData.savegameV1119Imported = true; + console.warn("Current user now has access to all levels due to 1119 savegame"); + return this.writeAsync(); + } + return Promise.resolve(); + } + /** * Returns if the app is currently running as the limited version * @returns {boolean} */ isLimitedVersion() { - return queryParamOptions.fullVersion - ? false - : (!G_IS_DEV && !G_IS_STANDALONE) || - (typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0); + if (IS_MAC) { + // On mac, the full version is always active + return false; + } + + if (G_IS_STANDALONE) { + // Standalone is never limited + return false; + } + + if (queryParamOptions.fullVersion) { + // Full version is activated via flag + return false; + } + + if (G_IS_DEV) { + return typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0; + } + + return true; } /** @@ -94,4 +134,20 @@ export class RestrictionManager extends ReadWriteProxy { getHasExtendedSettings() { return !this.isLimitedVersion(); } + + /** + * Returns if all upgrades are available + * @returns {boolean} + */ + getHasExtendedUpgrades() { + return !this.isLimitedVersion() || this.currentData.savegameV1119Imported; + } + + /** + * Returns if all levels & freeplay is available + * @returns {boolean} + */ + getHasExtendedLevelsAndFreeplay() { + return !this.isLimitedVersion() || this.currentData.savegameV1119Imported; + } } diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 25cc8390..dba39e8c 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -1,4 +1,3 @@ -import { IS_DEMO } from "../../core/config"; import { findNiceIntegerValue } from "../../core/utils"; import { GameMode } from "../game_mode"; import { ShapeDefinition } from "../shape_definition"; @@ -9,423 +8,442 @@ const finalGameShape = "RuCw--Cw:----Ru--"; const preparementShape = "CpRpCp--:SwSwSwSw"; const blueprintShape = "CbCbCbRb:CwCwCwCw"; -const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1]; +// Tiers need % of the previous tier as requirement too +const tierGrowth = 2.5; + +/** + * Generates all upgrades + * @returns {Object} */ +function generateUpgrades(limitedVersion = false) { + const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1]; + const numEndgameUpgrades = limitedVersion ? 0 : 1000 - fixedImprovements.length - 1; + + function generateInfiniteUnlocks() { + return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({ + required: [ + { shape: preparementShape, amount: 30000 + i * 10000 }, + { shape: finalGameShape, amount: 20000 + i * 5000 }, + { shape: rocketShape, amount: 20000 + i * 5000 }, + ], + excludePrevious: true, + })); + } + + // Fill in endgame upgrades + for (let i = 0; i < numEndgameUpgrades; ++i) { + fixedImprovements.push(0.1); + } -// @FIXME @TODO -const numEndgameUpgrades = !IS_DEMO ? 20 - fixedImprovements.length - 1 : 0; + const upgrades = { + belt: [ + { + required: [{ shape: "CuCuCuCu", amount: 60 }], + }, + { + required: [{ shape: "--CuCu--", amount: 500 }], + }, + { + required: [{ shape: "CpCpCpCp", amount: 1000 }], + }, + { + required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 6000 }], + }, + { + required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 25000 }], + }, + { + required: [{ shape: preparementShape, amount: 25000 }], + excludePrevious: true, + }, + { + required: [ + { shape: preparementShape, amount: 25000 }, + { shape: finalGameShape, amount: 50000 }, + ], + excludePrevious: true, + }, + ...generateInfiniteUnlocks(), + ], -function generateEndgameUpgrades() { - return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({ - required: [ - { shape: preparementShape, amount: 30000 + i * 10000 }, - { shape: finalGameShape, amount: 20000 + i * 5000 }, - { shape: rocketShape, amount: 20000 + i * 5000 }, + miner: [ + { + required: [{ shape: "RuRuRuRu", amount: 300 }], + }, + { + required: [{ shape: "Cu------", amount: 800 }], + }, + { + required: [{ shape: "ScScScSc", amount: 3500 }], + }, + { + required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }], + }, + { + required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }], + }, + { + required: [{ shape: preparementShape, amount: 25000 }], + excludePrevious: true, + }, + { + required: [ + { shape: preparementShape, amount: 25000 }, + { shape: finalGameShape, amount: 50000 }, + ], + excludePrevious: true, + }, + ...generateInfiniteUnlocks(), ], - excludePrevious: true, - })); -} -for (let i = 0; i < numEndgameUpgrades; ++i) { - fixedImprovements.push(0.1); + processors: [ + { + required: [{ shape: "SuSuSuSu", amount: 500 }], + }, + { + required: [{ shape: "RuRu----", amount: 600 }], + }, + { + required: [{ shape: "CgScScCg", amount: 3500 }], + }, + { + required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 25000 }], + }, + { + required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 50000 }], + }, + { + required: [{ shape: preparementShape, amount: 25000 }], + excludePrevious: true, + }, + { + required: [ + { shape: preparementShape, amount: 25000 }, + { shape: finalGameShape, amount: 50000 }, + ], + excludePrevious: true, + }, + ...generateInfiniteUnlocks(), + ], + + painting: [ + { + required: [{ shape: "RbRb----", amount: 600 }], + }, + { + required: [{ shape: "WrWrWrWr", amount: 3800 }], + }, + { + required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }], + }, + { + required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }], + }, + { + required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 50000 }], + }, + { + required: [{ shape: preparementShape, amount: 25000 }], + excludePrevious: true, + }, + { + required: [ + { shape: preparementShape, amount: 25000 }, + { shape: finalGameShape, amount: 50000 }, + ], + excludePrevious: true, + }, + ...generateInfiniteUnlocks(), + ], + }; + + // Automatically generate tier levels + for (const upgradeId in upgrades) { + const upgradeTiers = upgrades[upgradeId]; + + let currentTierRequirements = []; + for (let i = 0; i < upgradeTiers.length; ++i) { + const tierHandle = upgradeTiers[i]; + tierHandle.improvement = fixedImprovements[i]; + const originalRequired = tierHandle.required.slice(); + + for (let k = currentTierRequirements.length - 1; k >= 0; --k) { + const oldTierRequirement = currentTierRequirements[k]; + if (!tierHandle.excludePrevious) { + tierHandle.required.unshift({ + shape: oldTierRequirement.shape, + amount: oldTierRequirement.amount, + }); + } + } + currentTierRequirements.push( + ...originalRequired.map(req => ({ + amount: req.amount, + shape: req.shape, + })) + ); + currentTierRequirements.forEach(tier => { + tier.amount = findNiceIntegerValue(tier.amount * tierGrowth); + }); + } + } + + // VALIDATE + if (G_IS_DEV) { + for (const upgradeId in upgrades) { + upgrades[upgradeId].forEach(tier => { + tier.required.forEach(({ shape }) => { + try { + ShapeDefinition.fromShortKey(shape); + } catch (ex) { + throw new Error("Invalid upgrade goal: '" + ex + "' for shape" + shape); + } + }); + }); + } + } + + return upgrades; } -/** @type {Object} */ -const cachedUpgrades = { - belt: [ - { - required: [{ shape: "CuCuCuCu", amount: 60 }], - }, - { - required: [{ shape: "--CuCu--", amount: 500 }], - }, - { - required: [{ shape: "CpCpCpCp", amount: 1000 }], - }, - { - required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 6000 }], - }, - { - required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 25000 }], - }, +/** + * Generates the level definitions + * @param {boolean} limitedVersion + */ +export function generateLevelDefinitions(limitedVersion = false) { + const levelDefinitions = [ + // 1 + // Circle { - required: [{ shape: preparementShape, amount: 25000 }], - excludePrevious: true, - }, - { - required: [ - { shape: preparementShape, amount: 25000 }, - { shape: finalGameShape, amount: 50000 }, - ], - excludePrevious: true, + shape: "CuCuCuCu", // belts t1 + required: 30, + reward: enumHubGoalRewards.reward_cutter_and_trash, }, - ...generateEndgameUpgrades(), - ], - miner: [ - { - required: [{ shape: "RuRuRuRu", amount: 300 }], - }, - { - required: [{ shape: "Cu------", amount: 800 }], - }, - { - required: [{ shape: "ScScScSc", amount: 3500 }], - }, + // 2 + // Cutter { - required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }], + shape: "----CuCu", // + required: 40, + reward: enumHubGoalRewards.no_reward, }, - { - required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }], - }, - { - required: [{ shape: preparementShape, amount: 25000 }], - excludePrevious: true, - }, - { - required: [ - { shape: preparementShape, amount: 25000 }, - { shape: finalGameShape, amount: 50000 }, - ], - excludePrevious: true, - }, - ...generateEndgameUpgrades(), - ], - processors: [ - { - required: [{ shape: "SuSuSuSu", amount: 500 }], - }, + // 3 + // Rectangle { - required: [{ shape: "RuRu----", amount: 600 }], - }, - { - required: [{ shape: "CgScScCg", amount: 3500 }], - }, - { - required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 25000 }], + shape: "RuRuRuRu", // miners t1 + required: 70, + reward: enumHubGoalRewards.reward_balancer, }, + + // 4 { - required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 50000 }], + shape: "RuRu----", // processors t2 + required: 70, + reward: enumHubGoalRewards.reward_rotater, }, + + // 5 + // Rotater { - required: [{ shape: preparementShape, amount: 25000 }], - excludePrevious: true, + shape: "Cu----Cu", // belts t2 + required: 170, + reward: enumHubGoalRewards.reward_tunnel, }, + + // 6 { - required: [ - { shape: preparementShape, amount: 25000 }, - { shape: finalGameShape, amount: 50000 }, - ], - excludePrevious: true, + shape: "Cu------", // miners t2 + required: 270, + reward: enumHubGoalRewards.reward_painter, }, - ...generateEndgameUpgrades(), - ], - painting: [ + // 7 + // Painter { - required: [{ shape: "RbRb----", amount: 600 }], + shape: "CrCrCrCr", // unused + required: 300, + reward: enumHubGoalRewards.reward_rotater_ccw, }, + + // 8 { - required: [{ shape: "WrWrWrWr", amount: 3800 }], + shape: "RbRb----", // painter t2 + required: 480, + reward: enumHubGoalRewards.reward_mixer, }, + + // 9 + // Mixing (purple) { - required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }], + shape: "CpCpCpCp", // belts t3 + required: 600, + reward: enumHubGoalRewards.reward_merger, }, + + // 10 + // STACKER: Star shape + cyan { - required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }], + shape: "ScScScSc", // miners t3 + required: 800, + reward: enumHubGoalRewards.reward_stacker, }, + + // 11 + // Chainable miner { - required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 50000 }], + shape: "CgScScCg", // processors t3 + required: 1000, + reward: enumHubGoalRewards.reward_miner_chainable, }, + + // 12 + // Blueprints { - required: [{ shape: preparementShape, amount: 25000 }], - excludePrevious: true, + shape: "CbCbCbRb:CwCwCwCw", + required: 1000, + reward: enumHubGoalRewards.reward_blueprints, }, + + // 13 + // Tunnel Tier 2 { - required: [ - { shape: preparementShape, amount: 25000 }, - { shape: finalGameShape, amount: 50000 }, - ], - excludePrevious: true, + shape: "RpRpRpRp:CwCwCwCw", // painting t3 + required: 3800, + reward: enumHubGoalRewards.reward_underground_belt_tier_2, }, - ...generateEndgameUpgrades(), - ], -}; - -// Tiers need % of the previous tier as requirement too -const tierGrowth = 2.5; -// Automatically generate tier levels -for (const upgradeId in cachedUpgrades) { - const upgradeTiers = cachedUpgrades[upgradeId]; - - let currentTierRequirements = []; - for (let i = 0; i < upgradeTiers.length; ++i) { - const tierHandle = upgradeTiers[i]; - tierHandle.improvement = fixedImprovements[i]; - const originalRequired = tierHandle.required.slice(); - - for (let k = currentTierRequirements.length - 1; k >= 0; --k) { - const oldTierRequirement = currentTierRequirements[k]; - if (!tierHandle.excludePrevious) { - tierHandle.required.unshift({ - shape: oldTierRequirement.shape, - amount: oldTierRequirement.amount, - }); + // DEMO STOPS HERE + ...(limitedVersion + ? [ + { + shape: "RpRpRpRp:CwCwCwCw", + required: 0, + reward: enumHubGoalRewards.reward_demo_end, + }, + ] + : [ + // 14 + // Belt reader + { + shape: "--Cg----:--Cr----", // unused + required: 16, // Per second! + reward: enumHubGoalRewards.reward_belt_reader, + throughputOnly: true, + }, + + // 15 + // Storage + { + shape: "SrSrSrSr:CyCyCyCy", // unused + required: 10000, + reward: enumHubGoalRewards.reward_storage, + }, + + // 16 + // Quad Cutter + { + shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants) + required: 6000, + reward: enumHubGoalRewards.reward_cutter_quad, + }, + + // 17 + // Double painter + { + shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants) + required: 20000, + reward: enumHubGoalRewards.reward_painter_double, + }, + + // 18 + // Rotater (180deg) + { + shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused + required: 20000, + reward: enumHubGoalRewards.reward_rotater_180, + }, + + // 19 + // Compact splitter + { + shape: "CpRpCp--:SwSwSwSw", + required: 25000, + reward: enumHubGoalRewards.reward_splitter, + }, + + // 20 + // WIRES + { + shape: finalGameShape, + required: 25000, + reward: enumHubGoalRewards.reward_wires_painter_and_levers, + }, + + // 21 + // Filter + { + shape: "CrCwCrCw:CwCrCwCr:CrCwCrCw:CwCrCwCr", + required: 25000, + reward: enumHubGoalRewards.reward_filter, + }, + + // 22 + // Constant signal + { + shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy", + required: 25000, + reward: enumHubGoalRewards.reward_constant_signal, + }, + + // 23 + // Display + { + shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy", + required: 25000, + reward: enumHubGoalRewards.reward_display, + }, + + // 24 Logic gates + { + shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy", + required: 25000, + reward: enumHubGoalRewards.reward_logic_gates, + }, + + // 25 Virtual Processing + { + shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg", + required: 25000, + reward: enumHubGoalRewards.reward_virtual_processing, + }, + + // 26 Freeplay + { + shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw", + required: 50000, + reward: enumHubGoalRewards.reward_freeplay, + }, + ]), + ]; + + if (G_IS_DEV) { + levelDefinitions.forEach(({ shape }) => { + try { + ShapeDefinition.fromShortKey(shape); + } catch (ex) { + throw new Error("Invalid tutorial goal: '" + ex + "' for shape" + shape); } - } - currentTierRequirements.push( - ...originalRequired.map(req => ({ - amount: req.amount, - shape: req.shape, - })) - ); - currentTierRequirements.forEach(tier => { - tier.amount = findNiceIntegerValue(tier.amount * tierGrowth); }); } -} -// VALIDATE -if (G_IS_DEV) { - for (const upgradeId in cachedUpgrades) { - cachedUpgrades[upgradeId].forEach(tier => { - tier.required.forEach(({ shape }) => { - try { - ShapeDefinition.fromShortKey(shape); - } catch (ex) { - throw new Error("Invalid upgrade goal: '" + ex + "' for shape" + shape); - } - }); - }); - } + return levelDefinitions; } -const levelDefinitions = [ - // 1 - // Circle - { - shape: "CuCuCuCu", // belts t1 - required: 30, - reward: enumHubGoalRewards.reward_cutter_and_trash, - }, - - // 2 - // Cutter - { - shape: "----CuCu", // - required: 40, - reward: enumHubGoalRewards.no_reward, - }, - - // 3 - // Rectangle - { - shape: "RuRuRuRu", // miners t1 - required: 70, - reward: enumHubGoalRewards.reward_balancer, - }, - - // 4 - { - shape: "RuRu----", // processors t2 - required: 70, - reward: enumHubGoalRewards.reward_rotater, - }, - - // 5 - // Rotater - { - shape: "Cu----Cu", // belts t2 - required: 170, - reward: enumHubGoalRewards.reward_tunnel, - }, - - // 6 - { - shape: "Cu------", // miners t2 - required: 270, - reward: enumHubGoalRewards.reward_painter, - }, - - // 7 - // Painter - { - shape: "CrCrCrCr", // unused - required: 300, - reward: enumHubGoalRewards.reward_rotater_ccw, - }, - - // 8 - { - shape: "RbRb----", // painter t2 - required: 480, - reward: enumHubGoalRewards.reward_mixer, - }, - - // 9 - // Mixing (purple) - { - shape: "CpCpCpCp", // belts t3 - required: 600, - reward: enumHubGoalRewards.reward_merger, - }, - - // 10 - // STACKER: Star shape + cyan - { - shape: "ScScScSc", // miners t3 - required: 800, - reward: enumHubGoalRewards.reward_stacker, - }, - - // 11 - // Chainable miner - { - shape: "CgScScCg", // processors t3 - required: 1000, - reward: enumHubGoalRewards.reward_miner_chainable, - }, - - // 12 - // Blueprints - { - shape: "CbCbCbRb:CwCwCwCw", - required: 1000, - reward: enumHubGoalRewards.reward_blueprints, - }, - - // 13 - // Tunnel Tier 2 - { - shape: "RpRpRpRp:CwCwCwCw", // painting t3 - required: 3800, - reward: enumHubGoalRewards.reward_underground_belt_tier_2, - }, - - // DEMO STOPS HERE - ...(IS_DEMO - ? [ - { - shape: "RpRpRpRp:CwCwCwCw", - required: 0, - reward: enumHubGoalRewards.reward_demo_end, - }, - ] - : [ - // 14 - // Belt reader - { - shape: "--Cg----:--Cr----", // unused - required: 16, // Per second! - reward: enumHubGoalRewards.reward_belt_reader, - throughputOnly: true, - }, - - // 15 - // Storage - { - shape: "SrSrSrSr:CyCyCyCy", // unused - required: 10000, - reward: enumHubGoalRewards.reward_storage, - }, - - // 16 - // Quad Cutter - { - shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants) - required: 6000, - reward: enumHubGoalRewards.reward_cutter_quad, - }, - - // 17 - // Double painter - { - shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants) - required: 20000, - reward: enumHubGoalRewards.reward_painter_double, - }, - - // 18 - // Rotater (180deg) - { - shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused - required: 20000, - reward: enumHubGoalRewards.reward_rotater_180, - }, - - // 19 - // Compact splitter - { - shape: "CpRpCp--:SwSwSwSw", - required: 25000, - reward: enumHubGoalRewards.reward_splitter, - }, - - // 20 - // WIRES - { - shape: finalGameShape, - required: 25000, - reward: enumHubGoalRewards.reward_wires_painter_and_levers, - }, - - // 21 - // Filter - { - shape: "CrCwCrCw:CwCrCwCr:CrCwCrCw:CwCrCwCr", - required: 25000, - reward: enumHubGoalRewards.reward_filter, - }, - - // 22 - // Constant signal - { - shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy", - required: 25000, - reward: enumHubGoalRewards.reward_constant_signal, - }, - - // 23 - // Display - { - shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy", - required: 25000, - reward: enumHubGoalRewards.reward_display, - }, - - // 24 Logic gates - { - shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy", - required: 25000, - reward: enumHubGoalRewards.reward_logic_gates, - }, - - // 25 Virtual Processing - { - shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg", - required: 25000, - reward: enumHubGoalRewards.reward_virtual_processing, - }, - - // 26 Freeplay - { - shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw", - required: 50000, - reward: enumHubGoalRewards.reward_freeplay, - }, - ]), -]; - -if (G_IS_DEV) { - levelDefinitions.forEach(({ shape }) => { - try { - ShapeDefinition.fromShortKey(shape); - } catch (ex) { - throw new Error("Invalid tutorial goal: '" + ex + "' for shape" + shape); - } - }); -} +const fullVersionUpgrades = generateUpgrades(false); +const demoVersionUpgrades = generateUpgrades(true); + +const fullVersionLevels = generateLevelDefinitions(false); +const demoVersionLevels = generateLevelDefinitions(true); export class RegularGameMode extends GameMode { constructor(root) { @@ -433,7 +451,13 @@ export class RegularGameMode extends GameMode { } getUpgrades() { - return cachedUpgrades; + return this.root.app.restrictionMgr.getHasExtendedUpgrades() + ? fullVersionUpgrades + : demoVersionUpgrades; + } + + getIsFreeplayAvailable() { + return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay(); } getBlueprintShapeKey() { @@ -441,6 +465,8 @@ export class RegularGameMode extends GameMode { } getLevelDefinitions() { - return levelDefinitions; + return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay() + ? fullVersionLevels + : demoVersionLevels; } } diff --git a/src/js/savegame/savegame_manager.js b/src/js/savegame/savegame_manager.js index 587d173f..ed31dbcf 100644 --- a/src/js/savegame/savegame_manager.js +++ b/src/js/savegame/savegame_manager.js @@ -89,6 +89,14 @@ export class SavegameManager extends ReadWriteProxy { return new Savegame(this.app, { internalId, metaDataRef: metadata }); } + /** + * Returns if this manager has any savegame of a 1.1.19 version, which + * enables all levels + */ + getHasAnyLegacySavegames() { + return this.currentData.savegames.some(savegame => savegame.version === 1005 || savegame.level > 14); + } + /** * Deletes a savegame * @param {SavegameMetadata} game @@ -142,7 +150,9 @@ export class SavegameManager extends ReadWriteProxy { }); this.currentData.savegames.push(metaData); - this.sortSavegames(); + + // Notice: This is async and happening in the background + this.updateAfterSavegamesChanged(); return new Savegame(this.app, { internalId: id, @@ -150,8 +160,16 @@ export class SavegameManager extends ReadWriteProxy { }); } + /** + * Attempts to import a savegame + * @param {object} data + */ importSavegame(data) { const savegame = this.createNewSavegame(); + + // Track legacy savegames + const isOldSavegame = data.version < 1006; + const migrationResult = savegame.migrate(data); if (migrationResult.isBad()) { return Promise.reject("Failed to migrate: " + migrationResult.reason); @@ -163,7 +181,19 @@ export class SavegameManager extends ReadWriteProxy { return Promise.reject("Verification failed: " + verification.result); } - return savegame.writeSavegameAndMetadata().then(() => this.sortSavegames()); + return savegame + .writeSavegameAndMetadata() + .then(() => this.updateAfterSavegamesChanged()) + .then(() => this.app.restrictionMgr.onHasLegacySavegamesChanged(isOldSavegame)); + } + + /** + * Hook after the savegames got changed + */ + updateAfterSavegamesChanged() { + return this.sortSavegames() + .then(() => this.writeAsync()) + .then(() => this.app.restrictionMgr.onHasLegacySavegamesChanged(this.getHasAnyLegacySavegames())); } /** @@ -212,7 +242,7 @@ export class SavegameManager extends ReadWriteProxy { if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) { return Promise.resolve(); } - return this.sortSavegames().then(() => this.writeAsync()); + return this.updateAfterSavegamesChanged(); }); } } diff --git a/src/js/states/preload.js b/src/js/states/preload.js index b35b369d..c1746da6 100644 --- a/src/js/states/preload.js +++ b/src/js/states/preload.js @@ -145,6 +145,11 @@ export class PreloadState extends GameState { this.app.backgroundResourceLoader.startLoading(); }) + .then(() => this.setStatus("Initializing restrictions")) + .then(() => { + return this.app.restrictionMgr.initialize(); + }) + .then(() => this.setStatus("Initializing savegame")) .then(() => { return this.app.savegameMgr.initialize().catch(err => {