From 7c69331308c44cb9f9e9ffaff4da6a5003babeee Mon Sep 17 00:00:00 2001 From: Greg Considine Date: Mon, 22 Feb 2021 09:06:33 -0500 Subject: [PATCH] [WIP] Add boilerplate for achievement implementation --- .gitignore | 4 + src/js/application.js | 7 ++ src/js/core/config.local.js | 9 ++- src/js/game/achievement_manager.js | 56 +++++++++++++ src/js/game/core.js | 2 + src/js/game/root.js | 4 + src/js/platform/achievements.js | 28 +++++++ src/js/platform/achievements/achievements.js | 79 +++++++++++++++++++ .../platform/achievements/no_achievements.js | 11 +++ src/js/platform/browser/wrapper.js | 12 +++ translations/base-en.yaml | 5 ++ yarn.lock | 35 +++----- 12 files changed, 227 insertions(+), 25 deletions(-) create mode 100644 src/js/game/achievement_manager.js create mode 100644 src/js/platform/achievements.js create mode 100644 src/js/platform/achievements/achievements.js create mode 100644 src/js/platform/achievements/no_achievements.js diff --git a/.gitignore b/.gitignore index a0e08a62..ab6e7b72 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,7 @@ tmp_standalone_files # Local config config.local.js .DS_Store + +# Editor artifacts +*.*.swp +*.*.swo diff --git a/src/js/application.js b/src/js/application.js index d9ca7641..cd35004f 100644 --- a/src/js/application.js +++ b/src/js/application.js @@ -12,6 +12,9 @@ import { getPlatformName, waitNextFrame } from "./core/utils"; import { Vector } from "./core/vector"; import { AdProviderInterface } from "./platform/ad_provider"; import { NoAdProvider } from "./platform/ad_providers/no_ad_provider"; +import { AchievementsInterface } from "./platform/achievements"; +import { NoAchievements } from "./platform/achievements/no_achievements"; +import { Achievements } from "./platform/achievements/achievements"; import { AnalyticsInterface } from "./platform/analytics"; import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics"; import { SoundImplBrowser } from "./platform/browser/sound"; @@ -85,6 +88,9 @@ export class Application { /** @type {PlatformWrapperInterface} */ this.platformWrapper = null; + /** @type {AchievementsInterface} */ + this.achievements = null; + /** @type {AdProviderInterface} */ this.adProvider = null; @@ -137,6 +143,7 @@ export class Application { this.sound = new SoundImplBrowser(this); this.analytics = new GoogleAnalyticsImpl(this); this.gameAnalytics = new ShapezGameAnalytics(this); + this.achievements = new NoAchievements(this); } /** diff --git a/src/js/core/config.local.js b/src/js/core/config.local.js index 87aaaa14..5d980643 100644 --- a/src/js/core/config.local.js +++ b/src/js/core/config.local.js @@ -3,10 +3,10 @@ export default { /* dev:start */ // ----------------------------------------------------------------------------------- // Quickly enters the game and skips the main menu - good for fast iterating - // fastGameEnter: true, + fastGameEnter: true, // ----------------------------------------------------------------------------------- // Skips any delays like transitions between states and such - // noArtificialDelays: true, + noArtificialDelays: true, // ----------------------------------------------------------------------------------- // Disables writing of savegames, useful for testing the same savegame over and over // disableSavegameWrite: true, @@ -18,7 +18,7 @@ export default { // showAcceptorEjectors: true, // ----------------------------------------------------------------------------------- // Disables the music (Overrides any setting, can cause weird behaviour) - // disableMusic: true, + disableMusic: true, // ----------------------------------------------------------------------------------- // Do not render static map entities (=most buildings) // doNotRenderStatics: true, @@ -59,6 +59,9 @@ export default { // Enables ads in the local build (normally they are deactivated there) // testAds: true, // ----------------------------------------------------------------------------------- + // Allows unlocked achievements to be logged to console in the local build + testAchievements: true, + // ----------------------------------------------------------------------------------- // Disables the automatic switch to an overview when zooming out // disableMapOverview: true, // ----------------------------------------------------------------------------------- diff --git a/src/js/game/achievement_manager.js b/src/js/game/achievement_manager.js new file mode 100644 index 00000000..4fd8c197 --- /dev/null +++ b/src/js/game/achievement_manager.js @@ -0,0 +1,56 @@ +/* typehints:start */ +import { GameRoot } from "./root"; +/* typehints:end */ + +import { globalConfig } from "../core/config"; +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; + + if (!this.root.app.achievements.hasAchievements()) { + logger.debug("Bypassing achievement set up"); + // Set adhoc checks to reference a noop, ignore signals. + return; + } + + this.init(); + } + + init () { + return this.root.app.achievements.load() + .then(() => { + this.achievements = this.root.app.achievements.getAchievements(); + + return this.setChecks(); + }) + } + + 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 + } +} diff --git a/src/js/game/core.js b/src/js/game/core.js index 2df8989f..03fa93d1 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -35,6 +35,7 @@ import { RegularGameMode } from "./modes/regular"; import { ProductionAnalytics } from "./production_analytics"; import { GameRoot } from "./root"; import { ShapeDefinitionManager } from "./shape_definition_manager"; +import { AchievementManager } from "./achievement_manager"; import { SoundProxy } from "./sound_proxy"; import { GameTime } from "./time/game_time"; @@ -118,6 +119,7 @@ export class GameCore { root.entityMgr = new EntityManager(root); root.systemMgr = new GameSystemManager(root); root.shapeDefinitionMgr = new ShapeDefinitionManager(root); + root.achievementMgr = new AchievementManager(root); root.hubGoals = new HubGoals(root); root.productionAnalytics = new ProductionAnalytics(root); root.buffers = new BufferMaintainer(root); diff --git a/src/js/game/root.js b/src/js/game/root.js index 6f1e7c36..9a529bd2 100644 --- a/src/js/game/root.js +++ b/src/js/game/root.js @@ -8,6 +8,7 @@ import { createLogger } from "../core/logging"; import { GameTime } from "./time/game_time"; import { EntityManager } from "./entity_manager"; import { GameSystemManager } from "./game_system_manager"; +import { AchievementManager } from "./achievement_manager"; import { GameHUD } from "./hud/hud"; import { MapView } from "./map_view"; import { Camera } from "./camera"; @@ -119,6 +120,9 @@ export class GameRoot { /** @type {SoundProxy} */ this.soundProxy = null; + /** @type {AchievementManager} */ + this.achievementMgr = null; + /** @type {ShapeDefinitionManager} */ this.shapeDefinitionMgr = null; diff --git a/src/js/platform/achievements.js b/src/js/platform/achievements.js new file mode 100644 index 00000000..2b28d85d --- /dev/null +++ b/src/js/platform/achievements.js @@ -0,0 +1,28 @@ +/* typehints:start */ +import { Application } from "../application"; +/* typehints:end */ + +export class AchievementsInterface { + constructor(app) { + /** @type {Application} */ + this.app = app; + } + + /** + * Initializes the list of achievements + * @returns {Promise} + */ + initialize() { + abstract; + return Promise.reject(); + } + + /** + * Checks if achievements are supported in the current build + * @returns {boolean} + */ + hasAchievements() { + abstract; + return false; + } +} diff --git a/src/js/platform/achievements/achievements.js b/src/js/platform/achievements/achievements.js new file mode 100644 index 00000000..260b06c2 --- /dev/null +++ b/src/js/platform/achievements/achievements.js @@ -0,0 +1,79 @@ +import { 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"); + +// Include API id per key +export const ACHIEVEMENTS = { + painting: "painting" +} + +export class Achievements extends AchievementsInterface { + initialize() { + this.authTicket = null; + this.achievementNames = null; + this.achievements = null; + this.connected = false; + this.connectPromise = Promise.resolve(); + + if (globalConfig.debug.testAchievements) { + return Promise.resolve(); + } + + // Check for resolve in AchievementManager via load() to not block game state + // transition + this.connectPromise = this.fetchAuthTicket() + .then(() => this.fetchAchievementNames()); + + return Promise.resolve(); + } + + fetchAuthTicket () { + return Promise.resolve(); + } + + fetchAchievementNames () { + return Promise.resolve(); + } + + load () { + this.achievements = newEmptyMap(); + + for (let key in ACHIEVEMENTS) { + this.achievements[key] = newEmptyMap(); + this.achievements[key].unlocked = false; + this.achievements[key].invalid = false; + } + + return this.connectPromise + .then(() => { + // factor in game state, save data, then Steam data (if accessible) as + // source of truth. + }) + } + + /** + * @param {string} key + */ + fetchAchievement (key) { + return Promise.resolve(); + } + + /** + * @param {string} key + */ + unlockAchievement (key) { + return Promise.resolve(); + } + + getAchievements() { + return this.achievements; + } + + hasAchievements() { + return true; + } +} diff --git a/src/js/platform/achievements/no_achievements.js b/src/js/platform/achievements/no_achievements.js new file mode 100644 index 00000000..b64c6937 --- /dev/null +++ b/src/js/platform/achievements/no_achievements.js @@ -0,0 +1,11 @@ +import { AchievementsInterface } from "../achievements"; + +export class NoAchievements extends AchievementsInterface { + initialize() { + return Promise.resolve(); + } + + hasAchievements() { + return false; + } +} diff --git a/src/js/platform/browser/wrapper.js b/src/js/platform/browser/wrapper.js index 232a743b..452038fe 100644 --- a/src/js/platform/browser/wrapper.js +++ b/src/js/platform/browser/wrapper.js @@ -4,6 +4,7 @@ import { queryParamOptions } from "../../core/query_parameters"; import { clamp } from "../../core/utils"; import { GamedistributionAdProvider } from "../ad_providers/gamedistribution"; import { NoAdProvider } from "../ad_providers/no_ad_provider"; +import { Achievements } from "../achievements/achievements"; import { PlatformWrapperInterface } from "../wrapper"; import { StorageImplBrowser } from "./storage"; import { StorageImplBrowserIndexedDB } from "./storage_indexed_db"; @@ -71,6 +72,7 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { return this.detectStorageImplementation() .then(() => this.initializeAdProvider()) + .then(() => this.initializeAchievements()) .then(() => super.initialize()); } @@ -196,6 +198,16 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { }); } + initializeAchievements() { + if (G_IS_STANDALONE || (G_IS_DEV && globalConfig.debug.testAchievements)) { + this.app.achievements = new Achievements(this.app); + return this.app.achievements.initialize(); + } + + logger.log("Achievements are not supported in this environment"); + return Promise.resolve(); + } + exitApp() { // Can not exit app } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 62ed2808..64d06d7c 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -1207,6 +1207,11 @@ demo: settingNotAvailable: Not available in the demo. +achievements: + painting: + displayName: Painting + description: Paint a shape + tips: - The hub will accept any input, not just the current shape! - Make sure your factories are modular - it will pay out! diff --git a/yarn.lock b/yarn.lock index 4a7612d0..f42f48c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5103,14 +5103,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -line-column@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" - integrity sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI= - dependencies: - isarray "^1.0.0" - isobject "^2.0.0" - load-bmfont@^1.3.1, load-bmfont@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.0.tgz#75f17070b14a8c785fe7f5bee2e6fd4f98093b6b" @@ -5619,10 +5611,10 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== -nanoid@^3.1.12: - version "3.1.12" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" - integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== +nanoid@^3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== nanomatch@^1.2.9: version "1.2.13" @@ -6927,6 +6919,15 @@ postcss-zindex@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" +postcss@>=5.0.0: + version "8.2.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe" + integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg== + dependencies: + colorette "^1.2.1" + nanoid "^3.1.20" + source-map "^0.6.1" + postcss@^5.0.2: version "5.2.18" resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" @@ -6955,16 +6956,6 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.1.tgz#c3a287dd10e4f6c84cb3791052b96a5d859c9389" - integrity sha512-9DGLSsjooH3kSNjTZUOt2eIj2ZTW0VI2PZ/3My+8TC7KIbH2OKwUlISfDsf63EP4aiRUt3XkEWMWvyJHvJelEg== - dependencies: - colorette "^1.2.1" - line-column "^1.0.2" - nanoid "^3.1.12" - source-map "^0.6.1" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"