From 85999f3f8b046ee43a089ce7006b74f865ba8656 Mon Sep 17 00:00:00 2001 From: Greg Considine Date: Mon, 22 Feb 2021 16:02:52 -0500 Subject: [PATCH] [WIP] Implement painting, cutting, rotating achievements (to log only) --- src/js/game/achievement_manager.js | 48 +++----- src/js/game/shape_definition_manager.js | 10 ++ src/js/platform/achievements.js | 33 +++++- src/js/platform/achievements/achievements.js | 107 ++++++++++-------- .../platform/achievements/no_achievements.js | 7 ++ 5 files changed, 122 insertions(+), 83 deletions(-) diff --git a/src/js/game/achievement_manager.js b/src/js/game/achievement_manager.js index 4fd8c197..4f6db4c0 100644 --- a/src/js/game/achievement_manager.js +++ b/src/js/game/achievement_manager.js @@ -8,49 +8,29 @@ import { createLogger } from "../core/logging"; const logger = createLogger("achievement_manager"); export class AchievementManager { - static getId() { - return "AchievementManager"; - } - constructor(root) { this.root = root; - this.achievements = null; + this.achievements = this.root.app.achievements; - if (!this.root.app.achievements.hasAchievements()) { - logger.debug("Bypassing achievement set up"); - // Set adhoc checks to reference a noop, ignore signals. - return; - } - - this.init(); + this.load() } - init () { - return this.root.app.achievements.load() + load () { + return this.achievements.load() .then(() => { - this.achievements = this.root.app.achievements.getAchievements(); + if (!this.achievements.hasAchievements()) { + logger.log("Achievements disabled"); + return; + } - return this.setChecks(); + logger.log("There are", this.achievements.count, "achievements"); + }) + .catch(err => { + logger.error("Achievements failed to load", err); }) } - setChecks () { - logger.debug("loaded", this.achievements); - - // set checks on achievements - - //this.root.signals.itemProduced.add(this.onItemProduced, this); - } - - /** - * @param {BaseItem} item - */ - onItemProduced(item) { - logger.debug(item); - } - - // Have one check function per achievement - isPainted () { - return + unlock (key) { + this.achievements.unlock(key); } } diff --git a/src/js/game/shape_definition_manager.js b/src/js/game/shape_definition_manager.js index 5bcfcc4b..eba950e7 100644 --- a/src/js/game/shape_definition_manager.js +++ b/src/js/game/shape_definition_manager.js @@ -4,6 +4,7 @@ import { enumColors } from "./colors"; import { ShapeItem } from "./items/shape_item"; import { GameRoot } from "./root"; import { enumSubShape, ShapeDefinition } from "./shape_definition"; +import { ACHIEVEMENTS } from "../platform/achievements"; const logger = createLogger("shape_definition_manager"); @@ -96,6 +97,8 @@ export class ShapeDefinitionManager extends BasicSerializableObject { const rightSide = definition.cloneFilteredByQuadrants([2, 3]); const leftSide = definition.cloneFilteredByQuadrants([0, 1]); + this.root.achievementMgr.unlock(ACHIEVEMENTS.cutting); + return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [ this.registerOrReturnHandle(rightSide), this.registerOrReturnHandle(leftSide), @@ -137,6 +140,8 @@ export class ShapeDefinitionManager extends BasicSerializableObject { const rotated = definition.cloneRotateCW(); + this.root.achievementMgr.unlock(ACHIEVEMENTS.rotating); + return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle( rotated )); @@ -203,9 +208,14 @@ export class ShapeDefinitionManager extends BasicSerializableObject { */ shapeActionPaintWith(definition, color) { const key = "paint/" + definition.getHash() + "/" + color; + logger.debug("shapePainted", definition, color); if (this.operationCache[key]) { + logger.debug("shapePaintedCache", definition, color); return /** @type {ShapeDefinition} */ (this.operationCache[key]); } + + this.root.achievementMgr.unlock(ACHIEVEMENTS.painting); + const colorized = definition.cloneAndPaintWith(color); return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle( colorized diff --git a/src/js/platform/achievements.js b/src/js/platform/achievements.js index 2b28d85d..7122f42e 100644 --- a/src/js/platform/achievements.js +++ b/src/js/platform/achievements.js @@ -2,6 +2,14 @@ import { Application } from "../application"; /* typehints:end */ +export const ACHIEVEMENTS = { + painting: "painting", + cutting: "cutting", + rotating: "rotating", + stacking: "stacking", + blueprints: "blueprints", +} + export class AchievementsInterface { constructor(app) { /** @type {Application} */ @@ -9,7 +17,30 @@ export class AchievementsInterface { } /** - * Initializes the list of achievements + * Load achievements into an initial state, bypassing unlocked and/or + * irrelevant achievements where possible. + * + * @params key + * @returns {Promise} + */ + load() { + abstract; + return Promise.reject(); + } + + /** + * Call to unlock an achievement + * @params [key] - A property within the ACHIEVEMENTS enum or empty if + * bypassing. + * @returns {(undefined|Promise)} + */ + unlock(key) { + abstract; + return Promise.reject(); + } + + /** + * Initializes the list of achievements. * @returns {Promise} */ initialize() { diff --git a/src/js/platform/achievements/achievements.js b/src/js/platform/achievements/achievements.js index 260b06c2..608663d7 100644 --- a/src/js/platform/achievements/achievements.js +++ b/src/js/platform/achievements/achievements.js @@ -1,76 +1,87 @@ -import { AchievementsInterface } from "../achievements"; +import { ACHIEVEMENTS, AchievementsInterface } from "../achievements"; import { globalConfig } from "../../core/config"; import { createLogger } from "../../core/logging"; -import { newEmptyMap } from "../../core/utils"; -//import { T } from "../../translations"; -const logger = createLogger("achievements/default"); +const logger = createLogger("achievements/steam"); -// Include API id per key -export const ACHIEVEMENTS = { - painting: "painting" +const IDS = { + painting: "", + cutting: "", + rotating: "", + stacking: "", + blueprints: "" } export class Achievements extends AchievementsInterface { initialize() { - this.authTicket = null; - this.achievementNames = null; - this.achievements = null; - this.connected = false; - this.connectPromise = Promise.resolve(); + this.map = new Map(); + this.type = "Steam"; + this.count = 0; - if (globalConfig.debug.testAchievements) { - return Promise.resolve(); + logger.log("Initializing", this.type, "achievements"); + + for (let key in ACHIEVEMENTS) { + this.map[key] = new Map(); + this.map[key].id = IDS[key]; + this.map[key].key = key; + this.map[key].unlocked = false; + this.map[key].relevant = true; + this.count++; } - // Check for resolve in AchievementManager via load() to not block game state - // transition - this.connectPromise = this.fetchAuthTicket() - .then(() => this.fetchAchievementNames()); + this.logOnly = globalConfig.debug.testAchievements; return Promise.resolve(); } - fetchAuthTicket () { - return Promise.resolve(); - } - - fetchAchievementNames () { - return Promise.resolve(); - } - load () { - this.achievements = newEmptyMap(); + // TODO: inspect safe file and update achievements + // Consider removing load since there's no async behavior anticipated + return Promise.resolve(); + } - for (let key in ACHIEVEMENTS) { - this.achievements[key] = newEmptyMap(); - this.achievements[key].unlocked = false; - this.achievements[key].invalid = false; + /** + * @param {string} key + */ + unlock (key) { + let achievement = this.map[key]; + + if (!achievement) { + logger.error("Achievement does not exist:", key); + return; } - return this.connectPromise + if (!achievement.relevant) { + logger.debug("Achievement unlocked/irrelevant:", key); + return; + } + + achievement.activate = achievement.activate || this.activate(achievement) .then(() => { - // factor in game state, save data, then Steam data (if accessible) as - // source of truth. + achievement.unlocked = true; + achievement.relevant = false; + + logger.log("Achievement unlocked:", key); }) + .catch(err => { + logger.error("Failed to unlock achievement", err); + }) + .finally(() => { + achievement.activate = null; + }) + } - /** - * @param {string} key - */ - fetchAchievement (key) { - return Promise.resolve(); - } + activate (achievement) { + if (this.logOnly) { + return Promise.resolve(); + } - /** - * @param {string} key - */ - unlockAchievement (key) { - return Promise.resolve(); - } + return new Promise((resolve, reject) => { + //TODO: Implement greenworks activate - getAchievements() { - return this.achievements; + return resolve(); + }); } hasAchievements() { diff --git a/src/js/platform/achievements/no_achievements.js b/src/js/platform/achievements/no_achievements.js index b64c6937..5c628edb 100644 --- a/src/js/platform/achievements/no_achievements.js +++ b/src/js/platform/achievements/no_achievements.js @@ -5,7 +5,14 @@ export class NoAchievements extends AchievementsInterface { return Promise.resolve(); } + load() { + return Promise.resolve(); + } + hasAchievements() { return false; } + + unlock() { + } }