Ingame achievements (+ dev ingame)
@ -43,7 +43,9 @@ function listen () {
|
||||
}
|
||||
|
||||
ipcMain.handle("steam:get-achievement-names", getAchievementNames);
|
||||
ipcMain.handle("steam:get-achievement", getAchievement);
|
||||
ipcMain.handle("steam:activate-achievement", activateAchievement);
|
||||
ipcMain.handle("steam:deactivate-achievement", deactivateAchievement);
|
||||
}
|
||||
|
||||
function isInitialized(event) {
|
||||
@ -61,9 +63,21 @@ function getAchievementNames(event) {
|
||||
});
|
||||
}
|
||||
|
||||
function getAchievement(event, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
greenworks.getAchievement(id, () => resolve(), err => reject(err))
|
||||
});
|
||||
}
|
||||
|
||||
function activateAchievement(event, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
greenworks.activateAchievement(id, () => resolve(), err => reject(err))
|
||||
greenworks.activateAchievement(id, (is_achieved) => resolve(is_achieved), err => reject(err))
|
||||
});
|
||||
}
|
||||
|
||||
function deactivateAchievement(event, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
greenworks.clearAchievement(id, () => resolve(), err => reject(err))
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -13051,16 +13051,11 @@ yaml-loader@^0.6.0:
|
||||
loader-utils "^1.4.0"
|
||||
yaml "^1.8.3"
|
||||
|
||||
yaml@^1.10.0:
|
||||
yaml@^1.10.0, yaml@^1.8.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
||||
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
|
||||
|
||||
yaml@^1.8.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz"
|
||||
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
|
||||
|
||||
yargs-parser@5.0.0-security.0:
|
||||
version "5.0.0-security.0"
|
||||
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz"
|
||||
|
BIN
res/ui/achievements/belt500Tiles.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
res/ui/achievements/blueprint100k.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
res/ui/achievements/blueprint1m.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
res/ui/achievements/completeLvl26.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
res/ui/achievements/cutShape.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
res/ui/achievements/darkMode.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
res/ui/achievements/destroy1000.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
res/ui/achievements/irrelevantShape.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
res/ui/achievements/level100.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
res/ui/achievements/level50.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
res/ui/achievements/logoBefore18.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
res/ui/achievements/mam.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
res/ui/achievements/mapMarkers15.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
res/ui/achievements/noBeltUpgradesUntilBp.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
res/ui/achievements/noInverseRotater.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
res/ui/achievements/oldLevel17.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
res/ui/achievements/openWires.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
res/ui/achievements/paintShape.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
res/ui/achievements/place5000Wires.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
res/ui/achievements/placeBlueprint.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
res/ui/achievements/placeBp1000.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
res/ui/achievements/play10h.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
res/ui/achievements/play1h.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
res/ui/achievements/play20h.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
res/ui/achievements/produceLogo.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
res/ui/achievements/produceMsLogo.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
res/ui/achievements/produceRocket.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
res/ui/achievements/rotateShape.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
res/ui/achievements/speedrunBp120.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
res/ui/achievements/speedrunBp30.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
res/ui/achievements/speedrunBp60.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
res/ui/achievements/stack4Layers.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
res/ui/achievements/stackShape.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
res/ui/achievements/store100Unique.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
res/ui/achievements/storeShape.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
res/ui/achievements/throughputBp25.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
res/ui/achievements/throughputBp50.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
res/ui/achievements/throughputLogo25.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
res/ui/achievements/throughputLogo50.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
res/ui/achievements/throughputRocket10.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
res/ui/achievements/throughputRocket20.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
res/ui/achievements/trash1000.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
res/ui/achievements/unlockWires.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
res/ui/achievements/upgradesTier5.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
res/ui/achievements/upgradesTier8.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
res/ui/icons/achievements.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
94
src/css/ingame_hud/achievements.scss
Normal file
@ -0,0 +1,94 @@
|
||||
#ingame_HUD_Achievements {
|
||||
.content {
|
||||
@include S(padding-right, 10px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@include S(width, 500px);
|
||||
|
||||
.achievement {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
background: #eee;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(margin-bottom, 4px);
|
||||
@include S(padding, 5px, 10px);
|
||||
@include S(grid-row-gap, 1px);
|
||||
@include S(height, 85px);
|
||||
grid-template-rows: #{D(20px)} auto;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: $darkModeControlsBackground;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@include S(width, 70px);
|
||||
@include S(height, 70px);
|
||||
background: center center / 80% no-repeat;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 4;
|
||||
}
|
||||
|
||||
.info {
|
||||
grid-column: 2;
|
||||
grid-row: 2 / 3;
|
||||
.title {
|
||||
@include PlainText;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
color: $colorGreenBright;
|
||||
}
|
||||
.description {
|
||||
@include PlainText;
|
||||
color: lighten($colorGreenBright, 20);
|
||||
align-self: start;
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
button.reset {
|
||||
grid-column: 3;
|
||||
grid-row: 2;
|
||||
align-self: center;
|
||||
justify-self: end;
|
||||
|
||||
transition: all 0.2s ease-in-out;
|
||||
transition-property: background-color, opacity;
|
||||
|
||||
background-color: $colorGreenBright;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($colorGreenBright, 10);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.unlocked) {
|
||||
button.reset {
|
||||
background-color: #aaa;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.info {
|
||||
.title {
|
||||
color: black;
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.description {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -37,8 +37,16 @@
|
||||
|
||||
@include DarkThemeInvert;
|
||||
|
||||
&.shop {
|
||||
&.achievements {
|
||||
grid-column: 1;
|
||||
& {
|
||||
/* @load-async */
|
||||
background-image: uiResource("icons/achievements.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.shop {
|
||||
grid-column: 2;
|
||||
& {
|
||||
/* @load-async */
|
||||
background-image: uiResource("icons/shop.png");
|
||||
@ -46,7 +54,7 @@
|
||||
}
|
||||
|
||||
&.stats {
|
||||
grid-column: 2;
|
||||
grid-column: 3;
|
||||
& {
|
||||
/* @load-async */
|
||||
background-image: uiResource("icons/statistics.png");
|
||||
@ -58,7 +66,7 @@
|
||||
/* @load-async */
|
||||
background-image: uiResource("icons/save.png");
|
||||
}
|
||||
grid-column: 3;
|
||||
grid-column: 4;
|
||||
@include MakeAnimationWrappedEvenOdd(0.5s ease-in-out) {
|
||||
0% {
|
||||
transform: scale(1, 1);
|
||||
@ -92,7 +100,7 @@
|
||||
}
|
||||
|
||||
&.settings {
|
||||
grid-column: 4;
|
||||
grid-column: 5;
|
||||
& {
|
||||
/* @load-async */
|
||||
background-image: uiResource("icons/settings_menu_settings.png");
|
||||
|
@ -55,6 +55,7 @@
|
||||
@import "ingame_hud/sandbox_controller";
|
||||
@import "ingame_hud/standalone_advantages";
|
||||
@import "ingame_hud/cat_memes";
|
||||
@import "ingame_hud/achievements";
|
||||
|
||||
// prettier-ignore
|
||||
$elements:
|
||||
@ -89,6 +90,7 @@ ingame_HUD_SandboxController,
|
||||
ingame_HUD_BetaOverlay,
|
||||
|
||||
// Dialogs
|
||||
ingame_HUD_Achievements,
|
||||
ingame_HUD_Shop,
|
||||
ingame_HUD_Statistics,
|
||||
ingame_HUD_ShapeViewer,
|
||||
|
@ -66,6 +66,20 @@ $icons: notification_saved, notification_success, notification_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
$achievements: belt500Tiles, blueprint100k, blueprint1m, completeLvl26, cutShape, darkMode, destroy1000,
|
||||
irrelevantShape, level100, level50, logoBefore18, mam, mapMarkers15, noBeltUpgradesUntilBp,
|
||||
noInverseRotater, oldLevel17, openWires, paintShape, place5000Wires, placeBlueprint, placeBp1000, play1h,
|
||||
play10h, play20h, produceLogo, produceMsLogo, produceRocket, rotateShape, speedrunBp30, speedrunBp60,
|
||||
speedrunBp120, stack4Layers, stackShape, store100Unique, storeShape, throughputBp25, throughputBp50,
|
||||
throughputLogo25, throughputLogo50, throughputRocket10, throughputRocket20, trash1000, unlockWires,
|
||||
upgradesTier5, upgradesTier8;
|
||||
@each $achievement in $achievements {
|
||||
[data-icon="achievements/#{$achievement}.png"] {
|
||||
/* @load-async */
|
||||
background-image: uiResource("res/ui/achievements/#{$achievement}.png") !important;
|
||||
}
|
||||
}
|
||||
|
||||
$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi,
|
||||
th, hu, pl, ja, kor, no, pt-PT;
|
||||
|
||||
|
@ -49,6 +49,7 @@ import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
|
||||
import { HUDCatMemes } from "./parts/cat_memes";
|
||||
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
||||
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
|
||||
import { HUDAchievements } from "./parts/achievements";
|
||||
|
||||
export class GameHUD {
|
||||
/**
|
||||
@ -84,6 +85,7 @@ export class GameHUD {
|
||||
massSelector: new HUDMassSelector(this.root),
|
||||
shop: new HUDShop(this.root),
|
||||
statistics: new HUDStatistics(this.root),
|
||||
achievements: new HUDAchievements(this.root),
|
||||
waypoints: new HUDWaypoints(this.root),
|
||||
wireInfo: new HUDWireInfo(this.root),
|
||||
leverToggle: new HUDLeverToggle(this.root),
|
||||
|
118
src/js/game/hud/parts/achievements.js
Normal file
@ -0,0 +1,118 @@
|
||||
import { ClickDetector } from "../../../core/click_detector";
|
||||
import { InputReceiver } from "../../../core/input_receiver";
|
||||
import { formatBigNumber, getRomanNumber, makeDiv } from "../../../core/utils";
|
||||
import { ACHIEVEMENTS, enum_achievement_mappings } from "../../../platform/achievement_provider";
|
||||
import { T } from "../../../translations";
|
||||
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
|
||||
export class HUDAchievements extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.background = makeDiv(parent, "ingame_HUD_Achievements", ["ingameDialog"]);
|
||||
|
||||
// DIALOG Inner / Wrapper
|
||||
this.dialogInner = makeDiv(this.background, null, ["dialogInner"]);
|
||||
this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.achievements.title);
|
||||
this.closeButton = makeDiv(this.title, null, ["closeButton"]);
|
||||
this.trackClicks(this.closeButton, this.close);
|
||||
this.contentDiv = makeDiv(this.dialogInner, null, ["content"]);
|
||||
|
||||
this.achievementToElements = {};
|
||||
|
||||
// ACHIEVEMENTS
|
||||
for (const achievementKey in ACHIEVEMENTS) {
|
||||
const handle = {};
|
||||
|
||||
// Wrapper
|
||||
handle.elem = makeDiv(this.contentDiv, null, ["achievement"]);
|
||||
|
||||
// Icon
|
||||
handle.icon = makeDiv(handle.elem, null, ["icon"]);
|
||||
handle.icon.setAttribute("data-icon", "achievements/" + achievementKey + ".png");
|
||||
|
||||
// Info
|
||||
handle.info = makeDiv(handle.elem, null, ["info"]);
|
||||
|
||||
// Title
|
||||
const title = makeDiv(handle.info, null, ["title"], T.achievements[achievementKey].title);
|
||||
|
||||
// Description
|
||||
handle.elemDescription = makeDiv(
|
||||
handle.info,
|
||||
null,
|
||||
["description"],
|
||||
T.achievements[achievementKey].description
|
||||
);
|
||||
|
||||
// Reset button
|
||||
handle.resetButton = document.createElement("button");
|
||||
handle.resetButton.classList.add("reset", "styledButton");
|
||||
handle.resetButton.innerText = T.ingame.achievements.buttonReset;
|
||||
handle.elem.appendChild(handle.resetButton);
|
||||
|
||||
this.trackClicks(handle.resetButton, () => {
|
||||
this.root.achievementProxy.provider.collection.lock(
|
||||
achievementKey,
|
||||
enum_achievement_mappings[ACHIEVEMENTS[achievementKey]]
|
||||
);
|
||||
});
|
||||
|
||||
// Assign handle
|
||||
this.achievementToElements[achievementKey] = handle;
|
||||
}
|
||||
}
|
||||
|
||||
renderStatus() {
|
||||
for (const achievementKey in this.achievementToElements) {
|
||||
const handle = this.achievementToElements[achievementKey];
|
||||
if (!this.root.achievementProxy.provider.collection.map.get(ACHIEVEMENTS[achievementKey])) {
|
||||
if (!handle.elem.classList.contains("unlocked")) handle.elem.classList.add("unlocked");
|
||||
} else if (handle.elem.classList.contains("unlocked")) handle.elem.classList.remove("unlocked");
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.background, {
|
||||
attachClass: "visible",
|
||||
});
|
||||
|
||||
this.inputReciever = new InputReceiver("achievements");
|
||||
this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever);
|
||||
|
||||
this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this);
|
||||
this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuClose).add(this.close, this);
|
||||
this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuOpenAchievements).add(this.close, this);
|
||||
|
||||
this.close();
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
// Cleanup detectors
|
||||
for (const achievementKey in this.achievementToElements) {
|
||||
const handle = this.achievementToElements[achievementKey];
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visible = true;
|
||||
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.visible = false;
|
||||
this.root.app.inputMgr.makeSureDetached(this.inputReciever);
|
||||
this.update();
|
||||
}
|
||||
|
||||
update() {
|
||||
this.domAttach.update(this.visible);
|
||||
if (this.visible) {
|
||||
this.renderStatus();
|
||||
}
|
||||
}
|
||||
|
||||
isBlockingOverlay() {
|
||||
return this.visible;
|
||||
}
|
||||
}
|
@ -33,6 +33,13 @@ export class HUDGameMenu extends BaseHUDPart {
|
||||
visible: () =>
|
||||
!this.root.app.settings.getAllSettings().offerHints || this.root.hubGoals.level >= 3,
|
||||
},
|
||||
{
|
||||
id: "achievements",
|
||||
label: "Achievements",
|
||||
handler: () => this.root.hud.parts.achievements.show(),
|
||||
keybinding: KEYMAPPINGS.ingame.menuOpenAchievements,
|
||||
visible: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
/** @type {Array<{
|
||||
|
@ -25,6 +25,7 @@ export const KEYMAPPINGS = {
|
||||
ingame: {
|
||||
menuOpenShop: { keyCode: key("F") },
|
||||
menuOpenStats: { keyCode: key("G") },
|
||||
menuOpenAchievements: { keyCode: key("H") },
|
||||
menuClose: { keyCode: key("Q") },
|
||||
|
||||
toggleHud: { keyCode: 113 }, // F2
|
||||
|
@ -183,6 +183,7 @@ export class GameRoot {
|
||||
// Called with an achievement key and necessary args to validate it can be unlocked.
|
||||
achievementCheck: /** @type {TypedSignal<[string, any]>} */ (new Signal()),
|
||||
bulkAchievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()),
|
||||
achievementCompleted: /** @type {TypedSignal<[string, any]>} */ (new Signal()),
|
||||
};
|
||||
|
||||
// RNG's
|
||||
|
@ -58,6 +58,214 @@ export const ACHIEVEMENTS = {
|
||||
upgradesTier8: "upgradesTier8",
|
||||
};
|
||||
|
||||
export const enum_achievement_mappings = {
|
||||
[ACHIEVEMENTS.belt500Tiles]: function () {
|
||||
return {
|
||||
isValid: this.isBelt500TilesValid,
|
||||
signal: "entityAdded",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.blueprint100k]: function () {
|
||||
// @ts-ignore
|
||||
return this.createBlueprintOptions(100000);
|
||||
},
|
||||
[ACHIEVEMENTS.blueprint1m]: function () {
|
||||
// @ts-ignore
|
||||
return this.createBlueprintOptions(1000000);
|
||||
},
|
||||
[ACHIEVEMENTS.completeLvl26]: function () {
|
||||
// @ts-ignore
|
||||
return this.createLevelOptions(26);
|
||||
},
|
||||
[ACHIEVEMENTS.cutShape]: function () {
|
||||
return {};
|
||||
},
|
||||
[ACHIEVEMENTS.darkMode]: function () {
|
||||
return {
|
||||
isValid: this.isDarkModeValid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.destroy1000]: function () {
|
||||
return {
|
||||
isValid: this.isDestroy1000Valid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.irrelevantShape]: function () {
|
||||
return {
|
||||
isValid: this.isIrrelevantShapeValid,
|
||||
signal: "shapeDelivered",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.level100]: function () {
|
||||
// @ts-ignore
|
||||
return this.createLevelOptions(100);
|
||||
},
|
||||
[ACHIEVEMENTS.level50]: function () {
|
||||
// @ts-ignore
|
||||
return this.createLevelOptions(50);
|
||||
},
|
||||
[ACHIEVEMENTS.logoBefore18]: function () {
|
||||
return {
|
||||
isValid: this.isLogoBefore18Valid,
|
||||
signal: "itemProduced",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.mam]: function () {
|
||||
return {
|
||||
isValid: this.isMamValid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.mapMarkers15]: function () {
|
||||
return {
|
||||
isValid: this.isMapMarkers15Valid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.noBeltUpgradesUntilBp]: function () {
|
||||
return {
|
||||
isValid: this.isNoBeltUpgradesUntilBpValid,
|
||||
signal: "storyGoalCompleted",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.noInverseRotater]: function () {
|
||||
return {
|
||||
init: this.initNoInverseRotater,
|
||||
isValid: this.isNoInverseRotaterValid,
|
||||
signal: "storyGoalCompleted",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.oldLevel17]: function () {
|
||||
// @ts-ignore
|
||||
return this.createShapeOptions(SHAPE_OLD_LEVEL_17);
|
||||
},
|
||||
[ACHIEVEMENTS.openWires]: function () {
|
||||
return {
|
||||
isValid: this.isOpenWiresValid,
|
||||
signal: "editModeChanged",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.paintShape]: function () {
|
||||
return {};
|
||||
},
|
||||
[ACHIEVEMENTS.place5000Wires]: function () {
|
||||
return {
|
||||
isValid: this.isPlace5000WiresValid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.placeBlueprint]: function () {
|
||||
return {
|
||||
isValid: this.isPlaceBlueprintValid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.placeBp1000]: function () {
|
||||
return {
|
||||
isValid: this.isPlaceBp1000Valid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.play1h]: function () {
|
||||
// @ts-ignore
|
||||
return this.createTimeOptions(HOUR_1);
|
||||
},
|
||||
[ACHIEVEMENTS.play10h]: function () {
|
||||
// @ts-ignore
|
||||
return this.createTimeOptions(HOUR_10);
|
||||
},
|
||||
[ACHIEVEMENTS.play20h]: function () {
|
||||
// @ts-ignore
|
||||
return this.createTimeOptions(HOUR_20);
|
||||
},
|
||||
[ACHIEVEMENTS.produceLogo]: function () {
|
||||
// @ts-ignore
|
||||
return this.createShapeOptions(SHAPE_LOGO);
|
||||
},
|
||||
[ACHIEVEMENTS.produceRocket]: function () {
|
||||
// @ts-ignore
|
||||
return this.createShapeOptions(SHAPE_ROCKET);
|
||||
},
|
||||
[ACHIEVEMENTS.produceMsLogo]: function () {
|
||||
// @ts-ignore
|
||||
return this.createShapeOptions(SHAPE_MS_LOGO);
|
||||
},
|
||||
[ACHIEVEMENTS.rotateShape]: function () {
|
||||
return {};
|
||||
},
|
||||
[ACHIEVEMENTS.speedrunBp30]: function () {
|
||||
// @ts-ignore
|
||||
return this.createSpeedOptions(12, MINUTE_30);
|
||||
},
|
||||
[ACHIEVEMENTS.speedrunBp60]: function () {
|
||||
// @ts-ignore
|
||||
return this.createSpeedOptions(12, MINUTE_60);
|
||||
},
|
||||
[ACHIEVEMENTS.speedrunBp120]: function () {
|
||||
// @ts-ignore
|
||||
return this.createSpeedOptions(12, MINUTE_120);
|
||||
},
|
||||
[ACHIEVEMENTS.stack4Layers]: function () {
|
||||
return {
|
||||
isValid: this.isStack4LayersValid,
|
||||
signal: "itemProduced",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.stackShape]: function () {
|
||||
return {};
|
||||
},
|
||||
[ACHIEVEMENTS.store100Unique]: function () {
|
||||
return {
|
||||
init: this.initStore100Unique,
|
||||
isValid: this.isStore100UniqueValid,
|
||||
signal: "shapeDelivered",
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.storeShape]: function () {
|
||||
return {
|
||||
init: this.initStoreShape,
|
||||
isValid: this.isStoreShapeValid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.throughputBp25]: function () {
|
||||
// @ts-ignore
|
||||
return this.createRateOptions(SHAPE_BP, 25);
|
||||
},
|
||||
[ACHIEVEMENTS.throughputBp50]: function () {
|
||||
// @ts-ignore
|
||||
return this.createRateOptions(SHAPE_BP, 50);
|
||||
},
|
||||
[ACHIEVEMENTS.throughputLogo25]: function () {
|
||||
// @ts-ignore
|
||||
return this.createRateOptions(SHAPE_LOGO, 25);
|
||||
},
|
||||
[ACHIEVEMENTS.throughputLogo50]: function () {
|
||||
// @ts-ignore
|
||||
return this.createRateOptions(SHAPE_LOGO, 50);
|
||||
},
|
||||
[ACHIEVEMENTS.throughputRocket10]: function () {
|
||||
// @ts-ignore
|
||||
return this.createRateOptions(SHAPE_ROCKET, 25);
|
||||
},
|
||||
[ACHIEVEMENTS.throughputRocket20]: function () {
|
||||
// @ts-ignore
|
||||
return this.createRateOptions(SHAPE_ROCKET, 50);
|
||||
},
|
||||
[ACHIEVEMENTS.trash1000]: function () {
|
||||
return {
|
||||
init: this.initTrash1000,
|
||||
isValid: this.isTrash1000Valid,
|
||||
};
|
||||
},
|
||||
[ACHIEVEMENTS.unlockWires]: function () {
|
||||
// @ts-ignore
|
||||
return this.createLevelOptions(20);
|
||||
},
|
||||
[ACHIEVEMENTS.upgradesTier5]: function () {
|
||||
// @ts-ignore
|
||||
return this.createUpgradeOptions(5);
|
||||
},
|
||||
[ACHIEVEMENTS.upgradesTier8]: function () {
|
||||
// @ts-ignore
|
||||
return this.createUpgradeOptions(8);
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {keyof typeof THEMES} */
|
||||
const DARK_MODE = "dark";
|
||||
|
||||
@ -87,6 +295,7 @@ export class AchievementProviderInterface {
|
||||
/** @param {Application} app */
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
this.storage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,102 +371,16 @@ export class Achievement {
|
||||
export class AchievementCollection {
|
||||
/**
|
||||
* @param {function} activate - Resolves when provider activation is complete
|
||||
* @param {function} deactivate - Resolves when provider deactivation is complete
|
||||
*/
|
||||
constructor(activate) {
|
||||
constructor(activate, deactivate) {
|
||||
this.map = new Map();
|
||||
this.activate = activate;
|
||||
this.deactivate = deactivate;
|
||||
|
||||
this.add(ACHIEVEMENTS.belt500Tiles, {
|
||||
isValid: this.isBelt500TilesValid,
|
||||
signal: "entityAdded",
|
||||
});
|
||||
this.add(ACHIEVEMENTS.blueprint100k, this.createBlueprintOptions(100000));
|
||||
this.add(ACHIEVEMENTS.blueprint1m, this.createBlueprintOptions(1000000));
|
||||
this.add(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26));
|
||||
this.add(ACHIEVEMENTS.cutShape);
|
||||
this.add(ACHIEVEMENTS.darkMode, {
|
||||
isValid: this.isDarkModeValid,
|
||||
});
|
||||
this.add(ACHIEVEMENTS.destroy1000, {
|
||||
isValid: this.isDestroy1000Valid,
|
||||
});
|
||||
this.add(ACHIEVEMENTS.irrelevantShape, {
|
||||
isValid: this.isIrrelevantShapeValid,
|
||||
signal: "shapeDelivered",
|
||||
});
|
||||
this.add(ACHIEVEMENTS.level100, this.createLevelOptions(100));
|
||||
this.add(ACHIEVEMENTS.level50, this.createLevelOptions(50));
|
||||
this.add(ACHIEVEMENTS.logoBefore18, {
|
||||
isValid: this.isLogoBefore18Valid,
|
||||
signal: "itemProduced",
|
||||
});
|
||||
this.add(ACHIEVEMENTS.mam, {
|
||||
isValid: this.isMamValid,
|
||||
});
|
||||
this.add(ACHIEVEMENTS.mapMarkers15, {
|
||||
isValid: this.isMapMarkers15Valid,
|
||||
});
|
||||
this.add(ACHIEVEMENTS.noBeltUpgradesUntilBp, {
|
||||
isValid: this.isNoBeltUpgradesUntilBpValid,
|
||||
signal: "storyGoalCompleted",
|
||||
});
|
||||
this.add(ACHIEVEMENTS.noInverseRotater, {
|
||||
init: this.initNoInverseRotater,
|
||||
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.add(ACHIEVEMENTS.paintShape);
|
||||
this.add(ACHIEVEMENTS.place5000Wires, {
|
||||
isValid: this.isPlace5000WiresValid,
|
||||
});
|
||||
this.add(ACHIEVEMENTS.placeBlueprint, {
|
||||
isValid: this.isPlaceBlueprintValid,
|
||||
});
|
||||
this.add(ACHIEVEMENTS.placeBp1000, {
|
||||
isValid: this.isPlaceBp1000Valid,
|
||||
});
|
||||
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.add(ACHIEVEMENTS.stackShape);
|
||||
this.add(ACHIEVEMENTS.store100Unique, {
|
||||
init: this.initStore100Unique,
|
||||
isValid: this.isStore100UniqueValid,
|
||||
signal: "shapeDelivered",
|
||||
});
|
||||
this.add(ACHIEVEMENTS.storeShape, {
|
||||
init: this.initStoreShape,
|
||||
isValid: this.isStoreShapeValid,
|
||||
});
|
||||
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.add(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20));
|
||||
this.add(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5));
|
||||
this.add(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8));
|
||||
for (const key in ACHIEVEMENTS) {
|
||||
this.add(ACHIEVEMENTS[key], enum_achievement_mappings[ACHIEVEMENTS[key]].bind(this)());
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {GameRoot} root */
|
||||
@ -324,14 +447,14 @@ export class AchievementCollection {
|
||||
* @param {string} key - Maps to an Achievement
|
||||
* @param {any} data - Data received from signal dispatches for validation
|
||||
*/
|
||||
unlock(key, data) {
|
||||
unlock(key, data, force = false) {
|
||||
if (!this.map.has(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const achievement = this.map.get(key);
|
||||
|
||||
if (!achievement.isValid(data)) {
|
||||
if (!force && !achievement.isValid(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -339,12 +462,21 @@ export class AchievementCollection {
|
||||
.unlock()
|
||||
.then(() => {
|
||||
this.onActivate(null, key);
|
||||
this.root.signals.achievementCompleted.dispatch(key, data);
|
||||
})
|
||||
.catch(err => {
|
||||
this.onActivate(err, key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key - Maps to an Achievement
|
||||
*/
|
||||
lock(key, options) {
|
||||
this.add(key, options);
|
||||
this.deactivate(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up after achievement activation attempt with the provider. Could
|
||||
* utilize err to retry some number of times if needed.
|
||||
|
168
src/js/platform/browser/browser_achievement_provider.js
Normal file
@ -0,0 +1,168 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../../application";
|
||||
import { GameRoot } from "../../game/root";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../../core/logging";
|
||||
import { AchievementCollection, AchievementProviderInterface, ACHIEVEMENTS } from "../achievement_provider";
|
||||
import { ReadWriteProxy } from "../../core/read_write_proxy";
|
||||
import { ExplainedResult } from "../../core/explained_result";
|
||||
|
||||
const logger = createLogger("achievements/browser");
|
||||
|
||||
export class BrowserAchievementStorage extends ReadWriteProxy {
|
||||
constructor(app) {
|
||||
super(app, "app_achievements.bin");
|
||||
}
|
||||
|
||||
initialize() {
|
||||
return this.readAsync().then(() => {
|
||||
console.log(this.currentData);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
return this.writeAsync();
|
||||
}
|
||||
|
||||
/** @returns {ExplainedResult} */
|
||||
verify(data) {
|
||||
if (!data.unlocked) {
|
||||
return ExplainedResult.bad("missing key 'unlocked'");
|
||||
}
|
||||
if (!Array.isArray(data.unlocked)) {
|
||||
return ExplainedResult.bad("Bad array 'unlocked'");
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.unlocked.length; i++) {
|
||||
const achievement = data.unlocked[i];
|
||||
let exists = false;
|
||||
for (const key in ACHIEVEMENTS) {
|
||||
if (ACHIEVEMENTS[key] === achievement) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
// Should return the default data
|
||||
getDefaultData() {
|
||||
return {
|
||||
version: this.getCurrentVersion(),
|
||||
unlocked: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Should return the current version as an integer
|
||||
getCurrentVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Should migrate the data (Modify in place)
|
||||
/** @returns {ExplainedResult} */
|
||||
migrate(data) {
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserAchievementProvider extends AchievementProviderInterface {
|
||||
/** @param {Application} app */
|
||||
constructor(app) {
|
||||
super(app);
|
||||
|
||||
this.initialized = false;
|
||||
this.collection = new AchievementCollection(this.activate.bind(this), this.deactivate.bind(this));
|
||||
this.storage = new BrowserAchievementStorage(app);
|
||||
if (G_IS_DEV) {
|
||||
for (let key in ACHIEVEMENTS) {
|
||||
assert(this.collection.map.has(key), "Key not found in collection: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
logger.log("Collection created with", this.collection.map.size, "achievements");
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
hasAchievements() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
onLoad(root) {
|
||||
this.root = root;
|
||||
try {
|
||||
this.collection = new AchievementCollection(this.activate.bind(this), this.deactivate.bind(this));
|
||||
this.collection.initialize(root);
|
||||
|
||||
//Unlock already unlocked
|
||||
for (let i = 0; i < this.storage.currentData.unlocked.length; i++) {
|
||||
const achievement = this.storage.currentData.unlocked[i];
|
||||
this.collection.unlock(achievement, null, true);
|
||||
}
|
||||
|
||||
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
logger.error("Failed to initialize the collection");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {Promise<void>} */
|
||||
initialize() {
|
||||
return this.storage.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
activate(key) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (!this.storage.currentData.unlocked.includes(key))
|
||||
this.storage.currentData.unlocked.push(key);
|
||||
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => this.storage.save())
|
||||
.then(() => {
|
||||
logger.log("Achievement activated:", key);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error("Failed to activate achievement:", key, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
deactivate(key) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (this.storage.currentData.unlocked.includes(key))
|
||||
this.storage.currentData.unlocked.splice(
|
||||
this.storage.currentData.unlocked.indexOf(key),
|
||||
1
|
||||
);
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => this.storage.save())
|
||||
.then(() => {
|
||||
logger.log("Achievement deactivated:", key);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error("Failed to deactivate achievement:", key, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
@ -20,4 +20,8 @@ export class NoAchievementProvider extends AchievementProviderInterface {
|
||||
activate() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import { queryParamOptions } from "../../core/query_parameters";
|
||||
import { clamp } from "../../core/utils";
|
||||
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
|
||||
import { NoAdProvider } from "../ad_providers/no_ad_provider";
|
||||
import { SteamAchievementProvider } from "../electron/steam_achievement_provider";
|
||||
import { PlatformWrapperInterface } from "../wrapper";
|
||||
import { BrowserAchievementProvider } from "./browser_achievement_provider";
|
||||
import { NoAchievementProvider } from "./no_achievement_provider";
|
||||
import { StorageImplBrowser } from "./storage";
|
||||
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
||||
@ -200,8 +200,8 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
||||
}
|
||||
|
||||
initializeAchievementProvider() {
|
||||
if (G_IS_DEV && globalConfig.debug.testAchievements) {
|
||||
this.app.achievementProvider = new SteamAchievementProvider(this.app);
|
||||
if (G_IS_DEV) {
|
||||
this.app.achievementProvider = new BrowserAchievementProvider(this.app);
|
||||
|
||||
return this.app.achievementProvider.initialize().catch(err => {
|
||||
logger.error("Failed to initialize achievement provider, disabling:", err);
|
||||
|
@ -63,7 +63,7 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
||||
super(app);
|
||||
|
||||
this.initialized = false;
|
||||
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||
this.collection = new AchievementCollection(this.activate.bind(this), this.deactivate.bind(this));
|
||||
|
||||
if (G_IS_DEV) {
|
||||
for (let key in ACHIEVEMENT_IDS) {
|
||||
@ -87,11 +87,25 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
||||
this.root = root;
|
||||
|
||||
try {
|
||||
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||
this.collection = new AchievementCollection(this.activate.bind(this), this.deactivate.bind(this));
|
||||
this.collection.initialize(root);
|
||||
|
||||
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
||||
return Promise.resolve();
|
||||
let promise = Promise.resolve();
|
||||
|
||||
//Unlock already unlocked
|
||||
for (const id in ACHIEVEMENT_IDS) {
|
||||
promise.then(() =>
|
||||
this.ipc.invoke("steam:get-achievement", ACHIEVEMENT_IDS[id]).then(is_achieved => {
|
||||
if (is_achieved) this.collection.unlock(id, null, true);
|
||||
return Promise.resolve();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
||||
return Promise.resolve();
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Failed to initialize the collection");
|
||||
return Promise.reject(err);
|
||||
@ -140,4 +154,27 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
deactivate(key) {
|
||||
let promise;
|
||||
|
||||
if (!this.initialized) {
|
||||
promise = Promise.resolve();
|
||||
} else {
|
||||
promise = this.ipc.invoke("steam:deactivate-achievement", ACHIEVEMENT_IDS[key]);
|
||||
}
|
||||
|
||||
return promise
|
||||
.then(() => {
|
||||
logger.log("Achievement deactivated:", key);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error("Failed to deactivate achievement:", key, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -355,6 +355,11 @@ ingame:
|
||||
minute: <shapes> / m
|
||||
hour: <shapes> / h
|
||||
|
||||
# The "Achievements window"
|
||||
achievements:
|
||||
title: Achievements
|
||||
buttonReset: Reset
|
||||
|
||||
# Settings menu, when you press "ESC"
|
||||
settingsMenu:
|
||||
playtime: Playtime
|
||||
@ -492,6 +497,144 @@ shopUpgrades:
|
||||
name: Mixing & Painting
|
||||
description: Speed x<currentMult> → x<newMult>
|
||||
|
||||
# All achievements
|
||||
achievements:
|
||||
belt500Tiles:
|
||||
title: I need trains
|
||||
description: Have a belt with a length of 500 tiles
|
||||
blueprint100k:
|
||||
title: It's piling up
|
||||
description: Have 100k blueprint shapes stored in your hub
|
||||
blueprint1m:
|
||||
title: I'll use it later
|
||||
description: Have 1 million blueprint shapes stored in your hub
|
||||
completeLvl26:
|
||||
title: Freedom
|
||||
description: Complete level 26, unlocking the free play mode
|
||||
cutShape:
|
||||
title: Cutter
|
||||
description: Cut a shape
|
||||
darkMode:
|
||||
title: My eyes no longer hurt
|
||||
description:
|
||||
destroy1000:
|
||||
title: Perfectionist
|
||||
description: Destroy more than 1000 objects at once
|
||||
irrelevantShape:
|
||||
title: Oops
|
||||
description: Deliver a shape which is not the goal, nor relevant for any upgrade to the hub
|
||||
level100:
|
||||
title: Is this the end?
|
||||
description: Reach level 100
|
||||
level50:
|
||||
title: Can't stop
|
||||
description: Reach level 50
|
||||
logoBefore18:
|
||||
title: A bit early?
|
||||
description: Create the logo shape before level 18
|
||||
mam:
|
||||
title: MAM (Make Anything Machine)
|
||||
description: Complete any level (after level 26) without modifying your factory
|
||||
mapMarkers15:
|
||||
title: GPS
|
||||
description: Have 15 map markers
|
||||
noBeltUpgradesUntilBp:
|
||||
title: It's so slow
|
||||
description: Reach & Complete level 12 without upgrading your belts
|
||||
noInverseRotater:
|
||||
title: King of Inefficiency
|
||||
description: Use no inverse rotator until level 14
|
||||
oldLevel17:
|
||||
title: Memories from the past
|
||||
description: Deliver WrRgWrRg:CwCrCwCr:SgSgSgSg to the hub (Old level 17 shape)
|
||||
openWires:
|
||||
title: The next dimension
|
||||
description: Open the wires layer
|
||||
paintShape:
|
||||
title: Painter
|
||||
description: Paint a shape
|
||||
place5000Wires:
|
||||
title: Computer Guy
|
||||
description: Have more than 5,000 wires
|
||||
placeBlueprint:
|
||||
title: Now it's easy
|
||||
description: Place a blueprint
|
||||
placeBp1000:
|
||||
title: Copy-Pasta
|
||||
description: Place a blueprint with 1000 or more buildings
|
||||
play1h:
|
||||
title: Getting into it
|
||||
description: Play one savegame for more than 1 hour
|
||||
play10h:
|
||||
title: It's been a long time
|
||||
description: Play one savegame for more than 10 hours
|
||||
play20h:
|
||||
title: Addicted
|
||||
description: Play one savegame for more than 20 hours
|
||||
produceLogo:
|
||||
title: The logo!
|
||||
description: Produce the shapez.io logo
|
||||
produceMsLogo:
|
||||
title: I've seen that before ..
|
||||
description: Deliver RgRyRbRr to the hub
|
||||
produceRocket:
|
||||
title: To the moon
|
||||
description: Produce the rocket shape
|
||||
rotateShape:
|
||||
title: Rotater
|
||||
description: Rotate a shape
|
||||
speedrunBp30:
|
||||
title: Speedrun Master
|
||||
description: Reach & complete level 12 in under 30 minutes
|
||||
speedrunBp60:
|
||||
title: Speedrun Novice
|
||||
description: Reach & complete level 12 in under 60 minutes
|
||||
speedrunBp120:
|
||||
title: Not an idle game
|
||||
description: Reach & complete level 12 in under 120 minutes
|
||||
stack4Layers:
|
||||
title: Stack overflow
|
||||
description: Produce a shape with 4 layers
|
||||
stackShape:
|
||||
title: Wait, they stack?
|
||||
description: Stack a shape
|
||||
store100Unique:
|
||||
title: It's a mess
|
||||
description: Have 100 different shapes stored in your hub
|
||||
storeShape:
|
||||
title: Storage
|
||||
description: Store a shape in the storage
|
||||
throughputBp25:
|
||||
title: Efficiency 1
|
||||
description: Deliver 25 blueprints shapes / second to your hub
|
||||
throughputBp50:
|
||||
title: Efficiency 2
|
||||
description: Deliver 50 blueprints shapes / second to your hub
|
||||
throughputLogo25:
|
||||
title: Branding specialist 1
|
||||
description: Deliver 25 logo shapes / second to your hub
|
||||
throughputLogo50:
|
||||
title: Branding specialist 2
|
||||
description: Deliver 50 logo shapes / second to your hub
|
||||
throughputRocket10:
|
||||
title: Preparing to launch
|
||||
description: Deliver 10 rocket shapes / second to your hub
|
||||
throughputRocket20:
|
||||
title: SpaceY
|
||||
description: Deliver 20 rocket shapes / second to your hub
|
||||
trash1000:
|
||||
title: Get rid of them
|
||||
description: Have 1000 shapes trashed
|
||||
unlockWires:
|
||||
title: Wires
|
||||
description: Complete level 20, unlocking the wires layer
|
||||
upgradesTier5:
|
||||
title: Faster
|
||||
description: Have all upgades on tier 5 or higher
|
||||
upgradesTier8:
|
||||
title: Even faster
|
||||
description: Have all upgrades on tier 8 or higher
|
||||
|
||||
# Buildings and their name / description
|
||||
buildings:
|
||||
hub:
|
||||
@ -1096,6 +1239,7 @@ keybindings:
|
||||
|
||||
menuOpenShop: Upgrades
|
||||
menuOpenStats: Statistics
|
||||
menuOpenAchievements: Achievements
|
||||
menuClose: Close Menu
|
||||
|
||||
toggleHud: Toggle HUD
|
||||
|