1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

[WIP] Refactor achievements, jsdoc fixes, add npm script

- Refactor achievements to make use of Signals
- Move implemented achievement interfaces to appropriate
platform folders (SteamAchievements in currently in use
in browser wrapper for testing)
- Fix invalid jsdocs
- Add dev-standalone script to package.json scripts
This commit is contained in:
Greg Considine 2021-02-23 10:48:01 -05:00
parent 85999f3f8b
commit c41ad97eaf
11 changed files with 1665 additions and 56 deletions

1610
electron/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "cd gulp && yarn gulp main.serveDev", "dev": "cd gulp && yarn gulp main.serveDev",
"dev-standalone": "cd gulp && yarn gulp main.serveStandalone",
"tslint": "cd src/js && tsc", "tslint": "cd src/js && tsc",
"lint": "eslint src/js", "lint": "eslint src/js",
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*", "prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*",

View File

@ -12,9 +12,7 @@ import { getPlatformName, waitNextFrame } from "./core/utils";
import { Vector } from "./core/vector"; import { Vector } from "./core/vector";
import { AdProviderInterface } from "./platform/ad_provider"; import { AdProviderInterface } from "./platform/ad_provider";
import { NoAdProvider } from "./platform/ad_providers/no_ad_provider"; import { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
import { AchievementsInterface } from "./platform/achievements"; import { NoAchievements } from "./platform/browser/no_achievements";
import { NoAchievements } from "./platform/achievements/no_achievements";
import { Achievements } from "./platform/achievements/achievements";
import { AnalyticsInterface } from "./platform/analytics"; import { AnalyticsInterface } from "./platform/analytics";
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics"; import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
import { SoundImplBrowser } from "./platform/browser/sound"; import { SoundImplBrowser } from "./platform/browser/sound";
@ -35,6 +33,7 @@ import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
import { RestrictionManager } from "./core/restriction_manager"; import { RestrictionManager } from "./core/restriction_manager";
/** /**
* @typedef {import("./platform/achievements").AchievementsInterface} AchievementsInterface
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface * @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
* @typedef {import("./platform/sound").SoundInterface} SoundInterface * @typedef {import("./platform/sound").SoundInterface} SoundInterface
* @typedef {import("./platform/storage").StorageInterface} StorageInterface * @typedef {import("./platform/storage").StorageInterface} StorageInterface

View File

@ -12,22 +12,14 @@ export class AchievementManager {
this.root = root; this.root = root;
this.achievements = this.root.app.achievements; this.achievements = this.root.app.achievements;
this.load() if (!this.achievements.hasAchievements()) {
} logger.log("Achievements disabled");
return;
}
load () { logger.log("There are", this.achievements.count, "achievements");
return this.achievements.load()
.then(() => {
if (!this.achievements.hasAchievements()) {
logger.log("Achievements disabled");
return;
}
logger.log("There are", this.achievements.count, "achievements"); this.root.signals.achievementUnlocked.add(this.unlock, this);
})
.catch(err => {
logger.error("Achievements failed to load", err);
})
} }
unlock (key) { unlock (key) {

View File

@ -179,6 +179,9 @@ export class GameRoot {
// Called before actually placing an entity, use to perform additional logic // Called before actually placing an entity, use to perform additional logic
// for freeing space before actually placing. // for freeing space before actually placing.
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()), freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
// Called with the key of the unlocked achievement
achievementUnlocked: /** @type {TypedSignal<[string]>} */ (new Signal()),
}; };
// RNG's // RNG's

View File

@ -97,7 +97,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
const rightSide = definition.cloneFilteredByQuadrants([2, 3]); const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
const leftSide = definition.cloneFilteredByQuadrants([0, 1]); const leftSide = definition.cloneFilteredByQuadrants([0, 1]);
this.root.achievementMgr.unlock(ACHIEVEMENTS.cutting); this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.cutting);
return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [ return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [
this.registerOrReturnHandle(rightSide), this.registerOrReturnHandle(rightSide),
@ -140,7 +140,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
const rotated = definition.cloneRotateCW(); const rotated = definition.cloneRotateCW();
this.root.achievementMgr.unlock(ACHIEVEMENTS.rotating); this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.rotating);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle( return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
rotated rotated
@ -208,13 +208,11 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
*/ */
shapeActionPaintWith(definition, color) { shapeActionPaintWith(definition, color) {
const key = "paint/" + definition.getHash() + "/" + color; const key = "paint/" + definition.getHash() + "/" + color;
logger.debug("shapePainted", definition, color);
if (this.operationCache[key]) { if (this.operationCache[key]) {
logger.debug("shapePaintedCache", definition, color);
return /** @type {ShapeDefinition} */ (this.operationCache[key]); return /** @type {ShapeDefinition} */ (this.operationCache[key]);
} }
this.root.achievementMgr.unlock(ACHIEVEMENTS.painting); this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.painting);
const colorized = definition.cloneAndPaintWith(color); const colorized = definition.cloneAndPaintWith(color);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle( return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(

View File

@ -11,8 +11,8 @@ export const ACHIEVEMENTS = {
} }
export class AchievementsInterface { export class AchievementsInterface {
/** @param {Application} app */
constructor(app) { constructor(app) {
/** @type {Application} */
this.app = app; this.app = app;
} }
@ -30,13 +30,12 @@ export class AchievementsInterface {
/** /**
* Call to unlock an achievement * Call to unlock an achievement
* @params [key] - A property within the ACHIEVEMENTS enum or empty if * @params {string} [key] - A property within the ACHIEVEMENTS enum or empty if
* bypassing. * bypassing.
* @returns {(undefined|Promise)} * @returns {void}
*/ */
unlock(key) { unlock(key) {
abstract; abstract;
return Promise.reject();
} }
/** /**

View File

@ -1,10 +1,6 @@
import { AchievementsInterface } from "../achievements"; import { AchievementsInterface } from "../achievements";
export class NoAchievements extends AchievementsInterface { export class NoAchievements extends AchievementsInterface {
initialize() {
return Promise.resolve();
}
load() { load() {
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -4,7 +4,7 @@ import { queryParamOptions } from "../../core/query_parameters";
import { clamp } from "../../core/utils"; import { clamp } from "../../core/utils";
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution"; import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
import { NoAdProvider } from "../ad_providers/no_ad_provider"; import { NoAdProvider } from "../ad_providers/no_ad_provider";
import { Achievements } from "../achievements/achievements"; import { SteamAchievements } from "../electron/steam_achievements";
import { PlatformWrapperInterface } from "../wrapper"; import { PlatformWrapperInterface } from "../wrapper";
import { StorageImplBrowser } from "./storage"; import { StorageImplBrowser } from "./storage";
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db"; import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
@ -70,9 +70,13 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
logger.log("Embed provider:", this.embedProvider.id); logger.log("Embed provider:", this.embedProvider.id);
if (G_IS_DEV && globalConfig.debug.testAchievements) {
logger.log("Testing achievements");
this.app.achievements = new SteamAchievements(this.app);
}
return this.detectStorageImplementation() return this.detectStorageImplementation()
.then(() => this.initializeAdProvider()) .then(() => this.initializeAdProvider())
.then(() => this.initializeAchievements())
.then(() => super.initialize()); .then(() => super.initialize());
} }
@ -198,16 +202,6 @@ 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() { exitApp() {
// Can not exit app // Can not exit app
} }

View File

@ -12,8 +12,21 @@ const IDS = {
blueprints: "<id>" blueprints: "<id>"
} }
export class Achievements extends AchievementsInterface { /** @typedef {object} SteamAchievementMap
initialize() { * @property {string} id
* @property {string} key
* @property {boolean} unlocked
* @property {boolean} relevant
* @property {?Promise} activate
*/
/** @typedef {Map<string, SteamAchievement>} SteamAchievementMap */
export class SteamAchievements extends AchievementsInterface {
constructor(app) {
super(app);
/** @type {AchievementMap} */
this.map = new Map(); this.map = new Map();
this.type = "Steam"; this.type = "Steam";
this.count = 0; this.count = 0;
@ -21,21 +34,22 @@ export class Achievements extends AchievementsInterface {
logger.log("Initializing", this.type, "achievements"); logger.log("Initializing", this.type, "achievements");
for (let key in ACHIEVEMENTS) { for (let key in ACHIEVEMENTS) {
this.map[key] = new Map(); this.map.set(key, {
this.map[key].id = IDS[key]; id: IDS[key],
this.map[key].key = key; key: key,
this.map[key].unlocked = false; unlocked: false,
this.map[key].relevant = true; relevant: true,
activate: null
});
this.count++; this.count++;
} }
this.logOnly = globalConfig.debug.testAchievements; this.logOnly = globalConfig.debug.testAchievements;
return Promise.resolve();
} }
load () { load () {
// TODO: inspect safe file and update achievements // TODO: inspect save file and update achievements
// Consider removing load since there's no async behavior anticipated // Consider removing load since there's no async behavior anticipated
return Promise.resolve(); return Promise.resolve();
} }
@ -44,15 +58,15 @@ export class Achievements extends AchievementsInterface {
* @param {string} key * @param {string} key
*/ */
unlock (key) { unlock (key) {
let achievement = this.map[key]; if (!this.map.has(key)) {
if (!achievement) {
logger.error("Achievement does not exist:", key); logger.error("Achievement does not exist:", key);
return; return;
} }
let achievement = this.map.get(key);
if (!achievement.relevant) { if (!achievement.relevant) {
logger.debug("Achievement unlocked/irrelevant:", key); console.log("Achievement unlocked/irrelevant:", key);
return; return;
} }

View File

@ -2,6 +2,7 @@ import { PlatformWrapperImplBrowser } from "../browser/wrapper";
import { getIPCRenderer } from "../../core/utils"; import { getIPCRenderer } from "../../core/utils";
import { createLogger } from "../../core/logging"; import { createLogger } from "../../core/logging";
import { StorageImplElectron } from "./storage"; import { StorageImplElectron } from "./storage";
import { SteamAchievements } from "./steam_achievements";
import { PlatformWrapperInterface } from "../wrapper"; import { PlatformWrapperInterface } from "../wrapper";
const logger = createLogger("electron-wrapper"); const logger = createLogger("electron-wrapper");
@ -9,6 +10,8 @@ const logger = createLogger("electron-wrapper");
export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser { export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
initialize() { initialize() {
this.app.storage = new StorageImplElectron(this); this.app.storage = new StorageImplElectron(this);
this.app.achievements = new SteamAchievements(this.app);
return PlatformWrapperInterface.prototype.initialize.call(this); return PlatformWrapperInterface.prototype.initialize.call(this);
} }