Remove unused code (#526)

pull/544/head
Bjorn Stromberg 4 years ago committed by GitHub
parent be79766cdd
commit 79158e2b56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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.<string, SpriteDefinition>,
* meta: AtlasMeta
* }} SourceData
*/
export class AtlasDefinition {
constructor(sourceData) {
this.sourceFileName = sourceData.meta.image;
this.meta = sourceData.meta;
/** @type {Object.<string, SpriteDefinition>} */
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)

@ -115,7 +115,6 @@ export class BackgroundResourcesLoader {
})
.then(() => {
logger.log("⏰ Finish load: bare game");
Loader.createAtlasLinks();
this.bareGameReady = true;
initBuildingCodesAfterResourcesLoaded();
this.signalBareGameLoaded.dispatch();

@ -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 }) {

@ -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;
}
// CohenSutherland 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);
}

@ -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<MetaBuilding>} */
export let gMetaBuildingRegistry = new SingletonFactory();
/** @type {Object.<string, Array<typeof MetaBuilding>>} */
/** @type {Object.<string, Array<Class<MetaBuilding>>>} */
export let gBuildingsByCategory = null;
/** @type {FactoryTemplate<Component>} */
@ -28,7 +28,7 @@ export let gItemRegistry = new Factory("item");
// Helpers
/**
* @param {Object.<string, Array<typeof MetaBuilding>>} buildings
* @param {Object.<string, Array<Class<MetaBuilding>>>} buildings
*/
export function initBuildingsByCategory(buildings) {
gBuildingsByCategory = buildings;

@ -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<string, BaseSprite>} */
@ -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;
}
}

@ -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 {

@ -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.<string, SpriteAtlasLink>} */
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,

@ -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 "<null>";
}
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<Array<any>>}
*/
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<Array<any>>}
*/
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<any>>} 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<any>} promise The promise to delay
* @param {number} minTimeMs The time to make it run at least
* @returns {Promise<any>} 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<void>}
*/
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<string>=} 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<string>=} 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<string>=} 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<string>=} 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

@ -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" };

@ -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
*/

@ -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");

@ -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<import("./shape_definition").ShapeLayer>} */
let layers = [];
// @ts-ignore
const randomColor = () => randomChoice(Object.values(enumColors));
// @ts-ignore
const randomShape = () => randomChoice(Object.values(enumSubShape));
let anyIsMissingTwo = false;

@ -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();

@ -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 {

@ -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";

@ -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);

@ -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"]);

@ -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");

@ -28,25 +28,13 @@ export class MapChunk {
this.tileY = y * globalConfig.mapChunkSize;
/** @type {Array<Array<?Entity>>} */
this.contents = make2DUndefinedArray(
globalConfig.mapChunkSize,
globalConfig.mapChunkSize,
"map-chunk@" + this.x + "|" + this.y
);
this.contents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
/** @type {Array<Array<?Entity>>} */
this.wireContents = make2DUndefinedArray(
globalConfig.mapChunkSize,
globalConfig.mapChunkSize,
"map-chunk-wires@" + this.x + "|" + this.y
);
this.wireContents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
/** @type {Array<Array<?BaseItem>>} */
this.lowerLayer = make2DUndefinedArray(
globalConfig.mapChunkSize,
globalConfig.mapChunkSize,
"map-chunk-lower@" + this.x + "|" + this.y
);
this.lowerLayer = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
/** @type {Array<Entity>} */
this.containedEntities = [];

@ -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 {
/**

@ -131,22 +131,24 @@ declare interface Math {
degrees(number): number;
}
declare type Class<T = unknown> = new (...args: any[]) => T;
declare interface String {
padStart(size: number, fill?: string): string;
padEnd(size: number, fill: string): string;
}
declare interface FactoryTemplate<T> {
entries: Array<new (...args: any[]) => T>;
entries: Array<Class<T>>;
entryIds: Array<string>;
idToEntry: any;
getId(): string;
getAllIds(): Array<string>;
register(entry: new (...args: any[]) => T): void;
register(entry: Class<T>): void;
hasId(id: string): boolean;
findById(id: string): new (...args: any[]) => T;
getEntries(): Array<new (...args: any[]) => T>;
findById(id: string): Class<T>;
getEntries(): Array<Class<T>>;
getNumEntries(): number;
}
@ -156,10 +158,10 @@ declare interface SingletonFactoryTemplate<T> {
getId(): string;
getAllIds(): Array<string>;
register(classHandle: new (...args: any[]) => T): void;
register(classHandle: Class<T>): void;
hasId(id: string): boolean;
findById(id: string): T;
findByClass(classHandle: new (...args: any[]) => T): T;
findByClass(classHandle: Class<T>): T;
getEntries(): Array<T>;
getNumEntries(): number;
}

@ -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) {

@ -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;

@ -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<SavegameMetadata>
* }} SavegamesData
*/
export class SavegameManager extends ReadWriteProxy {
constructor(app) {
super(app, "savegames.bin");
/** @type {SavegamesData} */
this.currentData = this.getDefaultData();
}

@ -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);
// 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);
// Check component id is known
if (!componentClass) {
return ExplainedResult.bad("Unknown component id: " + componentId);
}
// Check component data is ok
const componentVerifyError = /** @type {typeof Component} */ (componentClass).verify(
componentData
);
if (componentVerifyError) {
return ExplainedResult.bad(
"Component " + componentId + " has invalid data: " + componentVerifyError
);
}
}
for (let i = 0; i < savegame.entities.length; ++i) {
/** @type {Entity} */
const entity = savegame.entities[i];
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);
// 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) {
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}
*/

@ -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<Entity>,
* beltPaths: Array<any>
* }} 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<SavegameMetadata>
* }} SavegamesData
*/
export default {};

@ -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();
}
/**

@ -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");

@ -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() {

Loading…
Cancel
Save