mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Add more achievements, refactor achievement code
This commit is contained in:
parent
dab41d19e7
commit
9597f3e89c
@ -12,7 +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 { NoAchievements } from "./platform/browser/no_achievements";
|
||||
import { NoAchievementProvider } from "./platform/browser/no_achievement_provider";
|
||||
import { AnalyticsInterface } from "./platform/analytics";
|
||||
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
|
||||
import { SoundImplBrowser } from "./platform/browser/sound";
|
||||
@ -33,7 +33,7 @@ import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
|
||||
import { RestrictionManager } from "./core/restriction_manager";
|
||||
|
||||
/**
|
||||
* @typedef {import("./platform/achievements").AchievementsInterface} AchievementsInterface
|
||||
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
|
||||
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
|
||||
* @typedef {import("./platform/sound").SoundInterface} SoundInterface
|
||||
* @typedef {import("./platform/storage").StorageInterface} StorageInterface
|
||||
@ -87,8 +87,8 @@ export class Application {
|
||||
/** @type {PlatformWrapperInterface} */
|
||||
this.platformWrapper = null;
|
||||
|
||||
/** @type {AchievementsInterface} */
|
||||
this.achievements = null;
|
||||
/** @type {AchievementProviderInterface} */
|
||||
this.achievementProvider = null;
|
||||
|
||||
/** @type {AdProviderInterface} */
|
||||
this.adProvider = null;
|
||||
@ -142,7 +142,7 @@ export class Application {
|
||||
this.sound = new SoundImplBrowser(this);
|
||||
this.analytics = new GoogleAnalyticsImpl(this);
|
||||
this.gameAnalytics = new ShapezGameAnalytics(this);
|
||||
this.achievements = new NoAchievements(this);
|
||||
this.achievementProvider = new NoAchievementProvider(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,28 +0,0 @@
|
||||
/* 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 {
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.achievements = this.root.app.achievements;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
unlock (key) {
|
||||
this.achievements.unlock(key);
|
||||
}
|
||||
}
|
29
src/js/game/achievement_proxy.js
Normal file
29
src/js/game/achievement_proxy.js
Normal file
@ -0,0 +1,29 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "./root";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../core/logging";
|
||||
|
||||
const logger = createLogger("achievement_proxy");
|
||||
|
||||
export class AchievementProxy {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.provider = this.root.app.achievementProvider;
|
||||
|
||||
if (!this.provider.hasAchievements()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.provider.initialize()
|
||||
.then(() => {
|
||||
this.root.signals.achievementUnlocked.add(this.provider.unlock, this.provider);
|
||||
|
||||
logger.log("Listening for unlocked achievements");
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error("Ignoring achievement signals", err);
|
||||
})
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ import { DrawParameters } from "../core/draw_parameters";
|
||||
import { findNiceIntegerValue } from "../core/utils";
|
||||
import { Vector } from "../core/vector";
|
||||
import { Entity } from "./entity";
|
||||
import { ACHIEVEMENTS } from "../platform/achievements";
|
||||
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
export class Blueprint {
|
||||
|
@ -35,7 +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 { AchievementProxy } from "./achievement_proxy";
|
||||
import { SoundProxy } from "./sound_proxy";
|
||||
import { GameTime } from "./time/game_time";
|
||||
|
||||
@ -112,6 +112,7 @@ export class GameCore {
|
||||
root.logic = new GameLogic(root);
|
||||
root.hud = new GameHUD(root);
|
||||
root.time = new GameTime(root);
|
||||
root.achievementProxy = new AchievementProxy(root);
|
||||
root.automaticSave = new AutomaticSave(root);
|
||||
root.soundProxy = new SoundProxy(root);
|
||||
|
||||
@ -119,7 +120,6 @@ 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);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { RandomNumberGenerator } from "../core/rng";
|
||||
import { clamp } from "../core/utils";
|
||||
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { enumColors } from "./colors";
|
||||
import { enumItemProcessorTypes } from "./components/item_processor";
|
||||
@ -260,6 +261,12 @@ export class HubGoals extends BasicSerializableObject {
|
||||
this.computeNextGoal();
|
||||
|
||||
this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward);
|
||||
|
||||
if (this.level - 1 === 20) {
|
||||
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.wires);
|
||||
} else if (this.level - 1 === 26) {
|
||||
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.freedom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +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 { AchievementProxy } from "./achievement_proxy";
|
||||
import { GameHUD } from "./hud/hud";
|
||||
import { MapView } from "./map_view";
|
||||
import { Camera } from "./camera";
|
||||
@ -120,8 +120,8 @@ export class GameRoot {
|
||||
/** @type {SoundProxy} */
|
||||
this.soundProxy = null;
|
||||
|
||||
/** @type {AchievementManager} */
|
||||
this.achievementMgr = null;
|
||||
/** @type {AchievementProxy} */
|
||||
this.achievementProxy = null;
|
||||
|
||||
/** @type {ShapeDefinitionManager} */
|
||||
this.shapeDefinitionMgr = null;
|
||||
|
@ -4,7 +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";
|
||||
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
||||
|
||||
const logger = createLogger("shape_definition_manager");
|
||||
|
||||
@ -251,6 +251,9 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
||||
return this.shapeKeyToDefinition[id];
|
||||
}
|
||||
this.shapeKeyToDefinition[id] = definition;
|
||||
|
||||
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.theLogo, definition);
|
||||
|
||||
// logger.log("Registered shape with key (2)", id);
|
||||
return definition;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { createLogger } from "../../core/logging";
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
||||
import { enumDirection, enumDirectionToVector } from "../../core/vector";
|
||||
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { BeltComponent } from "../components/belt";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
@ -274,6 +275,11 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
||||
// It's a storage
|
||||
if (storageComp.canAcceptItem(item)) {
|
||||
storageComp.takeItem(item);
|
||||
|
||||
if (storageComp.storedCount === 1) {
|
||||
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.storage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
enumInvertedDirections,
|
||||
Vector,
|
||||
} from "../../core/vector";
|
||||
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { arrayWireRotationVariantToType, MetaWireBuilding } from "../buildings/wire";
|
||||
import { getCodeFromBuildingData } from "../building_codes";
|
||||
@ -697,6 +698,12 @@ export class WireSystem extends GameSystemWithFilter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.components.Wire && entity.registered &&
|
||||
this.root.entityMgr.componentToEntity.Wire.length === 100) {
|
||||
|
||||
this.root.signals.achievementUnlocked.dispatch(ACHIEVEMENTS.networked);
|
||||
}
|
||||
|
||||
// Invalidate affected area
|
||||
const originalRect = staticComp.getTileSpaceBounds();
|
||||
const affectedArea = originalRect.expandedInAllDirections(1);
|
||||
|
138
src/js/platform/achievement_provider.js
Normal file
138
src/js/platform/achievement_provider.js
Normal file
@ -0,0 +1,138 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
export const ACHIEVEMENTS = {
|
||||
painting: "painting",
|
||||
cutting: "cutting",
|
||||
rotating: "rotating",
|
||||
stacking: "stacking",
|
||||
blueprints: "blueprints",
|
||||
wires: "wires",
|
||||
storage: "storage",
|
||||
freedom: "freedom",
|
||||
networked: "networked",
|
||||
theLogo: "theLogo",
|
||||
};
|
||||
|
||||
export class AchievementProviderInterface {
|
||||
/** @param {Application} app */
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the achievement provider.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
initialize() {
|
||||
abstract;
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to unlock an achievement
|
||||
* @param {string} [key] - A property within the ACHIEVEMENTS enum or empty if
|
||||
* bypassing.
|
||||
* @returns {void}
|
||||
*/
|
||||
unlock(key) {
|
||||
abstract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if achievements are supported in the current build
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAchievements() {
|
||||
abstract;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class Achievement {
|
||||
/**
|
||||
* @param {string} key - An ACHIEVEMENTS key
|
||||
*/
|
||||
constructor (key) {
|
||||
this.key = key;
|
||||
this.unlock = null;
|
||||
this.isValid = null;
|
||||
}
|
||||
}
|
||||
|
||||
export class AchievementCollection {
|
||||
/**
|
||||
* @param {string[]} keys - An array of ACHIEVEMENTS keys
|
||||
* @param {function} [activate] - Resolves when provider activation is complete
|
||||
*/
|
||||
constructor (keys, activate) {
|
||||
this.map = new Map();
|
||||
this.activate = activate ? activate : () => Promise.resolve();
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
assert(ACHIEVEMENTS[keys[i]], "Achievement does not exist: " + keys[i]);
|
||||
|
||||
const achievement = new Achievement(keys[i]);
|
||||
this.setValidation(achievement);
|
||||
this.map.set(keys[i], achievement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key - Maps to an Achievement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(key) {
|
||||
return this.map.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key - Maps to an Achievement
|
||||
* @param {*} [details] - Additional information as needed to validate
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValid(key, details) {
|
||||
return this.map.get(key).isValid(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key - Maps to an Achievement
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
unlock(key) {
|
||||
const achievement = this.map.get(key);
|
||||
|
||||
return achievement.unlock = achievement.unlock || this.activate(achievement)
|
||||
.then(() => {
|
||||
this.map.delete(key);
|
||||
})
|
||||
.catch(err => {
|
||||
achievement.unlock = null;
|
||||
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Achievement} achievement - Achievement receiving a validation function
|
||||
*/
|
||||
setValidation(achievement) {
|
||||
switch (achievement.key) {
|
||||
case ACHIEVEMENTS.theLogo:
|
||||
achievement.isValid = this.isTheLogoValid;
|
||||
break;
|
||||
default:
|
||||
achievement.isValid = () => true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} shortKey - The shape's shortKey to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTheLogoValid(shortKey) {
|
||||
return shortKey === "RuCw--Cw:----Ru--";
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
export const ACHIEVEMENTS = {
|
||||
painting: "painting",
|
||||
cutting: "cutting",
|
||||
rotating: "rotating",
|
||||
stacking: "stacking",
|
||||
blueprints: "blueprints",
|
||||
}
|
||||
|
||||
export class AchievementsInterface {
|
||||
/** @param {Application} app */
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load achievements into an initial state, bypassing unlocked and/or
|
||||
* irrelevant achievements where possible.
|
||||
*
|
||||
* @params key
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
load() {
|
||||
abstract;
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to unlock an achievement
|
||||
* @params {string} [key] - A property within the ACHIEVEMENTS enum or empty if
|
||||
* bypassing.
|
||||
* @returns {void}
|
||||
*/
|
||||
unlock(key) {
|
||||
abstract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the list of achievements.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
initialize() {
|
||||
abstract;
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if achievements are supported in the current build
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAchievements() {
|
||||
abstract;
|
||||
return false;
|
||||
}
|
||||
}
|
14
src/js/platform/browser/no_achievement_provider.js
Normal file
14
src/js/platform/browser/no_achievement_provider.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { AchievementProviderInterface } from "../achievement_provider";
|
||||
|
||||
export class NoAchievementProvider extends AchievementProviderInterface {
|
||||
hasAchievements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
unlock() {
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { AchievementsInterface } from "../achievements";
|
||||
|
||||
export class NoAchievements extends AchievementsInterface {
|
||||
load() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
hasAchievements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unlock() {
|
||||
}
|
||||
}
|
@ -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 { SteamAchievements } from "../electron/steam_achievements";
|
||||
import { SteamAchievementProvider } from "../electron/steam_achievement_provider";
|
||||
import { PlatformWrapperInterface } from "../wrapper";
|
||||
import { StorageImplBrowser } from "./storage";
|
||||
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
||||
@ -72,7 +72,7 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.testAchievements) {
|
||||
logger.log("Testing achievements");
|
||||
this.app.achievements = new SteamAchievements(this.app);
|
||||
this.app.achievementProvider = new SteamAchievementProvider(this.app);
|
||||
}
|
||||
|
||||
return this.detectStorageImplementation()
|
||||
|
118
src/js/platform/electron/steam_achievement_provider.js
Normal file
118
src/js/platform/electron/steam_achievement_provider.js
Normal file
@ -0,0 +1,118 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../../application";
|
||||
import { Achievement } from "../achievement_provider";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../../core/logging";
|
||||
import { getIPCRenderer } from "../../core/utils";
|
||||
import {
|
||||
ACHIEVEMENTS,
|
||||
AchievementCollection,
|
||||
AchievementProviderInterface
|
||||
} from "../achievement_provider";
|
||||
|
||||
const logger = createLogger("achievements/steam");
|
||||
|
||||
const ACHIEVEMENT_IDS = {
|
||||
[ACHIEVEMENTS.painting]: "<id>",
|
||||
[ACHIEVEMENTS.cutting]: "achievement_01", // Test ID
|
||||
[ACHIEVEMENTS.rotating]: "<id>",
|
||||
[ACHIEVEMENTS.stacking]: "<id>",
|
||||
[ACHIEVEMENTS.blueprints]: "<id>",
|
||||
[ACHIEVEMENTS.wires]: "<id>",
|
||||
[ACHIEVEMENTS.storage]: "<id>",
|
||||
[ACHIEVEMENTS.freedom]: "<id>",
|
||||
[ACHIEVEMENTS.networked]: "<id>",
|
||||
[ACHIEVEMENTS.theLogo]: "<id>"
|
||||
};
|
||||
|
||||
export class SteamAchievementProvider extends AchievementProviderInterface {
|
||||
/** @param {Application} app */
|
||||
constructor(app) {
|
||||
super(app);
|
||||
|
||||
this.initialized = false;
|
||||
this.collection = new AchievementCollection(
|
||||
Object.keys(ACHIEVEMENT_IDS),
|
||||
this.activate.bind(this)
|
||||
);
|
||||
|
||||
logger.log("Steam achievement collection created");
|
||||
}
|
||||
|
||||
initialize () {
|
||||
if (!G_IS_STANDALONE) {
|
||||
logger.warn("Steam listener isn't active. Achievements won't sync.");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.ipc = getIPCRenderer();
|
||||
|
||||
return this.ipc.invoke("steam:is-initialized")
|
||||
.then(initialized => {
|
||||
if (!initialized) {
|
||||
logger.warn("Steam failed to intialize. Achievements won't sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
logger.log("Steam achievement provider initialized");
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error("Steam achievement provider error", err);
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key - Maps to an Achievement
|
||||
* @param {*} [details] - Additional information as needed to validate
|
||||
*/
|
||||
unlock (key, details) {
|
||||
if (!this.collection.has(key)) {
|
||||
console.log("Achievement already unlocked", key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.collection.isValid(key, details)) {
|
||||
console.log("Achievement is invalid", key);
|
||||
return;
|
||||
}
|
||||
|
||||
this.collection.unlock(key)
|
||||
.then(() => {
|
||||
logger.log("Achievement unlocked:", key);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error("Failed to unlock achievement", err);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key - Maps to an API ID for the achievement
|
||||
* @returns {string}
|
||||
*/
|
||||
getApiId (key) {
|
||||
return ACHIEVEMENT_IDS[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Achievement} achievement
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
activate (achievement) {
|
||||
if (!this.initialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.ipc.invoke("steam:activate-achievement", this.getApiId(achievement.key))
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAchievements() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { createLogger } from "../../core/logging";
|
||||
import { getIPCRenderer } from "../../core/utils";
|
||||
import { ACHIEVEMENTS, AchievementsInterface } from "../achievements";
|
||||
|
||||
const logger = createLogger("achievements/steam");
|
||||
|
||||
const IDS = {
|
||||
painting: "<id>",
|
||||
cutting: "achievement_01", // Test ID
|
||||
rotating: "<id>",
|
||||
stacking: "<id>",
|
||||
blueprints: "<id>",
|
||||
wires: "<id>",
|
||||
}
|
||||
|
||||
/** @typedef {object} SteamAchievement
|
||||
* @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 {SteamAchievementMap} */
|
||||
this.map = new Map();
|
||||
this.type = "Steam";
|
||||
this.count = 0;
|
||||
this.steamInitialized = false;
|
||||
|
||||
|
||||
logger.log("Initializing", this.type, "achievements");
|
||||
|
||||
for (let key in ACHIEVEMENTS) {
|
||||
this.map.set(key, {
|
||||
id: IDS[key],
|
||||
key: key,
|
||||
unlocked: false,
|
||||
relevant: true,
|
||||
activate: null
|
||||
});
|
||||
|
||||
this.count++;
|
||||
}
|
||||
|
||||
this.load()
|
||||
}
|
||||
|
||||
load () {
|
||||
// TODO: inspect save file and update achievements
|
||||
|
||||
if (!G_IS_STANDALONE) {
|
||||
logger.warn("Steam listener isn't active. Achievements won't sync.");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.ipc = getIPCRenderer();
|
||||
|
||||
return this.ipc.invoke("steam:is-initialized")
|
||||
.then(initialized => {
|
||||
if (!initialized) {
|
||||
logger.warn("Steam failed to intialize. Achievements won't sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("Steam listener is active");
|
||||
|
||||
this.steamInitialized = true;
|
||||
|
||||
return this.ipc.invoke("steam:get-achievement-names")
|
||||
.then(result => {
|
||||
logger.log("steam:get-achievement-names", result);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
unlock (key) {
|
||||
if (!this.map.has(key)) {
|
||||
logger.warn("Achievement does not exist:", key);
|
||||
return;
|
||||
}
|
||||
|
||||
let achievement = this.map.get(key);
|
||||
|
||||
if (!achievement.relevant) {
|
||||
console.log("Achievement unlocked/irrelevant:", key);
|
||||
return;
|
||||
}
|
||||
|
||||
achievement.activate = achievement.activate || this.activate(achievement)
|
||||
.then(() => {
|
||||
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 {SteamAchievement} achievement
|
||||
*/
|
||||
activate (achievement) {
|
||||
if (!this.steamInitialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.ipc.invoke("steam:activate-achievement", achievement.id)
|
||||
}
|
||||
|
||||
hasAchievements() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -2,7 +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 { SteamAchievementProvider } from "./steam_achievement_provider";
|
||||
import { PlatformWrapperInterface } from "../wrapper";
|
||||
|
||||
const logger = createLogger("electron-wrapper");
|
||||
@ -20,7 +20,7 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
||||
this.app.ticker.frameEmitted.add(this.steamOverlayFixRedrawCanvas, this);
|
||||
|
||||
this.app.storage = new StorageImplElectron(this);
|
||||
this.app.achievements = new SteamAchievements(this.app);
|
||||
this.app.achievementProvider = new SteamAchievementProvider(this.app);
|
||||
|
||||
return PlatformWrapperInterface.prototype.initialize.call(this);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user