From 4120b3265cc0d1a71f6e9c8b8415fcf59cfdb795 Mon Sep 17 00:00:00 2001 From: Greg Considine Date: Tue, 9 Mar 2021 15:00:58 -0500 Subject: [PATCH] Add achievements. Add savefile migration --- src/js/game/achievement_proxy.js | 119 ++++++-- src/js/game/achievement_resolver.js | 4 - src/js/game/root.js | 2 +- src/js/platform/achievement_provider.js | 254 +++++++++++++----- .../electron/steam_achievement_provider.js | 19 +- src/js/savegame/savegame.js | 8 +- .../savegame/savegame_interface_registry.js | 2 + src/js/savegame/savegame_typedefs.js | 7 +- src/js/savegame/schemas/1008.js | 7 + 9 files changed, 311 insertions(+), 111 deletions(-) delete mode 100644 src/js/game/achievement_resolver.js diff --git a/src/js/game/achievement_proxy.js b/src/js/game/achievement_proxy.js index 4aa12ac5..9ac211d9 100644 --- a/src/js/game/achievement_proxy.js +++ b/src/js/game/achievement_proxy.js @@ -1,34 +1,23 @@ /* typehints:start */ +import { Entity } from "./entity"; import { GameRoot } from "./root"; /* typehints:end */ import { globalConfig } from "../core/config"; import { createLogger } from "../core/logging"; import { ACHIEVEMENTS } from "../platform/achievement_provider"; -import { BasicSerializableObject } from "../savegame/serialization"; -//import { typeAchievementCollection } from "./achievement_resolver"; +import { getBuildingDataFromCode } from "./building_codes"; const logger = createLogger("achievement_proxy"); -export class AchievementProxy extends BasicSerializableObject { - static getId() { - return "AchievementProxy"; - } - - static getSchema() { - return { -// collection: typeAchievementCollection - }; - } - - deserialize(data, root) { - - } +const ROTATER = "rotater"; +const DEFAULT = "default"; +const BELT = "belt"; +const LEVEL_26 = 26; +export class AchievementProxy { /** @param {GameRoot} root */ constructor(root) { - super(); - this.root = root; this.provider = this.root.app.achievementProvider; this.disabled = true; @@ -38,8 +27,8 @@ export class AchievementProxy extends BasicSerializableObject { } this.sliceTime = 0; - this.sliceIteration = 0; - this.sliceIterationLimit = 2; + this.sliceIteration = 1; + this.sliceIterationLimit = 10; this.root.signals.postLoadHook.add(this.onLoad, this); } @@ -47,10 +36,9 @@ export class AchievementProxy extends BasicSerializableObject { onLoad() { this.provider.onLoad(this.root) .then(() => { - logger.log("Recieving achievement signals"); - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode); - this.startSlice(); this.disabled = false; + logger.log("Recieving achievement signals"); + this.initialize(); }) .catch(err => { this.disabled = true; @@ -58,14 +46,29 @@ export class AchievementProxy extends BasicSerializableObject { }); } - // Have certain checks every 30 seconds, 10 seconds, etc. - // Re-check relevance every so often - // Consider disabling checks if no longer relevant + initialize() { + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode); + + if (this.has(ACHIEVEMENTS.mam)) { + this.root.signals.storyGoalCompleted.add(this.onStoryGoalCompleted, this); + } + + if (this.has(ACHIEVEMENTS.noInverseRotater)) { + this.root.signals.entityAdded.add(this.onEntityAdded, this); + } + + if (this.has(ACHIEVEMENTS.noBeltUpgradesUntilBp)) { + this.root.signals.upgradePurchased.add(this.onUpgradePurchased, this); + } + + this.startSlice(); + } + startSlice() { this.sliceTime = this.root.time.now(); // Every slice - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.storeShape, this.sliceTime); + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.storeShape); // Every other slice if (this.sliceIteration % 2 === 0) { @@ -88,8 +91,13 @@ export class AchievementProxy extends BasicSerializableObject { ); } + // Every 10th slice + if (this.sliceIteration % 10 === 0) { + this.provider.collection.clean(); + } + if (this.sliceIteration === this.sliceIterationLimit) { - this.sliceIteration = 0; + this.sliceIteration = 1; } else { this.sliceIteration++; } @@ -104,4 +112,59 @@ export class AchievementProxy extends BasicSerializableObject { this.startSlice(); } } + + /** + * @param {string} key + * @returns {boolean} + */ + has(key) { + return this.provider.collection.map.has(key); + } + + /** @param {Entity} entity */ + onEntityAdded(entity) { + if (!entity.components.StaticMapEntity) { + return; + } + + const building = getBuildingDataFromCode(entity.components.StaticMapEntity.code) + + if (building.metaInstance.id !== ROTATER) { + return; + } + + if (building.variant === DEFAULT) { + return; + } + + this.root.savegame.currentData.stats.usedInverseRotater = true; + this.root.signals.entityAdded.remove(this.onEntityAdded); + } + + /** @param {number} level */ + onStoryGoalCompleted(level) { + if (level === LEVEL_26) { + this.root.signals.entityAdded.add(this.onMamFailure, this); + this.root.signals.entityDestroyed.add(this.onMamFailure, this); + } else if (level === LEVEL_26 + 1) { + this.root.signals.storyGoalCompleted.remove(this.onStoryGoalCompleted, this); + } + } + + onMamFailure() { + this.root.savegame.currentData.stats.failedMam = true; + this.root.signals.entityAdded.remove(this.onMamFailure); + this.root.signals.entityDestroyed.remove(this.onMamFailure); + this.root.signals.storyGoalCompleted.remove(this.onStoryGoalCompleted); + } + + /** @param {string} upgrade */ + onUpgradePurchased(upgrade) { + if (upgrade !== BELT) { + return; + } + + this.root.savegame.currentData.stats.upgradedBelt = true; + this.root.signals.upgradePurchased.remove(this.onUpgradePurchased); + } } diff --git a/src/js/game/achievement_resolver.js b/src/js/game/achievement_resolver.js deleted file mode 100644 index f24717b6..00000000 --- a/src/js/game/achievement_resolver.js +++ /dev/null @@ -1,4 +0,0 @@ -export function achievementResolver(root, data) { - -} - diff --git a/src/js/game/root.js b/src/js/game/root.js index 1fc4c31a..07498ac0 100644 --- a/src/js/game/root.js +++ b/src/js/game/root.js @@ -182,7 +182,7 @@ export class GameRoot { // Called with an achievement key and necessary args to validate it can be unlocked. achievementCheck: /** @type {TypedSignal<[string, *]>} */ (new Signal()), - bulkAchievementCheck: /** @type {TypedSignal<[string, ...*]>} */ (new Signal()), + bulkAchievementCheck: /** @type {TypedSignal<[string, any]...>} */ (new Signal()), }; // RNG's diff --git a/src/js/platform/achievement_provider.js b/src/js/platform/achievement_provider.js index fc96e916..c7f23e1a 100644 --- a/src/js/platform/achievement_provider.js +++ b/src/js/platform/achievement_provider.js @@ -1,14 +1,12 @@ /* typehints:start */ import { Application } from "../application"; -import { BaseItem } from "./base_item"; import { StorageComponent } from "../game/components/storage"; +import { ShapeItem } from "../game/items/shape_item"; import { Entity } from "../game/entity"; import { GameRoot } from "../game/root"; import { ShapeDefinition } from "../game/shape_definition"; /* typehints:end */ -import { enumAnalyticsDataSource } from "../game/production_analytics"; - export const ACHIEVEMENTS = { belt500Tiles: "belt500Tiles", blueprint100k: "blueprint100k", @@ -21,7 +19,10 @@ export const ACHIEVEMENTS = { level100: "level100", level50: "level50", logoBefore18: "logoBefore18", + mam: "mam", mapMarkers15: "mapMarkers15", + noBeltUpgradesUntilBp: "noBeltUpgradesUntilBp", + noInverseRotater: "noInverseRotater", oldLevel17: "oldLevel17", openWires: "openWires", paintShape: "paintShape", @@ -64,11 +65,13 @@ const MINUTE_60 = MINUTE_30 * 2; const MINUTE_120 = MINUTE_30 * 4; const PRODUCED = "produced"; const RATE_SLICE_COUNT = 10; +const ROTATER_CCW_CODE = 12; +const ROTATER_180_CODE = 13; const SHAPE_BP = "CbCbCbRb:CwCwCwCw"; const SHAPE_LOGO = "RuCw--Cw:----Ru--"; const SHAPE_MS_LOGO = "RgRyRbRr"; -const SHAPE_ROCKET = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw"; const SHAPE_OLD_LEVEL_17 = "WrRgWrRg:CwCrCwCr:SgSgSgSg"; +const SHAPE_ROCKET = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw"; const WIRE_LAYER = "wires"; export class AchievementProviderInterface { @@ -132,6 +135,10 @@ export class Achievement { this.signal = null; } + init() { + + } + isValid() { return true; } @@ -157,92 +164,109 @@ export class AchievementCollection { this.map = new Map(); this.activate = activate; - this.createAndSet(ACHIEVEMENTS.belt500Tiles, { + this.add(ACHIEVEMENTS.belt500Tiles, { isValid: this.isBelt500TilesValid, signal: "entityAdded", }); - this.createAndSet(ACHIEVEMENTS.blueprint100k, { + this.add(ACHIEVEMENTS.blueprint100k, { isValid: this.isBlueprint100kValid, signal: "shapeDelivered", }); - this.createAndSet(ACHIEVEMENTS.blueprint1m, { + this.add(ACHIEVEMENTS.blueprint1m, { isValid: this.isBlueprint1mValid, signal: "shapeDelivered", }); - this.createAndSet(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26)); - this.createAndSet(ACHIEVEMENTS.cutShape); - this.createAndSet(ACHIEVEMENTS.darkMode, { + this.add(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26)); + this.add(ACHIEVEMENTS.cutShape); + this.add(ACHIEVEMENTS.darkMode, { isValid: this.isDarkModeValid, }); - this.createAndSet(ACHIEVEMENTS.destroy1000, { + this.add(ACHIEVEMENTS.destroy1000, { isValid: this.isDestroy1000Valid, }); - this.createAndSet(ACHIEVEMENTS.irrelevantShape, { + this.add(ACHIEVEMENTS.irrelevantShape, { isValid: this.isIrrelevantShapeValid, signal: "shapeDelivered", }); - this.createAndSet(ACHIEVEMENTS.level100, this.createLevelOptions(100)); - this.createAndSet(ACHIEVEMENTS.level50, this.createLevelOptions(50)); - this.createAndSet(ACHIEVEMENTS.logoBefore18, { + this.add(ACHIEVEMENTS.level100, this.createLevelOptions(100)); + this.add(ACHIEVEMENTS.level50, this.createLevelOptions(50)); + this.add(ACHIEVEMENTS.logoBefore18, { isRelevant: this.isLogoBefore18Relevant, isValid: this.isLogoBefore18Valid, signal: "itemProduced" }); - this.createAndSet(ACHIEVEMENTS.mapMarkers15, { + this.add(ACHIEVEMENTS.mam, { + isRelevant: this.isMamRelevant, + isValid: this.isMamValid, + signal: "storyGoalCompleted", + }); + this.add(ACHIEVEMENTS.mapMarkers15, { isRelevant: this.isMapMarkers15Relevant, isValid: this.isMapMarkers15Valid, }); - this.createAndSet(ACHIEVEMENTS.oldLevel17, this.createShapeOptions(SHAPE_OLD_LEVEL_17)); - this.createAndSet(ACHIEVEMENTS.openWires, { + this.add(ACHIEVEMENTS.noBeltUpgradesUntilBp, { + init: this.initNoBeltUpgradesUntilBp, + isRelevant: this.isNoBeltUpgradesUntilBpRelevant, + isValid: this.isNoBeltUpgradesUntilBpValid, + signal: "storyGoalCompleted", + }); + this.add(ACHIEVEMENTS.noInverseRotater, { + init: this.initNoInverseRotater, + isRelevant: this.isNoInverseRotaterRelevant, + isValid: this.isNoInverseRotaterValid, + signal: "storyGoalCompleted", + }); + this.add(ACHIEVEMENTS.oldLevel17, this.createShapeOptions(SHAPE_OLD_LEVEL_17)); + this.add(ACHIEVEMENTS.openWires, { isValid: this.isOpenWiresValid, signal: "editModeChanged", }); - this.createAndSet(ACHIEVEMENTS.paintShape); - this.createAndSet(ACHIEVEMENTS.place5000Wires, { + this.add(ACHIEVEMENTS.paintShape); + this.add(ACHIEVEMENTS.place5000Wires, { isValid: this.isPlace5000WiresValid, }); - this.createAndSet(ACHIEVEMENTS.placeBlueprint, { + this.add(ACHIEVEMENTS.placeBlueprint, { isValid: this.isPlaceBlueprintValid, }); - this.createAndSet(ACHIEVEMENTS.placeBp1000, { + this.add(ACHIEVEMENTS.placeBp1000, { isValid: this.isPlaceBp1000Valid, }); - this.createAndSet(ACHIEVEMENTS.play1h, this.createTimeOptions(HOUR_1)); - this.createAndSet(ACHIEVEMENTS.play10h, this.createTimeOptions(HOUR_10)); - this.createAndSet(ACHIEVEMENTS.play20h, this.createTimeOptions(HOUR_20)); - this.createAndSet(ACHIEVEMENTS.produceLogo, this.createShapeOptions(SHAPE_LOGO)); - this.createAndSet(ACHIEVEMENTS.produceRocket, this.createShapeOptions(SHAPE_ROCKET)); - this.createAndSet(ACHIEVEMENTS.produceMsLogo, this.createShapeOptions(SHAPE_MS_LOGO)); - this.createAndSet(ACHIEVEMENTS.rotateShape); - this.createAndSet(ACHIEVEMENTS.speedrunBp30, this.createSpeedOptions(12, MINUTE_30)); - this.createAndSet(ACHIEVEMENTS.speedrunBp60, this.createSpeedOptions(12, MINUTE_60)); - this.createAndSet(ACHIEVEMENTS.speedrunBp120, this.createSpeedOptions(12, MINUTE_120)); - this.createAndSet(ACHIEVEMENTS.stack4Layers, { + this.add(ACHIEVEMENTS.play1h, this.createTimeOptions(HOUR_1)); + this.add(ACHIEVEMENTS.play10h, this.createTimeOptions(HOUR_10)); + this.add(ACHIEVEMENTS.play20h, this.createTimeOptions(HOUR_20)); + this.add(ACHIEVEMENTS.produceLogo, this.createShapeOptions(SHAPE_LOGO)); + this.add(ACHIEVEMENTS.produceRocket, this.createShapeOptions(SHAPE_ROCKET)); + this.add(ACHIEVEMENTS.produceMsLogo, this.createShapeOptions(SHAPE_MS_LOGO)); + this.add(ACHIEVEMENTS.rotateShape); + this.add(ACHIEVEMENTS.speedrunBp30, this.createSpeedOptions(12, MINUTE_30)); + this.add(ACHIEVEMENTS.speedrunBp60, this.createSpeedOptions(12, MINUTE_60)); + this.add(ACHIEVEMENTS.speedrunBp120, this.createSpeedOptions(12, MINUTE_120)); + this.add(ACHIEVEMENTS.stack4Layers, { isValid: this.isStack4LayersValid, signal: "itemProduced", }); - this.createAndSet(ACHIEVEMENTS.stackShape); - this.createAndSet(ACHIEVEMENTS.store100Unique, { + this.add(ACHIEVEMENTS.stackShape); + this.add(ACHIEVEMENTS.store100Unique, { isRelevant: this.isStore100UniqueRelevant, isValid: this.isStore100UniqueValid, signal: "shapeDelivered", }); - this.createAndSet(ACHIEVEMENTS.storeShape, { + this.add(ACHIEVEMENTS.storeShape, { isValid: this.isStoreShapeValid, }); - this.createAndSet(ACHIEVEMENTS.throughputBp25, this.createRateOptions(SHAPE_BP, 25)); - this.createAndSet(ACHIEVEMENTS.throughputBp50, this.createRateOptions(SHAPE_BP, 50)); - this.createAndSet(ACHIEVEMENTS.throughputLogo25, this.createRateOptions(SHAPE_LOGO, 25)); - this.createAndSet(ACHIEVEMENTS.throughputLogo50, this.createRateOptions(SHAPE_LOGO, 50)); - this.createAndSet(ACHIEVEMENTS.throughputRocket10, this.createRateOptions(SHAPE_ROCKET, 25)); - this.createAndSet(ACHIEVEMENTS.throughputRocket20, this.createRateOptions(SHAPE_ROCKET, 50)); - this.createAndSet(ACHIEVEMENTS.trash1000, { + this.add(ACHIEVEMENTS.throughputBp25, this.createRateOptions(SHAPE_BP, 25)); + this.add(ACHIEVEMENTS.throughputBp50, this.createRateOptions(SHAPE_BP, 50)); + this.add(ACHIEVEMENTS.throughputLogo25, this.createRateOptions(SHAPE_LOGO, 25)); + this.add(ACHIEVEMENTS.throughputLogo50, this.createRateOptions(SHAPE_LOGO, 50)); + this.add(ACHIEVEMENTS.throughputRocket10, this.createRateOptions(SHAPE_ROCKET, 25)); + this.add(ACHIEVEMENTS.throughputRocket20, this.createRateOptions(SHAPE_ROCKET, 50)); + this.add(ACHIEVEMENTS.trash1000, { init: this.initTrash1000, isValid: this.isTrash1000Valid, }); - this.createAndSet(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20)); - this.createAndSet(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5)); - this.createAndSet(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8)); + this.add(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20)); + this.add(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5)); + this.add(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8)); } /** @param {GameRoot} root */ @@ -269,17 +293,19 @@ export class AchievementCollection { if (!this.hasDefaultReceivers()) { this.root.signals.achievementCheck.remove(this.unlock); + this.root.signals.bulkAchievementCheck.remove(this.bulkUnlock); } } /** * @param {string} key - Maps to an Achievement * @param {object} [options] - * @param {function} [options.isValid] + * @param {function} [options.init] * @param {function} [options.isRelevant] + * @param {function} [options.isValid] * @param {string} [options.signal] */ - createAndSet(key, options = {}) { + add(key, options = {}) { if (G_IS_DEV) { assert(ACHIEVEMENTS[key], "Achievement key not found: ", key); } @@ -324,7 +350,7 @@ export class AchievementCollection { const achievement = this.map.get(key); - if (!achievement.isValid(data, achievement.state)) { + if (!achievement.isValid(data)) { return; } @@ -362,6 +388,22 @@ export class AchievementCollection { this.map.delete(key); } + /** + * Intended to be called on occasion to prune achievements that became + * irrelevant during a play session. + */ + clean() { + for (let [key, achievement] of this.map.entries()) { + if (!achievement.activatePromise && !achievement.isRelevant()) { + this.remove(key); + } + } + } + + /** + * Check if the collection-level achievementCheck receivers are still + * necessary. + */ hasDefaultReceivers() { if (!this.map.size) { return false; @@ -376,6 +418,11 @@ export class AchievementCollection { return false; } + /* + * Remaining methods exist to extend Achievement instances within the + * collection. + */ + hasAllUpgradesAtTier(tier) { const upgrades = this.root.gameMode.getUpgrades(); @@ -389,7 +436,7 @@ export class AchievementCollection { } /** - * @param {BaseItem} item + * @param {ShapeItem} item * @param {string} shape * @returns {boolean} */ @@ -409,7 +456,7 @@ export class AchievementCollection { return { isValid: () => { return this.root.productionAnalytics.getCurrentShapeRate( - enumAnalyticsDataSource.produced, + PRODUCED, this.root.shapeDefinitionMgr.getShapeFromShortKey(shape) ) >= rate; } @@ -503,11 +550,35 @@ export class AchievementCollection { return this.root.hubGoals.level < 18; } - /** @param {BaseItem} item @returns {boolean} */ + /** @param {ShapeItem} item @returns {boolean} */ isLogoBefore18Valid(item) { return this.root.hubGoals.level < 18 && this.isShape(item, SHAPE_LOGO); } + initMam() { + const stats = this.root.savegame.currentData.stats; + + if (stats.failedMam === true) { + return; + } + + if (this.root.hubGoals.level === 26 && stats.failedMam === false) { + return; + } + + stats.failedMam = this.root.hubGoals.level < 26; + } + + /** @returns {boolean} */ + isMamRelevant() { + return this.root.hubGoals.level <= 26 && !this.root.savegame.currentData.stats.failedMam; + } + + /** @params {number} level @returns {boolean} */ + isMamValid(level) { + return level === 27 && !this.root.savegame.currentData.stats.failedMam; + } + /** @returns {boolean} */ isMapMarkers15Relevant() { return this.root.hud.parts.waypoints.waypoints.length < 16; // 16 - HUB @@ -518,6 +589,58 @@ export class AchievementCollection { return count === 15; } + initNoBeltUpgradesUntilBp() { + const stats = this.root.savegame.currentData.stats; + + if (stats.upgradedBelt === true) { + return; + } + + stats.upgradedBelt = this.root.hubGoals.upgradeLevels.belt > 0; + } + + /** @returns {boolean} */ + isNoBeltUpgradesUntilBpRelevant() { + return this.root.hubGoals.level <= 12 && + !this.root.savegame.currentData.stats.upgradedBelt; + } + + /** @params {number} level @returns {boolean} */ + isNoBeltUpgradesUntilBpValid(level) { + return level === 12 && !this.root.savegame.currentData.stats.upgradedBelt; + } + + initNoInverseRotater() { + if (this.root.savegame.currentData.stats.usedInverseRotater === true) { + return; + } + + const entities = this.root.entityMgr.componentToEntity.StaticMapEntity; + + let usedInverseRotater = false; + for (var i = 0; i < entities.length; i++) { + const entity = entities[i].components.StaticMapEntity; + + if (entity.code === ROTATER_CCW_CODE || entity.code === ROTATER_180_CODE) { + usedInverseRotater = true; + break; + } + } + + this.root.savegame.currentData.stats.usedInverseRotater = usedInverseRotater; + } + + /** @returns {boolean} */ + isNoInverseRotaterRelevant() { + return this.root.hubGoals.level < 14 && + !this.root.savegame.currentData.stats.usedInverseRotater; + } + + /** @param {number} level @returns {boolean} */ + isNoInverseRotaterValid(level) { + return level === 14 && !this.root.savegame.currentData.stats.usedInverseRotater; + } + /** @param {string} currentLayer @returns {boolean} */ isOpenWiresValid(currentLayer) { return currentLayer === WIRE_LAYER; @@ -542,7 +665,7 @@ export class AchievementCollection { return count >= 1000; } - /** @param {string} key @param {BaseItem} item @returns {boolean} */ + /** @param {ShapeItem} item @returns {boolean} */ isStack4LayersValid(item) { return item.getItemType() === ITEM_SHAPE && item.definition.layers.length === 4; } @@ -557,7 +680,7 @@ export class AchievementCollection { return Object.keys(this.root.hubGoals.storedShapes).length === 100; } - /** @param {StorageComponent} storage @returns {boolean} */ + /** @returns {boolean} */ isStoreShapeValid() { const entities = this.root.systemMgr.systems.storage.allEntities; @@ -574,21 +697,18 @@ export class AchievementCollection { return false; } - initTrash1000(achievement) { - // get state from root - console.log(this.root.savegame.currentData.dump); + initTrash1000() { + if (Number(this.root.savegame.currentData.stats.trashedCount)) { + return; + } - achievement.state = achievement.state || { - count: 0 - }; + this.root.savegame.currentData.stats.trashedCount = 0; } - /** - * @params {number} count - * @params {object} state - The achievement's current state - * @returns {boolean} */ - isTrash1000Valid(count, state) { - state.count += count; - return state.count >= 1000; + /** @params {number} count @returns {boolean} */ + isTrash1000Valid(count) { + this.root.savegame.currentData.stats.trashedCount += count; + + return this.root.savegame.currentData.stats.trashedCount >= 1000; } } diff --git a/src/js/platform/electron/steam_achievement_provider.js b/src/js/platform/electron/steam_achievement_provider.js index b39fef79..c78453cb 100644 --- a/src/js/platform/electron/steam_achievement_provider.js +++ b/src/js/platform/electron/steam_achievement_provider.js @@ -5,7 +5,11 @@ import { GameRoot } from "../../game/root"; import { createLogger } from "../../core/logging"; import { getIPCRenderer } from "../../core/utils"; -import { ACHIEVEMENTS, AchievementCollection, AchievementProviderInterface } from "../achievement_provider"; +import { + ACHIEVEMENTS, + AchievementCollection, + AchievementProviderInterface +} from "../achievement_provider"; const logger = createLogger("achievements/steam"); @@ -21,9 +25,12 @@ const ACHIEVEMENT_IDS = { [ACHIEVEMENTS.level100]: "level_100", [ACHIEVEMENTS.level50]: "level_50", [ACHIEVEMENTS.logoBefore18]: "logo_before_18", + [ACHIEVEMENTS.mam]: "mam", [ACHIEVEMENTS.mapMarkers15]: "map_markers_15", [ACHIEVEMENTS.openWires]: "open_wires", [ACHIEVEMENTS.oldLevel17]: "old_level_17", + [ACHIEVEMENTS.noBeltUpgradesUntilBp]: "no_belt_upgrades_until_bp", + [ACHIEVEMENTS.noInverseRotater]: "no_inverse_rotator", // [sic] [ACHIEVEMENTS.paintShape]: "paint_shape", [ACHIEVEMENTS.place5000Wires]: "place_5000_wires", [ACHIEVEMENTS.placeBlueprint]: "place_blueprint", @@ -60,7 +67,6 @@ export class SteamAchievementProvider extends AchievementProviderInterface { super(app); this.initialized = false; - this.saveId = null; this.collection = new AchievementCollection(this.activate.bind(this)); if (G_IS_DEV) { @@ -85,15 +91,10 @@ export class SteamAchievementProvider extends AchievementProviderInterface { this.root = root; try { - if (!this.saveId || this.saveId === this.root.savegame.internalId) { - this.collection.initialize(root); - } else { - this.collection = new AchievementCollection(this.activate.bind(this)); - this.collection.initialize(root); - } + this.collection = new AchievementCollection(this.activate.bind(this)); + this.collection.initialize(root); logger.log("Initialized", this.collection.map.size, "relevant achievements"); - this.saveId = this.root.savegame.internalId; return Promise.resolve(); } catch (err) { logger.error("Failed to initialize the collection"); diff --git a/src/js/savegame/savegame.js b/src/js/savegame/savegame.js index 3fea2d5e..9fe55cd1 100644 --- a/src/js/savegame/savegame.js +++ b/src/js/savegame/savegame.js @@ -12,6 +12,7 @@ import { SavegameInterface_V1004 } from "./schemas/1004"; import { SavegameInterface_V1005 } from "./schemas/1005"; import { SavegameInterface_V1006 } from "./schemas/1006"; import { SavegameInterface_V1007 } from "./schemas/1007"; +import { SavegameInterface_V1008 } from "./schemas/1008"; const logger = createLogger("savegame"); @@ -52,7 +53,7 @@ export class Savegame extends ReadWriteProxy { * @returns {number} */ static getCurrentVersion() { - return 1007; + return 1008; } /** @@ -126,6 +127,11 @@ export class Savegame extends ReadWriteProxy { data.version = 1007; } + if (data.version === 1007) { + SavegameInterface_V1008.migrate1007to1008(data); + data.version = 1008; + } + return ExplainedResult.good(); } diff --git a/src/js/savegame/savegame_interface_registry.js b/src/js/savegame/savegame_interface_registry.js index 96389c50..395040b3 100644 --- a/src/js/savegame/savegame_interface_registry.js +++ b/src/js/savegame/savegame_interface_registry.js @@ -8,6 +8,7 @@ import { SavegameInterface_V1004 } from "./schemas/1004"; import { SavegameInterface_V1005 } from "./schemas/1005"; import { SavegameInterface_V1006 } from "./schemas/1006"; import { SavegameInterface_V1007 } from "./schemas/1007"; +import { SavegameInterface_V1008 } from "./schemas/1008"; /** @type {Object.} */ export const savegameInterfaces = { @@ -19,6 +20,7 @@ export const savegameInterfaces = { 1005: SavegameInterface_V1005, 1006: SavegameInterface_V1006, 1007: SavegameInterface_V1007, + 1008: SavegameInterface_V1008, }; const logger = createLogger("savegame_interface_registry"); diff --git a/src/js/savegame/savegame_typedefs.js b/src/js/savegame/savegame_typedefs.js index 0f94cd6a..afdf387d 100644 --- a/src/js/savegame/savegame_typedefs.js +++ b/src/js/savegame/savegame_typedefs.js @@ -1,7 +1,12 @@ /** * @typedef {import("../game/entity").Entity} Entity * - * @typedef {{}} SavegameStats + * @typedef {{ + * failedMam: boolean, + * trashedCount: number, + * upgradedBelt: boolean, + * usedInverseRotater: boolean + * }} SavegameStats * * @typedef {{ * camera: any, diff --git a/src/js/savegame/schemas/1008.js b/src/js/savegame/schemas/1008.js index 2b99ce21..675586bc 100644 --- a/src/js/savegame/schemas/1008.js +++ b/src/js/savegame/schemas/1008.js @@ -22,5 +22,12 @@ export class SavegameInterface_V1008 extends SavegameInterface_V1007 { if (!dump) { return true; } + + Object.assign(data.stats, { + failedMam: false, + trashedCount: 0, + upgradedBelt: false, + usedInverseRotater: false + }); } }