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,
"scripts": {
"dev": "cd gulp && yarn gulp main.serveDev",
"dev-standalone": "cd gulp && yarn gulp main.serveStandalone",
"tslint": "cd src/js && tsc",
"lint": "eslint src/js",
"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 { 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 { NoAchievements } from "./platform/browser/no_achievements";
import { AnalyticsInterface } from "./platform/analytics";
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
import { SoundImplBrowser } from "./platform/browser/sound";
@ -35,6 +33,7 @@ import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
import { RestrictionManager } from "./core/restriction_manager";
/**
* @typedef {import("./platform/achievements").AchievementsInterface} AchievementsInterface
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
* @typedef {import("./platform/sound").SoundInterface} SoundInterface
* @typedef {import("./platform/storage").StorageInterface} StorageInterface

View File

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

View File

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

View File

@ -97,7 +97,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
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] = [
this.registerOrReturnHandle(rightSide),
@ -140,7 +140,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
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(
rotated
@ -208,13 +208,11 @@ 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);
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.painting);
const colorized = definition.cloneAndPaintWith(color);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(

View File

@ -11,8 +11,8 @@ export const ACHIEVEMENTS = {
}
export class AchievementsInterface {
/** @param {Application} app */
constructor(app) {
/** @type {Application} */
this.app = app;
}
@ -30,13 +30,12 @@ export class AchievementsInterface {
/**
* 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.
* @returns {(undefined|Promise)}
* @returns {void}
*/
unlock(key) {
abstract;
return Promise.reject();
}
/**

View File

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

View File

@ -4,7 +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 { SteamAchievements } from "../electron/steam_achievements";
import { PlatformWrapperInterface } from "../wrapper";
import { StorageImplBrowser } from "./storage";
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
@ -70,9 +70,13 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
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()
.then(() => this.initializeAdProvider())
.then(() => this.initializeAchievements())
.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() {
// Can not exit app
}

View File

@ -12,8 +12,21 @@ const IDS = {
blueprints: "<id>"
}
export class Achievements extends AchievementsInterface {
initialize() {
/** @typedef {object} SteamAchievementMap
* @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.type = "Steam";
this.count = 0;
@ -21,21 +34,22 @@ export class Achievements extends AchievementsInterface {
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.map.set(key, {
id: IDS[key],
key: key,
unlocked: false,
relevant: true,
activate: null
});
this.count++;
}
this.logOnly = globalConfig.debug.testAchievements;
return Promise.resolve();
}
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
return Promise.resolve();
}
@ -44,15 +58,15 @@ export class Achievements extends AchievementsInterface {
* @param {string} key
*/
unlock (key) {
let achievement = this.map[key];
if (!achievement) {
if (!this.map.has(key)) {
logger.error("Achievement does not exist:", key);
return;
}
let achievement = this.map.get(key);
if (!achievement.relevant) {
logger.debug("Achievement unlocked/irrelevant:", key);
console.log("Achievement unlocked/irrelevant:", key);
return;
}

View File

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