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