diff --git a/gulp/webpack.config.js b/gulp/webpack.config.js index 274cc3ef..26923796 100644 --- a/gulp/webpack.config.js +++ b/gulp/webpack.config.js @@ -36,7 +36,6 @@ module.exports = ({ watch = false, standalone = false }) => { lzString.compressToEncodedURIComponent("http://localhost:10005/v1") ), G_IS_DEV: "true", - G_IS_PROD: "false", G_IS_RELEASE: "false", G_IS_MOBILE_APP: "false", G_IS_BROWSER: "true", diff --git a/gulp/webpack.production.config.js b/gulp/webpack.production.config.js index 837cfe8b..f80a69d2 100644 --- a/gulp/webpack.production.config.js +++ b/gulp/webpack.production.config.js @@ -24,7 +24,6 @@ module.exports = ({ assertAlways: "window.assert", abstract: "window.assert(false, 'abstract method called');", G_IS_DEV: "false", - G_IS_PROD: "true", G_IS_RELEASE: environment === "prod" ? "true" : "false", G_IS_STANDALONE: standalone ? "true" : "false", G_IS_BROWSER: isBrowser ? "true" : "false", diff --git a/src/js/application.js b/src/js/application.js index ee913a3f..f08b467e 100644 --- a/src/js/application.js +++ b/src/js/application.js @@ -33,6 +33,7 @@ import { MainMenuState } from "./states/main_menu"; import { MobileWarningState } from "./states/mobile_warning"; import { PreloadState } from "./states/preload"; import { SettingsState } from "./states/settings"; +import { ShapezGameAnalytics } from "./platform/browser/game_analytics"; const logger = createLogger("application"); @@ -130,8 +131,7 @@ export class Application { this.adProvider = new NoAdProvider(this); this.sound = new SoundImplBrowser(this); this.analytics = new GoogleAnalyticsImpl(this); - // this.gameAnalytics = new ShapezGameAnalytics(this); - this.gameAnalytics = new NoGameAnalytics(this); + this.gameAnalytics = new ShapezGameAnalytics(this); } /** diff --git a/src/js/core/config.js b/src/js/core/config.js index 62e5d87d..6825e762 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -9,7 +9,7 @@ export const IS_DEBUG = export const IS_DEMO = queryParamOptions.fullVersion ? false - : (G_IS_PROD && !G_IS_STANDALONE) || + : (!G_IS_DEV && !G_IS_STANDALONE) || (typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0); export const SUPPORT_TOUCH = false; diff --git a/src/js/core/utils.js b/src/js/core/utils.js index fdf97880..23368317 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -769,7 +769,7 @@ export function quantizeFloat(value) { * @param {number} tickRate Interval of the timer */ export function checkTimerExpired(now, lastTick, tickRate) { - if (!G_IS_PROD) { + if (G_IS_DEV) { if (quantizeFloat(now) !== now) { console.error("Got non-quantizied time:" + now + " vs " + quantizeFloat(now)); now = quantizeFloat(now); diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts index dc2246d0..bc99d55e 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -1,7 +1,6 @@ // Globals defined by webpack declare const G_IS_DEV: boolean; -declare const G_IS_PROD: boolean; declare function assert(condition: boolean | object | string, ...errorMessage: string[]): void; declare function assertAlways(condition: boolean | object | string, ...errorMessage: string[]): void; diff --git a/src/js/platform/browser/game_analytics.js b/src/js/platform/browser/game_analytics.js index be1a8940..73da3696 100644 --- a/src/js/platform/browser/game_analytics.js +++ b/src/js/platform/browser/game_analytics.js @@ -1,11 +1,12 @@ -import { GameAnalyticsInterface } from "../game_analytics"; -import { createLogger } from "../../core/logging"; -import { ShapeDefinition } from "../../game/shape_definition"; -import { Savegame } from "../../savegame/savegame"; -import { FILE_NOT_FOUND } from "../storage"; import { globalConfig } from "../../core/config"; -import { InGameState } from "../../states/ingame"; +import { createLogger } from "../../core/logging"; import { GameRoot } from "../../game/root"; +import { InGameState } from "../../states/ingame"; +import { GameAnalyticsInterface } from "../game_analytics"; +import { FILE_NOT_FOUND } from "../storage"; +import { blueprintShape, UPGRADES } from "../../game/upgrades"; +import { tutorialGoals } from "../../game/tutorial_goals"; +import { BeltComponent } from "../../game/components/belt"; import { StaticMapEntityComponent } from "../../game/components/static_map_entity"; const logger = createLogger("game_analytics"); @@ -14,16 +15,32 @@ const analyticsUrl = G_IS_DEV ? "http://localhost:8001" : "https://analytics.sha // Be sure to increment the ID whenever it changes to make sure all // users are tracked -const analyticsLocalFile = "analytics_token.3.bin"; +const analyticsLocalFile = "shapez_token_123.bin"; export class ShapezGameAnalytics extends GameAnalyticsInterface { + get environment() { + if (G_IS_DEV) { + return "dev"; + } + + if (G_IS_STANDALONE) { + return "steam"; + } + + if (G_IS_RELEASE) { + return "prod"; + } + + return "beta"; + } + /** * @returns {Promise} */ initialize() { this.syncKey = null; - setInterval(() => this.sendTimePoints(), 120 * 1000); + setInterval(() => this.sendTimePoints(), 60 * 1000); // Retrieve sync key from player return this.app.storage.readFileAsync(analyticsLocalFile).then( @@ -38,7 +55,7 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface { // Perform call to get a new key from the API this.sendToApi("/v1/register", { - environment: G_APP_ENVIRONMENT, + environment: this.environment, }) .then(res => { // Try to read and parse the key from the api @@ -135,10 +152,12 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface { playerKey: this.syncKey, gameKey: savegameId, ingameTime: root.time.now(), + environment: this.environment, category, value, version: G_BUILD_VERSION, - gameDump: this.generateGameDump(root, category === "sync"), + level: root.hubGoals.level, + gameDump: this.generateGameDump(root), }); } @@ -151,52 +170,58 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface { } /** - * Generates a game dump - * @param {GameRoot} root - * @param {boolean=} metaOnly + * Returns true if the shape is interesting + * @param {string} key */ - generateGameDump(root, metaOnly = false) { - let staticEntities = []; + isInterestingShape(key) { + if (key === blueprintShape) { + return true; + } - const entities = root.entityMgr.getAllWithComponent(StaticMapEntityComponent); - - // Limit the entities - if (!metaOnly && entities.length < 500) { - for (let i = 0; i < entities.length; ++i) { - const entity = entities[i]; - const staticComp = entity.components.StaticMapEntity; - const payload = {}; - payload.origin = staticComp.origin; - payload.tileSize = staticComp.tileSize; - payload.rotation = staticComp.rotation; - - if (entity.components.Belt) { - payload.type = "belt"; - } else if (entity.components.UndergroundBelt) { - payload.type = "tunnel"; - } else if (entity.components.ItemProcessor) { - payload.type = entity.components.ItemProcessor.type; - } else if (entity.components.Miner) { - payload.type = "extractor"; - } else { - logger.warn("Unkown entity type", entity); - } - staticEntities.push(payload); + // Check if its a story goal + for (let i = 0; i < tutorialGoals.length; ++i) { + if (key === tutorialGoals[i].shape) { + return true; } } - return { - storedShapes: root.hubGoals.storedShapes, - gainedRewards: root.hubGoals.gainedRewards, - upgradeLevels: root.hubGoals.upgradeLevels, - staticEntities, - }; + // Check if its required to unlock an upgrade + for (const upgradeKey in UPGRADES) { + const handle = UPGRADES[upgradeKey]; + const tiers = handle.tiers; + for (let i = 0; i < tiers.length; ++i) { + const tier = tiers[i]; + const required = tier.required; + for (let k = 0; k < required.length; ++k) { + if (required[k].shape === key) { + return true; + } + } + } + } + + return false; } /** - * @param {ShapeDefinition} definition + * Generates a game dump + * @param {GameRoot} root */ - handleShapeDelivered(definition) {} + generateGameDump(root) { + const shapeIds = Object.keys(root.hubGoals.storedShapes).filter(this.isInterestingShape.bind(this)); + let shapes = {}; + for (let i = 0; i < shapeIds.length; ++i) { + shapes[shapeIds[i]] = root.hubGoals.storedShapes[shapeIds[i]]; + } + return { + shapes, + upgrades: root.hubGoals.upgradeLevels, + belts: root.entityMgr.getAllWithComponent(BeltComponent).length, + buildings: + root.entityMgr.getAllWithComponent(StaticMapEntityComponent).length - + root.entityMgr.getAllWithComponent(BeltComponent).length, + }; + } /** */ @@ -204,6 +229,12 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface { this.sendGameEvent("game_start", ""); } + /** + */ + handleGameResumed() { + this.sendTimePoints(); + } + /** * Handles the given level completed * @param {number} level diff --git a/src/js/platform/game_analytics.js b/src/js/platform/game_analytics.js index c3e2fa64..ea6aa293 100644 --- a/src/js/platform/game_analytics.js +++ b/src/js/platform/game_analytics.js @@ -25,9 +25,9 @@ export class GameAnalyticsInterface { handleGameStarted() {} /** - * @param {ShapeDefinition} definition + * Handles a resumed game */ - handleShapeDelivered(definition) {} + handleGameResumed() {} /** * Handles the given level completed diff --git a/src/js/states/ingame.js b/src/js/states/ingame.js index a5901a3d..33d7e15b 100644 --- a/src/js/states/ingame.js +++ b/src/js/states/ingame.js @@ -217,7 +217,6 @@ export class InGameState extends GameState { this.core.initializeRoot(this, this.savegame); if (this.savegame.hasGameDump()) { - this.app.gameAnalytics.handleGameStarted(); this.stage4bResumeGame(); } else { this.app.gameAnalytics.handleGameStarted(); @@ -245,6 +244,7 @@ export class InGameState extends GameState { this.onInitializationFailure("Savegame is corrupt and can not be restored."); return; } + this.app.gameAnalytics.handleGameResumed(); this.stage5FirstUpdate(); } }