From 79158e2b56d9bbfd2c6dd75bbe7b0f02535913a4 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Thu, 6 Aug 2020 18:28:28 +0900 Subject: [PATCH] Remove unused code (#526) --- src/js/application.js | 10 +- src/js/core/atlas_definitions.js | 41 ++- src/js/core/background_resources_loader.js | 1 - src/js/core/draw_parameters.js | 8 +- src/js/core/draw_utils.js | 282 +--------------- src/js/core/global_registries.js | 16 +- src/js/core/loader.js | 71 ++-- src/js/core/rectangle.js | 2 +- src/js/core/sprites.js | 9 +- src/js/core/utils.js | 353 +------------------- src/js/game/buildings/miner.js | 2 +- src/js/game/component.js | 6 + src/js/game/dynamic_tickrate.js | 1 - src/js/game/hub_goals.js | 5 +- src/js/game/hud/parts/building_placer.js | 5 +- src/js/game/hud/parts/game_menu.js | 3 +- src/js/game/hud/parts/pinned_shapes.js | 2 +- src/js/game/hud/parts/settings_menu.js | 14 +- src/js/game/hud/parts/statistics.js | 10 +- src/js/game/map.js | 1 - src/js/game/map_chunk.js | 18 +- src/js/game/map_chunk_view.js | 9 - src/js/globals.d.ts | 14 +- src/js/platform/game_analytics.js | 8 +- src/js/savegame/savegame.js | 28 +- src/js/savegame/savegame_manager.js | 20 +- src/js/savegame/savegame_serializer.js | 98 +++--- src/js/savegame/savegame_typedefs.js | 31 +- src/js/savegame/serialization_data_types.js | 32 +- src/js/states/main_menu.js | 34 +- src/js/states/preload.js | 11 +- 31 files changed, 265 insertions(+), 880 deletions(-) diff --git a/src/js/application.js b/src/js/application.js index bf70239d..e5e22b60 100644 --- a/src/js/application.js +++ b/src/js/application.js @@ -14,13 +14,9 @@ import { AdProviderInterface } from "./platform/ad_provider"; import { NoAdProvider } from "./platform/ad_providers/no_ad_provider"; import { AnalyticsInterface } from "./platform/analytics"; import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics"; -import { NoGameAnalytics } from "./platform/browser/no_game_analytics"; import { SoundImplBrowser } from "./platform/browser/sound"; import { PlatformWrapperImplBrowser } from "./platform/browser/wrapper"; import { PlatformWrapperImplElectron } from "./platform/electron/wrapper"; -import { GameAnalyticsInterface } from "./platform/game_analytics"; -import { SoundInterface } from "./platform/sound"; -import { StorageInterface } from "./platform/storage"; import { PlatformWrapperInterface } from "./platform/wrapper"; import { ApplicationSettings } from "./profile/application_settings"; import { SavegameManager } from "./savegame/savegame_manager"; @@ -34,6 +30,12 @@ import { PreloadState } from "./states/preload"; import { SettingsState } from "./states/settings"; import { ShapezGameAnalytics } from "./platform/browser/game_analytics"; +/** + * @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface + * @typedef {import("./platform/sound").SoundInterface} SoundInterface + * @typedef {import("./platform/storage").StorageInterface} StorageInterface + */ + const logger = createLogger("application"); // Set the name of the hidden property and the change event for visibility diff --git a/src/js/core/atlas_definitions.js b/src/js/core/atlas_definitions.js index 42cd2bce..38d36b59 100644 --- a/src/js/core/atlas_definitions.js +++ b/src/js/core/atlas_definitions.js @@ -1,20 +1,38 @@ /** + * @typedef {{ w: number, h: number }} Size + * @typedef {{ x: number, y: number }} Position * @typedef {{ - * frame: { x: number, y: number, w: number, h: number }, - * rotated: false, - * spriteSourceSize: { x: number, y: number, w: number, h: number }, - * sourceSize: { w: number, h: number}, - * trimmed: true + * frame: Position & Size, + * rotated: boolean, + * spriteSourceSize: Position & Size, + * sourceSize: Size, + * trimmed: boolean * }} SpriteDefinition + * + * @typedef {{ + * app: string, + * version: string, + * image: string, + * format: string, + * size: Size, + * scale: string, + * smartupdate: string + * }} AtlasMeta + * + * @typedef {{ + * frames: Object., + * meta: AtlasMeta + * }} SourceData */ export class AtlasDefinition { - constructor(sourceData) { - this.sourceFileName = sourceData.meta.image; - this.meta = sourceData.meta; - - /** @type {Object.} */ - this.sourceData = sourceData.frames; + /** + * @param {SourceData} sourceData + */ + constructor({ frames, meta }) { + this.meta = meta; + this.sourceData = frames; + this.sourceFileName = meta.image; } getFullSourcePath() { @@ -22,6 +40,7 @@ export class AtlasDefinition { } } +/** @type {AtlasDefinition[]} **/ export const atlasFiles = require // @ts-ignore .context("../../../res_built/atlas/", false, /.*\.json/i) diff --git a/src/js/core/background_resources_loader.js b/src/js/core/background_resources_loader.js index 213e5869..b3a7671b 100644 --- a/src/js/core/background_resources_loader.js +++ b/src/js/core/background_resources_loader.js @@ -115,7 +115,6 @@ export class BackgroundResourcesLoader { }) .then(() => { logger.log("⏰ Finish load: bare game"); - Loader.createAtlasLinks(); this.bareGameReady = true; initBuildingCodesAfterResourcesLoaded(); this.signalBareGameLoaded.dispatch(); diff --git a/src/js/core/draw_parameters.js b/src/js/core/draw_parameters.js index dcdf6d13..45620a9e 100644 --- a/src/js/core/draw_parameters.js +++ b/src/js/core/draw_parameters.js @@ -1,9 +1,9 @@ -import { Rectangle } from "./rectangle"; import { globalConfig } from "./config"; -/* typehints:start */ -import { GameRoot } from "../game/root"; -/* typehints:end */ +/** + * @typedef {import("../game/root").GameRoot} GameRoot + * @typedef {import("./rectangle").Rectangle} Rectangle + */ export class DrawParameters { constructor({ context, visibleRect, desiredAtlasScale, zoomLevel, root }) { diff --git a/src/js/core/draw_utils.js b/src/js/core/draw_utils.js index 1b37b929..ea5b70c2 100644 --- a/src/js/core/draw_utils.js +++ b/src/js/core/draw_utils.js @@ -1,18 +1,13 @@ -/* typehints:start */ -import { AtlasSprite } from "./sprites"; -import { DrawParameters } from "./draw_parameters"; -/* typehints:end */ - -import { Vector } from "./vector"; -import { Rectangle } from "./rectangle"; -import { createLogger } from "./logging"; - -const logger = createLogger("draw_utils"); +/** + * @typedef {import("./sprites").AtlasSprite} AtlasSprite + * @typedef {import("./draw_parameters").DrawParameters} DrawParameters + */ export function initDrawUtils() { CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) { + this.beginPath(); + if (r < 0.05) { - this.beginPath(); this.rect(x, y, w, h); return; } @@ -20,25 +15,26 @@ export function initDrawUtils() { if (w < 2 * r) { r = w / 2; } + if (h < 2 * r) { r = h / 2; } - this.beginPath(); + this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); - // this.closePath(); }; CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) { + this.beginPath(); + if (r < 0.05) { - this.beginPath(); this.rect(x, y, 1, 1); return; } - this.beginPath(); + this.arc(x, y, r, 0, 2.0 * Math.PI); }; } @@ -62,259 +58,3 @@ export function drawRotatedSprite({ parameters, sprite, x, y, angle, size, offse parameters.context.rotate(-angle); parameters.context.translate(-x, -y); } - -export function drawLineFast(context, { x1, x2, y1, y2, color = null, lineSize = 1 }) { - const dX = x2 - x1; - const dY = y2 - y1; - - const angle = Math.atan2(dY, dX) + 0.0 * Math.PI; - const len = Math.hypot(dX, dY); - - context.translate(x1, y1); - context.rotate(angle); - - if (color) { - context.fillStyle = color; - } - - context.fillRect(0, -lineSize / 2, len, lineSize); - - context.rotate(-angle); - context.translate(-x1, -y1); -} - -const INSIDE = 0; -const LEFT = 1; -const RIGHT = 2; -const BOTTOM = 4; -const TOP = 8; - -// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm - -function computeOutCode(x, y, xmin, xmax, ymin, ymax) { - let code = INSIDE; - - if (x < xmin) - // to the left of clip window - code |= LEFT; - else if (x > xmax) - // to the right of clip window - code |= RIGHT; - if (y < ymin) - // below the clip window - code |= BOTTOM; - else if (y > ymax) - // above the clip window - code |= TOP; - - return code; -} - -// Cohen–Sutherland clipping algorithm clips a line from -// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with -// diagonal from (xmin, ymin) to (xmax, ymax). -/** - * - * @param {CanvasRenderingContext2D} context - */ -export function drawLineFastClipped(context, rect, { x0, y0, x1, y1, color = null, lineSize = 1 }) { - const xmin = rect.x; - const ymin = rect.y; - const xmax = rect.right(); - const ymax = rect.bottom(); - - // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle - let outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax); - let outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax); - let accept = false; - - // eslint-disable-next-line no-constant-condition - while (true) { - if (!(outcode0 | outcode1)) { - // bitwise OR is 0: both points inside window; trivially accept and exit loop - accept = true; - break; - } else if (outcode0 & outcode1) { - // bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP, - // or BOTTOM), so both must be outside window; exit loop (accept is false) - break; - } else { - // failed both tests, so calculate the line segment to clip - // from an outside point to an intersection with clip edge - let x, y; - - // At least one endpoint is outside the clip rectangle; pick it. - let outcodeOut = outcode0 ? outcode0 : outcode1; - - // Now find the intersection point; - // use formulas: - // slope = (y1 - y0) / (x1 - x0) - // x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax - // y = y0 + slope * (xm - x0), where xm is xmin or xmax - // No need to worry about divide-by-zero because, in each case, the - // outcode bit being tested guarantees the denominator is non-zero - if (outcodeOut & TOP) { - // point is above the clip window - x = x0 + ((x1 - x0) * (ymax - y0)) / (y1 - y0); - y = ymax; - } else if (outcodeOut & BOTTOM) { - // point is below the clip window - x = x0 + ((x1 - x0) * (ymin - y0)) / (y1 - y0); - y = ymin; - } else if (outcodeOut & RIGHT) { - // point is to the right of clip window - y = y0 + ((y1 - y0) * (xmax - x0)) / (x1 - x0); - x = xmax; - } else if (outcodeOut & LEFT) { - // point is to the left of clip window - y = y0 + ((y1 - y0) * (xmin - x0)) / (x1 - x0); - x = xmin; - } - - // Now we move outside point to intersection point to clip - // and get ready for next pass. - if (outcodeOut == outcode0) { - x0 = x; - y0 = y; - outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax); - } else { - x1 = x; - y1 = y; - outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax); - } - } - } - if (accept) { - // Following functions are left for implementation by user based on - // their platform (OpenGL/graphics.h etc.) - // DrawRectangle(xmin, ymin, xmax, ymax); - // LineSegment(x0, y0, x1, y1); - drawLineFast(context, { - x1: x0, - y1: y0, - x2: x1, - y2: y1, - color, - lineSize, - }); - } -} - -/** - * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * Assumes h, s, and l are contained in the set [0, 1] and - * returns r, g, and b in the set [0, 255]. - * - * @param {number} h The hue - * @param {number} s The saturation - * @param {number} l The lightness - * @return {Array} The RGB representation - */ -export function hslToRgb(h, s, l) { - let r; - let g; - let b; - - if (s === 0) { - r = g = b = l; // achromatic - } else { - // tslint:disable-next-line:no-shadowed-variable - const hue2rgb = function (p, q, t) { - if (t < 0) { - t += 1; - } - if (t > 1) { - t -= 1; - } - if (t < 1 / 6) { - return p + (q - p) * 6 * t; - } - if (t < 1 / 2) { - return q; - } - if (t < 2 / 3) { - return p + (q - p) * (2 / 3 - t) * 6; - } - return p; - }; - - let q = l < 0.5 ? l * (1 + s) : l + s - l * s; - let p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - - return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; -} - -export function wrapText(context, text, x, y, maxWidth, lineHeight, stroke = false) { - var words = text.split(" "); - var line = ""; - - for (var n = 0; n < words.length; n++) { - var testLine = line + words[n] + " "; - var metrics = context.measureText(testLine); - var testWidth = metrics.width; - if (testWidth > maxWidth && n > 0) { - if (stroke) { - context.strokeText(line, x, y); - } else { - context.fillText(line, x, y); - } - line = words[n] + " "; - y += lineHeight; - } else { - line = testLine; - } - } - - if (stroke) { - context.strokeText(line, x, y); - } else { - context.fillText(line, x, y); - } -} - -/** - * Returns a rotated trapez, used for spotlight culling - * @param {number} x - * @param {number} y - * @param {number} w - * @param {number} h - * @param {number} leftHeight - * @param {number} angle - */ -export function rotateTrapezRightFaced(x, y, w, h, leftHeight, angle) { - const halfY = y + h / 2; - const points = [ - new Vector(x, halfY - leftHeight / 2), - new Vector(x + w, y), - new Vector(x, halfY + leftHeight / 2), - new Vector(x + w, y + h), - ]; - - return Rectangle.getAroundPointsRotated(points, angle); -} - -/** - * Converts values from 0 .. 255 to values like 07, 7f, 5d etc - * @param {number} value - * @returns {string} - */ -export function mapClampedColorValueToHex(value) { - const hex = "0123456789abcdef"; - return hex[Math.floor(value / 16)] + hex[value % 16]; -} - -/** - * Converts rgb to a hex string - * @param {number} r - * @param {number} g - * @param {number} b - * @returns {string} - */ -export function rgbToHex(r, g, b) { - return mapClampedColorValueToHex(r) + mapClampedColorValueToHex(g) + mapClampedColorValueToHex(b); -} diff --git a/src/js/core/global_registries.js b/src/js/core/global_registries.js index 321732e0..ad45850c 100644 --- a/src/js/core/global_registries.js +++ b/src/js/core/global_registries.js @@ -1,19 +1,19 @@ import { SingletonFactory } from "./singleton_factory"; import { Factory } from "./factory"; -/* typehints:start */ -import { BaseGameSpeed } from "../game/time/base_game_speed"; -import { Component } from "../game/component"; -import { BaseItem } from "../game/base_item"; -import { MetaBuilding } from "../game/meta_building"; -/* typehints:end */ +/** + * @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed + * @typedef {import("../game/component").Component} Component + * @typedef {import("../game/base_item").BaseItem} BaseItem + * @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding + // These factories are here to remove circular dependencies /** @type {SingletonFactoryTemplate} */ export let gMetaBuildingRegistry = new SingletonFactory(); -/** @type {Object.>} */ +/** @type {Object.>>} */ export let gBuildingsByCategory = null; /** @type {FactoryTemplate} */ @@ -28,7 +28,7 @@ export let gItemRegistry = new Factory("item"); // Helpers /** - * @param {Object.>} buildings + * @param {Object.>>} buildings */ export function initBuildingsByCategory(buildings) { gBuildingsByCategory = buildings; diff --git a/src/js/core/loader.js b/src/js/core/loader.js index 8888ecbf..d7f544e3 100644 --- a/src/js/core/loader.js +++ b/src/js/core/loader.js @@ -1,20 +1,19 @@ -/* typehints:start */ -import { Application } from "../application"; -/* typehints:end */ - -import { AtlasDefinition } from "./atlas_definitions"; import { makeOffscreenBuffer } from "./buffer_utils"; import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites"; import { cachebust } from "./cachebust"; import { createLogger } from "./logging"; +/** + * @typedef {import("../application").Application} Application + * @typedef {import("./atlas_definitions").AtlasDefinition} AtlasDefinition; + */ + const logger = createLogger("loader"); const missingSpriteIds = {}; class LoaderImpl { constructor() { - /** @type {Application} */ this.app = null; /** @type {Map} */ @@ -23,6 +22,9 @@ class LoaderImpl { this.rawImages = []; } + /** + * @param {Application} app + */ linkAppAfterBoot(app) { this.app = app; this.makeSpriteNotFoundCanvas(); @@ -58,7 +60,7 @@ class LoaderImpl { } /** - * Retursn a regular sprite from the cache + * Returns a regular sprite from the cache * @param {string} key * @returns {RegularSprite} */ @@ -155,44 +157,34 @@ class LoaderImpl { * @param {AtlasDefinition} atlas * @param {HTMLImageElement} loadedImage */ - internalParseAtlas(atlas, loadedImage) { + internalParseAtlas({ meta: { scale }, sourceData }, loadedImage) { this.rawImages.push(loadedImage); - for (const spriteKey in atlas.sourceData) { - const spriteData = atlas.sourceData[spriteKey]; + for (const spriteName in sourceData) { + const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName]; - let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteKey)); + let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteName)); if (!sprite) { - sprite = new AtlasSprite({ - spriteName: spriteKey, - }); - this.sprites.set(spriteKey, sprite); + sprite = new AtlasSprite(spriteName); + this.sprites.set(spriteName, sprite); } const link = new SpriteAtlasLink({ - packedX: spriteData.frame.x, - packedY: spriteData.frame.y, - packedW: spriteData.frame.w, - packedH: spriteData.frame.h, - packOffsetX: spriteData.spriteSourceSize.x, - packOffsetY: spriteData.spriteSourceSize.y, + packedX: frame.x, + packedY: frame.y, + packedW: frame.w, + packedH: frame.h, + packOffsetX: spriteSourceSize.x, + packOffsetY: spriteSourceSize.y, atlas: loadedImage, - w: spriteData.sourceSize.w, - h: spriteData.sourceSize.h, + w: sourceSize.w, + h: sourceSize.h, }); - sprite.linksByResolution[atlas.meta.scale] = link; + sprite.linksByResolution[scale] = link; } } - /** - * Creates the links for the sprites after the atlas has been loaded. Used so we - * don't have to store duplicate sprites. - */ - createAtlasLinks() { - // NOT USED - } - /** * Makes the canvas which shows the question mark, shown when a sprite was not found */ @@ -216,14 +208,9 @@ class LoaderImpl { // @ts-ignore canvas.src = "not-found"; - const resolutions = ["0.1", "0.25", "0.5", "0.75", "1"]; - const sprite = new AtlasSprite({ - spriteName: "not-found", - }); - - for (let i = 0; i < resolutions.length; ++i) { - const res = resolutions[i]; - const link = new SpriteAtlasLink({ + const sprite = new AtlasSprite("not-found"); + ["0.1", "0.25", "0.5", "0.75", "1"].forEach(resolution => { + sprite.linksByResolution[resolution] = new SpriteAtlasLink({ packedX: 0, packedY: 0, w: dims, @@ -234,8 +221,8 @@ class LoaderImpl { packedH: dims, atlas: canvas, }); - sprite.linksByResolution[res] = link; - } + }); + this.spriteNotFoundSprite = sprite; } } diff --git a/src/js/core/rectangle.js b/src/js/core/rectangle.js index 75279e58..6b4315aa 100644 --- a/src/js/core/rectangle.js +++ b/src/js/core/rectangle.js @@ -1,5 +1,5 @@ import { globalConfig } from "./config"; -import { clamp, epsilonCompare, round2Digits } from "./utils"; +import { epsilonCompare, round2Digits } from "./utils"; import { Vector } from "./vector"; export class Rectangle { diff --git a/src/js/core/sprites.js b/src/js/core/sprites.js index 8427f2ef..6cf495cf 100644 --- a/src/js/core/sprites.js +++ b/src/js/core/sprites.js @@ -1,6 +1,6 @@ import { DrawParameters } from "./draw_parameters"; import { Rectangle } from "./rectangle"; -import { epsilonCompare, round3Digits } from "./utils"; +import { round3Digits } from "./utils"; const floorSpriteCoordinates = false; @@ -63,10 +63,9 @@ export class SpriteAtlasLink { export class AtlasSprite extends BaseSprite { /** * - * @param {object} param0 - * @param {string} param0.spriteName + * @param {string} spriteName */ - constructor({ spriteName = "sprite" }) { + constructor(spriteName = "sprite") { super(); /** @type {Object.} */ this.linksByResolution = {}; @@ -197,8 +196,6 @@ export class AtlasSprite extends BaseSprite { destH = intersection.h; } - // assert(epsilonCompare(scaleW, scaleH), "Sprite should be square for cached rendering"); - if (floorSpriteCoordinates) { parameters.context.drawImage( link.atlas, diff --git a/src/js/core/utils.js b/src/js/core/utils.js index cb00e465..0aa97992 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -1,46 +1,7 @@ -import { globalConfig, IS_DEBUG } from "./config"; -import { Vector } from "./vector"; import { T } from "../translations"; -// Constants -export const TOP = new Vector(0, -1); -export const RIGHT = new Vector(1, 0); -export const BOTTOM = new Vector(0, 1); -export const LEFT = new Vector(-1, 0); -export const ALL_DIRECTIONS = [TOP, RIGHT, BOTTOM, LEFT]; - const bigNumberSuffixTranslationKeys = ["thousands", "millions", "billions", "trillions"]; -/** - * Returns the build id - * @returns {string} - */ -export function getBuildId() { - if (G_IS_DEV && IS_DEBUG) { - return "local-dev"; - } else if (G_IS_DEV) { - return "dev-" + getPlatformName() + "-" + G_BUILD_COMMIT_HASH; - } else { - return "prod-" + getPlatformName() + "-" + G_BUILD_COMMIT_HASH; - } -} - -/** - * Returns the environment id (dev, prod, etc) - * @returns {string} - */ -export function getEnvironmentId() { - if (G_IS_DEV && IS_DEBUG) { - return "local-dev"; - } else if (G_IS_DEV) { - return "dev-" + getPlatformName(); - } else if (G_IS_RELEASE) { - return "release-" + getPlatformName(); - } else { - return "staging-" + getPlatformName(); - } -} - /** * Returns if this platform is android * @returns {boolean} @@ -66,7 +27,7 @@ export function isIos() { /** * Returns a platform name - * @returns {string} + * @returns {"android" | "browser" | "ios" | "standalone" | "unknown"} */ export function getPlatformName() { if (G_IS_STANDALONE) { @@ -96,60 +57,13 @@ export function getIPCRenderer() { return ipcRenderer; } -/** - * Formats a sensitive token by only displaying the first digits of it. Use for - * stuff like savegame keys etc which should not appear in the log. - * @param {string} key - */ -export function formatSensitive(key) { - if (!key) { - return ""; - } - key = key || ""; - return "[" + key.substr(0, 8) + "...]"; -} - -/** - * Creates a new 2D array with the given fill method - * @param {number} w Width - * @param {number} h Height - * @param {(function(number, number) : any) | number | boolean | string | null | undefined} filler Either Fill method, which should return the content for each cell, or static content - * @param {string=} context Optional context for memory tracking - * @returns {Array>} - */ -export function make2DArray(w, h, filler, context = null) { - if (typeof filler === "function") { - const tiles = new Array(w); - for (let x = 0; x < w; ++x) { - const row = new Array(h); - for (let y = 0; y < h; ++y) { - row[y] = filler(x, y); - } - tiles[x] = row; - } - return tiles; - } else { - const tiles = new Array(w); - const row = new Array(h); - for (let y = 0; y < h; ++y) { - row[y] = filler; - } - - for (let x = 0; x < w; ++x) { - tiles[x] = row.slice(); - } - return tiles; - } -} - /** * Makes a new 2D array with undefined contents * @param {number} w * @param {number} h - * @param {string=} context * @returns {Array>} */ -export function make2DUndefinedArray(w, h, context = null) { +export function make2DUndefinedArray(w, h) { const result = new Array(w); for (let x = 0; x < w; ++x) { result[x] = new Array(h); @@ -157,33 +71,6 @@ export function make2DUndefinedArray(w, h, context = null) { return result; } -/** - * Clears a given 2D array with the given fill method - * @param {Array>} array - * @param {number} w Width - * @param {number} h Height - * @param {(function(number, number) : any) | number | boolean | string | null | undefined} filler Either Fill method, which should return the content for each cell, or static content - */ -export function clear2DArray(array, w, h, filler) { - assert(array.length === w, "Array dims mismatch w"); - assert(array[0].length === h, "Array dims mismatch h"); - if (typeof filler === "function") { - for (let x = 0; x < w; ++x) { - const row = array[x]; - for (let y = 0; y < h; ++y) { - row[y] = filler(x, y); - } - } - } else { - for (let x = 0; x < w; ++x) { - const row = array[x]; - for (let y = 0; y < h; ++y) { - row[y] = filler; - } - } - } -} - /** * Creates a new map (an empty object without any props) */ @@ -215,7 +102,9 @@ export function accessNestedPropertyReverse(obj, keys) { /** * Chooses a random entry of an array - * @param {Array | string} arr + * @template T + * @param {T[]} arr + * @returns {T} */ export function randomChoice(arr) { return arr[Math.floor(Math.random() * arr.length)]; @@ -304,23 +193,6 @@ export function arrayDeleteValue(array, value) { return arrayDelete(array, index); } -// Converts a direction into a 0 .. 7 index -/** - * Converts a direction into a index from 0 .. 7, used for miners, zombies etc which have 8 sprites - * @param {Vector} offset direction - * @param {boolean} inverse if inverse, the direction is reversed - * @returns {number} in range [0, 7] - */ -export function angleToSpriteIndex(offset, inverse = false) { - const twoPi = 2.0 * Math.PI; - const factor = inverse ? -1 : 1; - const offs = inverse ? 2.5 : 3.5; - const angle = (factor * Math.atan2(offset.y, offset.x) + offs * Math.PI) % twoPi; - - const index = Math.round((angle / twoPi) * 8) % 8; - return index; -} - /** * Compare two floats for epsilon equality * @param {number} a @@ -331,15 +203,6 @@ export function epsilonCompare(a, b, epsilon = 1e-5) { return Math.abs(a - b) < epsilon; } -/** - * Compare a float for epsilon equal to 0 - * @param {number} a - * @returns {boolean} - */ -export function epsilonIsZero(a) { - return epsilonCompare(a, 0); -} - /** * Interpolates two numbers * @param {number} a @@ -399,17 +262,6 @@ export function findNiceIntegerValue(num) { return Math.ceil(findNiceValue(num)); } -/** - * Smart rounding + fractional handling - * @param {number} n - */ -function roundSmart(n) { - if (n < 100) { - return n.toFixed(1); - } - return Math.round(n); -} - /** * Formats a big number * @param {number} num @@ -477,92 +329,12 @@ export function formatBigNumberFull(num, divider = T.global.thousandsDivider) { return out.substring(0, out.length - 1); } -/** - * Delayes a promise so that it will resolve after a *minimum* amount of time only - * @param {Promise} promise The promise to delay - * @param {number} minTimeMs The time to make it run at least - * @returns {Promise} The delayed promise - */ -export function artificialDelayedPromise(promise, minTimeMs = 500) { - if (G_IS_DEV && globalConfig.debug.noArtificialDelays) { - return promise; - } - - const startTime = performance.now(); - return promise.then( - result => { - const timeTaken = performance.now() - startTime; - const waitTime = Math.floor(minTimeMs - timeTaken); - if (waitTime > 0) { - return new Promise(resolve => { - setTimeout(() => { - resolve(result); - }, waitTime); - }); - } else { - return result; - } - }, - error => { - const timeTaken = performance.now() - startTime; - const waitTime = Math.floor(minTimeMs - timeTaken); - if (waitTime > 0) { - // @ts-ignore - return new Promise((resolve, reject) => { - setTimeout(() => { - reject(error); - }, waitTime); - }); - } else { - throw error; - } - } - ); -} - -/** - * Computes a sine-based animation which pulsates from 0 .. 1 .. 0 - * @param {number} time Current time in seconds - * @param {number} duration Duration of the full pulse in seconds - * @param {number} seed Seed to offset the animation - */ -export function pulseAnimation(time, duration = 1.0, seed = 0.0) { - return Math.sin((time * Math.PI * 2.0) / duration + seed * 5642.86729349) * 0.5 + 0.5; -} - -/** - * Returns the smallest angle between two angles - * @param {number} a - * @param {number} b - * @returns {number} 0 .. 2 PI - */ -export function smallestAngle(a, b) { - return safeMod(a - b + Math.PI, 2.0 * Math.PI) - Math.PI; -} - -/** - * Modulo which works for negative numbers - * @param {number} n - * @param {number} m - */ -export function safeMod(n, m) { - return ((n % m) + m) % m; -} - -/** - * Wraps an angle between 0 and 2 pi - * @param {number} angle - */ -export function wrapAngle(angle) { - return safeMod(angle, 2.0 * Math.PI); -} - /** * Waits two frames so the ui is updated * @returns {Promise} */ export function waitNextFrame() { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { window.requestAnimationFrame(function () { window.requestAnimationFrame(function () { resolve(); @@ -617,27 +389,13 @@ export function clamp(v, minimum = 0, maximum = 1) { return Math.max(minimum, Math.min(maximum, v)); } -/** - * Measures how long a function took - * @param {string} name - * @param {function():void} target - */ -export function measure(name, target) { - const now = performance.now(); - for (let i = 0; i < 25; ++i) { - target(); - } - const dur = (performance.now() - now) / 25.0; - console.warn("->", name, "took", dur.toFixed(2), "ms"); -} - /** * Helper method to create a new div element * @param {string=} id * @param {Array=} classes * @param {string=} innerHTML */ -export function makeDivElement(id = null, classes = [], innerHTML = "") { +function makeDivElement(id = null, classes = [], innerHTML = "") { const div = document.createElement("div"); if (id) { div.id = id; @@ -662,20 +420,6 @@ export function makeDiv(parent, id = null, classes = [], innerHTML = "") { return div; } -/** - * Helper method to create a new div and place before reference Node - * @param {Element} parent - * @param {Element} referenceNode - * @param {string=} id - * @param {Array=} classes - * @param {string=} innerHTML - */ -export function makeDivBefore(parent, referenceNode, id = null, classes = [], innerHTML = "") { - const div = makeDivElement(id, classes, innerHTML); - parent.insertBefore(div, referenceNode); - return div; -} - /** * Helper method to create a new button element * @param {Array=} classes @@ -703,19 +447,6 @@ export function makeButton(parent, classes = [], innerHTML = "") { return element; } -/** - * Helper method to create a new button and place before reference Node - * @param {Element} parent - * @param {Element} referenceNode - * @param {Array=} classes - * @param {string=} innerHTML - */ -export function makeButtonBefore(parent, referenceNode, classes = [], innerHTML = "") { - const element = makeButtonElement(classes, innerHTML); - parent.insertBefore(element, referenceNode); - return element; -} - /** * Removes all children of the given element * @param {Element} elem @@ -728,20 +459,10 @@ export function removeAllChildren(elem) { } } -export function smartFadeNumber(current, newOne, minFade = 0.01, maxFade = 0.9) { - const tolerance = Math.min(current, newOne) * 0.5 + 10; - let fade = minFade; - if (Math.abs(current - newOne) < tolerance) { - fade = maxFade; - } - - return current * fade + newOne * (1 - fade); -} - /** * Fixes lockstep simulation by converting times like 34.0000000003 to 34.00. - * We use 3 digits of precision, this allows to store sufficient precision of 1 ms without - * the risk to simulation errors due to resync issues + * We use 3 digits of precision, this allows us to store precision of 1 ms without + * the risking simulation errors due to resync issues * @param {number} value */ export function quantizeFloat(value) { @@ -840,37 +561,6 @@ export function isSupportedBrowser() { } } -/** - * Helper function to create a json schema object - * @param {any} properties - */ -export function schemaObject(properties) { - return { - type: "object", - required: Object.keys(properties).slice(), - additionalProperties: false, - properties, - }; -} - -/** - * Quickly - * @param {number} x - * @param {number} y - * @param {number} deg - * @returns {Vector} - */ -export function fastRotateMultipleOf90(x, y, deg) { - switch (deg) { - case 0: { - return new Vector(x, y); - } - case 90: { - return new Vector(x, y); - } - } -} - /** * Formats an amount of seconds into something like "5s ago" * @param {number} secs Seconds @@ -928,31 +618,6 @@ export function formatSeconds(secs) { } } -/** - * Generates a file download - * @param {string} filename - * @param {string} text - */ -export function generateFileDownload(filename, text) { - var element = document.createElement("a"); - element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text)); - element.setAttribute("download", filename); - - element.style.display = "none"; - document.body.appendChild(element); - - element.click(); - document.body.removeChild(element); -} - -/** - * Capitalizes the first letter - * @param {string} str - */ -export function capitalizeFirstLetter(str) { - return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase(); -} - /** * Formats a number like 2.5 to "2.5 items / s" * @param {number} speed diff --git a/src/js/game/buildings/miner.js b/src/js/game/buildings/miner.js index ed87bc85..17fccf46 100644 --- a/src/js/game/buildings/miner.js +++ b/src/js/game/buildings/miner.js @@ -6,7 +6,7 @@ import { MetaBuilding, defaultBuildingVariant } from "../meta_building"; import { GameRoot } from "../root"; import { enumHubGoalRewards } from "../tutorial_goals"; import { T } from "../../translations"; -import { round1Digit, round2Digits, formatItemsPerSecond } from "../../core/utils"; +import { formatItemsPerSecond } from "../../core/utils"; /** @enum {string} */ export const enumMinerVariants = { chainable: "chainable" }; diff --git a/src/js/game/component.js b/src/js/game/component.js index 1d44d60f..7d30faff 100644 --- a/src/js/game/component.js +++ b/src/js/game/component.js @@ -44,3 +44,9 @@ export class Component extends BasicSerializableObject { } /* dev:end */ } + +/** + * TypeScript does not support Abstract Static methods (https://github.com/microsoft/TypeScript/issues/34516) + * One workaround is to declare the type of the component and reference that for static methods + * @typedef {typeof Component} StaticComponent + */ diff --git a/src/js/game/dynamic_tickrate.js b/src/js/game/dynamic_tickrate.js index f289e2c1..a5033acf 100644 --- a/src/js/game/dynamic_tickrate.js +++ b/src/js/game/dynamic_tickrate.js @@ -1,7 +1,6 @@ import { GameRoot } from "./root"; import { createLogger } from "../core/logging"; import { globalConfig } from "../core/config"; -import { round3Digits } from "../core/utils"; const logger = createLogger("dynamic_tickrate"); diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 36f8f107..cd319059 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -1,5 +1,4 @@ import { globalConfig } from "../core/config"; -import { queryParamOptions } from "../core/query_parameters"; import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils"; import { BasicSerializableObject, types } from "../savegame/serialization"; import { enumColors } from "./colors"; @@ -7,7 +6,7 @@ import { enumItemProcessorTypes } from "./components/item_processor"; import { GameRoot, enumLayer } from "./root"; import { enumSubShape, ShapeDefinition } from "./shape_definition"; import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals"; -import { UPGRADES, blueprintShape } from "./upgrades"; +import { UPGRADES } from "./upgrades"; export class HubGoals extends BasicSerializableObject { static getId() { @@ -328,9 +327,7 @@ export class HubGoals extends BasicSerializableObject { /** @type {Array} */ let layers = []; - // @ts-ignore const randomColor = () => randomChoice(Object.values(enumColors)); - // @ts-ignore const randomShape = () => randomChoice(Object.values(enumSubShape)); let anyIsMissingTwo = false; diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index d5770d0a..ce6e116b 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -1,7 +1,7 @@ import { ClickDetector } from "../../../core/click_detector"; -import { globalConfig, THIRDPARTY_URLS } from "../../../core/config"; +import { globalConfig } from "../../../core/config"; import { DrawParameters } from "../../../core/draw_parameters"; -import { drawRotatedSprite, rotateTrapezRightFaced } from "../../../core/draw_utils"; +import { drawRotatedSprite } from "../../../core/draw_utils"; import { Loader } from "../../../core/loader"; import { clamp, makeDiv, removeAllChildren } from "../../../core/utils"; import { @@ -323,7 +323,6 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic { // Fade in / out parameters.context.lineWidth = 1; - // parameters.context.globalAlpha = 0.3 + pulseAnimation(this.root.time.realtimeNow(), 0.9) * 0.7; // Determine the bounds and visualize them const entityBounds = staticComp.getTileSpaceBounds(); diff --git a/src/js/game/hud/parts/game_menu.js b/src/js/game/hud/parts/game_menu.js index 64285624..2f820f7a 100644 --- a/src/js/game/hud/parts/game_menu.js +++ b/src/js/game/hud/parts/game_menu.js @@ -1,10 +1,9 @@ import { BaseHUDPart } from "../base_hud_part"; -import { makeDiv, randomInt } from "../../../core/utils"; +import { makeDiv } from "../../../core/utils"; import { SOUNDS } from "../../../platform/sound"; import { enumNotificationType } from "./notifications"; import { T } from "../../../translations"; import { KEYMAPPINGS } from "../../key_action_mapper"; -import { IS_DEMO } from "../../../core/config"; import { DynamicDomAttach } from "../dynamic_dom_attach"; export class HUDGameMenu extends BaseHUDPart { diff --git a/src/js/game/hud/parts/pinned_shapes.js b/src/js/game/hud/parts/pinned_shapes.js index bda49f1e..2f7dd11e 100644 --- a/src/js/game/hud/parts/pinned_shapes.js +++ b/src/js/game/hud/parts/pinned_shapes.js @@ -1,5 +1,5 @@ import { ClickDetector } from "../../../core/click_detector"; -import { formatBigNumber, makeDiv, arrayDelete, arrayDeleteValue } from "../../../core/utils"; +import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils"; import { ShapeDefinition } from "../../shape_definition"; import { BaseHUDPart } from "../base_hud_part"; import { blueprintShape, UPGRADES } from "../../upgrades"; diff --git a/src/js/game/hud/parts/settings_menu.js b/src/js/game/hud/parts/settings_menu.js index 6034ab9d..391fde01 100644 --- a/src/js/game/hud/parts/settings_menu.js +++ b/src/js/game/hud/parts/settings_menu.js @@ -1,13 +1,11 @@ import { BaseHUDPart } from "../base_hud_part"; -import { makeDiv, formatSeconds, formatBigNumberFull } from "../../../core/utils"; +import { makeDiv, formatBigNumberFull } from "../../../core/utils"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { InputReceiver } from "../../../core/input_receiver"; import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; import { T } from "../../../translations"; import { StaticMapEntityComponent } from "../../components/static_map_entity"; -import { ItemProcessorComponent } from "../../components/item_processor"; import { BeltComponent } from "../../components/belt"; -import { IS_DEMO } from "../../../core/config"; export class HUDSettingsMenu extends BaseHUDPart { createElements(parent) { @@ -57,16 +55,7 @@ export class HUDSettingsMenu extends BaseHUDPart { } returnToMenu() { - // if (IS_DEMO) { - // const { cancel, deleteGame } = this.root.hud.parts.dialogs.showWarning( - // T.dialogs.leaveNotPossibleInDemo.title, - // T.dialogs.leaveNotPossibleInDemo.desc, - // ["cancel:good", "deleteGame:bad"] - // ); - // deleteGame.add(() => this.root.gameState.goBackToMenu()); - // } else { this.root.gameState.goBackToMenu(); - // } } goToSettings() { @@ -102,7 +91,6 @@ export class HUDSettingsMenu extends BaseHUDPart { show() { this.visible = true; document.body.classList.add("ingameDialogOpen"); - // this.background.classList.add("visible"); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60); diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js index e1a747a2..e15af4fb 100644 --- a/src/js/game/hud/parts/statistics.js +++ b/src/js/game/hud/parts/statistics.js @@ -1,5 +1,5 @@ import { InputReceiver } from "../../../core/input_receiver"; -import { makeButton, makeDiv, removeAllChildren, capitalizeFirstLetter } from "../../../core/utils"; +import { makeButton, makeDiv, removeAllChildren } from "../../../core/utils"; import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; import { enumAnalyticsDataSource } from "../../production_analytics"; import { BaseHUDPart } from "../base_hud_part"; @@ -7,6 +7,14 @@ import { DynamicDomAttach } from "../dynamic_dom_attach"; import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle"; import { T } from "../../../translations"; +/** + * Capitalizes the first letter + * @param {string} str + */ +function capitalizeFirstLetter(str) { + return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase(); +} + export class HUDStatistics extends BaseHUDPart { createElements(parent) { this.background = makeDiv(parent, "ingame_HUD_Statistics", ["ingameDialog"]); diff --git a/src/js/game/map.js b/src/js/game/map.js index 3f8a16f8..b0992627 100644 --- a/src/js/game/map.js +++ b/src/js/game/map.js @@ -5,7 +5,6 @@ import { Entity } from "./entity"; import { createLogger } from "../core/logging"; import { BaseItem } from "./base_item"; import { MapChunkView } from "./map_chunk_view"; -import { randomInt } from "../core/utils"; import { BasicSerializableObject, types } from "../savegame/serialization"; const logger = createLogger("map"); diff --git a/src/js/game/map_chunk.js b/src/js/game/map_chunk.js index dd9ba81d..84b9e47a 100644 --- a/src/js/game/map_chunk.js +++ b/src/js/game/map_chunk.js @@ -28,25 +28,13 @@ export class MapChunk { this.tileY = y * globalConfig.mapChunkSize; /** @type {Array>} */ - this.contents = make2DUndefinedArray( - globalConfig.mapChunkSize, - globalConfig.mapChunkSize, - "map-chunk@" + this.x + "|" + this.y - ); + this.contents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize); /** @type {Array>} */ - this.wireContents = make2DUndefinedArray( - globalConfig.mapChunkSize, - globalConfig.mapChunkSize, - "map-chunk-wires@" + this.x + "|" + this.y - ); + this.wireContents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize); /** @type {Array>} */ - this.lowerLayer = make2DUndefinedArray( - globalConfig.mapChunkSize, - globalConfig.mapChunkSize, - "map-chunk-lower@" + this.x + "|" + this.y - ); + this.lowerLayer = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize); /** @type {Array} */ this.containedEntities = []; diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js index c3f09b39..491939f4 100644 --- a/src/js/game/map_chunk_view.js +++ b/src/js/game/map_chunk_view.js @@ -1,15 +1,6 @@ import { MapChunk } from "./map_chunk"; import { GameRoot } from "./root"; -import { globalConfig } from "../core/config"; import { DrawParameters } from "../core/draw_parameters"; -import { round1Digit } from "../core/utils"; -import { Rectangle } from "../core/rectangle"; -import { createLogger } from "../core/logging"; -import { smoothenDpi } from "../core/dpi_manager"; -import { THEME } from "./theme"; - -const logger = createLogger("chunk"); -const chunkSizePixels = globalConfig.mapChunkSize * globalConfig.tileSize; export class MapChunkView extends MapChunk { /** diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts index cc6118ca..3f842e7e 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -131,22 +131,24 @@ declare interface Math { degrees(number): number; } +declare type Class = new (...args: any[]) => T; + declare interface String { padStart(size: number, fill?: string): string; padEnd(size: number, fill: string): string; } declare interface FactoryTemplate { - entries: Array T>; + entries: Array>; entryIds: Array; idToEntry: any; getId(): string; getAllIds(): Array; - register(entry: new (...args: any[]) => T): void; + register(entry: Class): void; hasId(id: string): boolean; - findById(id: string): new (...args: any[]) => T; - getEntries(): Array T>; + findById(id: string): Class; + getEntries(): Array>; getNumEntries(): number; } @@ -156,10 +158,10 @@ declare interface SingletonFactoryTemplate { getId(): string; getAllIds(): Array; - register(classHandle: new (...args: any[]) => T): void; + register(classHandle: Class): void; hasId(id: string): boolean; findById(id: string): T; - findByClass(classHandle: new (...args: any[]) => T): T; + findByClass(classHandle: Class): T; getEntries(): Array; getNumEntries(): number; } diff --git a/src/js/platform/game_analytics.js b/src/js/platform/game_analytics.js index ea6aa293..765b2d67 100644 --- a/src/js/platform/game_analytics.js +++ b/src/js/platform/game_analytics.js @@ -1,8 +1,6 @@ -/* typehints:start */ -import { Application } from "../application"; -import { ShapeDefinition } from "../game/shape_definition"; -import { Savegame } from "../savegame/savegame"; -/* typehints:end */ +/** + * @typedef {import("../application").Application} Application + */ export class GameAnalyticsInterface { constructor(app) { diff --git a/src/js/savegame/savegame.js b/src/js/savegame/savegame.js index 1db813d7..2a7102a9 100644 --- a/src/js/savegame/savegame.js +++ b/src/js/savegame/savegame.js @@ -1,8 +1,3 @@ -/* typehints:start */ -import { Application } from "../application"; -import { GameRoot } from "../game/root"; -/* typehints:end */ - import { ReadWriteProxy } from "../core/read_write_proxy"; import { ExplainedResult } from "../core/explained_result"; import { SavegameSerializer } from "./savegame_serializer"; @@ -18,20 +13,29 @@ import { SavegameInterface_V1005 } from "./schemas/1005"; const logger = createLogger("savegame"); +/** + * @typedef {import("../application").Application} Application + * @typedef {import("../game/root").GameRoot} GameRoot + * @typedef {import("./savegame_typedefs").SavegameData} SavegameData + * @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata + * @typedef {import("./savegame_typedefs").SavegameStats} SavegameStats + * @typedef {import("./savegame_typedefs").SerializedGame} SerializedGame + */ + export class Savegame extends ReadWriteProxy { /** * * @param {Application} app * @param {object} param0 * @param {string} param0.internalId - * @param {import("./savegame_manager").SavegameMetadata} param0.metaDataRef Handle to the meta data + * @param {SavegameMetadata} param0.metaDataRef Handle to the meta data */ constructor(app, { internalId, metaDataRef }) { super(app, "savegame-" + internalId + ".bin"); this.internalId = internalId; this.metaDataRef = metaDataRef; - /** @type {import("./savegame_typedefs").SavegameData} */ + /** @type {SavegameData} */ this.currentData = this.getDefaultData(); assert( @@ -65,7 +69,7 @@ export class Savegame extends ReadWriteProxy { /** * Returns the savegames default data - * @returns {import("./savegame_typedefs").SavegameData} + * @returns {SavegameData} */ getDefaultData() { return { @@ -78,7 +82,7 @@ export class Savegame extends ReadWriteProxy { /** * Migrates the savegames data - * @param {import("./savegame_typedefs").SavegameData} data + * @param {SavegameData} data */ migrate(data) { if (data.version < 1000) { @@ -115,7 +119,7 @@ export class Savegame extends ReadWriteProxy { /** * Verifies the savegames data - * @param {import("./savegame_typedefs").SavegameData} data + * @param {SavegameData} data */ verify(data) { if (!data.dump) { @@ -140,7 +144,7 @@ export class Savegame extends ReadWriteProxy { } /** * Returns the statistics of the savegame - * @returns {import("./savegame_typedefs").SavegameStats} + * @returns {SavegameStats} */ getStatistics() { return this.currentData.stats; @@ -163,7 +167,7 @@ export class Savegame extends ReadWriteProxy { /** * Returns the current game dump - * @returns {import("./savegame_typedefs").SerializedGame} + * @returns {SerializedGame} */ getCurrentDump() { return this.currentData.dump; diff --git a/src/js/savegame/savegame_manager.js b/src/js/savegame/savegame_manager.js index e3052806..42e56734 100644 --- a/src/js/savegame/savegame_manager.js +++ b/src/js/savegame/savegame_manager.js @@ -7,31 +7,21 @@ const logger = createLogger("savegame_manager"); const Rusha = require("rusha"); +/** + * @typedef {import("./savegame_typedefs").SavegamesData} SavegamesData + * @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata + */ + /** @enum {string} */ export const enumLocalSavegameStatus = { offline: "offline", synced: "synced", }; -/** - * @typedef {{ - * lastUpdate: number, - * version: number, - * internalId: string, - * level: number - * }} SavegameMetadata - * - * @typedef {{ - * version: number, - * savegames: Array - * }} SavegamesData - */ - export class SavegameManager extends ReadWriteProxy { constructor(app) { super(app, "savegames.bin"); - /** @type {SavegamesData} */ this.currentData = this.getDefaultData(); } diff --git a/src/js/savegame/savegame_serializer.js b/src/js/savegame/savegame_serializer.js index eff802a0..92db738b 100644 --- a/src/js/savegame/savegame_serializer.js +++ b/src/js/savegame/savegame_serializer.js @@ -1,18 +1,20 @@ -/* typehints:start */ -import { Component } from "../game/component"; -import { GameRoot } from "../game/root"; -/* typehints:end */ - import { ExplainedResult } from "../core/explained_result"; import { createLogger } from "../core/logging"; -// import { BuildingComponent } from "../components/impl/building"; import { gComponentRegistry } from "../core/global_registries"; import { SerializerInternal } from "./serializer_internal"; +/** + * @typedef {import("../game/component").Component} Component + * @typedef {import("../game/component").StaticComponent} StaticComponent + * @typedef {import("../game/entity").Entity} Entity + * @typedef {import("../game/root").GameRoot} GameRoot + * @typedef {import("../savegame/savegame_typedefs").SerializedGame} SerializedGame + */ + const logger = createLogger("savegame_serializer"); /** - * Allows to serialize a savegame + * Serializes a savegame */ export class SavegameSerializer { constructor() { @@ -26,7 +28,7 @@ export class SavegameSerializer { * @returns {object} */ generateDumpFromGameRoot(root, sanityChecks = true) { - // Now store generic savegame payload + /** @type {SerializedGame} */ const data = { camera: root.camera.serialize(), time: root.time.serialize(), @@ -35,11 +37,10 @@ export class SavegameSerializer { hubGoals: root.hubGoals.serialize(), pinnedShapes: root.hud.parts.pinnedShapes.serialize(), waypoints: root.hud.parts.waypoints.serialize(), + entities: this.internal.serializeEntityArray(root.entityMgr.entities), beltPaths: root.systemMgr.systems.belt.serializePaths(), }; - data.entities = this.internal.serializeEntityArray(root.entityMgr.entities); - if (!G_IS_RELEASE) { if (sanityChecks) { // Sanity check @@ -55,7 +56,7 @@ export class SavegameSerializer { /** * Verifies if there are logical errors in the savegame - * @param {object} savegame + * @param {SerializedGame} savegame * @returns {ExplainedResult} */ verifyLogicalErrors(savegame) { @@ -66,47 +67,44 @@ export class SavegameSerializer { const seenUids = []; // Check for duplicate UIDS - for (const entityListId in savegame.entities) { - for (let i = 0; i < savegame.entities[entityListId].length; ++i) { - const list = savegame.entities[entityListId][i]; - for (let k = 0; k < list.length; ++k) { - const entity = list[k]; - const uid = entity.uid; - if (!Number.isInteger(uid)) { - return ExplainedResult.bad("Entity has invalid uid: " + uid); - } - if (seenUids.indexOf(uid) >= 0) { - return ExplainedResult.bad("Duplicate uid " + uid); - } - seenUids.push(uid); + for (let i = 0; i < savegame.entities.length; ++i) { + /** @type {Entity} */ + const entity = savegame.entities[i]; - // Verify components - if (!entity.components) { - return ExplainedResult.bad( - "Entity is missing key 'components': " + JSON.stringify(entity) - ); - } - const components = entity.components; - for (const componentId in components) { - // Verify component data - const componentData = components[componentId]; - const componentClass = gComponentRegistry.findById(componentId); + const uid = entity.uid; + if (!Number.isInteger(uid)) { + return ExplainedResult.bad("Entity has invalid uid: " + uid); + } + if (seenUids.indexOf(uid) >= 0) { + return ExplainedResult.bad("Duplicate uid " + uid); + } + seenUids.push(uid); - // Check component id is known - if (!componentClass) { - return ExplainedResult.bad("Unknown component id: " + componentId); - } + // Verify components + if (!entity.components) { + return ExplainedResult.bad("Entity is missing key 'components': " + JSON.stringify(entity)); + } - // Check component data is ok - const componentVerifyError = /** @type {typeof Component} */ (componentClass).verify( - componentData - ); - if (componentVerifyError) { - return ExplainedResult.bad( - "Component " + componentId + " has invalid data: " + componentVerifyError - ); - } - } + const components = entity.components; + for (const componentId in components) { + const componentClass = gComponentRegistry.findById(componentId); + + // Check component id is known + if (!componentClass) { + return ExplainedResult.bad("Unknown component id: " + componentId); + } + + // Verify component data + const componentData = components[componentId]; + const componentVerifyError = /** @type {StaticComponent} */ (componentClass).verify( + componentData + ); + + // Check component data is ok + if (componentVerifyError) { + return ExplainedResult.bad( + "Component " + componentId + " has invalid data: " + componentVerifyError + ); } } } @@ -116,7 +114,7 @@ export class SavegameSerializer { /** * Tries to load the savegame from a given dump - * @param {import("./savegame_typedefs").SerializedGame} savegame + * @param {SerializedGame} savegame * @param {GameRoot} root * @returns {ExplainedResult} */ diff --git a/src/js/savegame/savegame_typedefs.js b/src/js/savegame/savegame_typedefs.js index 642865cd..f5bb08c2 100644 --- a/src/js/savegame/savegame_typedefs.js +++ b/src/js/savegame/savegame_typedefs.js @@ -1,15 +1,8 @@ -import { Entity } from "../game/entity"; - -/** - * @typedef {{ - * }} SavegameStats - */ - /** + * @typedef {import("../game/entity").Entity} Entity + * + * @typedef {{}} SavegameStats * - */ - -/** * @typedef {{ * camera: any, * time: any, @@ -21,13 +14,25 @@ import { Entity } from "../game/entity"; * entities: Array, * beltPaths: Array * }} SerializedGame - */ - -/** + * * @typedef {{ * version: number, * dump: SerializedGame, * stats: SavegameStats, * lastUpdate: number, * }} SavegameData + * + * @typedef {{ + * lastUpdate: number, + * version: number, + * internalId: string, + * level: number + * }} SavegameMetadata + * + * @typedef {{ + * version: number, + * savegames: Array + * }} SavegamesData */ + +export default {}; diff --git a/src/js/savegame/serialization_data_types.js b/src/js/savegame/serialization_data_types.js index 86b177c1..0f9b4542 100644 --- a/src/js/savegame/serialization_data_types.js +++ b/src/js/savegame/serialization_data_types.js @@ -4,7 +4,7 @@ import { BasicSerializableObject } from "./serialization"; /* typehints:end */ import { Vector } from "../core/vector"; -import { round4Digits, schemaObject, accessNestedPropertyReverse } from "../core/utils"; +import { round4Digits } from "../core/utils"; export const globalJsonSchemaDefs = {}; /** @@ -28,6 +28,19 @@ export function schemaToJsonSchema(schema) { return jsonSchema; } +/** + * Helper function to create a json schema object + * @param {any} properties + */ +function schemaObject(properties) { + return { + type: "object", + required: Object.keys(properties).slice(), + additionalProperties: false, + properties, + }; +} + /** * Base serialization data type */ @@ -75,23 +88,6 @@ export class BaseDataType { return { $ref: "#/definitions/" + key, }; - - // return this.getAsJsonSchemaUncached(); - // if (!globalJsonSchemaDefs[key]) { - // // schema.$id = key; - // globalJsonSchemaDefs[key] = { - // $id: key, - // definitions: { - // ["d-" + key]: schema - // } - // }; - // } - - // return { - // $ref: key + "#/definitions/d-" + key - // } - - // // return this.getAsJsonSchemaUncached(); } /** diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 927f02f6..3d39e826 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -1,11 +1,10 @@ import { GameState } from "../core/game_state"; import { cachebust } from "../core/cachebust"; -import { globalConfig, IS_DEBUG, IS_DEMO, THIRDPARTY_URLS } from "../core/config"; +import { globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../core/config"; import { makeDiv, makeButtonElement, formatSecondsToTimeAgo, - generateFileDownload, waitNextFrame, isSupportedBrowser, makeButton, @@ -14,9 +13,29 @@ import { import { ReadWriteProxy } from "../core/read_write_proxy"; import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs"; import { T } from "../translations"; -import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper"; import { getApplicationSettingById } from "../profile/application_settings"; -import { EnumSetting } from "../profile/setting_types"; + +/** + * @typedef {import("../savegame/savegame_typedefs").SavegameMetadata} SavegameMetadata + * @typedef {import("../profile/setting_types").EnumSetting} EnumSetting + */ + +/** + * Generates a file download + * @param {string} filename + * @param {string} text + */ +function generateFileDownload(filename, text) { + var element = document.createElement("a"); + element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text)); + element.setAttribute("download", filename); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + document.body.removeChild(element); +} export class MainMenuState extends GameState { constructor() { @@ -128,7 +147,6 @@ export class MainMenuState extends GameState { const closeLoader = this.dialogs.showLoadingDialog(); const reader = new FileReader(); reader.addEventListener("load", event => { - // @ts-ignore const contents = event.target.result; let realContent; @@ -394,7 +412,7 @@ export class MainMenuState extends GameState { } /** - * @param {object} game + * @param {SavegameMetadata} game */ resumeGame(game) { this.app.analytics.trackUiClick("resume_game"); @@ -419,7 +437,7 @@ export class MainMenuState extends GameState { } /** - * @param {object} game + * @param {SavegameMetadata} game */ deleteGame(game) { this.app.analytics.trackUiClick("delete_game"); @@ -447,7 +465,7 @@ export class MainMenuState extends GameState { } /** - * @param {object} game + * @param {SavegameMetadata} game */ downloadGame(game) { this.app.analytics.trackUiClick("download_game"); diff --git a/src/js/states/preload.js b/src/js/states/preload.js index f1551893..0f47e8d6 100644 --- a/src/js/states/preload.js +++ b/src/js/states/preload.js @@ -1,6 +1,6 @@ import { GameState } from "../core/game_state"; import { createLogger } from "../core/logging"; -import { findNiceValue, waitNextFrame } from "../core/utils"; +import { findNiceValue } from "../core/utils"; import { cachebust } from "../core/cachebust"; import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper"; import { T, autoDetectLanguageId, updateApplicationLanguage } from "../translations"; @@ -228,11 +228,7 @@ export class PreloadState extends GameState { this.statusBar.style.width = percentage + "%"; this.statusBarText.innerText = findNiceValue(percentage) + "%"; - if (G_IS_DEV) { - return Promise.resolve(); - } return Promise.resolve(); - // return waitNextFrame(); } showFailMessage(text) { @@ -279,11 +275,6 @@ export class PreloadState extends GameState { if (confirm("Are you sure you want to reset the app? This will delete all your savegames")) { this.resetApp(); } - // const signals = this.dialogs.showWarning(T.preload.reset_app_warning.title, T.preload.reset_app_warning.desc, [ - // "delete:bad:timeout", - // "cancel:good", - // ]); - // signals.delete.add(this.resetApp, this); } resetApp() {