1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Merge branch 'master' into master

This commit is contained in:
forsaken-hero 2020-08-10 21:15:15 +01:00 committed by GitHub
commit 5ac66145cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
172 changed files with 5077 additions and 6418 deletions

View File

@ -44,3 +44,10 @@ jobs:
uses: ibiqlik/action-yamllint@v1.0.0
with:
file_or_dir: translations/*.yaml
- name: TSLint
run: |
cd gulp
yarn gulp translations.fullBuild
cd ..
yarn tslint

View File

@ -50,7 +50,7 @@ If you want to add a new feature or in generally contribute I recommend to get i
### Code
The game is based on a custom engine which itself is based on the YORG.io 3 game egine (Actually it shares almost the same core).
The game is based on a custom engine which itself is based on the YORG.io 3 game engine (Actually it shares almost the same core).
The code within the engine is relatively clean with some code for the actual game on top being hacky.
This project is based on ES5. Some ES2015 features are used but most of them are too slow, especially when polyfilled. For example, `Array.prototype.forEach` is only used within non-critical loops since its slower than a plain for loop.

View File

@ -11,6 +11,7 @@ module.exports = {
);
return commitHash.replace(/^\s+|\s+$/g, "");
},
getAllResourceImages() {
return glob
.sync("res/**/*.@(png|svg|jpg)", { cwd: ".." })
@ -24,18 +25,6 @@ module.exports = {
});
},
getAllAtlasImages() {
return glob
.sync("res_built/atlas/*.png", { cwd: ".." })
.map(s => s.replace("res_built/atlas/", "res/"));
},
getAllSounds() {
return glob
.sync("res_built/sounds/**/*.mp3", { cwd: ".." })
.map(s => s.replace("res_built/sounds/", "res/sounds/"));
},
getVersion() {
return trim(fs.readFileSync(path.join(__dirname, "..", "version")).toString());
},

19
gulp/cordova.js vendored
View File

@ -132,25 +132,6 @@ function gulptasksCordova($, gulp, buildFolder) {
})
);
});
// gulp.task("pushToStagingRepo", (cb) => {
// var cmd = spawn('../push-pgb.sh', ['https://TOKEN@github.com/tobspr/shapezapp-cordova-buildslave.git'],
// { stdio: 'inherit', stdout: 'inherit', stderr: 'inherit', shell: true });
// cmd.on('close', function (code) {
// console.log('push staging exited with code ' + code + " / " + cmd.stdout + " / " + cmd.stderr);
// cb(code);
// });
// });
// gulp.task("pushToProdRepo", (cb) => {
// var cmd = spawn('../push-pgb.sh', ['https://TOKEN@github.com/tobspr/shapezapp-cordova-buildslave-release.git'],
// { stdio: 'inherit', stdout: 'inherit', stderr: 'inherit', shell: true });
// cmd.on('close', function (code) {
// console.log('push prod exited with code ' + code + " / " + cmd.stdout + " / " + cmd.stderr);
// cb(code);
// });
// });
}
module.exports = {

View File

@ -77,7 +77,6 @@ function gulptasksCSS($, gulp, buildFolder, browserSync) {
.pipe($.plumber())
.pipe($.sass.sync({ outputStyle: "compressed" }).on("error", $.sass.logError))
.pipe($.postcss(postcssPlugins(true, { cachebust: true })))
// .pipe($.cssbeautify())
.pipe(gulp.dest(buildFolder))
);
});
@ -90,7 +89,6 @@ function gulptasksCSS($, gulp, buildFolder, browserSync) {
.pipe($.plumber())
.pipe($.sass.sync({ outputStyle: "compressed" }).on("error", $.sass.logError))
.pipe($.postcss(postcssPlugins(true, { cachebust: false })))
// .pipe($.cssbeautify())
.pipe(gulp.dest(buildFolder))
);
});

View File

@ -9,7 +9,7 @@ function computeIntegrityHash(fullPath, algorithm = "sha256") {
return algorithm + "-" + hash;
}
function gulptasksHTML($, gulp, buildFolder, browserSync) {
function gulptasksHTML($, gulp, buildFolder) {
const commitHash = buildUtils.getRevision();
async function buildHtml(
apiUrl,
@ -27,9 +27,8 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
return gulp
.src("../src/html/" + (standalone ? "index.standalone.html" : "index.html"))
.pipe(
$.dom(function () {
// @ts-ignore
const document = /** @type {Document} */ (this);
$.dom(/** @this {Document} **/ function () {
const document = this;
// Preconnect to api
const prefetchLink = document.createElement("link");
@ -38,21 +37,6 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
prefetchLink.setAttribute("crossorigin", "anonymous");
document.head.appendChild(prefetchLink);
// // Append css preload
// const cssPreload = document.createElement("link");
// cssPreload.rel = "preload";
// cssPreload.href = cachebust("main.css");
// cssPreload.setAttribute("as", "style");
// document.head.appendChild(cssPreload);
// document.head.appendChild(prefetchLink);
// // Append js preload
// const jsPreload = document.createElement("link");
// jsPreload.rel = "preload";
// jsPreload.href = cachebust("bundle.js");
// jsPreload.setAttribute("as", "script");
// document.head.appendChild(jsPreload);
// Append css
const css = document.createElement("link");
css.rel = "stylesheet";
@ -68,17 +52,6 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
}
document.head.appendChild(css);
if (analytics) {
// Logrocket
// const logrocketScript = document.createElement("script");
// logrocketScript.src = "https://cdn.lr-ingest.io/LogRocket.min.js";
// logrocketScript.setAttribute("crossorigin", "anonymous");
// document.head.appendChild(logrocketScript);
// const logrocketInit = document.createElement("script");
// logrocketInit.textContent = "window.LogRocket && window.LogRocket.init('TODO: GET LOGROCKET ID');";
// document.head.appendChild(logrocketInit);
}
if (app) {
// Append cordova link
const cdv = document.createElement("script");
@ -114,18 +87,9 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
// Do not need to preload in app or standalone
if (!hasLocalFiles) {
// Preload images
const images = buildUtils.getAllResourceImages();
// Preload essentials
const preloads = ["fonts/GameFont.woff2"];
// for (let i = 0; i < images.length; ++i) {
// if (preloads.indexOf(images[i]) < 0) {
// preloads.push(images[i]);
// }
// }
preloads.forEach(src => {
const preloadLink = document.createElement("link");
preloadLink.rel = "preload";
@ -138,23 +102,6 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
}
document.head.appendChild(preloadLink);
});
// Sound preloads
// const sounds = buildUtils.getAllSounds();
// sounds.forEach((src) => {
// if (src.indexOf("sounds/music/") >= 0) {
// // skip music
// return;
// }
// const preloadLink = document.createElement("link");
// preloadLink.rel = "preload";
// preloadLink.href = cachebust(src);
// // preloadLink.setAttribute("crossorigin", "anonymous");
// preloadLink.setAttribute("as", "fetch");
// document.head.appendChild(preloadLink);
// });
}
const loadingSvg = `background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46YXV0bztiYWNrZ3JvdW5kOjAgMCIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgZGlzcGxheT0iYmxvY2siPjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5Mzc0NyIgc3Ryb2tlLXdpZHRoPSIzIiByPSI0MiIgc3Ryb2tlLWRhc2hhcnJheT0iMTk3LjkyMDMzNzE3NjE1Njk4IDY3Ljk3MzQ0NTcyNTM4NTY2IiB0cmFuc2Zvcm09InJvdGF0ZSg0OC4yNjUgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iNS41NTU1NTU1NTU1NTU1NTVzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIi8+PC9jaXJjbGU+PC9zdmc+")`;
@ -167,7 +114,7 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
font-display: swap;
src: url('${cachebust("res/fonts/GameFont.woff2")}') format('woff2');
}
#ll_fp {
font-family: GameFont;
font-size: 14px;
@ -177,7 +124,7 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
left: 0;
opacity: 0.05;
}
#ll_p {
display: flex;
position: fixed;
@ -190,7 +137,7 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
center;
align-items: center;
}
#ll_p > div {
position: absolute;
text-align: center;
@ -201,7 +148,7 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
font-family: 'GameFont', sans-serif;
font-size: 20px;
}
#ll_p > span {
width: 60px;
height: 60px;

View File

@ -6,12 +6,6 @@ function requireUncached(module) {
}
function gulptasksJS($, gulp, buildFolder, browserSync) {
gulp.task("js.prettify", () => {
return gulp
.src(path.join(buildFolder, "bundle.js"))
.pipe($.jsbeautifier(require("./jsbeautify.json")))
.pipe(gulp.dest(buildFolder));
});
//// DEV
@ -71,6 +65,7 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
gulp.task("js.staging", gulp.parallel("js.staging.transpiled", "js.staging.latest"));
//// PROD
gulp.task("js.prod.transpiled", () => {
return gulp
.src("../src/js/main.js")
@ -167,8 +162,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
)
.pipe(gulp.dest(buildFolder));
});
// TODO: Tasks for te app
}
module.exports = {

View File

@ -3,7 +3,6 @@
"version": "1.0.0",
"description": "builder",
"private": true,
"main": "main.js",
"scripts": {
"gulp": "gulp"
},
@ -41,21 +40,18 @@
"lz-string": "^1.4.4",
"markdown-loader": "^5.1.0",
"node-sri": "^1.1.1",
"obfuscator-loader": "^1.1.2",
"phonegap-plugin-mobile-accessibility": "^1.0.5",
"promise-polyfill": "^8.1.0",
"query-string": "^6.8.1",
"rusha": "^0.8.13",
"serialize-error": "^3.0.0",
"sloc": "^0.2.1",
"strictdom": "^1.0.1",
"string-replace-webpack-plugin": "^0.1.3",
"terser-webpack-plugin": "^1.1.0",
"through2": "^3.0.1",
"uglify-template-string-loader": "^1.1.0",
"unused-files-webpack-plugin": "^3.4.0",
"webpack": "^4.31.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.1.0",
"webpack-deep-scope-plugin": "^1.6.0",
"webpack-plugin-replace": "^1.1.1",
@ -77,8 +73,6 @@
"gulp-cache": "^1.1.3",
"gulp-cached": "^1.1.1",
"gulp-clean": "^0.4.0",
"gulp-cssbeautify": "^2.0.1",
"gulp-csslint": "^1.0.1",
"gulp-dom": "^1.0.0",
"gulp-flatten": "^0.4.0",
"gulp-fluent-ffmpeg": "^2.0.0",
@ -86,8 +80,6 @@
"gulp-htmlmin": "^5.0.1",
"gulp-if": "^3.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-javascript-obfuscator": "^1.1.5",
"gulp-jsbeautifier": "^3.0.0",
"gulp-load-plugins": "^2.0.3",
"gulp-phonegap-build": "^0.1.5",
"gulp-plumber": "^1.2.1",
@ -105,16 +97,14 @@
"imagemin-pngquant": "^9.0.0",
"jimp": "^0.6.1",
"js-yaml": "^3.13.1",
"onesky-fetch": "^0.0.7",
"postcss-assets": "^5.0.0",
"postcss-preset-env": "^6.5.0",
"postcss-round-subpixels": "^1.2.0",
"postcss-unprefix": "^2.1.3",
"sass-unused": "^0.3.0",
"speed-measure-webpack-plugin": "^1.3.1",
"strip-json-comments": "^3.0.1",
"trim": "^0.0.1",
"webpack-stream": "^5.1.0",
"webpack-stream": "^5.2.1",
"yaml-loader": "^0.6.0"
}
}

View File

@ -1,11 +1,11 @@
const packager = require("electron-packager");
const path = require("path");
const buildutils = require("./buildutils");
const { getVersion } = require("./buildutils");
const fs = require("fs");
const fse = require("fs-extra");
const execSync = require("child_process").execSync;
function gulptasksStandalone($, gulp, buildFolder) {
function gulptasksStandalone($, gulp) {
const electronBaseDir = path.join(__dirname, "..", "electron");
const tempDestDir = path.join(__dirname, "..", "tmp_standalone_files");
@ -47,49 +47,7 @@ function gulptasksStandalone($, gulp, buildFolder) {
});
gulp.task("standalone.prepare.minifyCode", () => {
return (
gulp
.src(path.join(electronBaseDir, "*.js"))
// .pipe(
// $.terser({
// ecma: 6,
// parse: {},
// module: false,
// toplevel: true,
// keep_classnames: false,
// keep_fnames: false,
// safari10: false,
// compress: {
// arguments: false, // breaks
// drop_console: false,
// // keep_fargs: false,
// keep_infinity: true,
// passes: 2,
// module: false,
// toplevel: true,
// unsafe_math: true,
// unsafe_arrows: false,
// warnings: true,
// },
// mangle: {
// eval: true,
// keep_classnames: false,
// keep_fnames: false,
// module: false,
// toplevel: true,
// safari10: false,
// },
// output: {
// comments: false,
// ascii_only: true,
// beautify: false,
// braces: false,
// ecma: 6,
// },
// })
// )
.pipe(gulp.dest(tempDestBuildDir))
);
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
});
gulp.task("standalone.prepare.copyGamefiles", () => {
@ -122,15 +80,14 @@ function gulptasksStandalone($, gulp, buildFolder) {
* @param {'win32'|'linux'|'darwin'} platform
* @param {'x64'|'ia32'} arch
* @param {function():void} cb
* @param {boolean=} isRelease
*/
function packageStandalone(platform, arch, cb, isRelease = false) {
function packageStandalone(platform, arch, cb) {
const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
packager({
dir: tempDestBuildDir,
appCopyright: "Tobias Springer",
appVersion: buildutils.getVersion(),
appVersion: getVersion(),
buildVersion: "1.0.0",
arch,
platform,
@ -164,19 +121,6 @@ function gulptasksStandalone($, gulp, buildFolder) {
'#!/usr/bin/env bash\n./shapezio --no-sandbox "$@"\n'
);
fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
} else if (platform === "win32") {
// Optional: Create a playable copy. Shouldn't be required
// const playablePath = appPath + "_playable";
// fse.copySync(appPath, playablePath);
// fs.writeFileSync(path.join(playablePath, "steam_appid.txt"), "1134480");
// fs.writeFileSync(
// path.join(playablePath, "play.bat"),
// "start shapezio --dev --disable-direct-composition --in-process-gpu\r\n"
// );
// fs.writeFileSync(
// path.join(playablePath, "play_local.bat"),
// "start shapezio --local --dev --disable-direct-composition --in-process-gpu\r\n"
// );
}
if (platform === "darwin") {
@ -226,11 +170,11 @@ function gulptasksStandalone($, gulp, buildFolder) {
);
}
gulp.task("standalone.package.prod.win64", cb => packageStandalone("win32", "x64", cb, true));
gulp.task("standalone.package.prod.win32", cb => packageStandalone("win32", "ia32", cb, true));
gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb, true));
gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb, true));
gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb, true));
gulp.task("standalone.package.prod.win64", cb => packageStandalone("win32", "x64", cb));
gulp.task("standalone.package.prod.win32", cb => packageStandalone("win32", "ia32", cb));
gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb));
gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb));
gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb));
gulp.task(
"standalone.package.prod",
@ -240,8 +184,6 @@ function gulptasksStandalone($, gulp, buildFolder) {
"standalone.package.prod.win64",
"standalone.package.prod.linux64",
"standalone.package.prod.darwin64"
// "standalone.package.prod.win32",
// "standalone.package.prod.linux32",
)
)
);

View File

@ -5,7 +5,7 @@ const yaml = require("gulp-yaml");
const translationsSourceDir = path.join(__dirname, "..", "translations");
const translationsJsonDir = path.join(__dirname, "..", "src", "js", "built-temp");
function gulptasksTranslations($, gulp, buildFolder) {
function gulptasksTranslations($, gulp) {
gulp.task("translations.convertToJson", () => {
return gulp
.src(path.join(translationsSourceDir, "*.yaml"))

View File

@ -2,9 +2,8 @@
const path = require("path");
const webpack = require("webpack");
const utils = require("./buildutils");
const { getRevision, getVersion, getAllResourceImages } = require("./buildutils");
const lzString = require("lz-string");
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CircularDependencyPlugin = require("circular-dependency-plugin");
module.exports = ({ watch = false, standalone = false }) => {
@ -41,9 +40,9 @@ module.exports = ({ watch = false, standalone = false }) => {
G_IS_BROWSER: "true",
G_IS_STANDALONE: standalone ? "true" : "false",
G_BUILD_TIME: "" + new Date().getTime(),
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()),
G_BUILD_VERSION: JSON.stringify(utils.getVersion()),
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()),
G_BUILD_COMMIT_HASH: JSON.stringify(getRevision()),
G_BUILD_VERSION: JSON.stringify(getVersion()),
G_ALL_UI_IMAGES: JSON.stringify(getAllResourceImages()),
}),
new CircularDependencyPlugin({
@ -60,7 +59,6 @@ module.exports = ({ watch = false, standalone = false }) => {
// set the current working directory for displaying module paths
cwd: path.join(__dirname, "..", "src", "js"),
}),
// new BundleAnalyzerPlugin()
],
module: {
rules: [

View File

@ -2,14 +2,12 @@
const path = require("path");
const webpack = require("webpack");
const utils = require("./buildutils");
const { getRevision, getVersion, getAllResourceImages } = require("./buildutils");
const lzString = require("lz-string");
const TerserPlugin = require("terser-webpack-plugin");
const StringReplacePlugin = require("string-replace-webpack-plugin");
// const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const UnusedFilesPlugin = require("unused-files-webpack-plugin").UnusedFilesWebpackPlugin;
// const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
module.exports = ({
enableAssert = false,
@ -34,9 +32,9 @@ module.exports = ({
G_APP_ENVIRONMENT: JSON.stringify(environment),
G_HAVE_ASSERT: enableAssert ? "true" : "false",
G_BUILD_TIME: "" + new Date().getTime(),
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()),
G_BUILD_VERSION: JSON.stringify(utils.getVersion()),
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()),
G_BUILD_COMMIT_HASH: JSON.stringify(getRevision()),
G_BUILD_VERSION: JSON.stringify(getVersion()),
G_ALL_UI_IMAGES: JSON.stringify(getAllResourceImages()),
};
const minifyNames = environment === "prod";
@ -143,9 +141,9 @@ module.exports = ({
ecma: es6 ? 6 : 5,
preamble:
"/* shapez.io Codebase - Copyright 2020 Tobias Springer - " +
utils.getVersion() +
getVersion() +
" @ " +
utils.getRevision() +
getRevision() +
" */",
},
},
@ -164,15 +162,6 @@ module.exports = ({
cwd: path.join(__dirname, "..", "src", "js"),
patterns: ["../src/js/**/*.js"],
}),
// new webpack.SourceMapDevToolPlugin({
// filename: "[name].map",
// publicPath: "/v/" + utils.getRevision() + "/",
// }),
// new ReplaceCompressBlocks()
// new webpack.optimize.ModuleConcatenationPlugin()
// new WebpackDeepScopeAnalysisPlugin()
// new BundleAnalyzerPlugin()
],
module: {
rules: [

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"scripts": {
"dev": "cd gulp && yarn gulp main.serveDev",
"tslint": "cd src/js && tsc",
"lint": "npx eslint src/js",
"lint": "eslint src/js",
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*",
"publishOnItchWindows": "butler push tmp_standalone_files/shapez.io-standalone-win32-x64 tobspr/shapezio:windows --userversion-file version",
"publishOnItchLinux": "butler push tmp_standalone_files/shapez.io-standalone-linux-x64 tobspr/shapezio:linux --userversion-file version",
@ -46,20 +46,18 @@
"lz-string": "^1.4.4",
"markdown-loader": "^4.0.0",
"match-all": "^1.2.5",
"obfuscator-loader": "^1.1.2",
"phonegap-plugin-mobile-accessibility": "^1.0.5",
"promise-polyfill": "^8.1.0",
"query-string": "^6.8.1",
"rusha": "^0.8.13",
"serialize-error": "^3.0.0",
"sloc": "^0.2.1",
"strictdom": "^1.0.1",
"string-replace-webpack-plugin": "^0.1.3",
"terser-webpack-plugin": "^1.1.0",
"typescript": "3.9.3",
"uglify-template-string-loader": "^1.1.0",
"unused-files-webpack-plugin": "^3.4.0",
"webpack": "^4.31.0",
"webpack": "^4.43.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.1.0",
"webpack-deep-scope-plugin": "^1.6.0",
@ -87,16 +85,14 @@
"imagemin-pngquant": "^8.0.0",
"jimp": "^0.6.1",
"js-yaml": "^3.13.1",
"onesky-fetch": "^0.0.7",
"postcss-assets": "^5.0.0",
"postcss-preset-env": "^6.5.0",
"postcss-round-subpixels": "^1.2.0",
"postcss-unprefix": "^2.1.3",
"prettier": "^2.0.4",
"sass-unused": "^0.3.0",
"speed-measure-webpack-plugin": "^1.3.1",
"strip-json-comments": "^3.0.1",
"trim": "^0.0.1",
"webpack-stream": "^5.1.0"
"yarn": "^1.22.4"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 523 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -449,6 +449,7 @@
<key type="filename">sprites/blueprints/miner-chainable.png</key>
<key type="filename">sprites/blueprints/miner.png</key>
<key type="filename">sprites/blueprints/rotater-ccw.png</key>
<key type="filename">sprites/blueprints/rotater-fl.png</key>
<key type="filename">sprites/blueprints/rotater.png</key>
<key type="filename">sprites/blueprints/splitter-compact-inverse.png</key>
<key type="filename">sprites/blueprints/splitter-compact.png</key>
@ -459,6 +460,7 @@
<key type="filename">sprites/blueprints/underground_belt_exit.png</key>
<key type="filename">sprites/buildings/miner-chainable.png</key>
<key type="filename">sprites/buildings/rotater-ccw.png</key>
<key type="filename">sprites/buildings/rotater-fl.png</key>
<key type="filename">sprites/buildings/splitter-compact-inverse.png</key>
<key type="filename">sprites/buildings/splitter-compact.png</key>
<key type="filename">sprites/buildings/underground_belt_entry-tier2.png</key>
@ -566,21 +568,6 @@
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/misc/lock_direction_indicator.png</key>
<struct type="IndividualSpriteSettings">
<key>pivotPoint</key>
<point_f>0.5,0.5</point_f>
<key>spriteScale</key>
<double>1</double>
<key>scale9Enabled</key>
<false/>
<key>scale9Borders</key>
<rect>12,12,24,24</rect>
<key>scale9Paddings</key>
<rect>12,12,24,24</rect>
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/misc/storage_overlay.png</key>
<struct type="IndividualSpriteSettings">
<key>pivotPoint</key>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -73,6 +73,9 @@ body {
scrollbar-face-color: #888;
scrollbar-track-color: rgba(255, 255, 255, 0.1);
// Firefox
scrollbar-color: #cdd0d4 rgba(#000, 0.05);
overflow: hidden;
@include Text;
@ -369,7 +372,7 @@ canvas {
}
.pressed {
transform: scale(0.95) !important;
transform: scale(0.98) !important;
animation: none !important;
}

View File

@ -29,7 +29,6 @@
.buildings {
display: grid;
grid-auto-flow: column;
@include S(margin-bottom, 2px);
.building {
color: $accentColorDark;
@ -43,7 +42,7 @@
@include S(width, 35px);
@include S(height, 40px);
background: center center / 70% no-repeat;
background: center center / 65% no-repeat;
&:not(.unlocked) {
@include S(width, 20px);

View File

@ -55,6 +55,7 @@
.dialogInner {
opacity: 1;
}
backdrop-filter: blur(D(3px));
}
.dialogInner {

View File

@ -1,6 +1,23 @@
#state_AboutState {
> .container .content {
@include S(max-width, 600px);
@include PlainText;
padding: 0;
background: transparent;
}
.head {
@include S(padding, 20px);
img {
display: block;
margin: 0 auto;
@include S(max-width, 200px);
}
}
.text {
@include S(margin, 10px);
}
a {

View File

@ -1,5 +1,6 @@
#state_ChangelogState {
.content {
@include S(max-width, 800px);
display: flex;
flex-direction: column;
}

View File

@ -55,7 +55,7 @@
opacity: 0;
display: none;
transform: translate(50%, 50%);
filter: blur(10px);
filter: blur(D(3px));
$opacity: 0.2;
&.loaded {
@ -178,6 +178,10 @@
transform: translateX(50%) rotate(-7deg) scale(1.1);
}
}
@include DarkThemeOverride {
color: $colorBlueBright;
}
}
}
@ -201,33 +205,6 @@
flex-grow: 1;
@include S(margin-bottom, 10px);
}
.contest {
flex-grow: 1;
background: rgb(32, 187, 166);
@include S(padding, 15px);
h3 {
@include Heading;
color: #fff;
font-weight: bold;
text-transform: uppercase;
@include S(margin-bottom, 5px);
}
p {
color: #fff;
@include Text;
strong {
font-weight: bold;
}
@include S(margin-bottom, 5px);
}
button {
background: #fff;
color: #333538;
}
}
}
.mainContainer {

View File

@ -1,113 +1,188 @@
#state_SettingsState {
.content {
.versionbar {
@include S(margin-top, 20px);
@include SuperSmallText;
display: grid;
align-items: center;
grid-template-columns: 1fr auto;
.buildVersion {
display: flex;
flex-direction: column;
color: #aaadaf;
$colorCategoryButton: #eee;
$colorCategoryButtonSelected: #5f748b;
.container .content {
display: flex;
overflow-y: scroll;
.categoryContainer {
width: 100%;
.category {
display: none;
&.active {
display: block;
}
.setting {
@include S(padding, 10px);
background: #eeeff5;
@include S(border-radius, $globalBorderRadius);
@include S(margin-bottom, 5px);
label {
text-transform: uppercase;
@include Text;
}
.desc {
@include S(margin-top, 5px);
@include SuperSmallText;
color: #aaadb2;
}
> .row {
display: grid;
align-items: center;
grid-template-columns: 1fr auto;
}
&.disabled {
// opacity: 0.3;
pointer-events: none;
* {
pointer-events: none !important;
cursor: default !important;
}
position: relative;
.standaloneOnlyHint {
@include PlainText;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: all;
display: flex;
align-items: center;
justify-content: center;
background: rgba(#fff, 0.5);
text-transform: uppercase;
color: $colorRedBright;
}
}
.value.enum {
background: #fff;
@include PlainText;
display: flex;
align-items: flex-start;
pointer-events: all;
cursor: pointer;
justify-content: center;
@include S(min-width, 100px);
@include S(border-radius, $globalBorderRadius);
@include S(padding, 4px);
@include S(padding-right, 15px);
background: #fff uiResource("icons/enum_selector.png") calc(100% - #{D(5px)})
calc(50% + #{D(1px)}) / #{D(15px)} no-repeat;
transition: background-color 0.12s ease-in-out;
&:hover {
background-color: #fafafa;
}
}
}
}
}
button.about {
background-color: $colorGreenBright;
}
.sidebar {
display: flex;
flex-direction: column;
@include S(min-width, 210px);
@include S(max-width, 320px);
width: 30%;
height: 100%;
position: sticky;
top: 0;
@include S(margin-left, 20px);
@include S(margin-right, 32px);
.setting {
@include S(padding, 10px);
background: #eeeff5;
@include S(border-radius, $globalBorderRadius);
@include S(margin-bottom, 5px);
label {
text-transform: uppercase;
@include Text;
.other {
margin-top: auto;
}
.desc {
@include S(margin-top, 5px);
button {
@include S(margin-top, 4px);
width: calc(100% - #{D(20px)});
text-align: start;
&::after {
content: unset;
}
}
button.categoryButton,
button.about {
background-color: $colorCategoryButton;
color: #777a7f;
&.active {
background-color: $colorCategoryButtonSelected;
color: #fff;
&:hover {
opacity: 1;
}
}
&.pressed {
transform: none !important;
}
}
.versionbar {
@include S(margin-top, 20px);
@include SuperSmallText;
color: #aaadb2;
}
> .row {
display: grid;
align-items: center;
grid-template-columns: 1fr auto;
}
&.disabled {
// opacity: 0.3;
pointer-events: none;
* {
pointer-events: none !important;
cursor: default !important;
}
position: relative;
.standaloneOnlyHint {
@include PlainText;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: all;
.buildVersion {
display: flex;
align-items: center;
justify-content: center;
background: rgba(#fff, 0.5);
text-transform: uppercase;
color: $colorRedBright;
}
}
.value.enum {
background: #fff;
@include PlainText;
display: flex;
align-items: flex-start;
pointer-events: all;
cursor: pointer;
justify-content: center;
@include S(min-width, 100px);
@include S(border-radius, $globalBorderRadius);
@include S(padding, 4px);
@include S(padding-right, 15px);
background: #fff uiResource("icons/enum_selector.png") calc(100% - #{D(5px)})
calc(50% + #{D(1px)}) / #{D(15px)} no-repeat;
transition: background-color 0.12s ease-in-out;
&:hover {
background-color: #fafafa;
flex-direction: column;
color: #aaadaf;
}
}
}
}
@include DarkThemeOverride {
.content {
.setting {
background: darken($darkModeGameBackground, 10);
.container .content {
.sidebar {
button.categoryButton,
button.about {
background-color: #3f3f47;
.value.enum {
// dirty but works
filter: invert(0.85);
color: #222;
}
.value.checkbox {
background-color: #74767b;
&.checked {
&.active {
background-color: $colorBlueBright;
}
}
}
.categoryContainer {
.category {
.setting {
background: darken($darkModeGameBackground, 10);
.value.enum {
// dirty but works
filter: invert(0.78) sepia(40%) hue-rotate(190deg);
color: #222;
}
.value.checkbox {
background-color: #74767b;
&.checked {
background-color: $colorBlueBright;
}
}
}
}
}
}
}
}

View File

@ -1,24 +1,18 @@
.gameState.textualState {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
$padding: 15px;
.headerBar,
> .container .content {
@include S(width, 500px);
}
display: grid;
grid-template-rows: auto 1fr;
box-sizing: border-box;
@include S(padding, 32px);
height: 100vh;
.headerBar {
display: flex;
align-items: center;
justify-content: flex-start;
h1 {
display: flex;
pointer-events: all;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
pointer-events: all;
cursor: pointer;
@include SuperHeading;
text-transform: uppercase;
@ -39,11 +33,17 @@
}
> .container {
display: flex;
justify-content: center;
width: 100%;
overflow-y: auto;
> .content {
width: 100%;
background: #fff;
@include S(border-radius, $globalBorderRadius);
@include S(padding, 10px);
height: calc(80vh - #{D(60px)});
height: 100%;
overflow-y: auto;
box-sizing: border-box;
pointer-events: all;

View File

@ -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
@ -385,7 +387,7 @@ export class Application {
}
const scale = this.getEffectiveUiScale();
waitNextFrame().then(() => document.documentElement.style.setProperty("--ui-scale", scale));
waitNextFrame().then(() => document.documentElement.style.setProperty("--ui-scale", `${scale}`));
window.focus();
}
}

View File

@ -4,8 +4,10 @@ export const CHANGELOG = [
date: "unreleased",
entries: [
"WIRES",
"Reworked menu UI design (by dengr1605)",
"Allow holding ALT in belt planner to reverse direction (by jakobhellermann)",
"Clear cursor when trying to pipette the same building twice (by hexy)",
"Fixed level 18 stacker bug: If you experienced it already, you know it, if not, I don't want to spoiler (by hexy)",
"Added keybinding to close menus (by isaisstillalive / Sandwichs-del)",
"Fix rare crash regarding the buildings toolbar (by isaisstillalive)",
"Fixed some phrases (by EnderDoom77)",
@ -24,7 +26,7 @@ export const CHANGELOG = [
version: "1.1.19",
date: "02.07.2020",
entries: [
"There are now notifications every 15 minutes in the demo version to buy the full version (For further details and the reason, check the #surveys channel in the discord)",
"There are now notifications every 15 minutes in the demo version to buy the full version (For further details and the reason, check the #surveys channel in the Discord)",
"I'm still working on the wires update, I hope to release it mid july!",
],
},
@ -151,7 +153,7 @@ export const CHANGELOG = [
version: "1.1.10",
date: "12.06.2020",
entries: [
"There are now linux builds on steam! Please report any issues in the discord!",
"There are now linux builds on steam! Please report any issues in the Discord!",
"Steam cloud saves are now available!",
"Added and update more translations (Big thank you to all translators!)",
"Prevent invalid connection if existing underground tunnel entrance exists (by jaysc)",
@ -221,7 +223,7 @@ export const CHANGELOG = [
entries: [
"The official trailer is now ready! Check it out <a href='https://www.youtube.com/watch?v=KyorY1uIqiQ' target='_blank'>here</a>!",
"The <a href='https://steam.shapez.io' target='_blank'>steam page</a> is now live!",
"Experimental linux builds are now available! Please give me feedback on them in the discord",
"Experimental linux builds are now available! Please give me feedback on them in the Discord",
"Allow hovering pinned shapes to enlarge them",
"Allow deselecting blueprints with right click and 'Q'",
"Move default key for deleting from 'X' to 'DEL'",

View File

@ -14,12 +14,11 @@ export class AnimationFrame {
this.frameEmitted = new Signal();
this.bgFrameEmitted = new Signal();
this.lastTime = null;
this.bgLastTime = null;
this.lastTime = performance.now();
this.bgLastTime = performance.now();
this.boundMethod = this.handleAnimationFrame.bind(this);
/** @type {Worker} */
this.backgroundWorker = new BackgroundAnimationFrameEmitterWorker();
this.backgroundWorker.addEventListener("error", err => {
logger.error("Error in background fps worker:", err);
@ -27,22 +26,16 @@ export class AnimationFrame {
this.backgroundWorker.addEventListener("message", this.handleBackgroundTick.bind(this));
}
/**
*
* @param {MessageEvent} event
*/
handleBackgroundTick(event) {
handleBackgroundTick() {
const time = performance.now();
if (!this.bgLastTime) {
// First update, first delta is always 16ms
this.bgFrameEmitted.dispatch(1000 / 60);
} else {
let dt = time - this.bgLastTime;
if (dt > maxDtMs) {
dt = resetDtMs;
}
this.bgFrameEmitted.dispatch(dt);
let dt = time - this.bgLastTime;
if (dt > maxDtMs) {
dt = resetDtMs;
}
this.bgFrameEmitted.dispatch(dt);
this.bgLastTime = time;
}
@ -52,18 +45,15 @@ export class AnimationFrame {
}
handleAnimationFrame(time) {
if (!this.lastTime) {
// First update, first delta is always 16ms
this.frameEmitted.dispatch(1000 / 60);
} else {
let dt = time - this.lastTime;
if (dt > maxDtMs) {
// warn(this, "Clamping", dt, "to", resetDtMs);
dt = resetDtMs;
}
this.frameEmitted.dispatch(dt);
let dt = time - this.lastTime;
if (dt > maxDtMs) {
dt = resetDtMs;
}
this.frameEmitted.dispatch(dt);
this.lastTime = time;
window.requestAnimationFrame(this.boundMethod);
}
}

View File

@ -1,7 +1,9 @@
// @ts-ignore
import CompressionWorker from "../webworkers/compression.worker";
import { createLogger } from "./logging";
import { compressX64 } from "./lzstring";
import { round2Digits } from "./utils";
const logger = createLogger("async_compression");
export let compressionPrefix = String.fromCodePoint(1);
@ -35,7 +37,6 @@ if (!checkCryptPrefix(compressionPrefix)) {
class AsynCompression {
constructor() {
/** @type {Worker} */
this.worker = new CompressionWorker();
this.currentJobId = 1000;
@ -52,7 +53,7 @@ class AsynCompression {
}
const duration = performance.now() - jobData.startTime;
// log(this, "Got response from worker within", duration.toFixed(2), "ms");
logger.log("Got job", jobId, "response within", round2Digits(duration), "ms");
const resolver = jobData.resolver;
delete this.currentJobs[jobId];
resolver(result);
@ -76,6 +77,7 @@ class AsynCompression {
* @param {string} text
*/
compressFileAsync(text) {
logger.log("Compressing", text.length, "bytes async");
return this.internalQueueJob("compressFile", {
text,
compressionPrefix,
@ -100,6 +102,8 @@ class AsynCompression {
resolver: resolve,
startTime: performance.now(),
};
logger.log("Posting job", job, "/", jobId);
this.worker.postMessage({ jobId, job, data });
});
}

View File

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

View File

@ -7,6 +7,7 @@ import { createLogger } from "./logging";
import { Signal } from "./signal";
import { SOUNDS, MUSIC } from "../platform/sound";
import { AtlasDefinition, atlasFiles } from "./atlas_definitions";
import { initBuildingCodesAfterResourcesLoaded } from "../game/meta_building_registry";
const logger = createLogger("background_loader");
@ -114,8 +115,8 @@ export class BackgroundResourcesLoader {
})
.then(() => {
logger.log("⏰ Finish load: bare game");
Loader.createAtlasLinks();
this.bareGameReady = true;
initBuildingCodesAfterResourcesLoaded();
this.signalBareGameLoaded.dispatch();
this.internalStartLoadingAdditionalGameAssets();
});

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import { FILE_NOT_FOUND } from "../platform/storage";
import { accessNestedPropertyReverse } from "./utils";
import { IS_DEBUG, globalConfig } from "./config";
import { ExplainedResult } from "./explained_result";
import { decompressX64, compressX64 } from ".//lzstring";
import { decompressX64, compressX64 } from "./lzstring";
import { asyncCompressor, compressionPrefix } from "./async_compression";
import { compressObject, decompressObject } from "../savegame/savegame_compressor";

View File

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

View File

@ -1,19 +1,12 @@
import { globalConfig } from "./config";
import { decompressX64, compressX64 } from "./lzstring";
import { createHash } from "rusha";
const Rusha = require("rusha");
const encryptKey = globalConfig.info.sgSalt;
export function decodeHashedString(s) {
return decompressX64(s);
}
import { decompressX64 } from "./lzstring";
export function sha1(str) {
return Rusha.createHash().update(str).digest("hex");
return createHash().update(str).digest("hex");
}
// Window.location.host
export function getNameOfProvider() {
return window[decodeHashedString("DYewxghgLgliB2Q")][decodeHashedString("BYewzgLgdghgtgUyA")];
return window[decompressX64("DYewxghgLgliB2Q")][decompressX64("BYewzgLgdghgtgUyA")];
}

View File

@ -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 = {};
@ -158,7 +157,9 @@ export class AtlasSprite extends BaseSprite {
const scale = parameters.desiredAtlasScale;
const link = this.linksByResolution[scale];
assert(link, "Link not known: " + scale + " (having " + Object.keys(this.linksByResolution) + ")");
if (!link) {
assert(false, `Link not known: ${scale} (having ${Object.keys(this.linksByResolution)})`);
}
const scaleW = w / link.w;
const scaleH = h / link.h;
@ -195,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,

View File

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

View File

@ -1,14 +1,14 @@
import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters";
import { gItemRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle";
import { epsilonCompare, round4Digits } from "../core/utils";
import { Vector } from "../core/vector";
import { enumDirection, enumDirectionToVector, Vector, enumInvertedDirections } from "../core/vector";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { BaseItem } from "./base_item";
import { Entity } from "./entity";
import { GameRoot, enumLayer } from "./root";
import { Rectangle } from "../core/rectangle";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { gItemRegistry } from "../core/global_registries";
import { GameRoot } from "./root";
const logger = createLogger("belt_path");
@ -125,14 +125,6 @@ export class BeltPath extends BasicSerializableObject {
return globalConfig.beltItemSpacingByLayer[this.layer];
}
/**
* Returns the layer of the this path
* @returns {enumLayer}
*/
getLayer() {
return this.entityPath[0].layer;
}
/**
* Tries to accept the item
* @param {BaseItem} item
@ -167,7 +159,7 @@ export class BeltPath extends BasicSerializableObject {
* @returns {BaseItem|null}
*/
findItemAtTile(tile) {
// TODO: This breaks color blind mode otherwise
// @TODO: This breaks color blind mode otherwise
return null;
}
@ -186,27 +178,85 @@ export class BeltPath extends BasicSerializableObject {
}
/**
* Updates all ejectors on the path, so that only the last ejector
* Recomputes the layer of the path and the target acceptor
*/
onPathChanged() {
this.ejectorComp = this.entityPath[this.entityPath.length - 1].components.ItemEjector;
this.ejectorSlot = this.ejectorComp.slots[0];
this.layer = this.entityPath[0].layer;
this.acceptorTarget = this.computeAcceptingEntityAndSlot();
}
for (let i = 0; i < this.entityPath.length; ++i) {
const ejectorComp = this.entityPath[i].components.ItemEjector;
const isLast = i === this.entityPath.length - 1;
ejectorComp.enabled = isLast;
/**
* Called by the belt system when the surroundings changed
*/
onSurroundingsChanged() {
this.onPathChanged();
}
// Clear all slots of non-end entities
if (!isLast) {
for (let k = 0; k < ejectorComp.slots.length; ++k) {
ejectorComp.slots[k].item = null;
ejectorComp.slots[k].progress = 0.0;
/**
* Finds the entity which accepts our items
* @return {{ entity: Entity, slot: number, direction?: enumDirection }}
*/
computeAcceptingEntityAndSlot() {
const lastEntity = this.entityPath[this.entityPath.length - 1];
const lastStatic = lastEntity.components.StaticMapEntity;
const lastBeltComp = lastEntity.components.Belt;
// Figure out where and into which direction we eject items
const ejectSlotWsTile = lastStatic.localTileToWorld(new Vector(0, 0));
const ejectSlotWsDirection = lastStatic.localDirectionToWorld(lastBeltComp.direction);
const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
// Try to find the given acceptor component to take the item
// Since there can be cross layer dependencies, check on all layers
const targetEntities = this.root.map.getLayersContentsMultipleXY(
ejectSlotTargetWsTile.x,
ejectSlotTargetWsTile.y
);
for (let i = 0; i < targetEntities.length; ++i) {
const targetEntity = targetEntities[i];
const targetStaticComp = targetEntity.components.StaticMapEntity;
const targetBeltComp = targetEntity.components.Belt;
// Check for belts (special case)
if (targetBeltComp) {
const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
if (ejectSlotWsDirection === beltAcceptingDirection) {
return {
entity: targetEntity,
direction: null,
slot: 0,
};
}
}
}
this.layer = this.entityPath[0].layer;
// Check for item acceptors
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
if (!targetAcceptorComp) {
// Entity doesn't accept items
continue;
}
const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection);
const matchingSlot = targetAcceptorComp.findMatchingSlot(
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
ejectingDirection,
lastEntity.layer
);
if (!matchingSlot) {
// No matching slot found
continue;
}
return {
entity: targetEntity,
slot: matchingSlot.index,
direction: enumInvertedDirections[ejectingDirection],
};
}
}
// Following code will be compiled out outside of dev versions
@ -251,11 +301,6 @@ export class BeltPath extends BasicSerializableObject {
return fail("Reference to destroyed entity " + entity.uid);
}
const enabledState = i === this.entityPath.length - 1;
if (entity.components.ItemEjector.enabled !== enabledState) {
return fail("Item ejector enabled state is not synchronized (index =" + i + ")");
}
const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity);
if (!followUp) {
return fail(
@ -283,20 +328,6 @@ export class BeltPath extends BasicSerializableObject {
}
}
// Check for right ejector component and slot
if (this.ejectorComp !== this.entityPath[this.entityPath.length - 1].components.ItemEjector) {
return fail("Stale ejectorComp handle");
}
if (this.ejectorSlot !== this.ejectorComp.slots[0]) {
return fail("Stale ejector slot handle");
}
if (!this.ejectorComp) {
return fail("Ejector comp not set");
}
if (!this.ejectorSlot) {
return fail("Ejector slot not set");
}
// Check spacing
if (this.spacingToFirstItem > this.totalLength + 0.005) {
return fail(
@ -370,14 +401,6 @@ export class BeltPath extends BasicSerializableObject {
const beltComp = entity.components.Belt;
// If the last belt has something on its ejector, put that into the path first
const pendingItem = this.ejectorComp.takeSlotItem(0);
if (pendingItem) {
// Ok, so we have a pending item
DEBUG && logger.log("Taking pending item and putting it back on the path");
this.items.push([0, pendingItem]);
}
// Append the entity
this.entityPath.push(entity);
this.onPathChanged();
@ -970,7 +993,7 @@ export class BeltPath extends BasicSerializableObject {
beltSpeed *= 100;
}
let minimumDistance = this.ejectorSlot.item ? this.getItemSpacing() : 0;
let minimumDistance = 0;
// Try to reduce spacing
let remainingAmount = beltSpeed;
@ -994,11 +1017,28 @@ export class BeltPath extends BasicSerializableObject {
minimumDistance = this.getItemSpacing();
}
// Check if we have an item which is ready to be emitted
const lastItem = this.items[this.items.length - 1];
if (lastItem && lastItem[_nextDistance] === 0) {
// Take over
if (this.ejectorComp.tryEject(0, lastItem[_item])) {
if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
// Pass over the item
if (
this.root.systemMgr.systems.itemEjector.tryPassOverItem(
lastItem[_item],
this.acceptorTarget.entity,
this.acceptorTarget.slot
)
) {
this.items.pop();
// Also trigger animation
const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
if (targetAcceptorComp) {
targetAcceptorComp.onItemAccepted(
this.acceptorTarget.slot,
this.acceptorTarget.direction,
lastItem[_item]
);
}
}
}
@ -1134,7 +1174,7 @@ export class BeltPath extends BasicSerializableObject {
const beltLength = beltComp.getEffectiveLengthTiles(this.layer);
// Check if the current items are on the belt
while (trackPos + beltLength >= currentItemPos) {
while (trackPos + beltLength >= currentItemPos - 1e-51) {
// Its on the belt, render it now
const staticComp = entity.components.StaticMapEntity;
assert(

View File

@ -81,10 +81,6 @@ export class Blueprint {
for (let i = 0; i < this.entities.length; ++i) {
const entity = this.entities[i];
const staticComp = entity.components.StaticMapEntity;
if (!staticComp.blueprintSpriteKey) {
logger.warn("Blueprint entity without sprite!");
return;
}
const newPos = staticComp.origin.add(tile);
const rect = staticComp.getTileSpaceBounds();
@ -98,7 +94,7 @@ export class Blueprint {
staticComp.drawSpriteOnFullEntityBounds(
parameters,
Loader.getSprite(staticComp.blueprintSpriteKey),
staticComp.getBlueprintSprite(),
0,
true,
newPos

View File

@ -0,0 +1,79 @@
/* typehints:start */
import { MetaBuilding } from "./meta_building";
import { AtlasSprite } from "../core/sprites";
/* typehints:end */
/**
* @typedef {{
* metaClass: typeof MetaBuilding,
* metaInstance?: MetaBuilding,
* variant?: string,
* rotationVariant?: number,
* sprite?: AtlasSprite,
* blueprintSprite?: AtlasSprite,
* silhouetteColor?: string
* }} BuildingVariantIdentifier
*/
/**
* Stores a lookup table for all building variants (for better performance)
* @type {Object<number, BuildingVariantIdentifier>}
*/
export const gBuildingVariants = {
// Set later
};
/**
* Registers a new variant
* @param {number} id
* @param {typeof MetaBuilding} meta
* @param {string} variant
* @param {number} rotationVariant
*/
export function registerBuildingVariant(
id,
meta,
variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */,
rotationVariant = 0
) {
assert(!gBuildingVariants[id], "Duplicate id: " + id);
gBuildingVariants[id] = {
metaClass: meta,
variant,
rotationVariant,
};
}
/**
*
* @param {number} code
* @returns {BuildingVariantIdentifier}
*/
export function getBuildingDataFromCode(code) {
assert(gBuildingVariants[code], "Invalid building code: " + code);
return gBuildingVariants[code];
}
/**
* Finds the code for a given variant
* @param {MetaBuilding} metaBuilding
* @param {string} variant
* @param {number} rotationVariant
*/
export function getCodeFromBuildingData(metaBuilding, variant, rotationVariant) {
for (const key in gBuildingVariants) {
const data = gBuildingVariants[key];
if (
data.metaInstance.getId() === metaBuilding.getId() &&
data.variant === variant &&
data.rotationVariant === rotationVariant
) {
return +key;
}
}
assertAlways(
false,
"Building not found by data: " + metaBuilding.getId() + " / " + variant + " / " + rotationVariant
);
return 0;
}

View File

@ -1,4 +1,3 @@
import { Loader } from "../../core/loader";
import { formatItemsPerSecond } from "../../core/utils";
import { enumAngleToDirection, enumDirection, Vector } from "../../core/vector";
import { SOUNDS } from "../../platform/sound";
@ -40,6 +39,10 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
return SOUNDS.placeBelt;
}
getSprite() {
return null;
}
/**
* Creates the entity at the given location
* @param {Entity} entity
@ -50,34 +53,8 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
direction: enumDirection.top, // updated later
})
);
// Make this entity replaceabel
// Make this entity replaceable
entity.addComponent(new ReplaceableMapEntityComponent());
entity.addComponent(
new ItemAcceptorComponent({
slots: [
{
pos: new Vector(0, 0),
directions: [enumDirection.bottom],
layer: this.getLayer(),
},
],
animated: false,
})
);
entity.addComponent(
new ItemEjectorComponent({
slots: [
{
pos: new Vector(0, 0),
direction: enumDirection.top, // updated later
layer: this.getLayer(),
},
],
instantEject: true,
})
);
}
/**
@ -87,8 +64,6 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
*/
updateVariants(entity, rotationVariant) {
entity.components.Belt.direction = arrayBeltVariantToRotation[rotationVariant];
entity.components.ItemEjector.slots[0].direction = arrayBeltVariantToRotation[rotationVariant];
entity.components.StaticMapEntity.spriteKey = null;
}
/**

View File

@ -77,7 +77,6 @@ export class MetaEnergyGenerator extends MetaBuilding {
layer: enumLayer.wires,
},
],
instantEject: true,
})
);

View File

@ -6,6 +6,7 @@ import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/it
import { UnremovableComponent } from "../components/unremovable";
import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building";
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
export class MetaHubBuilding extends MetaBuilding {
constructor() {
@ -28,6 +29,11 @@ export class MetaHubBuilding extends MetaBuilding {
return null;
}
getSprite() {
// We render it ourself
return null;
}
/**
* Creates the entity at the given location
* @param {Entity} entity
@ -41,10 +47,20 @@ export class MetaHubBuilding extends MetaBuilding {
})
);
// We render the sprite ourself
entity.components.StaticMapEntity.spriteKey = null;
entity.addComponent(new UnremovableComponent());
entity.addComponent(
new WiredPinsComponent({
slots: [
{
pos: new Vector(0, 0),
type: enumPinSlotType.logicalEjector,
direction: enumDirection.top,
},
],
})
);
entity.addComponent(
new ItemAcceptorComponent({
slots: [

View File

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

View File

@ -1,14 +1,14 @@
import { globalConfig } from "../../core/config";
import { enumDirection, Vector } from "../../core/vector";
import { ItemAcceptorComponent } from "../components/item_acceptor";
import { ItemEjectorComponent } from "../components/item_ejector";
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
import { Entity } from "../entity";
import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
import { GameRoot, enumLayer } from "../root";
import { GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals";
import { T } from "../../translations";
import { formatItemsPerSecond } from "../../core/utils";
import { BeltUnderlaysComponent } from "../components/belt_underlays";
/** @enum {string} */
export const enumSplitterVariants = { compact: "compact", compactInverse: "compact-inverse" };
@ -88,6 +88,8 @@ export class MetaSplitterBuilding extends MetaBuilding {
slots: [], // set later
})
);
entity.addComponent(new BeltUnderlaysComponent({ underlays: [] }));
}
/**
@ -115,9 +117,9 @@ export class MetaSplitterBuilding extends MetaBuilding {
{ pos: new Vector(1, 0), direction: enumDirection.top },
]);
entity.components.ItemAcceptor.beltUnderlays = [
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.regular },
{ pos: new Vector(1, 0), direction: enumDirection.top, layer: enumLayer.regular },
entity.components.BeltUnderlays.underlays = [
{ pos: new Vector(0, 0), direction: enumDirection.top },
{ pos: new Vector(1, 0), direction: enumDirection.top },
];
break;
@ -143,8 +145,8 @@ export class MetaSplitterBuilding extends MetaBuilding {
{ pos: new Vector(0, 0), direction: enumDirection.top },
]);
entity.components.ItemAcceptor.beltUnderlays = [
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.regular },
entity.components.BeltUnderlays.underlays = [
{ pos: new Vector(0, 0), direction: enumDirection.top },
];
break;

View File

@ -71,6 +71,10 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
return super.getAvailableVariants(root);
}
/**
* @param {number} rotationVariant
* @param {string} variant
*/
getPreviewSprite(rotationVariant, variant) {
let suffix = "";
if (variant !== defaultBuildingVariant) {
@ -87,6 +91,10 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
}
}
/**
* @param {number} rotationVariant
* @param {string} variant
*/
getBlueprintSprite(rotationVariant, variant) {
let suffix = "";
if (variant !== defaultBuildingVariant) {
@ -103,6 +111,14 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
}
}
/**
* @param {number} rotationVariant
* @param {string} variant
*/
getSprite(rotationVariant, variant) {
return this.getPreviewSprite(rotationVariant, variant);
}
/**
* @param {GameRoot} root
*/
@ -201,10 +217,6 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
*/
updateVariants(entity, rotationVariant, variant) {
entity.components.UndergroundBelt.tier = enumUndergroundBeltVariantToTier[variant];
entity.components.StaticMapEntity.spriteKey = this.getPreviewSprite(
rotationVariant,
variant
).spriteName;
switch (arrayUndergroundRotationVariantToMode[rotationVariant]) {
case enumUndergroundBeltMode.sender: {

View File

@ -65,7 +65,6 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
entity.addComponent(
new ItemEjectorComponent({
slots: [], // set later
instantEject: true,
})
);
}
@ -88,7 +87,7 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
]);
entity.components.ItemEjector.setSlots([
{ pos: new Vector(0, 0), direction: enumDirection.left, layer: enumLayer.wires },
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.wires },
{ pos: new Vector(0, 0), direction: enumDirection.right, layer: enumLayer.wires },
]);
@ -98,7 +97,7 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
entity.components.ItemAcceptor.setSlots([
{
pos: new Vector(0, 0),
directions: [enumDirection.left],
directions: [enumDirection.top],
layer: enumLayer.wires,
},
{
@ -109,7 +108,7 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
]);
entity.components.ItemEjector.setSlots([
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.wires },
{ pos: new Vector(0, 0), direction: enumDirection.bottom, layer: enumLayer.wires },
]);
break;
}

View File

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

View File

@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage";
import { EnergyGeneratorComponent } from "./components/energy_generator";
import { WiredPinsComponent } from "./components/wired_pins";
import { EnergyConsumerComponent } from "./components/energy_consumer";
import { BeltUnderlaysComponent } from "./components/belt_underlays";
export function initComponentRegistry() {
gComponentRegistry.register(StaticMapEntityComponent);
@ -29,6 +30,7 @@ export function initComponentRegistry() {
gComponentRegistry.register(EnergyGeneratorComponent);
gComponentRegistry.register(WiredPinsComponent);
gComponentRegistry.register(EnergyConsumerComponent);
gComponentRegistry.register(BeltUnderlaysComponent);
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS

View File

@ -7,6 +7,40 @@ import { enumLayer } from "../root";
export const curvedBeltLength = /* Math.PI / 4 */ 0.78;
/** @type {import("./item_acceptor").ItemAcceptorSlot} */
export const FAKE_BELT_ACCEPTOR_SLOT = {
pos: new Vector(0, 0),
directions: [enumDirection.bottom],
layer: enumLayer.regular,
};
/** @type {Object<enumDirection, import("./item_ejector").ItemEjectorSlot>} */
export const FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION = {
[enumDirection.top]: {
pos: new Vector(0, 0),
direction: enumDirection.top,
item: null,
layer: enumLayer.regular,
progress: 0,
},
[enumDirection.right]: {
pos: new Vector(0, 0),
direction: enumDirection.right,
item: null,
layer: enumLayer.regular,
progress: 0,
},
[enumDirection.left]: {
pos: new Vector(0, 0),
direction: enumDirection.left,
item: null,
layer: enumLayer.regular,
progress: 0,
},
};
export class BeltComponent extends Component {
static getId() {
return "Belt";
@ -56,6 +90,27 @@ export class BeltComponent extends Component {
return this.direction === enumDirection.top ? 1.0 : curvedBeltLength;
}
/**
* Returns fake acceptor slot used for matching
* @returns {import("./item_acceptor").ItemAcceptorSlot}
*/
getFakeAcceptorSlot() {
return FAKE_BELT_ACCEPTOR_SLOT;
}
/**
* Returns fake acceptor slot used for matching
* @returns {import("./item_ejector").ItemEjectorSlot}
*/
getFakeEjectorSlots() {
assert(
FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction],
"Invalid belt direction: ",
this.direction
);
return FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction];
}
/**
* Converts from belt space (0 = start of belt ... 1 = end of belt) to the local
* belt coordinates (-0.5|-0.5 to 0.5|0.5)

View File

@ -0,0 +1,44 @@
import { Component } from "../component";
import { types } from "../../savegame/serialization";
import { enumDirection, Vector } from "../../core/vector";
export class BeltUnderlaysComponent extends Component {
static getId() {
return "BeltUnderlays";
}
static getSchema() {
return {
underlays: types.array(
types.structured({
pos: types.vector,
direction: types.enum(enumDirection),
})
),
};
}
duplicateWithoutContents() {
const beltUnderlaysCopy = [];
for (let i = 0; i < this.underlays.length; ++i) {
const underlay = this.underlays[i];
beltUnderlaysCopy.push({
pos: underlay.pos.copy(),
direction: underlay.direction,
});
}
return new BeltUnderlaysComponent({
underlays: beltUnderlaysCopy,
});
}
/**
* @param {object} param0
* @param {Array<{pos: Vector, direction: enumDirection}>=} param0.underlays Where to render belt underlays
*/
constructor({ underlays }) {
super();
this.underlays = underlays;
}
}

View File

@ -6,27 +6,4 @@ export class HubComponent extends Component {
static getId() {
return "Hub";
}
static getSchema() {
return {
definitionsToAnalyze: types.array(types.knownType(ShapeDefinition)),
};
}
constructor() {
super();
/**
* Shape definitions in queue to be analyzed and counted towards the goal
* @type {Array<ShapeDefinition>}
*/
this.definitionsToAnalyze = [];
}
/**
* @param {ShapeDefinition} definition
*/
queueShapeDefinition(definition) {
this.definitionsToAnalyze.push(definition);
}
}

View File

@ -39,16 +39,6 @@ export class ItemAcceptorComponent extends Component {
directions: types.array(types.enum(enumDirection)),
filter: types.nullable(types.enum(enumItemType)),
// TODO: MIGRATE
layer: types.enum(enumLayer),
})
),
animated: types.bool,
beltUnderlays: types.array(
types.structured({
pos: types.vector,
direction: types.enum(enumDirection),
// TODO: MIGRATE
layer: types.enum(enumLayer),
})
@ -68,20 +58,8 @@ export class ItemAcceptorComponent extends Component {
});
}
const beltUnderlaysCopy = [];
for (let i = 0; i < this.beltUnderlays.length; ++i) {
const underlay = this.beltUnderlays[i];
beltUnderlaysCopy.push({
pos: underlay.pos.copy(),
direction: underlay.direction,
layer: underlay.layer,
});
}
return new ItemAcceptorComponent({
slots: slotsCopy,
beltUnderlays: beltUnderlaysCopy,
animated: this.animated,
});
}
@ -89,23 +67,16 @@ export class ItemAcceptorComponent extends Component {
*
* @param {object} param0
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items
* @param {boolean=} param0.animated Whether to animate item consumption
* @param {Array<{pos: Vector, direction: enumDirection, layer: enumLayer}>=} param0.beltUnderlays Where to render belt underlays
*/
constructor({ slots = [], beltUnderlays = [], animated = true }) {
constructor({ slots = [] }) {
super();
this.animated = animated;
/**
* Fixes belt animations
* @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>}
*/
this.itemConsumptionAnimations = [];
/* Which belt underlays to render */
this.beltUnderlays = beltUnderlays;
this.setSlots(slots);
}
@ -164,14 +135,12 @@ export class ItemAcceptorComponent extends Component {
* @param {BaseItem} item
*/
onItemAccepted(slotIndex, direction, item) {
if (this.animated) {
this.itemConsumptionAnimations.push({
item,
slotIndex,
direction,
animProgress: 0.0,
});
}
this.itemConsumptionAnimations.push({
item,
slotIndex,
direction,
animProgress: 0.0,
});
}
/**

View File

@ -5,6 +5,7 @@ import { types } from "../../savegame/serialization";
import { gItemRegistry } from "../../core/global_registries";
import { Entity } from "../entity";
import { enumLayer } from "../root";
import { BeltPath } from "../belt_path";
/**
* @typedef {{
@ -14,6 +15,7 @@ import { enumLayer } from "../root";
* layer: enumLayer,
* progress: number?,
* cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot,
* cachedBeltPath?: BeltPath,
* cachedTargetEntity?: Entity
* }} ItemEjectorSlot
*/
@ -24,10 +26,8 @@ export class ItemEjectorComponent extends Component {
}
static getSchema() {
// The cachedDestSlot, cachedTargetEntity, and cachedConnectedSlots fields
// are not serialized.
// The cachedDestSlot, cachedTargetEntity fields are not serialized.
return {
instantEject: types.bool,
slots: types.array(
types.structured({
pos: types.vector,
@ -55,7 +55,6 @@ export class ItemEjectorComponent extends Component {
return new ItemEjectorComponent({
slots: slotsCopy,
instantEject: this.instantEject,
});
}
@ -63,19 +62,12 @@ export class ItemEjectorComponent extends Component {
*
* @param {object} param0
* @param {Array<{pos: Vector, direction: enumDirection, layer?: enumLayer}>=} param0.slots The slots to eject on
* @param {boolean=} param0.instantEject If the ejection is instant
*/
constructor({ slots = [], instantEject = false }) {
constructor({ slots = [] }) {
super();
// How long items take to eject
this.instantEject = instantEject;
this.setSlots(slots);
/** @type {ItemEjectorSlot[]} */
this.cachedConnectedSlots = null;
/**
* Whether this ejector slot is enabled
*/
@ -162,7 +154,7 @@ export class ItemEjectorComponent extends Component {
return false;
}
this.slots[slotIndex].item = item;
this.slots[slotIndex].progress = this.instantEject ? 1 : 0;
this.slots[slotIndex].progress = 0;
return true;
}

View File

@ -5,6 +5,7 @@ import { AtlasSprite } from "../../core/sprites";
import { enumDirection, Vector } from "../../core/vector";
import { types } from "../../savegame/serialization";
import { Component } from "../component";
import { getBuildingDataFromCode } from "../building_codes";
export class StaticMapEntityComponent extends Component {
static getId() {
@ -17,21 +18,43 @@ export class StaticMapEntityComponent extends Component {
tileSize: types.tileVector,
rotation: types.float,
originalRotation: types.float,
spriteKey: types.nullable(types.string),
blueprintSpriteKey: types.string,
silhouetteColor: types.nullable(types.string),
// See building_codes.js
code: types.uint,
};
}
/**
* Returns the sprite
* @returns {AtlasSprite}
*/
getSprite() {
return getBuildingDataFromCode(this.code).sprite;
}
/**
* Returns the blueprint sprite
* @returns {AtlasSprite}
*/
getBlueprintSprite() {
return getBuildingDataFromCode(this.code).blueprintSprite;
}
/**
* Returns the silhouette color
* @returns {string}
*/
getSilhouetteColor() {
return getBuildingDataFromCode(this.code).silhouetteColor;
}
duplicateWithoutContents() {
return new StaticMapEntityComponent({
origin: this.origin.copy(),
tileSize: this.tileSize.copy(),
rotation: this.rotation,
originalRotation: this.originalRotation,
spriteKey: this.spriteKey,
silhouetteColor: this.silhouetteColor,
blueprintSpriteKey: this.blueprintSpriteKey,
code: this.code,
});
}
@ -42,18 +65,14 @@ export class StaticMapEntityComponent extends Component {
* @param {Vector=} param0.tileSize Size of the entity in tiles
* @param {number=} param0.rotation Rotation in degrees. Must be multiple of 90
* @param {number=} param0.originalRotation Original Rotation in degrees. Must be multiple of 90
* @param {string=} param0.spriteKey Optional sprite
* @param {string} param0.blueprintSpriteKey Blueprint sprite, required
* @param {string=} param0.silhouetteColor Optional silhouette color override
* @param {number=} param0.code Building code
*/
constructor({
origin = new Vector(),
tileSize = new Vector(1, 1),
rotation = 0,
originalRotation = 0,
spriteKey = null,
silhouetteColor = null,
blueprintSpriteKey = null,
code = 0,
}) {
super();
assert(
@ -63,11 +82,9 @@ export class StaticMapEntityComponent extends Component {
this.origin = origin;
this.tileSize = tileSize;
this.spriteKey = spriteKey;
this.rotation = rotation;
this.code = code;
this.originalRotation = originalRotation;
this.silhouetteColor = silhouetteColor;
this.blueprintSpriteKey = blueprintSpriteKey;
}
/**

View File

@ -8,6 +8,8 @@ export const enumPinSlotType = {
negativeEnergyEjector: "negativeEnergyEjector",
positiveEnergyAcceptor: "positiveEnergyAcceptor",
negativeEnergyAcceptor: "negativeEnergyAcceptor",
logicalEjector: "logicalEjector",
logicalAcceptor: "logicalAcceptor",
};
/** @typedef {{

View File

@ -398,7 +398,7 @@ export class GameCore {
if (!this.root.camera.getIsMapOverlayActive()) {
// Underlays for splitters / balancers
systems.itemAcceptor.drawUnderlays(params, enumLayer.regular);
systems.beltUnderlays.drawUnderlays(params, enumLayer.regular);
// Belt items
systems.belt.drawLayerBeltItems(params, enumLayer.regular);

View File

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

View File

@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage";
import { EnergyGeneratorComponent } from "./components/energy_generator";
import { WiredPinsComponent } from "./components/wired_pins";
import { EnergyConsumerComponent } from "./components/energy_consumer";
import { BeltUnderlaysComponent } from "./components/belt_underlays";
/* typehints:end */
/**
@ -65,6 +66,9 @@ export class EntityComponentStorage {
/** @type {EnergyConsumerComponent} */
this.EnergyConsumer;
/** @type {BeltUnderlaysComponent} */
this.BeltUnderlays;
/* typehints:end */
}
}

View File

@ -16,6 +16,7 @@ import { StorageSystem } from "./systems/storage";
import { EnergyGeneratorSystem } from "./systems/energy_generator";
import { WiredPinsSystem } from "./systems/wired_pins";
import { EnergyConsumerSystem } from "./systems/energy_consumer";
import { BeltUnderlaysSystem } from "./systems/belt_underlays";
const logger = createLogger("game_system_manager");
@ -68,6 +69,9 @@ export class GameSystemManager {
/** @type {EnergyConsumerSystem} */
energyConsumer: null,
/** @type {BeltUnderlaysSystem} */
beltUnderlays: null,
/* typehints:end */
};
this.systemUpdateOrder = [];
@ -110,6 +114,8 @@ export class GameSystemManager {
add("energyConsumer", EnergyConsumerSystem);
add("beltUnderlays", BeltUnderlaysSystem);
// IMPORTANT: Must be after belt system since belt system can change the
// orientation of an entity after it is placed -> the item acceptor cache
// then would be invalid

View File

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

View File

@ -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 {
@ -18,6 +18,7 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
import { HUDBuildingPlacerLogic } from "./building_placer_logic";
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
import { enumLayer } from "../../root";
import { getCodeFromBuildingData } from "../../building_codes";
export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
/**
@ -84,9 +85,8 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
label: "lock-direction-indicator",
});
// Loader.getSprite("sprites/misc/lock_direction_indicator.png").draw(context, 0, 0, 48, 48);
context.fillStyle = THEME.map.directionLock[enumLayer.wires].color;
context.strokeStyle = THEME.map.directionLock[enumLayer.wires].color;
context.fillStyle = THEME.map.directionLock[layer].color;
context.strokeStyle = THEME.map.directionLock[layer].color;
context.lineWidth = 2;
const padding = 5;
@ -313,12 +313,16 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
staticComp.rotation = rotation;
staticComp.tileSize = metaBuilding.getDimensions(this.currentVariant.get());
metaBuilding.updateVariants(this.fakeEntity, rotationVariant, this.currentVariant.get());
staticComp.code = getCodeFromBuildingData(
this.currentMetaBuilding.get(),
this.currentVariant.get(),
rotationVariant
);
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
// 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();

View File

@ -13,6 +13,7 @@ import { SOUNDS } from "../../../platform/sound";
import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
import { enumHubGoalRewards } from "../../tutorial_goals";
import { enumLayer } from "../../root";
import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
/**
* Contains all logic for the building placer - this doesn't include the rendering
@ -338,109 +339,27 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
}
// Try to extract the building
const extracted = this.hack_reconstructMetaBuildingAndVariantFromBuilding(contents);
const buildingCode = contents.components.StaticMapEntity.code;
const extracted = getBuildingDataFromCode(buildingCode);
// If the building we are picking is the same as the one we have, clear the cursor.
if (
!extracted ||
(extracted.metaBuilding === this.currentMetaBuilding.get() &&
extracted.variant === this.currentVariant.get())
this.currentMetaBuilding.get() &&
extracted.metaInstance.getId() === this.currentMetaBuilding.get().getId() &&
extracted.variant === this.currentVariant.get()
) {
this.currentMetaBuilding.set(null);
return;
}
this.currentMetaBuilding.set(extracted.metaBuilding);
this.currentMetaBuilding.set(extracted.metaInstance);
this.currentVariant.set(extracted.variant);
this.currentBaseRotation = contents.components.StaticMapEntity.rotation;
}
/**
* HACK!
*
* This attempts to reconstruct the meta building and its variant from a given entity
* @param {Entity} entity
* @returns {{ metaBuilding: MetaBuilding, variant: string }}
* Switches the side for the direction lock manually
*/
hack_reconstructMetaBuildingAndVariantFromBuilding(entity) {
if (entity.components.Hub) {
// Hub is not copyable
return null;
}
const matches = [];
const metaBuildings = gMetaBuildingRegistry.entries;
for (let i = 0; i < metaBuildings.length; ++i) {
const metaBuilding = metaBuildings[i];
const availableVariants = metaBuilding.getAvailableVariants(this.root);
checkVariant: for (let k = 0; k < availableVariants.length; ++k) {
const variant = availableVariants[k];
let unplaced = metaBuilding.createEntity({
root: this.root,
variant,
origin: new Vector(0, 0),
rotation: 0,
originalRotation: 0,
rotationVariant: 0,
});
// Compare if both entities share the same components
for (let component in entity.components) {
if ((entity.components[component] == null) !== (unplaced.components[component] == null)) {
continue checkVariant;
}
}
// Check for same item processor
if (
entity.components.ItemProcessor &&
entity.components.ItemProcessor.type != unplaced.components.ItemProcessor.type
) {
continue checkVariant;
}
// Check for underground belt
if (
entity.components.UndergroundBelt &&
entity.components.UndergroundBelt.tier != unplaced.components.UndergroundBelt.tier
) {
continue checkVariant;
}
// Check for same sprite key - except for underground belts
// since the sprite may vary here
if (
!entity.components.UndergroundBelt &&
entity.components.StaticMapEntity.spriteKey !=
unplaced.components.StaticMapEntity.spriteKey
) {
continue checkVariant;
}
if (metaBuilding.id === "wire" && entity.layer !== enumLayer.wires) {
continue checkVariant;
}
if (metaBuilding.id === "belt" && entity.layer !== enumLayer.regular) {
continue checkVariant;
}
matches.push({ metaBuilding, variant });
}
}
if (matches.length == 1) {
const staticEntity = entity.components.StaticMapEntity;
const key = staticEntity.spriteKey || staticEntity.blueprintSpriteKey;
assert(
key &&
key.includes(matches[0].metaBuilding.id) &&
(matches[0].variant === defaultBuildingVariant || key.includes(matches[0].variant))
);
return matches[0];
}
return null;
}
switchDirectionLockSide() {
this.currentDirectionLockSide = 1 - this.currentDirectionLockSide;
}
@ -673,7 +592,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
origin: new Vector(0, 0),
rotation: 0,
tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(),
blueprintSpriteKey: "",
code: getCodeFromBuildingData(metaBuilding, variant, 0),
})
);
metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get());

View File

@ -88,8 +88,8 @@ export class HUDDebugInfo extends BaseHUDPart {
const mouseTile = this.root.camera.screenToWorld(mousePos).toTileSpace();
const cameraTile = this.root.camera.center.toTileSpace();
this.trackedMousePosition.set(`Pos: <code>${mouseTile.x}</code> / <code>${mouseTile.y}</code>`);
this.trackedCameraPosition.set(`Center: <code>${cameraTile.x}</code> / <code>${cameraTile.y}</code>`);
this.trackedMousePosition.set(`Mouse: <code>${mouseTile.x}</code> / <code>${mouseTile.y}</code>`);
this.trackedCameraPosition.set(`Camera: <code>${cameraTile.x}</code> / <code>${cameraTile.y}</code>`);
}
/**

View File

@ -15,7 +15,9 @@ export class HUDEntityDebugger extends BaseHUDPart {
`
);
/** @type {HTMLElement} */
this.mousePosElem = this.element.querySelector(".mousePos");
/** @type {HTMLElement} */
this.chunkPosElem = this.element.querySelector(".chunkPos");
this.entityInfoElem = this.element.querySelector(".entityInfo");
}

View File

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

View File

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

View File

@ -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) {
@ -23,7 +21,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
<strong>${T.ingame.settingsMenu.beltsPlaced}</strong><span class="beltsPlaced"></span>
<strong>${T.ingame.settingsMenu.buildingsPlaced}</strong><span class="buildingsPlaced"></span>
<strong>${T.ingame.settingsMenu.playtime}</strong><span class="playtime"></span>
`
);
@ -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,20 +91,25 @@ 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);
this.statsElement.querySelector(".playtime").innerText = T.global.time.xMinutes.replace(
"<x>",
"" + totalMinutesPlayed
);
this.statsElement.querySelector(".buildingsPlaced").innerText = formatBigNumberFull(
/** @type {HTMLElement} */
const playtimeElement = this.statsElement.querySelector(".playtime");
/** @type {HTMLElement} */
const buildingsPlacedElement = this.statsElement.querySelector(".buildingsPlaced");
/** @type {HTMLElement} */
const beltsPlacedElement = this.statsElement.querySelector(".beltsPlaced");
playtimeElement.innerText = T.global.time.xMinutes.replace("<x>", `${totalMinutesPlayed}`);
buildingsPlacedElement.innerText = formatBigNumberFull(
this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent).length -
this.root.entityMgr.getAllWithComponent(BeltComponent).length
);
this.statsElement.querySelector(".beltsPlaced").innerText = formatBigNumberFull(
beltsPlacedElement.innerText = formatBigNumberFull(
this.root.entityMgr.getAllWithComponent(BeltComponent).length
);
}

View File

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

Some files were not shown because too many files have changed in this diff Show More