mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Add achievements. Add savefile migration
This commit is contained in:
parent
10b90a8df2
commit
4120b3265c
@ -1,34 +1,23 @@
|
|||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
|
import { Entity } from "./entity";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
import { globalConfig } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
||||||
import { BasicSerializableObject } from "../savegame/serialization";
|
import { getBuildingDataFromCode } from "./building_codes";
|
||||||
//import { typeAchievementCollection } from "./achievement_resolver";
|
|
||||||
|
|
||||||
const logger = createLogger("achievement_proxy");
|
const logger = createLogger("achievement_proxy");
|
||||||
|
|
||||||
export class AchievementProxy extends BasicSerializableObject {
|
const ROTATER = "rotater";
|
||||||
static getId() {
|
const DEFAULT = "default";
|
||||||
return "AchievementProxy";
|
const BELT = "belt";
|
||||||
}
|
const LEVEL_26 = 26;
|
||||||
|
|
||||||
static getSchema() {
|
|
||||||
return {
|
|
||||||
// collection: typeAchievementCollection
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize(data, root) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export class AchievementProxy {
|
||||||
/** @param {GameRoot} root */
|
/** @param {GameRoot} root */
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super();
|
|
||||||
|
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.provider = this.root.app.achievementProvider;
|
this.provider = this.root.app.achievementProvider;
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
@ -38,8 +27,8 @@ export class AchievementProxy extends BasicSerializableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.sliceTime = 0;
|
this.sliceTime = 0;
|
||||||
this.sliceIteration = 0;
|
this.sliceIteration = 1;
|
||||||
this.sliceIterationLimit = 2;
|
this.sliceIterationLimit = 10;
|
||||||
|
|
||||||
this.root.signals.postLoadHook.add(this.onLoad, this);
|
this.root.signals.postLoadHook.add(this.onLoad, this);
|
||||||
}
|
}
|
||||||
@ -47,10 +36,9 @@ export class AchievementProxy extends BasicSerializableObject {
|
|||||||
onLoad() {
|
onLoad() {
|
||||||
this.provider.onLoad(this.root)
|
this.provider.onLoad(this.root)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.log("Recieving achievement signals");
|
|
||||||
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode);
|
|
||||||
this.startSlice();
|
|
||||||
this.disabled = false;
|
this.disabled = false;
|
||||||
|
logger.log("Recieving achievement signals");
|
||||||
|
this.initialize();
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
@ -58,14 +46,29 @@ export class AchievementProxy extends BasicSerializableObject {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have certain checks every 30 seconds, 10 seconds, etc.
|
initialize() {
|
||||||
// Re-check relevance every so often
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode);
|
||||||
// Consider disabling checks if no longer relevant
|
|
||||||
|
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() {
|
startSlice() {
|
||||||
this.sliceTime = this.root.time.now();
|
this.sliceTime = this.root.time.now();
|
||||||
|
|
||||||
// Every slice
|
// Every slice
|
||||||
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.storeShape, this.sliceTime);
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.storeShape);
|
||||||
|
|
||||||
// Every other slice
|
// Every other slice
|
||||||
if (this.sliceIteration % 2 === 0) {
|
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) {
|
if (this.sliceIteration === this.sliceIterationLimit) {
|
||||||
this.sliceIteration = 0;
|
this.sliceIteration = 1;
|
||||||
} else {
|
} else {
|
||||||
this.sliceIteration++;
|
this.sliceIteration++;
|
||||||
}
|
}
|
||||||
@ -104,4 +112,59 @@ export class AchievementProxy extends BasicSerializableObject {
|
|||||||
this.startSlice();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export function achievementResolver(root, data) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -182,7 +182,7 @@ export class GameRoot {
|
|||||||
|
|
||||||
// Called with an achievement key and necessary args to validate it can be unlocked.
|
// Called with an achievement key and necessary args to validate it can be unlocked.
|
||||||
achievementCheck: /** @type {TypedSignal<[string, *]>} */ (new Signal()),
|
achievementCheck: /** @type {TypedSignal<[string, *]>} */ (new Signal()),
|
||||||
bulkAchievementCheck: /** @type {TypedSignal<[string, ...*]>} */ (new Signal()),
|
bulkAchievementCheck: /** @type {TypedSignal<[string, any]...>} */ (new Signal()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// RNG's
|
// RNG's
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
import { Application } from "../application";
|
import { Application } from "../application";
|
||||||
import { BaseItem } from "./base_item";
|
|
||||||
import { StorageComponent } from "../game/components/storage";
|
import { StorageComponent } from "../game/components/storage";
|
||||||
|
import { ShapeItem } from "../game/items/shape_item";
|
||||||
import { Entity } from "../game/entity";
|
import { Entity } from "../game/entity";
|
||||||
import { GameRoot } from "../game/root";
|
import { GameRoot } from "../game/root";
|
||||||
import { ShapeDefinition } from "../game/shape_definition";
|
import { ShapeDefinition } from "../game/shape_definition";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
import { enumAnalyticsDataSource } from "../game/production_analytics";
|
|
||||||
|
|
||||||
export const ACHIEVEMENTS = {
|
export const ACHIEVEMENTS = {
|
||||||
belt500Tiles: "belt500Tiles",
|
belt500Tiles: "belt500Tiles",
|
||||||
blueprint100k: "blueprint100k",
|
blueprint100k: "blueprint100k",
|
||||||
@ -21,7 +19,10 @@ export const ACHIEVEMENTS = {
|
|||||||
level100: "level100",
|
level100: "level100",
|
||||||
level50: "level50",
|
level50: "level50",
|
||||||
logoBefore18: "logoBefore18",
|
logoBefore18: "logoBefore18",
|
||||||
|
mam: "mam",
|
||||||
mapMarkers15: "mapMarkers15",
|
mapMarkers15: "mapMarkers15",
|
||||||
|
noBeltUpgradesUntilBp: "noBeltUpgradesUntilBp",
|
||||||
|
noInverseRotater: "noInverseRotater",
|
||||||
oldLevel17: "oldLevel17",
|
oldLevel17: "oldLevel17",
|
||||||
openWires: "openWires",
|
openWires: "openWires",
|
||||||
paintShape: "paintShape",
|
paintShape: "paintShape",
|
||||||
@ -64,11 +65,13 @@ const MINUTE_60 = MINUTE_30 * 2;
|
|||||||
const MINUTE_120 = MINUTE_30 * 4;
|
const MINUTE_120 = MINUTE_30 * 4;
|
||||||
const PRODUCED = "produced";
|
const PRODUCED = "produced";
|
||||||
const RATE_SLICE_COUNT = 10;
|
const RATE_SLICE_COUNT = 10;
|
||||||
|
const ROTATER_CCW_CODE = 12;
|
||||||
|
const ROTATER_180_CODE = 13;
|
||||||
const SHAPE_BP = "CbCbCbRb:CwCwCwCw";
|
const SHAPE_BP = "CbCbCbRb:CwCwCwCw";
|
||||||
const SHAPE_LOGO = "RuCw--Cw:----Ru--";
|
const SHAPE_LOGO = "RuCw--Cw:----Ru--";
|
||||||
const SHAPE_MS_LOGO = "RgRyRbRr";
|
const SHAPE_MS_LOGO = "RgRyRbRr";
|
||||||
const SHAPE_ROCKET = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
|
||||||
const SHAPE_OLD_LEVEL_17 = "WrRgWrRg:CwCrCwCr:SgSgSgSg";
|
const SHAPE_OLD_LEVEL_17 = "WrRgWrRg:CwCrCwCr:SgSgSgSg";
|
||||||
|
const SHAPE_ROCKET = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
||||||
const WIRE_LAYER = "wires";
|
const WIRE_LAYER = "wires";
|
||||||
|
|
||||||
export class AchievementProviderInterface {
|
export class AchievementProviderInterface {
|
||||||
@ -132,6 +135,10 @@ export class Achievement {
|
|||||||
this.signal = null;
|
this.signal = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
isValid() {
|
isValid() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -157,92 +164,109 @@ export class AchievementCollection {
|
|||||||
this.map = new Map();
|
this.map = new Map();
|
||||||
this.activate = activate;
|
this.activate = activate;
|
||||||
|
|
||||||
this.createAndSet(ACHIEVEMENTS.belt500Tiles, {
|
this.add(ACHIEVEMENTS.belt500Tiles, {
|
||||||
isValid: this.isBelt500TilesValid,
|
isValid: this.isBelt500TilesValid,
|
||||||
signal: "entityAdded",
|
signal: "entityAdded",
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.blueprint100k, {
|
this.add(ACHIEVEMENTS.blueprint100k, {
|
||||||
isValid: this.isBlueprint100kValid,
|
isValid: this.isBlueprint100kValid,
|
||||||
signal: "shapeDelivered",
|
signal: "shapeDelivered",
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.blueprint1m, {
|
this.add(ACHIEVEMENTS.blueprint1m, {
|
||||||
isValid: this.isBlueprint1mValid,
|
isValid: this.isBlueprint1mValid,
|
||||||
signal: "shapeDelivered",
|
signal: "shapeDelivered",
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26));
|
this.add(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26));
|
||||||
this.createAndSet(ACHIEVEMENTS.cutShape);
|
this.add(ACHIEVEMENTS.cutShape);
|
||||||
this.createAndSet(ACHIEVEMENTS.darkMode, {
|
this.add(ACHIEVEMENTS.darkMode, {
|
||||||
isValid: this.isDarkModeValid,
|
isValid: this.isDarkModeValid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.destroy1000, {
|
this.add(ACHIEVEMENTS.destroy1000, {
|
||||||
isValid: this.isDestroy1000Valid,
|
isValid: this.isDestroy1000Valid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.irrelevantShape, {
|
this.add(ACHIEVEMENTS.irrelevantShape, {
|
||||||
isValid: this.isIrrelevantShapeValid,
|
isValid: this.isIrrelevantShapeValid,
|
||||||
signal: "shapeDelivered",
|
signal: "shapeDelivered",
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.level100, this.createLevelOptions(100));
|
this.add(ACHIEVEMENTS.level100, this.createLevelOptions(100));
|
||||||
this.createAndSet(ACHIEVEMENTS.level50, this.createLevelOptions(50));
|
this.add(ACHIEVEMENTS.level50, this.createLevelOptions(50));
|
||||||
this.createAndSet(ACHIEVEMENTS.logoBefore18, {
|
this.add(ACHIEVEMENTS.logoBefore18, {
|
||||||
isRelevant: this.isLogoBefore18Relevant,
|
isRelevant: this.isLogoBefore18Relevant,
|
||||||
isValid: this.isLogoBefore18Valid,
|
isValid: this.isLogoBefore18Valid,
|
||||||
signal: "itemProduced"
|
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,
|
isRelevant: this.isMapMarkers15Relevant,
|
||||||
isValid: this.isMapMarkers15Valid,
|
isValid: this.isMapMarkers15Valid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.oldLevel17, this.createShapeOptions(SHAPE_OLD_LEVEL_17));
|
this.add(ACHIEVEMENTS.noBeltUpgradesUntilBp, {
|
||||||
this.createAndSet(ACHIEVEMENTS.openWires, {
|
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,
|
isValid: this.isOpenWiresValid,
|
||||||
signal: "editModeChanged",
|
signal: "editModeChanged",
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.paintShape);
|
this.add(ACHIEVEMENTS.paintShape);
|
||||||
this.createAndSet(ACHIEVEMENTS.place5000Wires, {
|
this.add(ACHIEVEMENTS.place5000Wires, {
|
||||||
isValid: this.isPlace5000WiresValid,
|
isValid: this.isPlace5000WiresValid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.placeBlueprint, {
|
this.add(ACHIEVEMENTS.placeBlueprint, {
|
||||||
isValid: this.isPlaceBlueprintValid,
|
isValid: this.isPlaceBlueprintValid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.placeBp1000, {
|
this.add(ACHIEVEMENTS.placeBp1000, {
|
||||||
isValid: this.isPlaceBp1000Valid,
|
isValid: this.isPlaceBp1000Valid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.play1h, this.createTimeOptions(HOUR_1));
|
this.add(ACHIEVEMENTS.play1h, this.createTimeOptions(HOUR_1));
|
||||||
this.createAndSet(ACHIEVEMENTS.play10h, this.createTimeOptions(HOUR_10));
|
this.add(ACHIEVEMENTS.play10h, this.createTimeOptions(HOUR_10));
|
||||||
this.createAndSet(ACHIEVEMENTS.play20h, this.createTimeOptions(HOUR_20));
|
this.add(ACHIEVEMENTS.play20h, this.createTimeOptions(HOUR_20));
|
||||||
this.createAndSet(ACHIEVEMENTS.produceLogo, this.createShapeOptions(SHAPE_LOGO));
|
this.add(ACHIEVEMENTS.produceLogo, this.createShapeOptions(SHAPE_LOGO));
|
||||||
this.createAndSet(ACHIEVEMENTS.produceRocket, this.createShapeOptions(SHAPE_ROCKET));
|
this.add(ACHIEVEMENTS.produceRocket, this.createShapeOptions(SHAPE_ROCKET));
|
||||||
this.createAndSet(ACHIEVEMENTS.produceMsLogo, this.createShapeOptions(SHAPE_MS_LOGO));
|
this.add(ACHIEVEMENTS.produceMsLogo, this.createShapeOptions(SHAPE_MS_LOGO));
|
||||||
this.createAndSet(ACHIEVEMENTS.rotateShape);
|
this.add(ACHIEVEMENTS.rotateShape);
|
||||||
this.createAndSet(ACHIEVEMENTS.speedrunBp30, this.createSpeedOptions(12, MINUTE_30));
|
this.add(ACHIEVEMENTS.speedrunBp30, this.createSpeedOptions(12, MINUTE_30));
|
||||||
this.createAndSet(ACHIEVEMENTS.speedrunBp60, this.createSpeedOptions(12, MINUTE_60));
|
this.add(ACHIEVEMENTS.speedrunBp60, this.createSpeedOptions(12, MINUTE_60));
|
||||||
this.createAndSet(ACHIEVEMENTS.speedrunBp120, this.createSpeedOptions(12, MINUTE_120));
|
this.add(ACHIEVEMENTS.speedrunBp120, this.createSpeedOptions(12, MINUTE_120));
|
||||||
this.createAndSet(ACHIEVEMENTS.stack4Layers, {
|
this.add(ACHIEVEMENTS.stack4Layers, {
|
||||||
isValid: this.isStack4LayersValid,
|
isValid: this.isStack4LayersValid,
|
||||||
signal: "itemProduced",
|
signal: "itemProduced",
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.stackShape);
|
this.add(ACHIEVEMENTS.stackShape);
|
||||||
this.createAndSet(ACHIEVEMENTS.store100Unique, {
|
this.add(ACHIEVEMENTS.store100Unique, {
|
||||||
isRelevant: this.isStore100UniqueRelevant,
|
isRelevant: this.isStore100UniqueRelevant,
|
||||||
isValid: this.isStore100UniqueValid,
|
isValid: this.isStore100UniqueValid,
|
||||||
signal: "shapeDelivered",
|
signal: "shapeDelivered",
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.storeShape, {
|
this.add(ACHIEVEMENTS.storeShape, {
|
||||||
isValid: this.isStoreShapeValid,
|
isValid: this.isStoreShapeValid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.throughputBp25, this.createRateOptions(SHAPE_BP, 25));
|
this.add(ACHIEVEMENTS.throughputBp25, this.createRateOptions(SHAPE_BP, 25));
|
||||||
this.createAndSet(ACHIEVEMENTS.throughputBp50, this.createRateOptions(SHAPE_BP, 50));
|
this.add(ACHIEVEMENTS.throughputBp50, this.createRateOptions(SHAPE_BP, 50));
|
||||||
this.createAndSet(ACHIEVEMENTS.throughputLogo25, this.createRateOptions(SHAPE_LOGO, 25));
|
this.add(ACHIEVEMENTS.throughputLogo25, this.createRateOptions(SHAPE_LOGO, 25));
|
||||||
this.createAndSet(ACHIEVEMENTS.throughputLogo50, this.createRateOptions(SHAPE_LOGO, 50));
|
this.add(ACHIEVEMENTS.throughputLogo50, this.createRateOptions(SHAPE_LOGO, 50));
|
||||||
this.createAndSet(ACHIEVEMENTS.throughputRocket10, this.createRateOptions(SHAPE_ROCKET, 25));
|
this.add(ACHIEVEMENTS.throughputRocket10, this.createRateOptions(SHAPE_ROCKET, 25));
|
||||||
this.createAndSet(ACHIEVEMENTS.throughputRocket20, this.createRateOptions(SHAPE_ROCKET, 50));
|
this.add(ACHIEVEMENTS.throughputRocket20, this.createRateOptions(SHAPE_ROCKET, 50));
|
||||||
this.createAndSet(ACHIEVEMENTS.trash1000, {
|
this.add(ACHIEVEMENTS.trash1000, {
|
||||||
init: this.initTrash1000,
|
init: this.initTrash1000,
|
||||||
isValid: this.isTrash1000Valid,
|
isValid: this.isTrash1000Valid,
|
||||||
});
|
});
|
||||||
this.createAndSet(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20));
|
this.add(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20));
|
||||||
this.createAndSet(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5));
|
this.add(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5));
|
||||||
this.createAndSet(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8));
|
this.add(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {GameRoot} root */
|
/** @param {GameRoot} root */
|
||||||
@ -269,17 +293,19 @@ export class AchievementCollection {
|
|||||||
|
|
||||||
if (!this.hasDefaultReceivers()) {
|
if (!this.hasDefaultReceivers()) {
|
||||||
this.root.signals.achievementCheck.remove(this.unlock);
|
this.root.signals.achievementCheck.remove(this.unlock);
|
||||||
|
this.root.signals.bulkAchievementCheck.remove(this.bulkUnlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} key - Maps to an Achievement
|
* @param {string} key - Maps to an Achievement
|
||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
* @param {function} [options.isValid]
|
* @param {function} [options.init]
|
||||||
* @param {function} [options.isRelevant]
|
* @param {function} [options.isRelevant]
|
||||||
|
* @param {function} [options.isValid]
|
||||||
* @param {string} [options.signal]
|
* @param {string} [options.signal]
|
||||||
*/
|
*/
|
||||||
createAndSet(key, options = {}) {
|
add(key, options = {}) {
|
||||||
if (G_IS_DEV) {
|
if (G_IS_DEV) {
|
||||||
assert(ACHIEVEMENTS[key], "Achievement key not found: ", key);
|
assert(ACHIEVEMENTS[key], "Achievement key not found: ", key);
|
||||||
}
|
}
|
||||||
@ -324,7 +350,7 @@ export class AchievementCollection {
|
|||||||
|
|
||||||
const achievement = this.map.get(key);
|
const achievement = this.map.get(key);
|
||||||
|
|
||||||
if (!achievement.isValid(data, achievement.state)) {
|
if (!achievement.isValid(data)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,6 +388,22 @@ export class AchievementCollection {
|
|||||||
this.map.delete(key);
|
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() {
|
hasDefaultReceivers() {
|
||||||
if (!this.map.size) {
|
if (!this.map.size) {
|
||||||
return false;
|
return false;
|
||||||
@ -376,6 +418,11 @@ export class AchievementCollection {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remaining methods exist to extend Achievement instances within the
|
||||||
|
* collection.
|
||||||
|
*/
|
||||||
|
|
||||||
hasAllUpgradesAtTier(tier) {
|
hasAllUpgradesAtTier(tier) {
|
||||||
const upgrades = this.root.gameMode.getUpgrades();
|
const upgrades = this.root.gameMode.getUpgrades();
|
||||||
|
|
||||||
@ -389,7 +436,7 @@ export class AchievementCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {BaseItem} item
|
* @param {ShapeItem} item
|
||||||
* @param {string} shape
|
* @param {string} shape
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
@ -409,7 +456,7 @@ export class AchievementCollection {
|
|||||||
return {
|
return {
|
||||||
isValid: () => {
|
isValid: () => {
|
||||||
return this.root.productionAnalytics.getCurrentShapeRate(
|
return this.root.productionAnalytics.getCurrentShapeRate(
|
||||||
enumAnalyticsDataSource.produced,
|
PRODUCED,
|
||||||
this.root.shapeDefinitionMgr.getShapeFromShortKey(shape)
|
this.root.shapeDefinitionMgr.getShapeFromShortKey(shape)
|
||||||
) >= rate;
|
) >= rate;
|
||||||
}
|
}
|
||||||
@ -503,11 +550,35 @@ export class AchievementCollection {
|
|||||||
return this.root.hubGoals.level < 18;
|
return this.root.hubGoals.level < 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {BaseItem} item @returns {boolean} */
|
/** @param {ShapeItem} item @returns {boolean} */
|
||||||
isLogoBefore18Valid(item) {
|
isLogoBefore18Valid(item) {
|
||||||
return this.root.hubGoals.level < 18 && this.isShape(item, SHAPE_LOGO);
|
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} */
|
/** @returns {boolean} */
|
||||||
isMapMarkers15Relevant() {
|
isMapMarkers15Relevant() {
|
||||||
return this.root.hud.parts.waypoints.waypoints.length < 16; // 16 - HUB
|
return this.root.hud.parts.waypoints.waypoints.length < 16; // 16 - HUB
|
||||||
@ -518,6 +589,58 @@ export class AchievementCollection {
|
|||||||
return count === 15;
|
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} */
|
/** @param {string} currentLayer @returns {boolean} */
|
||||||
isOpenWiresValid(currentLayer) {
|
isOpenWiresValid(currentLayer) {
|
||||||
return currentLayer === WIRE_LAYER;
|
return currentLayer === WIRE_LAYER;
|
||||||
@ -542,7 +665,7 @@ export class AchievementCollection {
|
|||||||
return count >= 1000;
|
return count >= 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {string} key @param {BaseItem} item @returns {boolean} */
|
/** @param {ShapeItem} item @returns {boolean} */
|
||||||
isStack4LayersValid(item) {
|
isStack4LayersValid(item) {
|
||||||
return item.getItemType() === ITEM_SHAPE && item.definition.layers.length === 4;
|
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;
|
return Object.keys(this.root.hubGoals.storedShapes).length === 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {StorageComponent} storage @returns {boolean} */
|
/** @returns {boolean} */
|
||||||
isStoreShapeValid() {
|
isStoreShapeValid() {
|
||||||
const entities = this.root.systemMgr.systems.storage.allEntities;
|
const entities = this.root.systemMgr.systems.storage.allEntities;
|
||||||
|
|
||||||
@ -574,21 +697,18 @@ export class AchievementCollection {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
initTrash1000(achievement) {
|
initTrash1000() {
|
||||||
// get state from root
|
if (Number(this.root.savegame.currentData.stats.trashedCount)) {
|
||||||
console.log(this.root.savegame.currentData.dump);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
achievement.state = achievement.state || {
|
this.root.savegame.currentData.stats.trashedCount = 0;
|
||||||
count: 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,11 @@ import { GameRoot } from "../../game/root";
|
|||||||
|
|
||||||
import { createLogger } from "../../core/logging";
|
import { createLogger } from "../../core/logging";
|
||||||
import { getIPCRenderer } from "../../core/utils";
|
import { getIPCRenderer } from "../../core/utils";
|
||||||
import { ACHIEVEMENTS, AchievementCollection, AchievementProviderInterface } from "../achievement_provider";
|
import {
|
||||||
|
ACHIEVEMENTS,
|
||||||
|
AchievementCollection,
|
||||||
|
AchievementProviderInterface
|
||||||
|
} from "../achievement_provider";
|
||||||
|
|
||||||
const logger = createLogger("achievements/steam");
|
const logger = createLogger("achievements/steam");
|
||||||
|
|
||||||
@ -21,9 +25,12 @@ const ACHIEVEMENT_IDS = {
|
|||||||
[ACHIEVEMENTS.level100]: "level_100",
|
[ACHIEVEMENTS.level100]: "level_100",
|
||||||
[ACHIEVEMENTS.level50]: "level_50",
|
[ACHIEVEMENTS.level50]: "level_50",
|
||||||
[ACHIEVEMENTS.logoBefore18]: "logo_before_18",
|
[ACHIEVEMENTS.logoBefore18]: "logo_before_18",
|
||||||
|
[ACHIEVEMENTS.mam]: "mam",
|
||||||
[ACHIEVEMENTS.mapMarkers15]: "map_markers_15",
|
[ACHIEVEMENTS.mapMarkers15]: "map_markers_15",
|
||||||
[ACHIEVEMENTS.openWires]: "open_wires",
|
[ACHIEVEMENTS.openWires]: "open_wires",
|
||||||
[ACHIEVEMENTS.oldLevel17]: "old_level_17",
|
[ACHIEVEMENTS.oldLevel17]: "old_level_17",
|
||||||
|
[ACHIEVEMENTS.noBeltUpgradesUntilBp]: "no_belt_upgrades_until_bp",
|
||||||
|
[ACHIEVEMENTS.noInverseRotater]: "no_inverse_rotator", // [sic]
|
||||||
[ACHIEVEMENTS.paintShape]: "paint_shape",
|
[ACHIEVEMENTS.paintShape]: "paint_shape",
|
||||||
[ACHIEVEMENTS.place5000Wires]: "place_5000_wires",
|
[ACHIEVEMENTS.place5000Wires]: "place_5000_wires",
|
||||||
[ACHIEVEMENTS.placeBlueprint]: "place_blueprint",
|
[ACHIEVEMENTS.placeBlueprint]: "place_blueprint",
|
||||||
@ -60,7 +67,6 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
|||||||
super(app);
|
super(app);
|
||||||
|
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.saveId = null;
|
|
||||||
this.collection = new AchievementCollection(this.activate.bind(this));
|
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||||
|
|
||||||
if (G_IS_DEV) {
|
if (G_IS_DEV) {
|
||||||
@ -85,15 +91,10 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
|||||||
this.root = root;
|
this.root = root;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.saveId || this.saveId === this.root.savegame.internalId) {
|
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||||
this.collection.initialize(root);
|
this.collection.initialize(root);
|
||||||
} else {
|
|
||||||
this.collection = new AchievementCollection(this.activate.bind(this));
|
|
||||||
this.collection.initialize(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
||||||
this.saveId = this.root.savegame.internalId;
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("Failed to initialize the collection");
|
logger.error("Failed to initialize the collection");
|
||||||
|
@ -12,6 +12,7 @@ import { SavegameInterface_V1004 } from "./schemas/1004";
|
|||||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
|
|
||||||
const logger = createLogger("savegame");
|
const logger = createLogger("savegame");
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static getCurrentVersion() {
|
static getCurrentVersion() {
|
||||||
return 1007;
|
return 1008;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,6 +127,11 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
data.version = 1007;
|
data.version = 1007;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.version === 1007) {
|
||||||
|
SavegameInterface_V1008.migrate1007to1008(data);
|
||||||
|
data.version = 1008;
|
||||||
|
}
|
||||||
|
|
||||||
return ExplainedResult.good();
|
return ExplainedResult.good();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { SavegameInterface_V1004 } from "./schemas/1004";
|
|||||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
|
|
||||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||||
export const savegameInterfaces = {
|
export const savegameInterfaces = {
|
||||||
@ -19,6 +20,7 @@ export const savegameInterfaces = {
|
|||||||
1005: SavegameInterface_V1005,
|
1005: SavegameInterface_V1005,
|
||||||
1006: SavegameInterface_V1006,
|
1006: SavegameInterface_V1006,
|
||||||
1007: SavegameInterface_V1007,
|
1007: SavegameInterface_V1007,
|
||||||
|
1008: SavegameInterface_V1008,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger("savegame_interface_registry");
|
const logger = createLogger("savegame_interface_registry");
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {import("../game/entity").Entity} Entity
|
* @typedef {import("../game/entity").Entity} Entity
|
||||||
*
|
*
|
||||||
* @typedef {{}} SavegameStats
|
* @typedef {{
|
||||||
|
* failedMam: boolean,
|
||||||
|
* trashedCount: number,
|
||||||
|
* upgradedBelt: boolean,
|
||||||
|
* usedInverseRotater: boolean
|
||||||
|
* }} SavegameStats
|
||||||
*
|
*
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* camera: any,
|
* camera: any,
|
||||||
|
@ -22,5 +22,12 @@ export class SavegameInterface_V1008 extends SavegameInterface_V1007 {
|
|||||||
if (!dump) {
|
if (!dump) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.assign(data.stats, {
|
||||||
|
failedMam: false,
|
||||||
|
trashedCount: 0,
|
||||||
|
upgradedBelt: false,
|
||||||
|
usedInverseRotater: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user