From db8cd2cd7788a40e003c896ec58269670e00914e Mon Sep 17 00:00:00 2001 From: DJ1TJOO Date: Sat, 13 Mar 2021 16:13:42 +0100 Subject: [PATCH] Achievements accessable in main menu --- src/css/ingame_hud/achievements.scss | 16 ++ src/css/states/main_menu.scss | 6 + src/js/application.js | 2 + src/js/platform/achievement_provider.js | 9 + .../browser/browser_achievement_provider.js | 14 ++ .../electron/steam_achievement_provider.js | 16 ++ src/js/states/achievements.js | 180 ++++++++++++++++++ src/js/states/main_menu.js | 6 + 8 files changed, 249 insertions(+) create mode 100644 src/js/states/achievements.js diff --git a/src/css/ingame_hud/achievements.scss b/src/css/ingame_hud/achievements.scss index 3979d0a0..c35af0b8 100644 --- a/src/css/ingame_hud/achievements.scss +++ b/src/css/ingame_hud/achievements.scss @@ -119,3 +119,19 @@ } } } + +#state_AchievementsState { + #ingame_HUD_Achievements { + z-index: unset; + overflow-y: scroll; + position: relative; + background-color: unset; + max-height: 100%; + align-items: unset; + + .content { + width: 100%; + display: block; + } + } +} diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index 7d3872fb..e66af26e 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -15,6 +15,7 @@ grid-auto-flow: column; @include S(grid-gap, 15px); + .achievementsButton, .settingsButton, .exitAppButton, .languageChoose { @@ -34,6 +35,11 @@ } } + .achievementsButton { + /* @load-async */ + background-image: uiResource("icons/achievements.png"); + } + .exitAppButton { /* @load-async */ background-image: uiResource("icons/main_menu_exit.png"); diff --git a/src/js/application.js b/src/js/application.js index 2c632ef9..858d97cd 100644 --- a/src/js/application.js +++ b/src/js/application.js @@ -31,6 +31,7 @@ import { PreloadState } from "./states/preload"; import { SettingsState } from "./states/settings"; import { ShapezGameAnalytics } from "./platform/browser/game_analytics"; import { RestrictionManager } from "./core/restriction_manager"; +import { AchievementsState } from "./states/achievements"; /** * @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface @@ -159,6 +160,7 @@ export class Application { KeybindingsState, AboutState, ChangelogState, + AchievementsState, ]; for (let i = 0; i < states.length; ++i) { diff --git a/src/js/platform/achievement_provider.js b/src/js/platform/achievement_provider.js index 01151995..c01c219a 100644 --- a/src/js/platform/achievement_provider.js +++ b/src/js/platform/achievement_provider.js @@ -318,6 +318,15 @@ export class AchievementProviderInterface { return Promise.reject(); } + /** + * Unlocks already unlocked achievements. + * @returns {Promise} + */ + unlockUnlocked() { + abstract; + return Promise.reject(); + } + /** * Opportunity to do additional initialization work with the GameRoot. * @param {GameRoot} root diff --git a/src/js/platform/browser/browser_achievement_provider.js b/src/js/platform/browser/browser_achievement_provider.js index 3d88d402..8554e4e1 100644 --- a/src/js/platform/browser/browser_achievement_provider.js +++ b/src/js/platform/browser/browser_achievement_provider.js @@ -115,6 +115,20 @@ export class BrowserAchievementProvider extends AchievementProviderInterface { } } + unlockUnlocked() { + let promise = Promise.resolve(); + + //Unlock already unlocked + for (let i = 0; i < this.storage.currentData.unlocked.length; i++) { + promise.then(() => { + const achievement = this.storage.currentData.unlocked[i]; + this.collection.unlock(achievement, null, true); + }); + } + + return promise; + } + /** @returns {Promise} */ initialize() { return Promise.resolve(); diff --git a/src/js/platform/electron/steam_achievement_provider.js b/src/js/platform/electron/steam_achievement_provider.js index ff192fa6..06b0758d 100644 --- a/src/js/platform/electron/steam_achievement_provider.js +++ b/src/js/platform/electron/steam_achievement_provider.js @@ -132,6 +132,22 @@ export class SteamAchievementProvider extends AchievementProviderInterface { }); } + unlockUnlocked() { + 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; + } + /** * @param {string} key * @returns {Promise} diff --git a/src/js/states/achievements.js b/src/js/states/achievements.js new file mode 100644 index 00000000..518396e6 --- /dev/null +++ b/src/js/states/achievements.js @@ -0,0 +1,180 @@ +import { TextualGameState } from "../core/textual_game_state"; +import { makeDiv } from "../core/utils"; +import { + ACHIEVEMENTS, + enum_achievement_mappings, + HIDDEN_ACHIEVEMENTS, +} from "../platform/achievement_provider"; +import { T } from "../translations"; + +export class AchievementsState extends TextualGameState { + constructor() { + super("AchievementsState"); + } + + getStateHeaderTitle() { + return T.ingame.achievements.title; + } + + onEnter(payload) { + this.app.achievementProvider.unlockUnlocked(); + + this.parent = makeDiv(document.querySelector(".content.mainContent"), "ingame_HUD_Achievements", [ + "ingameDialog", + ]); + this.contentDiv = makeDiv(this.parent, 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.app.achievementProvider.collection.lock( + achievementKey, + enum_achievement_mappings[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("", HIDDEN_ACHIEVEMENTS.length + "") + ); + + 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.dialogs.showWarning( + T.dialogs.resetAchievements.title, + T.dialogs.resetAchievements.description, + ["cancel:bad:escape", "ok:good:enter"] + ); + signals.ok.add(() => { + for (const achievementKey in ACHIEVEMENTS) { + if (!this.app.achievementProvider.collection.map.has(achievementKey)) + this.app.achievementProvider.collection.lock( + achievementKey, + enum_achievement_mappings[ACHIEVEMENTS[achievementKey]] + ); + } + }); + }); + } + + onRender(dt) { + let unlocked = 0; + let hidden = 0; + for (const achievementKey in this.achievementToElements) { + const handle = this.achievementToElements[achievementKey]; + + //Check if user has achievement + if (!this.app.achievementProvider.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( + "", + 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"); + } +} diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 4e42fbe9..f8593cde 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -48,6 +48,7 @@ export class MainMenuState extends GameState { : `` } + ${ G_IS_STANDALONE || G_IS_DEV @@ -232,6 +233,7 @@ export class MainMenuState extends GameState { }); this.trackClicks(qs(".settingsButton"), this.onSettingsButtonClicked); + this.trackClicks(qs(".achievementsButton"), this.onAchievementsButtonClicked); if (!G_CHINA_VERSION) { this.trackClicks(qs(".languageChoose"), this.onLanguageChooseClicked); @@ -569,6 +571,10 @@ export class MainMenuState extends GameState { this.moveToState("SettingsState"); } + onAchievementsButtonClicked() { + this.moveToState("AchievementsState"); + } + onTranslationHelpLinkClicked() { this.app.analytics.trackUiClick("translation_help_link"); this.app.platformWrapper.openExternalLink(