Ingame achievements (+ dev ingame)
@ -43,7 +43,9 @@ function listen () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle("steam:get-achievement-names", getAchievementNames);
|
ipcMain.handle("steam:get-achievement-names", getAchievementNames);
|
||||||
|
ipcMain.handle("steam:get-achievement", getAchievement);
|
||||||
ipcMain.handle("steam:activate-achievement", activateAchievement);
|
ipcMain.handle("steam:activate-achievement", activateAchievement);
|
||||||
|
ipcMain.handle("steam:deactivate-achievement", deactivateAchievement);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInitialized(event) {
|
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) {
|
function activateAchievement(event, id) {
|
||||||
return new Promise((resolve, reject) => {
|
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"
|
loader-utils "^1.4.0"
|
||||||
yaml "^1.8.3"
|
yaml "^1.8.3"
|
||||||
|
|
||||||
yaml@^1.10.0:
|
yaml@^1.10.0, yaml@^1.8.3:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
||||||
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
|
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:
|
yargs-parser@5.0.0-security.0:
|
||||||
version "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"
|
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;
|
@include DarkThemeInvert;
|
||||||
|
|
||||||
&.shop {
|
&.achievements {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
|
& {
|
||||||
|
/* @load-async */
|
||||||
|
background-image: uiResource("icons/achievements.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.shop {
|
||||||
|
grid-column: 2;
|
||||||
& {
|
& {
|
||||||
/* @load-async */
|
/* @load-async */
|
||||||
background-image: uiResource("icons/shop.png");
|
background-image: uiResource("icons/shop.png");
|
||||||
@ -46,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.stats {
|
&.stats {
|
||||||
grid-column: 2;
|
grid-column: 3;
|
||||||
& {
|
& {
|
||||||
/* @load-async */
|
/* @load-async */
|
||||||
background-image: uiResource("icons/statistics.png");
|
background-image: uiResource("icons/statistics.png");
|
||||||
@ -58,7 +66,7 @@
|
|||||||
/* @load-async */
|
/* @load-async */
|
||||||
background-image: uiResource("icons/save.png");
|
background-image: uiResource("icons/save.png");
|
||||||
}
|
}
|
||||||
grid-column: 3;
|
grid-column: 4;
|
||||||
@include MakeAnimationWrappedEvenOdd(0.5s ease-in-out) {
|
@include MakeAnimationWrappedEvenOdd(0.5s ease-in-out) {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(1, 1);
|
transform: scale(1, 1);
|
||||||
@ -92,7 +100,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.settings {
|
&.settings {
|
||||||
grid-column: 4;
|
grid-column: 5;
|
||||||
& {
|
& {
|
||||||
/* @load-async */
|
/* @load-async */
|
||||||
background-image: uiResource("icons/settings_menu_settings.png");
|
background-image: uiResource("icons/settings_menu_settings.png");
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
@import "ingame_hud/sandbox_controller";
|
@import "ingame_hud/sandbox_controller";
|
||||||
@import "ingame_hud/standalone_advantages";
|
@import "ingame_hud/standalone_advantages";
|
||||||
@import "ingame_hud/cat_memes";
|
@import "ingame_hud/cat_memes";
|
||||||
|
@import "ingame_hud/achievements";
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
$elements:
|
$elements:
|
||||||
@ -89,6 +90,7 @@ ingame_HUD_SandboxController,
|
|||||||
ingame_HUD_BetaOverlay,
|
ingame_HUD_BetaOverlay,
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
|
ingame_HUD_Achievements,
|
||||||
ingame_HUD_Shop,
|
ingame_HUD_Shop,
|
||||||
ingame_HUD_Statistics,
|
ingame_HUD_Statistics,
|
||||||
ingame_HUD_ShapeViewer,
|
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,
|
$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;
|
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 { HUDCatMemes } from "./parts/cat_memes";
|
||||||
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
||||||
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
|
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
|
||||||
|
import { HUDAchievements } from "./parts/achievements";
|
||||||
|
|
||||||
export class GameHUD {
|
export class GameHUD {
|
||||||
/**
|
/**
|
||||||
@ -84,6 +85,7 @@ export class GameHUD {
|
|||||||
massSelector: new HUDMassSelector(this.root),
|
massSelector: new HUDMassSelector(this.root),
|
||||||
shop: new HUDShop(this.root),
|
shop: new HUDShop(this.root),
|
||||||
statistics: new HUDStatistics(this.root),
|
statistics: new HUDStatistics(this.root),
|
||||||
|
achievements: new HUDAchievements(this.root),
|
||||||
waypoints: new HUDWaypoints(this.root),
|
waypoints: new HUDWaypoints(this.root),
|
||||||
wireInfo: new HUDWireInfo(this.root),
|
wireInfo: new HUDWireInfo(this.root),
|
||||||
leverToggle: new HUDLeverToggle(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: () =>
|
visible: () =>
|
||||||
!this.root.app.settings.getAllSettings().offerHints || this.root.hubGoals.level >= 3,
|
!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<{
|
/** @type {Array<{
|
||||||
|
@ -25,6 +25,7 @@ export const KEYMAPPINGS = {
|
|||||||
ingame: {
|
ingame: {
|
||||||
menuOpenShop: { keyCode: key("F") },
|
menuOpenShop: { keyCode: key("F") },
|
||||||
menuOpenStats: { keyCode: key("G") },
|
menuOpenStats: { keyCode: key("G") },
|
||||||
|
menuOpenAchievements: { keyCode: key("H") },
|
||||||
menuClose: { keyCode: key("Q") },
|
menuClose: { keyCode: key("Q") },
|
||||||
|
|
||||||
toggleHud: { keyCode: 113 }, // F2
|
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.
|
// Called with an achievement key and necessary args to validate it can be unlocked.
|
||||||
achievementCheck: /** @type {TypedSignal<[string, any]>} */ (new Signal()),
|
achievementCheck: /** @type {TypedSignal<[string, any]>} */ (new Signal()),
|
||||||
bulkAchievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()),
|
bulkAchievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()),
|
||||||
|
achievementCompleted: /** @type {TypedSignal<[string, any]>} */ (new Signal()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// RNG's
|
// RNG's
|
||||||
|
@ -58,6 +58,214 @@ export const ACHIEVEMENTS = {
|
|||||||
upgradesTier8: "upgradesTier8",
|
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} */
|
/** @type {keyof typeof THEMES} */
|
||||||
const DARK_MODE = "dark";
|
const DARK_MODE = "dark";
|
||||||
|
|
||||||
@ -87,6 +295,7 @@ export class AchievementProviderInterface {
|
|||||||
/** @param {Application} app */
|
/** @param {Application} app */
|
||||||
constructor(app) {
|
constructor(app) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
this.storage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,102 +371,16 @@ export class Achievement {
|
|||||||
export class AchievementCollection {
|
export class AchievementCollection {
|
||||||
/**
|
/**
|
||||||
* @param {function} activate - Resolves when provider activation is complete
|
* @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.map = new Map();
|
||||||
this.activate = activate;
|
this.activate = activate;
|
||||||
|
this.deactivate = deactivate;
|
||||||
|
|
||||||
this.add(ACHIEVEMENTS.belt500Tiles, {
|
for (const key in ACHIEVEMENTS) {
|
||||||
isValid: this.isBelt500TilesValid,
|
this.add(ACHIEVEMENTS[key], enum_achievement_mappings[ACHIEVEMENTS[key]].bind(this)());
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {GameRoot} root */
|
/** @param {GameRoot} root */
|
||||||
@ -324,14 +447,14 @@ export class AchievementCollection {
|
|||||||
* @param {string} key - Maps to an Achievement
|
* @param {string} key - Maps to an Achievement
|
||||||
* @param {any} data - Data received from signal dispatches for validation
|
* @param {any} data - Data received from signal dispatches for validation
|
||||||
*/
|
*/
|
||||||
unlock(key, data) {
|
unlock(key, data, force = false) {
|
||||||
if (!this.map.has(key)) {
|
if (!this.map.has(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const achievement = this.map.get(key);
|
const achievement = this.map.get(key);
|
||||||
|
|
||||||
if (!achievement.isValid(data)) {
|
if (!force && !achievement.isValid(data)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,12 +462,21 @@ export class AchievementCollection {
|
|||||||
.unlock()
|
.unlock()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.onActivate(null, key);
|
this.onActivate(null, key);
|
||||||
|
this.root.signals.achievementCompleted.dispatch(key, data);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.onActivate(err, key);
|
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
|
* Cleans up after achievement activation attempt with the provider. Could
|
||||||
* utilize err to retry some number of times if needed.
|
* 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() {
|
activate() {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deactivate() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import { queryParamOptions } from "../../core/query_parameters";
|
|||||||
import { clamp } from "../../core/utils";
|
import { clamp } from "../../core/utils";
|
||||||
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
|
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
|
||||||
import { NoAdProvider } from "../ad_providers/no_ad_provider";
|
import { NoAdProvider } from "../ad_providers/no_ad_provider";
|
||||||
import { SteamAchievementProvider } from "../electron/steam_achievement_provider";
|
|
||||||
import { PlatformWrapperInterface } from "../wrapper";
|
import { PlatformWrapperInterface } from "../wrapper";
|
||||||
|
import { BrowserAchievementProvider } from "./browser_achievement_provider";
|
||||||
import { NoAchievementProvider } from "./no_achievement_provider";
|
import { NoAchievementProvider } from "./no_achievement_provider";
|
||||||
import { StorageImplBrowser } from "./storage";
|
import { StorageImplBrowser } from "./storage";
|
||||||
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
||||||
@ -200,8 +200,8 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializeAchievementProvider() {
|
initializeAchievementProvider() {
|
||||||
if (G_IS_DEV && globalConfig.debug.testAchievements) {
|
if (G_IS_DEV) {
|
||||||
this.app.achievementProvider = new SteamAchievementProvider(this.app);
|
this.app.achievementProvider = new BrowserAchievementProvider(this.app);
|
||||||
|
|
||||||
return this.app.achievementProvider.initialize().catch(err => {
|
return this.app.achievementProvider.initialize().catch(err => {
|
||||||
logger.error("Failed to initialize achievement provider, disabling:", err);
|
logger.error("Failed to initialize achievement provider, disabling:", err);
|
||||||
|
@ -63,7 +63,7 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
|||||||
super(app);
|
super(app);
|
||||||
|
|
||||||
this.initialized = false;
|
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) {
|
if (G_IS_DEV) {
|
||||||
for (let key in ACHIEVEMENT_IDS) {
|
for (let key in ACHIEVEMENT_IDS) {
|
||||||
@ -87,11 +87,25 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
|||||||
this.root = root;
|
this.root = root;
|
||||||
|
|
||||||
try {
|
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);
|
this.collection.initialize(root);
|
||||||
|
|
||||||
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
let promise = Promise.resolve();
|
||||||
return 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) {
|
} catch (err) {
|
||||||
logger.error("Failed to initialize the collection");
|
logger.error("Failed to initialize the collection");
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
@ -140,4 +154,27 @@ export class SteamAchievementProvider extends AchievementProviderInterface {
|
|||||||
throw err;
|
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
|
minute: <shapes> / m
|
||||||
hour: <shapes> / h
|
hour: <shapes> / h
|
||||||
|
|
||||||
|
# The "Achievements window"
|
||||||
|
achievements:
|
||||||
|
title: Achievements
|
||||||
|
buttonReset: Reset
|
||||||
|
|
||||||
# Settings menu, when you press "ESC"
|
# Settings menu, when you press "ESC"
|
||||||
settingsMenu:
|
settingsMenu:
|
||||||
playtime: Playtime
|
playtime: Playtime
|
||||||
@ -492,6 +497,144 @@ shopUpgrades:
|
|||||||
name: Mixing & Painting
|
name: Mixing & Painting
|
||||||
description: Speed x<currentMult> → x<newMult>
|
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 and their name / description
|
||||||
buildings:
|
buildings:
|
||||||
hub:
|
hub:
|
||||||
@ -1096,6 +1239,7 @@ keybindings:
|
|||||||
|
|
||||||
menuOpenShop: Upgrades
|
menuOpenShop: Upgrades
|
||||||
menuOpenStats: Statistics
|
menuOpenStats: Statistics
|
||||||
|
menuOpenAchievements: Achievements
|
||||||
menuClose: Close Menu
|
menuClose: Close Menu
|
||||||
|
|
||||||
toggleHud: Toggle HUD
|
toggleHud: Toggle HUD
|
||||||
|