Ingame achievement viewer

pull/1488/head
Thomas B 2 years ago
parent 45cef34f02
commit 7499737605

@ -7,7 +7,7 @@
"license": "MIT",
"private": true,
"scripts": {
"dev": "cd gulp && yarn gulp main.serveDev",
"dev": "cd gulp && yarn gulp",
"devStandalone": "cd gulp && yarn gulp main.serveStandalone",
"tslint": "cd src/js && tsc",
"lint": "eslint src/js",

@ -0,0 +1,121 @@
#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;
&.hidden {
display: none;
}
&: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 {
display: flex;
flex-direction: column;
justify-content: center;
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);
}
}
&.reset {
button.reset {
background-color: $colorRedBright;
&:hover {
background-color: darken($colorRedBright, 10);
}
}
.info {
.title {
font-weight: bold;
color: $colorRedBright;
}
.description {
color: lighten($colorRedBright, 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;
}
}
}
}
}
}

@ -8,6 +8,10 @@
backdrop-filter: blur(D(1px));
.stats ~ .achievements {
grid-column: 1;
}
> button,
> .button {
@include PlainText;
@ -53,12 +57,20 @@
}
}
&.achievements {
grid-column: 3;
& {
/* @load-async */
background-image: uiResource("icons/achievements.png");
}
}
&.save {
& {
/* @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 +104,7 @@
}
&.settings {
grid-column: 4;
grid-column: 5;
& {
/* @load-async */
background-image: uiResource("icons/settings_menu_settings.png");

@ -32,6 +32,7 @@
@import "states/puzzle_menu";
@import "states/mods";
@import "ingame_hud/achievements.scss";
@import "ingame_hud/buildings_toolbar";
@import "ingame_hud/building_placer";
@import "ingame_hud/beta_overlay";
@ -109,6 +110,7 @@ ingame_HUD_SandboxController,
ingame_HUD_BetaOverlay,
// Dialogs
ingame_HUD_Achievements,
ingame_HUD_Shop,
ingame_HUD_Statistics,
ingame_HUD_ShapeViewer,

@ -62,7 +62,7 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2,
}
$icons: notification_saved, notification_success, notification_upgrade, notification_info,
notification_warning, notification_error;
notification_warning, notification_error, notification_achievement;
@each $icon in $icons {
[data-icon="icons/#{$icon}.png"] {
/* @load-async */
@ -70,6 +70,20 @@ $icons: notification_saved, notification_success, notification_upgrade, notifica
}
}
$achievements: reset, hidden, 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, fi, ro, he;

@ -0,0 +1,214 @@
import { InputReceiver } from "../../../core/input_receiver";
import { makeDiv } from "../../../core/utils";
import { ACHIEVEMENTS, HIDDEN_ACHIEVEMENTS } 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.resetElement = {};
// Wrapper
this.resetElement.elem = makeDiv(this.contentDiv, null, ["achievement", "reset", "unlocked"]);
// Icon
this.resetElement.icon = makeDiv(this.resetElement.elem, null, ["icon"]);
this.resetElement.icon.setAttribute("data-icon", "achievements/reset.png");
// Info
this.resetElement.info = makeDiv(this.resetElement.elem, null, ["info"]);
// Title
this.resetElement.title = makeDiv(
this.resetElement.info,
null,
["title"],
T.achievements.reset.title
);
// Description
this.resetElement.description = makeDiv(
this.resetElement.info,
null,
["description"],
T.achievements.reset.description
);
// Reset button
this.resetElement.resetButton = document.createElement("button");
this.resetElement.resetButton.classList.add("reset", "styledButton");
this.resetElement.resetButton.innerText = T.ingame.achievements.buttonReset;
this.resetElement.elem.appendChild(this.resetElement.resetButton);
this.trackClicks(this.resetElement.resetButton, () => {
const signals = this.root.hud.parts.dialogs.showWarning(
T.dialogs.resetAchievements.title,
T.dialogs.resetAchievements.description,
["cancel:bad:escape", "ok:good:enter"]
);
signals.ok.add(() => {
// TODO: Fix buttons
for (const achievementKey in ACHIEVEMENTS) {
if (!this.root.achievementProxy.provider.collection.map.has(achievementKey))
this.root.achievementProxy.provider.collection.lock(ACHIEVEMENTS[achievementKey]);
}
});
});
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, () => {
// TODO: Fix buttons
this.root.achievementProxy.provider.collection.lock(ACHIEVEMENTS[achievementKey]);
});
// Assign handle
this.achievementToElements[achievementKey] = handle;
}
this.hiddenElement = {};
// Wrapper
this.hiddenElement.hidden = makeDiv(this.contentDiv, null, ["achievement"]);
// Icon
this.hiddenElement.icon = makeDiv(this.hiddenElement.hidden, null, ["icon"]);
this.hiddenElement.icon.setAttribute("data-icon", "achievements/hidden.png");
// Info
this.hiddenElement.info = makeDiv(this.hiddenElement.hidden, null, ["info"]);
// Title
this.hiddenElement.title = makeDiv(
this.hiddenElement.info,
null,
["title"],
T.achievements.hidden.title
);
// Description
this.hiddenElement.description = makeDiv(
this.hiddenElement.info,
null,
["description"],
T.achievements.hidden.description.replace("<amountHidden>", HIDDEN_ACHIEVEMENTS.length + "")
);
}
renderStatus() {
let unlocked = 0;
let hidden = 0;
for (const achievementKey in this.achievementToElements) {
const handle = this.achievementToElements[achievementKey];
//Check if user has achievement
if (!this.root.achievementProxy.provider.collection.map.get(ACHIEVEMENTS[achievementKey])) {
if (!handle.elem.classList.contains("unlocked")) handle.elem.classList.add("unlocked");
if (handle.elem.classList.contains("hidden")) handle.elem.classList.remove("hidden");
unlocked++;
} else {
if (handle.elem.classList.contains("unlocked")) handle.elem.classList.remove("unlocked");
if (HIDDEN_ACHIEVEMENTS.includes(ACHIEVEMENTS[achievementKey])) {
if (!handle.elem.classList.contains("hidden")) handle.elem.classList.add("hidden");
hidden++;
}
}
}
this.hiddenElement.description.innerHTML = T.achievements.hidden.description.replace(
"<amountHidden>",
hidden + ""
);
if (unlocked > 0) {
if (!this.resetElement.elem.classList.contains("unlocked"))
this.resetElement.elem.classList.add("unlocked");
} else if (this.resetElement.elem.classList.contains("unlocked"))
this.resetElement.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,12 @@ 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,
},
];
/** @type {Array<{

@ -10,6 +10,7 @@ export const enumNotificationType = {
info: "info",
warning: "warning",
error: "error",
achievement: "achievement",
};
const notificationDuration = 3;
@ -29,6 +30,10 @@ export class HUDNotifications extends BaseHUDPart {
this.root.signals.gameSaved.add(() =>
this.internalShowNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
);
this.root.signals.achievementCompleted.add(key =>
this.internalShowNotification(T.achievements[key].title, enumNotificationType.achievement)
);
}
/**

@ -60,6 +60,7 @@ export const KEYMAPPINGS = {
ingame: {
menuOpenShop: { keyCode: keyToKeyCode("F") },
menuOpenStats: { keyCode: keyToKeyCode("G") },
menuOpenAchievements: { keyCode: keyToKeyCode("H") },
menuClose: { keyCode: keyToKeyCode("Q") },
toggleHud: { keyCode: KEYCODES.F2 },

@ -38,6 +38,7 @@ import { MetaItemProducerBuilding } from "../buildings/item_producer";
import { MOD_SIGNALS } from "../../mods/mod_signals";
import { finalGameShape, generateLevelsForVariant } from "./levels";
import { WEB_STEAM_SSO_AUTHENTICATED } from "../../core/steam_sso";
import { HUDAchievements } from "../hud/parts/achievements";
/** @typedef {{
* shape: string,
@ -350,6 +351,7 @@ export class RegularGameMode extends GameMode {
tutorialVideoOffer: HUDTutorialVideoOffer,
gameMenu: HUDGameMenu,
constantSignalEdit: HUDConstantSignalEdit,
achievements: HUDAchievements,
};
if (!IS_MOBILE) {

@ -189,6 +189,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()),
// Puzzle mode
puzzleComplete: /** @type {TypedSignal<[]>} */ (new Signal()),

@ -149,6 +149,13 @@ export class PreloadState extends GameState {
return this.app.settings.initialize();
})
.then(() => this.setStatus("Initializing achievement storage"))
.then(() => {
if (this.app.achievementProvider.storage)
return this.app.achievementProvider.storage.initialize();
else return Promise.resolve();
})
.then(() => {
// Initialize fullscreen
if (this.app.platformWrapper.getSupportsFullscreen()) {

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save