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 uses: ibiqlik/action-yamllint@v1.0.0
with: with:
file_or_dir: translations/*.yaml 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 ### 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. 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. 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, ""); return commitHash.replace(/^\s+|\s+$/g, "");
}, },
getAllResourceImages() { getAllResourceImages() {
return glob return glob
.sync("res/**/*.@(png|svg|jpg)", { cwd: ".." }) .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() { getVersion() {
return trim(fs.readFileSync(path.join(__dirname, "..", "version")).toString()); 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 = { module.exports = {

View File

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

View File

@ -9,7 +9,7 @@ function computeIntegrityHash(fullPath, algorithm = "sha256") {
return algorithm + "-" + hash; return algorithm + "-" + hash;
} }
function gulptasksHTML($, gulp, buildFolder, browserSync) { function gulptasksHTML($, gulp, buildFolder) {
const commitHash = buildUtils.getRevision(); const commitHash = buildUtils.getRevision();
async function buildHtml( async function buildHtml(
apiUrl, apiUrl,
@ -27,9 +27,8 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
return gulp return gulp
.src("../src/html/" + (standalone ? "index.standalone.html" : "index.html")) .src("../src/html/" + (standalone ? "index.standalone.html" : "index.html"))
.pipe( .pipe(
$.dom(function () { $.dom(/** @this {Document} **/ function () {
// @ts-ignore const document = this;
const document = /** @type {Document} */ (this);
// Preconnect to api // Preconnect to api
const prefetchLink = document.createElement("link"); const prefetchLink = document.createElement("link");
@ -38,21 +37,6 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
prefetchLink.setAttribute("crossorigin", "anonymous"); prefetchLink.setAttribute("crossorigin", "anonymous");
document.head.appendChild(prefetchLink); 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 // Append css
const css = document.createElement("link"); const css = document.createElement("link");
css.rel = "stylesheet"; css.rel = "stylesheet";
@ -68,17 +52,6 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
} }
document.head.appendChild(css); 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) { if (app) {
// Append cordova link // Append cordova link
const cdv = document.createElement("script"); const cdv = document.createElement("script");
@ -114,18 +87,9 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
// Do not need to preload in app or standalone // Do not need to preload in app or standalone
if (!hasLocalFiles) { if (!hasLocalFiles) {
// Preload images
const images = buildUtils.getAllResourceImages();
// Preload essentials // Preload essentials
const preloads = ["fonts/GameFont.woff2"]; 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 => { preloads.forEach(src => {
const preloadLink = document.createElement("link"); const preloadLink = document.createElement("link");
preloadLink.rel = "preload"; preloadLink.rel = "preload";
@ -138,23 +102,6 @@ function gulptasksHTML($, gulp, buildFolder, browserSync) {
} }
document.head.appendChild(preloadLink); 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+")`; const loadingSvg = `background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46YXV0bztiYWNrZ3JvdW5kOjAgMCIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgZGlzcGxheT0iYmxvY2siPjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5Mzc0NyIgc3Ryb2tlLXdpZHRoPSIzIiByPSI0MiIgc3Ryb2tlLWRhc2hhcnJheT0iMTk3LjkyMDMzNzE3NjE1Njk4IDY3Ljk3MzQ0NTcyNTM4NTY2IiB0cmFuc2Zvcm09InJvdGF0ZSg0OC4yNjUgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iNS41NTU1NTU1NTU1NTU1NTVzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIi8+PC9jaXJjbGU+PC9zdmc+")`;

View File

@ -6,12 +6,6 @@ function requireUncached(module) {
} }
function gulptasksJS($, gulp, buildFolder, browserSync) { 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 //// DEV
@ -71,6 +65,7 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
gulp.task("js.staging", gulp.parallel("js.staging.transpiled", "js.staging.latest")); gulp.task("js.staging", gulp.parallel("js.staging.transpiled", "js.staging.latest"));
//// PROD //// PROD
gulp.task("js.prod.transpiled", () => { gulp.task("js.prod.transpiled", () => {
return gulp return gulp
.src("../src/js/main.js") .src("../src/js/main.js")
@ -167,8 +162,6 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
) )
.pipe(gulp.dest(buildFolder)); .pipe(gulp.dest(buildFolder));
}); });
// TODO: Tasks for te app
} }
module.exports = { module.exports = {

View File

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

View File

@ -1,11 +1,11 @@
const packager = require("electron-packager"); const packager = require("electron-packager");
const path = require("path"); const path = require("path");
const buildutils = require("./buildutils"); const { getVersion } = require("./buildutils");
const fs = require("fs"); const fs = require("fs");
const fse = require("fs-extra"); const fse = require("fs-extra");
const execSync = require("child_process").execSync; const execSync = require("child_process").execSync;
function gulptasksStandalone($, gulp, buildFolder) { function gulptasksStandalone($, gulp) {
const electronBaseDir = path.join(__dirname, "..", "electron"); const electronBaseDir = path.join(__dirname, "..", "electron");
const tempDestDir = path.join(__dirname, "..", "tmp_standalone_files"); const tempDestDir = path.join(__dirname, "..", "tmp_standalone_files");
@ -47,49 +47,7 @@ function gulptasksStandalone($, gulp, buildFolder) {
}); });
gulp.task("standalone.prepare.minifyCode", () => { gulp.task("standalone.prepare.minifyCode", () => {
return ( return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
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))
);
}); });
gulp.task("standalone.prepare.copyGamefiles", () => { gulp.task("standalone.prepare.copyGamefiles", () => {
@ -122,15 +80,14 @@ function gulptasksStandalone($, gulp, buildFolder) {
* @param {'win32'|'linux'|'darwin'} platform * @param {'win32'|'linux'|'darwin'} platform
* @param {'x64'|'ia32'} arch * @param {'x64'|'ia32'} arch
* @param {function():void} cb * @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")); const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
packager({ packager({
dir: tempDestBuildDir, dir: tempDestBuildDir,
appCopyright: "Tobias Springer", appCopyright: "Tobias Springer",
appVersion: buildutils.getVersion(), appVersion: getVersion(),
buildVersion: "1.0.0", buildVersion: "1.0.0",
arch, arch,
platform, platform,
@ -164,19 +121,6 @@ function gulptasksStandalone($, gulp, buildFolder) {
'#!/usr/bin/env bash\n./shapezio --no-sandbox "$@"\n' '#!/usr/bin/env bash\n./shapezio --no-sandbox "$@"\n'
); );
fs.chmodSync(path.join(appPath, "play.sh"), 0o775); 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") { 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.win64", cb => packageStandalone("win32", "x64", cb));
gulp.task("standalone.package.prod.win32", cb => packageStandalone("win32", "ia32", cb, true)); gulp.task("standalone.package.prod.win32", cb => packageStandalone("win32", "ia32", cb));
gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb, true)); gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb));
gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb, true)); gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb));
gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb, true)); gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb));
gulp.task( gulp.task(
"standalone.package.prod", "standalone.package.prod",
@ -240,8 +184,6 @@ function gulptasksStandalone($, gulp, buildFolder) {
"standalone.package.prod.win64", "standalone.package.prod.win64",
"standalone.package.prod.linux64", "standalone.package.prod.linux64",
"standalone.package.prod.darwin64" "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 translationsSourceDir = path.join(__dirname, "..", "translations");
const translationsJsonDir = path.join(__dirname, "..", "src", "js", "built-temp"); const translationsJsonDir = path.join(__dirname, "..", "src", "js", "built-temp");
function gulptasksTranslations($, gulp, buildFolder) { function gulptasksTranslations($, gulp) {
gulp.task("translations.convertToJson", () => { gulp.task("translations.convertToJson", () => {
return gulp return gulp
.src(path.join(translationsSourceDir, "*.yaml")) .src(path.join(translationsSourceDir, "*.yaml"))

View File

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

View File

@ -2,14 +2,12 @@
const path = require("path"); const path = require("path");
const webpack = require("webpack"); const webpack = require("webpack");
const utils = require("./buildutils"); const { getRevision, getVersion, getAllResourceImages } = require("./buildutils");
const lzString = require("lz-string"); const lzString = require("lz-string");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const StringReplacePlugin = require("string-replace-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 UnusedFilesPlugin = require("unused-files-webpack-plugin").UnusedFilesWebpackPlugin;
// const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
module.exports = ({ module.exports = ({
enableAssert = false, enableAssert = false,
@ -34,9 +32,9 @@ module.exports = ({
G_APP_ENVIRONMENT: JSON.stringify(environment), G_APP_ENVIRONMENT: JSON.stringify(environment),
G_HAVE_ASSERT: enableAssert ? "true" : "false", G_HAVE_ASSERT: enableAssert ? "true" : "false",
G_BUILD_TIME: "" + new Date().getTime(), G_BUILD_TIME: "" + new Date().getTime(),
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()), G_BUILD_COMMIT_HASH: JSON.stringify(getRevision()),
G_BUILD_VERSION: JSON.stringify(utils.getVersion()), G_BUILD_VERSION: JSON.stringify(getVersion()),
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()), G_ALL_UI_IMAGES: JSON.stringify(getAllResourceImages()),
}; };
const minifyNames = environment === "prod"; const minifyNames = environment === "prod";
@ -143,9 +141,9 @@ module.exports = ({
ecma: es6 ? 6 : 5, ecma: es6 ? 6 : 5,
preamble: preamble:
"/* shapez.io Codebase - Copyright 2020 Tobias Springer - " + "/* 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"), cwd: path.join(__dirname, "..", "src", "js"),
patterns: ["../src/js/**/*.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: { module: {
rules: [ rules: [

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"scripts": { "scripts": {
"dev": "cd gulp && yarn gulp main.serveDev", "dev": "cd gulp && yarn gulp main.serveDev",
"tslint": "cd src/js && tsc", "tslint": "cd src/js && tsc",
"lint": "npx eslint src/js", "lint": "eslint src/js",
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*", "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", "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", "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", "lz-string": "^1.4.4",
"markdown-loader": "^4.0.0", "markdown-loader": "^4.0.0",
"match-all": "^1.2.5", "match-all": "^1.2.5",
"obfuscator-loader": "^1.1.2",
"phonegap-plugin-mobile-accessibility": "^1.0.5", "phonegap-plugin-mobile-accessibility": "^1.0.5",
"promise-polyfill": "^8.1.0", "promise-polyfill": "^8.1.0",
"query-string": "^6.8.1", "query-string": "^6.8.1",
"rusha": "^0.8.13", "rusha": "^0.8.13",
"serialize-error": "^3.0.0", "serialize-error": "^3.0.0",
"sloc": "^0.2.1",
"strictdom": "^1.0.1", "strictdom": "^1.0.1",
"string-replace-webpack-plugin": "^0.1.3", "string-replace-webpack-plugin": "^0.1.3",
"terser-webpack-plugin": "^1.1.0", "terser-webpack-plugin": "^1.1.0",
"typescript": "3.9.3", "typescript": "3.9.3",
"uglify-template-string-loader": "^1.1.0", "uglify-template-string-loader": "^1.1.0",
"unused-files-webpack-plugin": "^3.4.0", "unused-files-webpack-plugin": "^3.4.0",
"webpack": "^4.31.0", "webpack": "^4.43.0",
"webpack-bundle-analyzer": "^3.0.3", "webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.1.0", "webpack-cli": "^3.1.0",
"webpack-deep-scope-plugin": "^1.6.0", "webpack-deep-scope-plugin": "^1.6.0",
@ -87,16 +85,14 @@
"imagemin-pngquant": "^8.0.0", "imagemin-pngquant": "^8.0.0",
"jimp": "^0.6.1", "jimp": "^0.6.1",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"onesky-fetch": "^0.0.7",
"postcss-assets": "^5.0.0", "postcss-assets": "^5.0.0",
"postcss-preset-env": "^6.5.0", "postcss-preset-env": "^6.5.0",
"postcss-round-subpixels": "^1.2.0", "postcss-round-subpixels": "^1.2.0",
"postcss-unprefix": "^2.1.3", "postcss-unprefix": "^2.1.3",
"prettier": "^2.0.4", "prettier": "^2.0.4",
"sass-unused": "^0.3.0", "sass-unused": "^0.3.0",
"speed-measure-webpack-plugin": "^1.3.1",
"strip-json-comments": "^3.0.1", "strip-json-comments": "^3.0.1",
"trim": "^0.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-chainable.png</key>
<key type="filename">sprites/blueprints/miner.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-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/rotater.png</key>
<key type="filename">sprites/blueprints/splitter-compact-inverse.png</key> <key type="filename">sprites/blueprints/splitter-compact-inverse.png</key>
<key type="filename">sprites/blueprints/splitter-compact.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/blueprints/underground_belt_exit.png</key>
<key type="filename">sprites/buildings/miner-chainable.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-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-inverse.png</key>
<key type="filename">sprites/buildings/splitter-compact.png</key> <key type="filename">sprites/buildings/splitter-compact.png</key>
<key type="filename">sprites/buildings/underground_belt_entry-tier2.png</key> <key type="filename">sprites/buildings/underground_belt_entry-tier2.png</key>
@ -566,21 +568,6 @@
<key>scale9FromFile</key> <key>scale9FromFile</key>
<false/> <false/>
</struct> </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> <key type="filename">sprites/misc/storage_overlay.png</key>
<struct type="IndividualSpriteSettings"> <struct type="IndividualSpriteSettings">
<key>pivotPoint</key> <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-face-color: #888;
scrollbar-track-color: rgba(255, 255, 255, 0.1); scrollbar-track-color: rgba(255, 255, 255, 0.1);
// Firefox
scrollbar-color: #cdd0d4 rgba(#000, 0.05);
overflow: hidden; overflow: hidden;
@include Text; @include Text;
@ -369,7 +372,7 @@ canvas {
} }
.pressed { .pressed {
transform: scale(0.95) !important; transform: scale(0.98) !important;
animation: none !important; animation: none !important;
} }

View File

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

View File

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

View File

@ -1,6 +1,23 @@
#state_AboutState { #state_AboutState {
> .container .content { > .container .content {
@include S(max-width, 600px);
@include PlainText; @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 { a {

View File

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

View File

@ -55,7 +55,7 @@
opacity: 0; opacity: 0;
display: none; display: none;
transform: translate(50%, 50%); transform: translate(50%, 50%);
filter: blur(10px); filter: blur(D(3px));
$opacity: 0.2; $opacity: 0.2;
&.loaded { &.loaded {
@ -178,6 +178,10 @@
transform: translateX(50%) rotate(-7deg) scale(1.1); transform: translateX(50%) rotate(-7deg) scale(1.1);
} }
} }
@include DarkThemeOverride {
color: $colorBlueBright;
}
} }
} }
@ -201,33 +205,6 @@
flex-grow: 1; flex-grow: 1;
@include S(margin-bottom, 10px); @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 { .mainContainer {

View File

@ -1,113 +1,188 @@
#state_SettingsState { #state_SettingsState {
.content { $colorCategoryButton: #eee;
.versionbar { $colorCategoryButtonSelected: #5f748b;
@include S(margin-top, 20px);
@include SuperSmallText; .container .content {
display: grid; display: flex;
align-items: center; overflow-y: scroll;
grid-template-columns: 1fr auto;
.buildVersion { .categoryContainer {
display: flex; width: 100%;
flex-direction: column;
color: #aaadaf; .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 { .sidebar {
background-color: $colorGreenBright; 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 { .other {
@include S(padding, 10px); margin-top: auto;
background: #eeeff5;
@include S(border-radius, $globalBorderRadius);
@include S(margin-bottom, 5px);
label {
text-transform: uppercase;
@include Text;
} }
.desc { button {
@include S(margin-top, 5px); @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; @include SuperSmallText;
color: #aaadb2;
}
> .row {
display: grid; display: grid;
align-items: center; align-items: center;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
} .buildVersion {
&.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; display: flex;
align-items: center; flex-direction: column;
justify-content: center; color: #aaadaf;
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;
} }
} }
} }
} }
@include DarkThemeOverride { @include DarkThemeOverride {
.content { .container .content {
.setting { .sidebar {
background: darken($darkModeGameBackground, 10); button.categoryButton,
button.about {
background-color: #3f3f47;
.value.enum { &.active {
// dirty but works
filter: invert(0.85);
color: #222;
}
.value.checkbox {
background-color: #74767b;
&.checked {
background-color: $colorBlueBright; 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 { .gameState.textualState {
display: flex; display: grid;
flex-direction: column; grid-template-rows: auto 1fr;
justify-content: center; box-sizing: border-box;
align-items: center; @include S(padding, 32px);
$padding: 15px; height: 100vh;
.headerBar,
> .container .content {
@include S(width, 500px);
}
.headerBar { .headerBar {
display: flex; display: flex;
align-items: center;
justify-content: flex-start;
h1 { h1 {
display: flex; display: grid;
pointer-events: all; grid-template-columns: auto 1fr;
align-items: center; align-items: center;
pointer-events: all;
cursor: pointer; cursor: pointer;
@include SuperHeading; @include SuperHeading;
text-transform: uppercase; text-transform: uppercase;
@ -39,11 +33,17 @@
} }
> .container { > .container {
display: flex;
justify-content: center;
width: 100%;
overflow-y: auto;
> .content { > .content {
width: 100%;
background: #fff; background: #fff;
@include S(border-radius, $globalBorderRadius); @include S(border-radius, $globalBorderRadius);
@include S(padding, 10px); @include S(padding, 10px);
height: calc(80vh - #{D(60px)}); height: 100%;
overflow-y: auto; overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
pointer-events: all; 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 { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
import { AnalyticsInterface } from "./platform/analytics"; import { AnalyticsInterface } from "./platform/analytics";
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics"; import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
import { NoGameAnalytics } from "./platform/browser/no_game_analytics";
import { SoundImplBrowser } from "./platform/browser/sound"; import { SoundImplBrowser } from "./platform/browser/sound";
import { PlatformWrapperImplBrowser } from "./platform/browser/wrapper"; import { PlatformWrapperImplBrowser } from "./platform/browser/wrapper";
import { PlatformWrapperImplElectron } from "./platform/electron/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 { PlatformWrapperInterface } from "./platform/wrapper";
import { ApplicationSettings } from "./profile/application_settings"; import { ApplicationSettings } from "./profile/application_settings";
import { SavegameManager } from "./savegame/savegame_manager"; import { SavegameManager } from "./savegame/savegame_manager";
@ -34,6 +30,12 @@ import { PreloadState } from "./states/preload";
import { SettingsState } from "./states/settings"; import { SettingsState } from "./states/settings";
import { ShapezGameAnalytics } from "./platform/browser/game_analytics"; 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"); const logger = createLogger("application");
// Set the name of the hidden property and the change event for visibility // Set the name of the hidden property and the change event for visibility
@ -385,7 +387,7 @@ export class Application {
} }
const scale = this.getEffectiveUiScale(); const scale = this.getEffectiveUiScale();
waitNextFrame().then(() => document.documentElement.style.setProperty("--ui-scale", scale)); waitNextFrame().then(() => document.documentElement.style.setProperty("--ui-scale", `${scale}`));
window.focus(); window.focus();
} }
} }

View File

@ -4,8 +4,10 @@ export const CHANGELOG = [
date: "unreleased", date: "unreleased",
entries: [ entries: [
"WIRES", "WIRES",
"Reworked menu UI design (by dengr1605)",
"Allow holding ALT in belt planner to reverse direction (by jakobhellermann)", "Allow holding ALT in belt planner to reverse direction (by jakobhellermann)",
"Clear cursor when trying to pipette the same building twice (by hexy)", "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)", "Added keybinding to close menus (by isaisstillalive / Sandwichs-del)",
"Fix rare crash regarding the buildings toolbar (by isaisstillalive)", "Fix rare crash regarding the buildings toolbar (by isaisstillalive)",
"Fixed some phrases (by EnderDoom77)", "Fixed some phrases (by EnderDoom77)",
@ -24,7 +26,7 @@ export const CHANGELOG = [
version: "1.1.19", version: "1.1.19",
date: "02.07.2020", date: "02.07.2020",
entries: [ 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!", "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", version: "1.1.10",
date: "12.06.2020", date: "12.06.2020",
entries: [ 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!", "Steam cloud saves are now available!",
"Added and update more translations (Big thank you to all translators!)", "Added and update more translations (Big thank you to all translators!)",
"Prevent invalid connection if existing underground tunnel entrance exists (by jaysc)", "Prevent invalid connection if existing underground tunnel entrance exists (by jaysc)",
@ -221,7 +223,7 @@ export const CHANGELOG = [
entries: [ entries: [
"The official trailer is now ready! Check it out <a href='https://www.youtube.com/watch?v=KyorY1uIqiQ' target='_blank'>here</a>!", "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!", "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 hovering pinned shapes to enlarge them",
"Allow deselecting blueprints with right click and 'Q'", "Allow deselecting blueprints with right click and 'Q'",
"Move default key for deleting from 'X' to 'DEL'", "Move default key for deleting from 'X' to 'DEL'",

View File

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

View File

@ -1,7 +1,9 @@
// @ts-ignore // @ts-ignore
import CompressionWorker from "../webworkers/compression.worker"; import CompressionWorker from "../webworkers/compression.worker";
import { createLogger } from "./logging"; import { createLogger } from "./logging";
import { compressX64 } from "./lzstring"; import { round2Digits } from "./utils";
const logger = createLogger("async_compression"); const logger = createLogger("async_compression");
export let compressionPrefix = String.fromCodePoint(1); export let compressionPrefix = String.fromCodePoint(1);
@ -35,7 +37,6 @@ if (!checkCryptPrefix(compressionPrefix)) {
class AsynCompression { class AsynCompression {
constructor() { constructor() {
/** @type {Worker} */
this.worker = new CompressionWorker(); this.worker = new CompressionWorker();
this.currentJobId = 1000; this.currentJobId = 1000;
@ -52,7 +53,7 @@ class AsynCompression {
} }
const duration = performance.now() - jobData.startTime; 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; const resolver = jobData.resolver;
delete this.currentJobs[jobId]; delete this.currentJobs[jobId];
resolver(result); resolver(result);
@ -76,6 +77,7 @@ class AsynCompression {
* @param {string} text * @param {string} text
*/ */
compressFileAsync(text) { compressFileAsync(text) {
logger.log("Compressing", text.length, "bytes async");
return this.internalQueueJob("compressFile", { return this.internalQueueJob("compressFile", {
text, text,
compressionPrefix, compressionPrefix,
@ -100,6 +102,8 @@ class AsynCompression {
resolver: resolve, resolver: resolve,
startTime: performance.now(), startTime: performance.now(),
}; };
logger.log("Posting job", job, "/", jobId);
this.worker.postMessage({ jobId, job, data }); 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 {{ * @typedef {{
* frame: { x: number, y: number, w: number, h: number }, * frame: Position & Size,
* rotated: false, * rotated: boolean,
* spriteSourceSize: { x: number, y: number, w: number, h: number }, * spriteSourceSize: Position & Size,
* sourceSize: { w: number, h: number}, * sourceSize: Size,
* trimmed: true * trimmed: boolean
* }} SpriteDefinition * }} 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 { export class AtlasDefinition {
constructor(sourceData) { /**
this.sourceFileName = sourceData.meta.image; * @param {SourceData} sourceData
this.meta = sourceData.meta; */
constructor({ frames, meta }) {
/** @type {Object.<string, SpriteDefinition>} */ this.meta = meta;
this.sourceData = sourceData.frames; this.sourceData = frames;
this.sourceFileName = meta.image;
} }
getFullSourcePath() { getFullSourcePath() {
@ -22,6 +40,7 @@ export class AtlasDefinition {
} }
} }
/** @type {AtlasDefinition[]} **/
export const atlasFiles = require export const atlasFiles = require
// @ts-ignore // @ts-ignore
.context("../../../res_built/atlas/", false, /.*\.json/i) .context("../../../res_built/atlas/", false, /.*\.json/i)

View File

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

View File

@ -1,9 +1,9 @@
import { Rectangle } from "./rectangle";
import { globalConfig } from "./config"; import { globalConfig } from "./config";
/* typehints:start */ /**
import { GameRoot } from "../game/root"; * @typedef {import("../game/root").GameRoot} GameRoot
/* typehints:end */ * @typedef {import("./rectangle").Rectangle} Rectangle
*/
export class DrawParameters { export class DrawParameters {
constructor({ context, visibleRect, desiredAtlasScale, zoomLevel, root }) { constructor({ context, visibleRect, desiredAtlasScale, zoomLevel, root }) {

View File

@ -1,18 +1,13 @@
/* typehints:start */ /**
import { AtlasSprite } from "./sprites"; * @typedef {import("./sprites").AtlasSprite} AtlasSprite
import { DrawParameters } from "./draw_parameters"; * @typedef {import("./draw_parameters").DrawParameters} DrawParameters
/* typehints:end */ */
import { Vector } from "./vector";
import { Rectangle } from "./rectangle";
import { createLogger } from "./logging";
const logger = createLogger("draw_utils");
export function initDrawUtils() { export function initDrawUtils() {
CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) { CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) {
this.beginPath();
if (r < 0.05) { if (r < 0.05) {
this.beginPath();
this.rect(x, y, w, h); this.rect(x, y, w, h);
return; return;
} }
@ -20,25 +15,26 @@ export function initDrawUtils() {
if (w < 2 * r) { if (w < 2 * r) {
r = w / 2; r = w / 2;
} }
if (h < 2 * r) { if (h < 2 * r) {
r = h / 2; r = h / 2;
} }
this.beginPath();
this.moveTo(x + r, y); this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, 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 + h, x, y, r);
this.arcTo(x, y, x + w, y, r); this.arcTo(x, y, x + w, y, r);
// this.closePath();
}; };
CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) { CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) {
this.beginPath();
if (r < 0.05) { if (r < 0.05) {
this.beginPath();
this.rect(x, y, 1, 1); this.rect(x, y, 1, 1);
return; return;
} }
this.beginPath();
this.arc(x, y, r, 0, 2.0 * Math.PI); 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.rotate(-angle);
parameters.context.translate(-x, -y); 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 { SingletonFactory } from "./singleton_factory";
import { Factory } from "./factory"; import { Factory } from "./factory";
/* typehints:start */ /**
import { BaseGameSpeed } from "../game/time/base_game_speed"; * @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed
import { Component } from "../game/component"; * @typedef {import("../game/component").Component} Component
import { BaseItem } from "../game/base_item"; * @typedef {import("../game/base_item").BaseItem} BaseItem
import { MetaBuilding } from "../game/meta_building"; * @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding
/* typehints:end */
// These factories are here to remove circular dependencies // These factories are here to remove circular dependencies
/** @type {SingletonFactoryTemplate<MetaBuilding>} */ /** @type {SingletonFactoryTemplate<MetaBuilding>} */
export let gMetaBuildingRegistry = new SingletonFactory(); export let gMetaBuildingRegistry = new SingletonFactory();
/** @type {Object.<string, Array<typeof MetaBuilding>>} */ /** @type {Object.<string, Array<Class<MetaBuilding>>>} */
export let gBuildingsByCategory = null; export let gBuildingsByCategory = null;
/** @type {FactoryTemplate<Component>} */ /** @type {FactoryTemplate<Component>} */
@ -28,7 +28,7 @@ export let gItemRegistry = new Factory("item");
// Helpers // Helpers
/** /**
* @param {Object.<string, Array<typeof MetaBuilding>>} buildings * @param {Object.<string, Array<Class<MetaBuilding>>>} buildings
*/ */
export function initBuildingsByCategory(buildings) { export function initBuildingsByCategory(buildings) {
gBuildingsByCategory = 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 { makeOffscreenBuffer } from "./buffer_utils";
import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites"; import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites";
import { cachebust } from "./cachebust"; import { cachebust } from "./cachebust";
import { createLogger } from "./logging"; import { createLogger } from "./logging";
/**
* @typedef {import("../application").Application} Application
* @typedef {import("./atlas_definitions").AtlasDefinition} AtlasDefinition;
*/
const logger = createLogger("loader"); const logger = createLogger("loader");
const missingSpriteIds = {}; const missingSpriteIds = {};
class LoaderImpl { class LoaderImpl {
constructor() { constructor() {
/** @type {Application} */
this.app = null; this.app = null;
/** @type {Map<string, BaseSprite>} */ /** @type {Map<string, BaseSprite>} */
@ -23,6 +22,9 @@ class LoaderImpl {
this.rawImages = []; this.rawImages = [];
} }
/**
* @param {Application} app
*/
linkAppAfterBoot(app) { linkAppAfterBoot(app) {
this.app = app; this.app = app;
this.makeSpriteNotFoundCanvas(); 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 * @param {string} key
* @returns {RegularSprite} * @returns {RegularSprite}
*/ */
@ -155,44 +157,34 @@ class LoaderImpl {
* @param {AtlasDefinition} atlas * @param {AtlasDefinition} atlas
* @param {HTMLImageElement} loadedImage * @param {HTMLImageElement} loadedImage
*/ */
internalParseAtlas(atlas, loadedImage) { internalParseAtlas({ meta: { scale }, sourceData }, loadedImage) {
this.rawImages.push(loadedImage); this.rawImages.push(loadedImage);
for (const spriteKey in atlas.sourceData) { for (const spriteName in sourceData) {
const spriteData = atlas.sourceData[spriteKey]; const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName];
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteKey)); let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteName));
if (!sprite) { if (!sprite) {
sprite = new AtlasSprite({ sprite = new AtlasSprite(spriteName);
spriteName: spriteKey, this.sprites.set(spriteName, sprite);
});
this.sprites.set(spriteKey, sprite);
} }
const link = new SpriteAtlasLink({ const link = new SpriteAtlasLink({
packedX: spriteData.frame.x, packedX: frame.x,
packedY: spriteData.frame.y, packedY: frame.y,
packedW: spriteData.frame.w, packedW: frame.w,
packedH: spriteData.frame.h, packedH: frame.h,
packOffsetX: spriteData.spriteSourceSize.x, packOffsetX: spriteSourceSize.x,
packOffsetY: spriteData.spriteSourceSize.y, packOffsetY: spriteSourceSize.y,
atlas: loadedImage, atlas: loadedImage,
w: spriteData.sourceSize.w, w: sourceSize.w,
h: spriteData.sourceSize.h, 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 * Makes the canvas which shows the question mark, shown when a sprite was not found
*/ */
@ -216,14 +208,9 @@ class LoaderImpl {
// @ts-ignore // @ts-ignore
canvas.src = "not-found"; canvas.src = "not-found";
const resolutions = ["0.1", "0.25", "0.5", "0.75", "1"]; const sprite = new AtlasSprite("not-found");
const sprite = new AtlasSprite({ ["0.1", "0.25", "0.5", "0.75", "1"].forEach(resolution => {
spriteName: "not-found", sprite.linksByResolution[resolution] = new SpriteAtlasLink({
});
for (let i = 0; i < resolutions.length; ++i) {
const res = resolutions[i];
const link = new SpriteAtlasLink({
packedX: 0, packedX: 0,
packedY: 0, packedY: 0,
w: dims, w: dims,
@ -234,8 +221,8 @@ class LoaderImpl {
packedH: dims, packedH: dims,
atlas: canvas, atlas: canvas,
}); });
sprite.linksByResolution[res] = link; });
}
this.spriteNotFoundSprite = sprite; this.spriteNotFoundSprite = sprite;
} }
} }

View File

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

View File

@ -1,5 +1,5 @@
import { globalConfig } from "./config"; import { globalConfig } from "./config";
import { clamp, epsilonCompare, round2Digits } from "./utils"; import { epsilonCompare, round2Digits } from "./utils";
import { Vector } from "./vector"; import { Vector } from "./vector";
export class Rectangle { export class Rectangle {

View File

@ -1,19 +1,12 @@
import { globalConfig } from "./config"; import { createHash } from "rusha";
import { decompressX64, compressX64 } from "./lzstring";
const Rusha = require("rusha"); import { decompressX64 } from "./lzstring";
const encryptKey = globalConfig.info.sgSalt;
export function decodeHashedString(s) {
return decompressX64(s);
}
export function sha1(str) { export function sha1(str) {
return Rusha.createHash().update(str).digest("hex"); return createHash().update(str).digest("hex");
} }
// Window.location.host // Window.location.host
export function getNameOfProvider() { 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 { DrawParameters } from "./draw_parameters";
import { Rectangle } from "./rectangle"; import { Rectangle } from "./rectangle";
import { epsilonCompare, round3Digits } from "./utils"; import { round3Digits } from "./utils";
const floorSpriteCoordinates = false; const floorSpriteCoordinates = false;
@ -63,10 +63,9 @@ export class SpriteAtlasLink {
export class AtlasSprite extends BaseSprite { export class AtlasSprite extends BaseSprite {
/** /**
* *
* @param {object} param0 * @param {string} spriteName
* @param {string} param0.spriteName
*/ */
constructor({ spriteName = "sprite" }) { constructor(spriteName = "sprite") {
super(); super();
/** @type {Object.<string, SpriteAtlasLink>} */ /** @type {Object.<string, SpriteAtlasLink>} */
this.linksByResolution = {}; this.linksByResolution = {};
@ -158,7 +157,9 @@ export class AtlasSprite extends BaseSprite {
const scale = parameters.desiredAtlasScale; const scale = parameters.desiredAtlasScale;
const link = this.linksByResolution[scale]; 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 scaleW = w / link.w;
const scaleH = h / link.h; const scaleH = h / link.h;
@ -195,8 +196,6 @@ export class AtlasSprite extends BaseSprite {
destH = intersection.h; destH = intersection.h;
} }
// assert(epsilonCompare(scaleW, scaleH), "Sprite should be square for cached rendering");
if (floorSpriteCoordinates) { if (floorSpriteCoordinates) {
parameters.context.drawImage( parameters.context.drawImage(
link.atlas, link.atlas,

View File

@ -1,46 +1,7 @@
import { globalConfig, IS_DEBUG } from "./config";
import { Vector } from "./vector";
import { T } from "../translations"; 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"]; 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 if this platform is android
* @returns {boolean} * @returns {boolean}
@ -66,7 +27,7 @@ export function isIos() {
/** /**
* Returns a platform name * Returns a platform name
* @returns {string} * @returns {"android" | "browser" | "ios" | "standalone" | "unknown"}
*/ */
export function getPlatformName() { export function getPlatformName() {
if (G_IS_STANDALONE) { if (G_IS_STANDALONE) {
@ -96,60 +57,13 @@ export function getIPCRenderer() {
return ipcRenderer; 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 * Makes a new 2D array with undefined contents
* @param {number} w * @param {number} w
* @param {number} h * @param {number} h
* @param {string=} context
* @returns {Array<Array<any>>} * @returns {Array<Array<any>>}
*/ */
export function make2DUndefinedArray(w, h, context = null) { export function make2DUndefinedArray(w, h) {
const result = new Array(w); const result = new Array(w);
for (let x = 0; x < w; ++x) { for (let x = 0; x < w; ++x) {
result[x] = new Array(h); result[x] = new Array(h);
@ -157,33 +71,6 @@ export function make2DUndefinedArray(w, h, context = null) {
return result; 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) * 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 * Chooses a random entry of an array
* @param {Array | string} arr * @template T
* @param {T[]} arr
* @returns {T}
*/ */
export function randomChoice(arr) { export function randomChoice(arr) {
return arr[Math.floor(Math.random() * arr.length)]; return arr[Math.floor(Math.random() * arr.length)];
@ -304,23 +193,6 @@ export function arrayDeleteValue(array, value) {
return arrayDelete(array, index); 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 * Compare two floats for epsilon equality
* @param {number} a * @param {number} a
@ -331,15 +203,6 @@ export function epsilonCompare(a, b, epsilon = 1e-5) {
return Math.abs(a - b) < epsilon; 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 * Interpolates two numbers
* @param {number} a * @param {number} a
@ -399,17 +262,6 @@ export function findNiceIntegerValue(num) {
return Math.ceil(findNiceValue(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 * Formats a big number
* @param {number} num * @param {number} num
@ -477,92 +329,12 @@ export function formatBigNumberFull(num, divider = T.global.thousandsDivider) {
return out.substring(0, out.length - 1); 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 * Waits two frames so the ui is updated
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export function waitNextFrame() { export function waitNextFrame() {
return new Promise(function (resolve, reject) { return new Promise(function (resolve) {
window.requestAnimationFrame(function () { window.requestAnimationFrame(function () {
window.requestAnimationFrame(function () { window.requestAnimationFrame(function () {
resolve(); resolve();
@ -617,27 +389,13 @@ export function clamp(v, minimum = 0, maximum = 1) {
return Math.max(minimum, Math.min(maximum, v)); 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 * Helper method to create a new div element
* @param {string=} id * @param {string=} id
* @param {Array<string>=} classes * @param {Array<string>=} classes
* @param {string=} innerHTML * @param {string=} innerHTML
*/ */
export function makeDivElement(id = null, classes = [], innerHTML = "") { function makeDivElement(id = null, classes = [], innerHTML = "") {
const div = document.createElement("div"); const div = document.createElement("div");
if (id) { if (id) {
div.id = id; div.id = id;
@ -662,20 +420,6 @@ export function makeDiv(parent, id = null, classes = [], innerHTML = "") {
return div; 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 * Helper method to create a new button element
* @param {Array<string>=} classes * @param {Array<string>=} classes
@ -703,19 +447,6 @@ export function makeButton(parent, classes = [], innerHTML = "") {
return element; 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 * Removes all children of the given element
* @param {Element} elem * @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. * 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 * We use 3 digits of precision, this allows us to store precision of 1 ms without
* the risk to simulation errors due to resync issues * the risking simulation errors due to resync issues
* @param {number} value * @param {number} value
*/ */
export function quantizeFloat(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" * Formats an amount of seconds into something like "5s ago"
* @param {number} secs Seconds * @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" * Formats a number like 2.5 to "2.5 items / s"
* @param {number} speed * @param {number} speed

View File

@ -1,14 +1,14 @@
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters"; import { DrawParameters } from "../core/draw_parameters";
import { gItemRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle";
import { epsilonCompare, round4Digits } from "../core/utils"; 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 { BaseItem } from "./base_item";
import { Entity } from "./entity"; import { Entity } from "./entity";
import { GameRoot, enumLayer } from "./root"; import { GameRoot } from "./root";
import { Rectangle } from "../core/rectangle";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { gItemRegistry } from "../core/global_registries";
const logger = createLogger("belt_path"); const logger = createLogger("belt_path");
@ -125,14 +125,6 @@ export class BeltPath extends BasicSerializableObject {
return globalConfig.beltItemSpacingByLayer[this.layer]; 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 * Tries to accept the item
* @param {BaseItem} item * @param {BaseItem} item
@ -167,7 +159,7 @@ export class BeltPath extends BasicSerializableObject {
* @returns {BaseItem|null} * @returns {BaseItem|null}
*/ */
findItemAtTile(tile) { findItemAtTile(tile) {
// TODO: This breaks color blind mode otherwise // @TODO: This breaks color blind mode otherwise
return null; 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() { onPathChanged() {
this.ejectorComp = this.entityPath[this.entityPath.length - 1].components.ItemEjector; this.layer = this.entityPath[0].layer;
this.ejectorSlot = this.ejectorComp.slots[0]; this.acceptorTarget = this.computeAcceptingEntityAndSlot();
}
for (let i = 0; i < this.entityPath.length; ++i) { /**
const ejectorComp = this.entityPath[i].components.ItemEjector; * Called by the belt system when the surroundings changed
const isLast = i === this.entityPath.length - 1; */
ejectorComp.enabled = isLast; onSurroundingsChanged() {
this.onPathChanged();
}
// Clear all slots of non-end entities /**
if (!isLast) { * Finds the entity which accepts our items
for (let k = 0; k < ejectorComp.slots.length; ++k) { * @return {{ entity: Entity, slot: number, direction?: enumDirection }}
ejectorComp.slots[k].item = null; */
ejectorComp.slots[k].progress = 0.0; 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 // 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); 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); const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity);
if (!followUp) { if (!followUp) {
return fail( 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 // Check spacing
if (this.spacingToFirstItem > this.totalLength + 0.005) { if (this.spacingToFirstItem > this.totalLength + 0.005) {
return fail( return fail(
@ -370,14 +401,6 @@ export class BeltPath extends BasicSerializableObject {
const beltComp = entity.components.Belt; 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 // Append the entity
this.entityPath.push(entity); this.entityPath.push(entity);
this.onPathChanged(); this.onPathChanged();
@ -970,7 +993,7 @@ export class BeltPath extends BasicSerializableObject {
beltSpeed *= 100; beltSpeed *= 100;
} }
let minimumDistance = this.ejectorSlot.item ? this.getItemSpacing() : 0; let minimumDistance = 0;
// Try to reduce spacing // Try to reduce spacing
let remainingAmount = beltSpeed; let remainingAmount = beltSpeed;
@ -994,11 +1017,28 @@ export class BeltPath extends BasicSerializableObject {
minimumDistance = this.getItemSpacing(); minimumDistance = this.getItemSpacing();
} }
// Check if we have an item which is ready to be emitted
const lastItem = this.items[this.items.length - 1]; const lastItem = this.items[this.items.length - 1];
if (lastItem && lastItem[_nextDistance] === 0) { if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
// Take over // Pass over the item
if (this.ejectorComp.tryEject(0, lastItem[_item])) { if (
this.root.systemMgr.systems.itemEjector.tryPassOverItem(
lastItem[_item],
this.acceptorTarget.entity,
this.acceptorTarget.slot
)
) {
this.items.pop(); 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); const beltLength = beltComp.getEffectiveLengthTiles(this.layer);
// Check if the current items are on the belt // 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 // Its on the belt, render it now
const staticComp = entity.components.StaticMapEntity; const staticComp = entity.components.StaticMapEntity;
assert( assert(

View File

@ -81,10 +81,6 @@ export class Blueprint {
for (let i = 0; i < this.entities.length; ++i) { for (let i = 0; i < this.entities.length; ++i) {
const entity = this.entities[i]; const entity = this.entities[i];
const staticComp = entity.components.StaticMapEntity; const staticComp = entity.components.StaticMapEntity;
if (!staticComp.blueprintSpriteKey) {
logger.warn("Blueprint entity without sprite!");
return;
}
const newPos = staticComp.origin.add(tile); const newPos = staticComp.origin.add(tile);
const rect = staticComp.getTileSpaceBounds(); const rect = staticComp.getTileSpaceBounds();
@ -98,7 +94,7 @@ export class Blueprint {
staticComp.drawSpriteOnFullEntityBounds( staticComp.drawSpriteOnFullEntityBounds(
parameters, parameters,
Loader.getSprite(staticComp.blueprintSpriteKey), staticComp.getBlueprintSprite(),
0, 0,
true, true,
newPos 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 { formatItemsPerSecond } from "../../core/utils";
import { enumAngleToDirection, enumDirection, Vector } from "../../core/vector"; import { enumAngleToDirection, enumDirection, Vector } from "../../core/vector";
import { SOUNDS } from "../../platform/sound"; import { SOUNDS } from "../../platform/sound";
@ -40,6 +39,10 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
return SOUNDS.placeBelt; return SOUNDS.placeBelt;
} }
getSprite() {
return null;
}
/** /**
* Creates the entity at the given location * Creates the entity at the given location
* @param {Entity} entity * @param {Entity} entity
@ -50,34 +53,8 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
direction: enumDirection.top, // updated later direction: enumDirection.top, // updated later
}) })
); );
// Make this entity replaceabel // Make this entity replaceable
entity.addComponent(new ReplaceableMapEntityComponent()); 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) { updateVariants(entity, rotationVariant) {
entity.components.Belt.direction = arrayBeltVariantToRotation[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, layer: enumLayer.wires,
}, },
], ],
instantEject: true,
}) })
); );

View File

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

View File

@ -6,7 +6,7 @@ import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
import { GameRoot } from "../root"; import { GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals"; import { enumHubGoalRewards } from "../tutorial_goals";
import { T } from "../../translations"; import { T } from "../../translations";
import { round1Digit, round2Digits, formatItemsPerSecond } from "../../core/utils"; import { formatItemsPerSecond } from "../../core/utils";
/** @enum {string} */ /** @enum {string} */
export const enumMinerVariants = { chainable: "chainable" }; export const enumMinerVariants = { chainable: "chainable" };

View File

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

View File

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

View File

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

View File

@ -44,3 +44,9 @@ export class Component extends BasicSerializableObject {
} }
/* dev:end */ /* 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 { EnergyGeneratorComponent } from "./components/energy_generator";
import { WiredPinsComponent } from "./components/wired_pins"; import { WiredPinsComponent } from "./components/wired_pins";
import { EnergyConsumerComponent } from "./components/energy_consumer"; import { EnergyConsumerComponent } from "./components/energy_consumer";
import { BeltUnderlaysComponent } from "./components/belt_underlays";
export function initComponentRegistry() { export function initComponentRegistry() {
gComponentRegistry.register(StaticMapEntityComponent); gComponentRegistry.register(StaticMapEntityComponent);
@ -29,6 +30,7 @@ export function initComponentRegistry() {
gComponentRegistry.register(EnergyGeneratorComponent); gComponentRegistry.register(EnergyGeneratorComponent);
gComponentRegistry.register(WiredPinsComponent); gComponentRegistry.register(WiredPinsComponent);
gComponentRegistry.register(EnergyConsumerComponent); gComponentRegistry.register(EnergyConsumerComponent);
gComponentRegistry.register(BeltUnderlaysComponent);
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS // IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS

View File

@ -7,6 +7,40 @@ import { enumLayer } from "../root";
export const curvedBeltLength = /* Math.PI / 4 */ 0.78; 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 { export class BeltComponent extends Component {
static getId() { static getId() {
return "Belt"; return "Belt";
@ -56,6 +90,27 @@ export class BeltComponent extends Component {
return this.direction === enumDirection.top ? 1.0 : curvedBeltLength; 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 * 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) * 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() { static getId() {
return "Hub"; 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)), directions: types.array(types.enum(enumDirection)),
filter: types.nullable(types.enum(enumItemType)), 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 // TODO: MIGRATE
layer: types.enum(enumLayer), 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({ return new ItemAcceptorComponent({
slots: slotsCopy, slots: slotsCopy,
beltUnderlays: beltUnderlaysCopy,
animated: this.animated,
}); });
} }
@ -89,23 +67,16 @@ export class ItemAcceptorComponent extends Component {
* *
* @param {object} param0 * @param {object} param0
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items * @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(); super();
this.animated = animated;
/** /**
* Fixes belt animations * Fixes belt animations
* @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>} * @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>}
*/ */
this.itemConsumptionAnimations = []; this.itemConsumptionAnimations = [];
/* Which belt underlays to render */
this.beltUnderlays = beltUnderlays;
this.setSlots(slots); this.setSlots(slots);
} }
@ -164,14 +135,12 @@ export class ItemAcceptorComponent extends Component {
* @param {BaseItem} item * @param {BaseItem} item
*/ */
onItemAccepted(slotIndex, direction, item) { onItemAccepted(slotIndex, direction, item) {
if (this.animated) { this.itemConsumptionAnimations.push({
this.itemConsumptionAnimations.push({ item,
item, slotIndex,
slotIndex, direction,
direction, animProgress: 0.0,
animProgress: 0.0, });
});
}
} }
/** /**

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { round3Digits } from "../core/utils";
const logger = createLogger("dynamic_tickrate"); const logger = createLogger("dynamic_tickrate");

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { queryParamOptions } from "../core/query_parameters";
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils"; import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
import { BasicSerializableObject, types } from "../savegame/serialization"; import { BasicSerializableObject, types } from "../savegame/serialization";
import { enumColors } from "./colors"; import { enumColors } from "./colors";
@ -7,7 +6,7 @@ import { enumItemProcessorTypes } from "./components/item_processor";
import { GameRoot, enumLayer } from "./root"; import { GameRoot, enumLayer } from "./root";
import { enumSubShape, ShapeDefinition } from "./shape_definition"; import { enumSubShape, ShapeDefinition } from "./shape_definition";
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals"; import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
import { UPGRADES, blueprintShape } from "./upgrades"; import { UPGRADES } from "./upgrades";
export class HubGoals extends BasicSerializableObject { export class HubGoals extends BasicSerializableObject {
static getId() { static getId() {
@ -328,9 +327,7 @@ export class HubGoals extends BasicSerializableObject {
/** @type {Array<import("./shape_definition").ShapeLayer>} */ /** @type {Array<import("./shape_definition").ShapeLayer>} */
let layers = []; let layers = [];
// @ts-ignore
const randomColor = () => randomChoice(Object.values(enumColors)); const randomColor = () => randomChoice(Object.values(enumColors));
// @ts-ignore
const randomShape = () => randomChoice(Object.values(enumSubShape)); const randomShape = () => randomChoice(Object.values(enumSubShape));
let anyIsMissingTwo = false; let anyIsMissingTwo = false;

View File

@ -1,7 +1,7 @@
import { ClickDetector } from "../../../core/click_detector"; 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 { DrawParameters } from "../../../core/draw_parameters";
import { drawRotatedSprite, rotateTrapezRightFaced } from "../../../core/draw_utils"; import { drawRotatedSprite } from "../../../core/draw_utils";
import { Loader } from "../../../core/loader"; import { Loader } from "../../../core/loader";
import { clamp, makeDiv, removeAllChildren } from "../../../core/utils"; import { clamp, makeDiv, removeAllChildren } from "../../../core/utils";
import { import {
@ -18,6 +18,7 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
import { HUDBuildingPlacerLogic } from "./building_placer_logic"; import { HUDBuildingPlacerLogic } from "./building_placer_logic";
import { makeOffscreenBuffer } from "../../../core/buffer_utils"; import { makeOffscreenBuffer } from "../../../core/buffer_utils";
import { enumLayer } from "../../root"; import { enumLayer } from "../../root";
import { getCodeFromBuildingData } from "../../building_codes";
export class HUDBuildingPlacer extends HUDBuildingPlacerLogic { export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
/** /**
@ -84,9 +85,8 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
label: "lock-direction-indicator", label: "lock-direction-indicator",
}); });
// Loader.getSprite("sprites/misc/lock_direction_indicator.png").draw(context, 0, 0, 48, 48); context.fillStyle = THEME.map.directionLock[layer].color;
context.fillStyle = THEME.map.directionLock[enumLayer.wires].color; context.strokeStyle = THEME.map.directionLock[layer].color;
context.strokeStyle = THEME.map.directionLock[enumLayer.wires].color;
context.lineWidth = 2; context.lineWidth = 2;
const padding = 5; const padding = 5;
@ -313,12 +313,16 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
staticComp.rotation = rotation; staticComp.rotation = rotation;
staticComp.tileSize = metaBuilding.getDimensions(this.currentVariant.get()); staticComp.tileSize = metaBuilding.getDimensions(this.currentVariant.get());
metaBuilding.updateVariants(this.fakeEntity, rotationVariant, 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); const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
// Fade in / out // Fade in / out
parameters.context.lineWidth = 1; parameters.context.lineWidth = 1;
// parameters.context.globalAlpha = 0.3 + pulseAnimation(this.root.time.realtimeNow(), 0.9) * 0.7;
// Determine the bounds and visualize them // Determine the bounds and visualize them
const entityBounds = staticComp.getTileSpaceBounds(); const entityBounds = staticComp.getTileSpaceBounds();

View File

@ -13,6 +13,7 @@ import { SOUNDS } from "../../../platform/sound";
import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner"; import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
import { enumHubGoalRewards } from "../../tutorial_goals"; import { enumHubGoalRewards } from "../../tutorial_goals";
import { enumLayer } from "../../root"; import { enumLayer } from "../../root";
import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
/** /**
* Contains all logic for the building placer - this doesn't include the rendering * 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 // 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 the building we are picking is the same as the one we have, clear the cursor.
if ( if (
!extracted || this.currentMetaBuilding.get() &&
(extracted.metaBuilding === this.currentMetaBuilding.get() && extracted.metaInstance.getId() === this.currentMetaBuilding.get().getId() &&
extracted.variant === this.currentVariant.get()) extracted.variant === this.currentVariant.get()
) { ) {
this.currentMetaBuilding.set(null); this.currentMetaBuilding.set(null);
return; return;
} }
this.currentMetaBuilding.set(extracted.metaBuilding); this.currentMetaBuilding.set(extracted.metaInstance);
this.currentVariant.set(extracted.variant); this.currentVariant.set(extracted.variant);
this.currentBaseRotation = contents.components.StaticMapEntity.rotation; this.currentBaseRotation = contents.components.StaticMapEntity.rotation;
} }
/** /**
* HACK! * Switches the side for the direction lock manually
*
* This attempts to reconstruct the meta building and its variant from a given entity
* @param {Entity} entity
* @returns {{ metaBuilding: MetaBuilding, variant: string }}
*/ */
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() { switchDirectionLockSide() {
this.currentDirectionLockSide = 1 - this.currentDirectionLockSide; this.currentDirectionLockSide = 1 - this.currentDirectionLockSide;
} }
@ -673,7 +592,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
origin: new Vector(0, 0), origin: new Vector(0, 0),
rotation: 0, rotation: 0,
tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(), tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(),
blueprintSpriteKey: "", code: getCodeFromBuildingData(metaBuilding, variant, 0),
}) })
); );
metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get()); 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 mouseTile = this.root.camera.screenToWorld(mousePos).toTileSpace();
const cameraTile = this.root.camera.center.toTileSpace(); const cameraTile = this.root.camera.center.toTileSpace();
this.trackedMousePosition.set(`Pos: <code>${mouseTile.x}</code> / <code>${mouseTile.y}</code>`); this.trackedMousePosition.set(`Mouse: <code>${mouseTile.x}</code> / <code>${mouseTile.y}</code>`);
this.trackedCameraPosition.set(`Center: <code>${cameraTile.x}</code> / <code>${cameraTile.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"); this.mousePosElem = this.element.querySelector(".mousePos");
/** @type {HTMLElement} */
this.chunkPosElem = this.element.querySelector(".chunkPos"); this.chunkPosElem = this.element.querySelector(".chunkPos");
this.entityInfoElem = this.element.querySelector(".entityInfo"); this.entityInfoElem = this.element.querySelector(".entityInfo");
} }

View File

@ -1,10 +1,9 @@
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { makeDiv, randomInt } from "../../../core/utils"; import { makeDiv } from "../../../core/utils";
import { SOUNDS } from "../../../platform/sound"; import { SOUNDS } from "../../../platform/sound";
import { enumNotificationType } from "./notifications"; import { enumNotificationType } from "./notifications";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { KEYMAPPINGS } from "../../key_action_mapper"; import { KEYMAPPINGS } from "../../key_action_mapper";
import { IS_DEMO } from "../../../core/config";
import { DynamicDomAttach } from "../dynamic_dom_attach"; import { DynamicDomAttach } from "../dynamic_dom_attach";
export class HUDGameMenu extends BaseHUDPart { export class HUDGameMenu extends BaseHUDPart {

View File

@ -1,5 +1,5 @@
import { ClickDetector } from "../../../core/click_detector"; 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 { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { blueprintShape, UPGRADES } from "../../upgrades"; import { blueprintShape, UPGRADES } from "../../upgrades";

View File

@ -1,13 +1,11 @@
import { BaseHUDPart } from "../base_hud_part"; 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 { DynamicDomAttach } from "../dynamic_dom_attach";
import { InputReceiver } from "../../../core/input_receiver"; import { InputReceiver } from "../../../core/input_receiver";
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { StaticMapEntityComponent } from "../../components/static_map_entity"; import { StaticMapEntityComponent } from "../../components/static_map_entity";
import { ItemProcessorComponent } from "../../components/item_processor";
import { BeltComponent } from "../../components/belt"; import { BeltComponent } from "../../components/belt";
import { IS_DEMO } from "../../../core/config";
export class HUDSettingsMenu extends BaseHUDPart { export class HUDSettingsMenu extends BaseHUDPart {
createElements(parent) { createElements(parent) {
@ -57,16 +55,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
} }
returnToMenu() { 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(); this.root.gameState.goBackToMenu();
// }
} }
goToSettings() { goToSettings() {
@ -102,20 +91,25 @@ export class HUDSettingsMenu extends BaseHUDPart {
show() { show() {
this.visible = true; this.visible = true;
document.body.classList.add("ingameDialogOpen"); document.body.classList.add("ingameDialogOpen");
// this.background.classList.add("visible");
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60); 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(StaticMapEntityComponent).length -
this.root.entityMgr.getAllWithComponent(BeltComponent).length this.root.entityMgr.getAllWithComponent(BeltComponent).length
); );
this.statsElement.querySelector(".beltsPlaced").innerText = formatBigNumberFull(
beltsPlacedElement.innerText = formatBigNumberFull(
this.root.entityMgr.getAllWithComponent(BeltComponent).length this.root.entityMgr.getAllWithComponent(BeltComponent).length
); );
} }

View File

@ -1,5 +1,5 @@
import { InputReceiver } from "../../../core/input_receiver"; 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 { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
import { enumAnalyticsDataSource } from "../../production_analytics"; import { enumAnalyticsDataSource } from "../../production_analytics";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
@ -7,6 +7,14 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle"; import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle";
import { T } from "../../../translations"; 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 { export class HUDStatistics extends BaseHUDPart {
createElements(parent) { createElements(parent) {
this.background = makeDiv(parent, "ingame_HUD_Statistics", ["ingameDialog"]); this.background = makeDiv(parent, "ingame_HUD_Statistics", ["ingameDialog"]);

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