mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Ingame achievement viewer
This commit is contained in:
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",
|
||||
|
121
src/css/ingame_hud/achievements.scss
Normal file
121
src/css/ingame_hud/achievements.scss
Normal file
@ -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;
|
||||
|
||||
|
214
src/js/game/hud/parts/achievements.js
Normal file
214
src/js/game/hud/parts/achievements.js
Normal file
@ -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()) {
|
||||
|
Loading…
Reference in New Issue
Block a user