1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00
This commit is contained in:
Pascal Grossé 2020-08-11 15:52:22 +02:00
commit 00b201942f
191 changed files with 3604 additions and 7512 deletions

View File

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

19
gulp/cordova.js vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,9 +2,8 @@
const path = require("path");
const webpack = require("webpack");
const utils = require("./buildutils");
const { getRevision, getVersion, getAllResourceImages } = require("./buildutils");
const lzString = require("lz-string");
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CircularDependencyPlugin = require("circular-dependency-plugin");
module.exports = ({ watch = false, standalone = false }) => {
@ -41,9 +40,9 @@ module.exports = ({ watch = false, standalone = false }) => {
G_IS_BROWSER: "true",
G_IS_STANDALONE: standalone ? "true" : "false",
G_BUILD_TIME: "" + new Date().getTime(),
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()),
G_BUILD_VERSION: JSON.stringify(utils.getVersion()),
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()),
G_BUILD_COMMIT_HASH: JSON.stringify(getRevision()),
G_BUILD_VERSION: JSON.stringify(getVersion()),
G_ALL_UI_IMAGES: JSON.stringify(getAllResourceImages()),
}),
new CircularDependencyPlugin({
@ -60,7 +59,6 @@ module.exports = ({ watch = false, standalone = false }) => {
// set the current working directory for displaying module paths
cwd: path.join(__dirname, "..", "src", "js"),
}),
// new BundleAnalyzerPlugin()
],
module: {
rules: [
@ -94,6 +92,16 @@ module.exports = ({ watch = false, standalone = false }) => {
},
],
},
{
test: /\.worker\.js$/,
use: {
loader: "worker-loader",
options: {
fallback: false,
inline: true,
},
},
},
{
test: /\.ya?ml$/,
type: "json", // Required by Webpack v4

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"scripts": {
"dev": "cd gulp && yarn gulp main.serveDev",
"tslint": "cd src/js && tsc",
"lint": "npx eslint src/js",
"lint": "eslint src/js",
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*",
"publishOnItchWindows": "butler push tmp_standalone_files/shapez.io-standalone-win32-x64 tobspr/shapezio:windows --userversion-file version",
"publishOnItchLinux": "butler push tmp_standalone_files/shapez.io-standalone-linux-x64 tobspr/shapezio:linux --userversion-file version",
@ -34,6 +34,7 @@
"clipboard-copy": "^3.1.0",
"colors": "^1.3.3",
"core-js": "3",
"crc": "^3.8.0",
"cssnano-preset-advanced": "^4.0.7",
"email-validator": "^2.0.4",
"eslint": "7.1.0",
@ -46,7 +47,6 @@
"lz-string": "^1.4.4",
"markdown-loader": "^4.0.0",
"match-all": "^1.2.5",
"obfuscator-loader": "^1.1.2",
"phonegap-plugin-mobile-accessibility": "^1.0.5",
"promise-polyfill": "^8.1.0",
"query-string": "^6.8.1",
@ -86,14 +86,12 @@
"imagemin-pngquant": "^8.0.0",
"jimp": "^0.6.1",
"js-yaml": "^3.13.1",
"onesky-fetch": "^0.0.7",
"postcss-assets": "^5.0.0",
"postcss-preset-env": "^6.5.0",
"postcss-round-subpixels": "^1.2.0",
"postcss-unprefix": "^2.1.3",
"prettier": "^2.0.4",
"sass-unused": "^0.3.0",
"speed-measure-webpack-plugin": "^1.3.1",
"strip-json-comments": "^3.0.1",
"trim": "^0.0.1",
"yarn": "^1.22.4"

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 61 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: 1003 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 200 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 KiB

After

Width:  |  Height:  |  Size: 479 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: 913 KiB

View File

@ -256,10 +256,6 @@
<key type="filename">sprites/buildings/rotater.png</key>
<key type="filename">sprites/buildings/trash.png</key>
<key type="filename">sprites/misc/wires_overlay_tile.png</key>
<key type="filename">sprites/wires/pin_negative_accept.png</key>
<key type="filename">sprites/wires/pin_negative_eject.png</key>
<key type="filename">sprites/wires/pin_positive_accept.png</key>
<key type="filename">sprites/wires/pin_positive_eject.png</key>
<struct type="IndividualSpriteSettings">
<key>pivotPoint</key>
<point_f>0.5,0.5</point_f>
@ -358,19 +354,9 @@
<key type="filename">sprites/blueprints/belt_left.png</key>
<key type="filename">sprites/blueprints/belt_right.png</key>
<key type="filename">sprites/blueprints/belt_top.png</key>
<key type="filename">sprites/blueprints/wire_crossings-merger.png</key>
<key type="filename">sprites/blueprints/wire_crossings.png</key>
<key type="filename">sprites/blueprints/wire_left.png</key>
<key type="filename">sprites/blueprints/wire_right.png</key>
<key type="filename">sprites/blueprints/wire_top.png</key>
<key type="filename">sprites/buildings/belt_left.png</key>
<key type="filename">sprites/buildings/belt_right.png</key>
<key type="filename">sprites/buildings/belt_top.png</key>
<key type="filename">sprites/buildings/wire_crossings-merger.png</key>
<key type="filename">sprites/buildings/wire_crossings.png</key>
<key type="filename">sprites/buildings/wire_left.png</key>
<key type="filename">sprites/buildings/wire_right.png</key>
<key type="filename">sprites/buildings/wire_top.png</key>
<struct type="IndividualSpriteSettings">
<key>pivotPoint</key>
<point_f>0.5,0.5</point_f>
@ -385,28 +371,6 @@
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/blueprints/advanced_processor.png</key>
<key type="filename">sprites/blueprints/energy_generator.png</key>
<key type="filename">sprites/blueprints/painter-double.png</key>
<key type="filename">sprites/blueprints/trash-storage.png</key>
<key type="filename">sprites/buildings/advanced_processor.png</key>
<key type="filename">sprites/buildings/energy_generator.png</key>
<key type="filename">sprites/buildings/painter-double.png</key>
<key type="filename">sprites/misc/energy_generator_overlay.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>96,96,192,192</rect>
<key>scale9Paddings</key>
<rect>96,96,192,192</rect>
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/blueprints/cutter-quad.png</key>
<key type="filename">sprites/blueprints/painter-quad.png</key>
<key type="filename">sprites/buildings/cutter-quad.png</key>
@ -481,6 +445,23 @@
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/blueprints/painter-double.png</key>
<key type="filename">sprites/blueprints/trash-storage.png</key>
<key type="filename">sprites/buildings/painter-double.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>96,96,192,192</rect>
<key>scale9Paddings</key>
<rect>96,96,192,192</rect>
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/buildings/cutter.png</key>
<key type="filename">sprites/buildings/mixer.png</key>
<key type="filename">sprites/buildings/painter.png</key>
@ -568,21 +549,6 @@
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/misc/lock_direction_indicator.png</key>
<struct type="IndividualSpriteSettings">
<key>pivotPoint</key>
<point_f>0.5,0.5</point_f>
<key>spriteScale</key>
<double>1</double>
<key>scale9Enabled</key>
<false/>
<key>scale9Borders</key>
<rect>12,12,24,24</rect>
<key>scale9Paddings</key>
<rect>12,12,24,24</rect>
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/misc/storage_overlay.png</key>
<struct type="IndividualSpriteSettings">
<key>pivotPoint</key>
@ -598,27 +564,6 @@
<key>scale9FromFile</key>
<false/>
</struct>
<key type="filename">sprites/wires/battery_empty.png</key>
<key type="filename">sprites/wires/battery_full.png</key>
<key type="filename">sprites/wires/battery_low.png</key>
<key type="filename">sprites/wires/battery_medium.png</key>
<key type="filename">sprites/wires/negative_energy.png</key>
<key type="filename">sprites/wires/positive_energy.png</key>
<key type="filename">sprites/wires/waste_piled.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>16,16,32,32</rect>
<key>scale9Paddings</key>
<rect>16,16,32,32</rect>
<key>scale9FromFile</key>
<false/>
</struct>
</map>
<key>fileList</key>
<array>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -318,7 +318,7 @@ input {
canvas {
pointer-events: all;
image-rendering: auto;
image-rendering: pixelated;
// &.smoothed {
// }
// &.unsmoothed {

View File

@ -1,5 +1,4 @@
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt,
energy_generator, wire, advanced_processor, wire_crossings;
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt;
@each $building in $buildings {
[data-icon="building_icons/#{$building}.png"] {

View File

@ -14,13 +14,9 @@ import { AdProviderInterface } from "./platform/ad_provider";
import { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
import { AnalyticsInterface } from "./platform/analytics";
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
import { NoGameAnalytics } from "./platform/browser/no_game_analytics";
import { SoundImplBrowser } from "./platform/browser/sound";
import { PlatformWrapperImplBrowser } from "./platform/browser/wrapper";
import { PlatformWrapperImplElectron } from "./platform/electron/wrapper";
import { GameAnalyticsInterface } from "./platform/game_analytics";
import { SoundInterface } from "./platform/sound";
import { StorageInterface } from "./platform/storage";
import { PlatformWrapperInterface } from "./platform/wrapper";
import { ApplicationSettings } from "./profile/application_settings";
import { SavegameManager } from "./savegame/savegame_manager";
@ -34,6 +30,12 @@ import { PreloadState } from "./states/preload";
import { SettingsState } from "./states/settings";
import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
/**
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
* @typedef {import("./platform/sound").SoundInterface} SoundInterface
* @typedef {import("./platform/storage").StorageInterface} StorageInterface
*/
const logger = createLogger("application");
// Set the name of the hidden property and the change event for visibility

View File

@ -1,6 +1,7 @@
import { Signal } from "./signal";
import BackgroundAnimationFrameEmitterWorker from "worker-loader?inline=true&fallback=false!../webworkers/background_animation_frame_emittter.worker";
// @ts-ignore
import BackgroundAnimationFrameEmitterWorker from "../webworkers/background_animation_frame_emittter.worker";
import { createLogger } from "./logging";
const logger = createLogger("animation_frame");

View File

@ -1,5 +1,8 @@
import CompressionWorker from "worker-loader?inline=true&fallback=false!../webworkers/compression.worker";
// @ts-ignore
import CompressionWorker from "../webworkers/compression.worker";
import { createLogger } from "./logging";
import { round2Digits } from "./utils";
const logger = createLogger("async_compression");
@ -50,7 +53,15 @@ class AsynCompression {
}
const duration = performance.now() - jobData.startTime;
// log(this, "Got response from worker within", duration.toFixed(2), "ms");
logger.log(
"Got job",
jobId,
"response within",
round2Digits(duration),
"ms: ",
result.length,
"bytes"
);
const resolver = jobData.resolver;
delete this.currentJobs[jobId];
resolver(result);
@ -70,12 +81,13 @@ class AsynCompression {
}
/**
* Compresses file
* @param {string} text
* Compresses any object
* @param {any} obj
*/
compressFileAsync(text) {
return this.internalQueueJob("compressFile", {
text,
compressObjectAsync(obj) {
logger.log("Compressing object async (optimized)");
return this.internalQueueJob("compressObject", {
obj,
compressionPrefix,
});
}
@ -98,6 +110,8 @@ class AsynCompression {
resolver: resolve,
startTime: performance.now(),
};
logger.log("Posting job", job, "/", jobId);
this.worker.postMessage({ jobId, job, data });
});
}

View File

@ -1,20 +1,38 @@
/**
* @typedef {{ w: number, h: number }} Size
* @typedef {{ x: number, y: number }} Position
* @typedef {{
* frame: { x: number, y: number, w: number, h: number },
* rotated: false,
* spriteSourceSize: { x: number, y: number, w: number, h: number },
* sourceSize: { w: number, h: number},
* trimmed: true
* frame: Position & Size,
* rotated: boolean,
* spriteSourceSize: Position & Size,
* sourceSize: Size,
* trimmed: boolean
* }} SpriteDefinition
*
* @typedef {{
* app: string,
* version: string,
* image: string,
* format: string,
* size: Size,
* scale: string,
* smartupdate: string
* }} AtlasMeta
*
* @typedef {{
* frames: Object.<string, SpriteDefinition>,
* meta: AtlasMeta
* }} SourceData
*/
export class AtlasDefinition {
constructor(sourceData) {
this.sourceFileName = sourceData.meta.image;
this.meta = sourceData.meta;
/** @type {Object.<string, SpriteDefinition>} */
this.sourceData = sourceData.frames;
/**
* @param {SourceData} sourceData
*/
constructor({ frames, meta }) {
this.meta = meta;
this.sourceData = frames;
this.sourceFileName = meta.image;
}
getFullSourcePath() {
@ -22,6 +40,7 @@ export class AtlasDefinition {
}
}
/** @type {AtlasDefinition[]} **/
export const atlasFiles = require
// @ts-ignore
.context("../../../res_built/atlas/", false, /.*\.json/i)

View File

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

View File

@ -95,15 +95,18 @@ export class BufferMaintainer {
}
/**
*
* @param {string} key
* @param {string} subKey
* @param {function(HTMLCanvasElement, CanvasRenderingContext2D, number, number, number, object?) : void} redrawMethod
* @param {object=} additionalParams
* @param {object} param0
* @param {string} param0.key
* @param {string} param0.subKey
* @param {number} param0.w
* @param {number} param0.h
* @param {number} param0.dpi
* @param {function(HTMLCanvasElement, CanvasRenderingContext2D, number, number, number, object?) : void} param0.redrawMethod
* @param {object=} param0.additionalParams
* @returns {HTMLCanvasElement}
*
*/
getForKey(key, subKey, w, h, dpi, redrawMethod, additionalParams) {
getForKey({ key, subKey, w, h, dpi, redrawMethod, additionalParams }) {
// First, create parent key
let parent = this.cache.get(key);
if (!parent) {

View File

@ -34,6 +34,7 @@ export const globalConfig = {
assetsDpi: 192 / 32,
assetsSharpness: 1.2,
shapesSharpness: 1.4,
mapChunkSharpness: 1.0,
// Production analytics
statisticsGraphDpi: 2.5,
@ -45,18 +46,14 @@ export const globalConfig = {
// Map
mapChunkSize: 16,
mapChunkPrerenderMinZoom: -1,
mapChunkOverviewMinZoom: 0.7,
mapChunkOverviewMinZoom: 0.9,
// Belt speeds
// NOTICE: Update webpack.production.config too!
beltSpeedItemsPerSecond: 2,
minerSpeedItemsPerSecond: 0, // COMPUTED
beltItemSpacingByLayer: {
regular: 0.63,
wires: 0.4,
},
itemSpacingOnBelts: 0.63,
wiresSpeedItemsPerSecond: 6,
@ -116,14 +113,12 @@ globalConfig.minerSpeedItemsPerSecond = globalConfig.beltSpeedItemsPerSecond / 5
// Dynamic calculations
if (globalConfig.debug.disableMapOverview) {
globalConfig.mapChunkOverviewMinZoom = 0;
globalConfig.mapChunkPrerenderMinZoom = 0;
}
// Stuff for making the trailer
if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
globalConfig.debug.framePausesBetweenTicks = 32;
// globalConfig.mapChunkOverviewMinZoom = 0.0;
// globalConfig.mapChunkPrerenderMinZoom = globalConfig.mapChunkOverviewMinZoom;
// globalConfig.debug.instantBelts = true;
// globalConfig.debug.instantProcessors = true;
// globalConfig.debug.instantMiners = true;

View File

@ -15,8 +15,8 @@ export function getDeviceDPI() {
* @returns {number} Smoothed dpi
*/
export function smoothenDpi(dpi) {
if (dpi < 0.05) {
return 0.05;
if (dpi < 0.02) {
return 0.02;
} else if (dpi < 0.1) {
return round2Digits(dpi);
} else if (dpi < 1) {

View File

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

View File

@ -1,18 +1,13 @@
/* typehints:start */
import { AtlasSprite } from "./sprites";
import { DrawParameters } from "./draw_parameters";
/* typehints:end */
import { Vector } from "./vector";
import { Rectangle } from "./rectangle";
import { createLogger } from "./logging";
const logger = createLogger("draw_utils");
/**
* @typedef {import("./sprites").AtlasSprite} AtlasSprite
* @typedef {import("./draw_parameters").DrawParameters} DrawParameters
*/
export function initDrawUtils() {
CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) {
this.beginPath();
if (r < 0.05) {
this.beginPath();
this.rect(x, y, w, h);
return;
}
@ -20,25 +15,26 @@ export function initDrawUtils() {
if (w < 2 * r) {
r = w / 2;
}
if (h < 2 * r) {
r = h / 2;
}
this.beginPath();
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
// this.closePath();
};
CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) {
this.beginPath();
if (r < 0.05) {
this.beginPath();
this.rect(x, y, 1, 1);
return;
}
this.beginPath();
this.arc(x, y, r, 0, 2.0 * Math.PI);
};
}
@ -62,259 +58,3 @@ export function drawRotatedSprite({ parameters, sprite, x, y, angle, size, offse
parameters.context.rotate(-angle);
parameters.context.translate(-x, -y);
}
export function drawLineFast(context, { x1, x2, y1, y2, color = null, lineSize = 1 }) {
const dX = x2 - x1;
const dY = y2 - y1;
const angle = Math.atan2(dY, dX) + 0.0 * Math.PI;
const len = Math.hypot(dX, dY);
context.translate(x1, y1);
context.rotate(angle);
if (color) {
context.fillStyle = color;
}
context.fillRect(0, -lineSize / 2, len, lineSize);
context.rotate(-angle);
context.translate(-x1, -y1);
}
const INSIDE = 0;
const LEFT = 1;
const RIGHT = 2;
const BOTTOM = 4;
const TOP = 8;
// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
function computeOutCode(x, y, xmin, xmax, ymin, ymax) {
let code = INSIDE;
if (x < xmin)
// to the left of clip window
code |= LEFT;
else if (x > xmax)
// to the right of clip window
code |= RIGHT;
if (y < ymin)
// below the clip window
code |= BOTTOM;
else if (y > ymax)
// above the clip window
code |= TOP;
return code;
}
// CohenSutherland clipping algorithm clips a line from
// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
// diagonal from (xmin, ymin) to (xmax, ymax).
/**
*
* @param {CanvasRenderingContext2D} context
*/
export function drawLineFastClipped(context, rect, { x0, y0, x1, y1, color = null, lineSize = 1 }) {
const xmin = rect.x;
const ymin = rect.y;
const xmax = rect.right();
const ymax = rect.bottom();
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
let outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
let outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
let accept = false;
// eslint-disable-next-line no-constant-condition
while (true) {
if (!(outcode0 | outcode1)) {
// bitwise OR is 0: both points inside window; trivially accept and exit loop
accept = true;
break;
} else if (outcode0 & outcode1) {
// bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP,
// or BOTTOM), so both must be outside window; exit loop (accept is false)
break;
} else {
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
let x, y;
// At least one endpoint is outside the clip rectangle; pick it.
let outcodeOut = outcode0 ? outcode0 : outcode1;
// Now find the intersection point;
// use formulas:
// slope = (y1 - y0) / (x1 - x0)
// x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
// y = y0 + slope * (xm - x0), where xm is xmin or xmax
// No need to worry about divide-by-zero because, in each case, the
// outcode bit being tested guarantees the denominator is non-zero
if (outcodeOut & TOP) {
// point is above the clip window
x = x0 + ((x1 - x0) * (ymax - y0)) / (y1 - y0);
y = ymax;
} else if (outcodeOut & BOTTOM) {
// point is below the clip window
x = x0 + ((x1 - x0) * (ymin - y0)) / (y1 - y0);
y = ymin;
} else if (outcodeOut & RIGHT) {
// point is to the right of clip window
y = y0 + ((y1 - y0) * (xmax - x0)) / (x1 - x0);
x = xmax;
} else if (outcodeOut & LEFT) {
// point is to the left of clip window
y = y0 + ((y1 - y0) * (xmin - x0)) / (x1 - x0);
x = xmin;
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut == outcode0) {
x0 = x;
y0 = y;
outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
} else {
x1 = x;
y1 = y;
outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
}
}
}
if (accept) {
// Following functions are left for implementation by user based on
// their platform (OpenGL/graphics.h etc.)
// DrawRectangle(xmin, ymin, xmax, ymax);
// LineSegment(x0, y0, x1, y1);
drawLineFast(context, {
x1: x0,
y1: y0,
x2: x1,
y2: y1,
color,
lineSize,
});
}
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {Array} The RGB representation
*/
export function hslToRgb(h, s, l) {
let r;
let g;
let b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
// tslint:disable-next-line:no-shadowed-variable
const hue2rgb = function (p, q, t) {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
};
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
let p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
export function wrapText(context, text, x, y, maxWidth, lineHeight, stroke = false) {
var words = text.split(" ");
var line = "";
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + " ";
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
if (stroke) {
context.strokeText(line, x, y);
} else {
context.fillText(line, x, y);
}
line = words[n] + " ";
y += lineHeight;
} else {
line = testLine;
}
}
if (stroke) {
context.strokeText(line, x, y);
} else {
context.fillText(line, x, y);
}
}
/**
* Returns a rotated trapez, used for spotlight culling
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
* @param {number} leftHeight
* @param {number} angle
*/
export function rotateTrapezRightFaced(x, y, w, h, leftHeight, angle) {
const halfY = y + h / 2;
const points = [
new Vector(x, halfY - leftHeight / 2),
new Vector(x + w, y),
new Vector(x, halfY + leftHeight / 2),
new Vector(x + w, y + h),
];
return Rectangle.getAroundPointsRotated(points, angle);
}
/**
* Converts values from 0 .. 255 to values like 07, 7f, 5d etc
* @param {number} value
* @returns {string}
*/
export function mapClampedColorValueToHex(value) {
const hex = "0123456789abcdef";
return hex[Math.floor(value / 16)] + hex[value % 16];
}
/**
* Converts rgb to a hex string
* @param {number} r
* @param {number} g
* @param {number} b
* @returns {string}
*/
export function rgbToHex(r, g, b) {
return mapClampedColorValueToHex(r) + mapClampedColorValueToHex(g) + mapClampedColorValueToHex(b);
}

View File

@ -1,19 +1,19 @@
import { SingletonFactory } from "./singleton_factory";
import { Factory } from "./factory";
/* typehints:start */
import { BaseGameSpeed } from "../game/time/base_game_speed";
import { Component } from "../game/component";
import { BaseItem } from "../game/base_item";
import { MetaBuilding } from "../game/meta_building";
/* typehints:end */
/**
* @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed
* @typedef {import("../game/component").Component} Component
* @typedef {import("../game/base_item").BaseItem} BaseItem
* @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding
// These factories are here to remove circular dependencies
/** @type {SingletonFactoryTemplate<MetaBuilding>} */
export let gMetaBuildingRegistry = new SingletonFactory();
/** @type {Object.<string, Array<typeof MetaBuilding>>} */
/** @type {Object.<string, Array<Class<MetaBuilding>>>} */
export let gBuildingsByCategory = null;
/** @type {FactoryTemplate<Component>} */
@ -28,7 +28,7 @@ export let gItemRegistry = new Factory("item");
// Helpers
/**
* @param {Object.<string, Array<typeof MetaBuilding>>} buildings
* @param {Object.<string, Array<Class<MetaBuilding>>>} buildings
*/
export function initBuildingsByCategory(buildings) {
gBuildingsByCategory = buildings;

View File

@ -1,20 +1,19 @@
/* typehints:start */
import { Application } from "../application";
/* typehints:end */
import { AtlasDefinition } from "./atlas_definitions";
import { makeOffscreenBuffer } from "./buffer_utils";
import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites";
import { cachebust } from "./cachebust";
import { createLogger } from "./logging";
/**
* @typedef {import("../application").Application} Application
* @typedef {import("./atlas_definitions").AtlasDefinition} AtlasDefinition;
*/
const logger = createLogger("loader");
const missingSpriteIds = {};
class LoaderImpl {
constructor() {
/** @type {Application} */
this.app = null;
/** @type {Map<string, BaseSprite>} */
@ -23,6 +22,9 @@ class LoaderImpl {
this.rawImages = [];
}
/**
* @param {Application} app
*/
linkAppAfterBoot(app) {
this.app = app;
this.makeSpriteNotFoundCanvas();
@ -58,7 +60,7 @@ class LoaderImpl {
}
/**
* Retursn a regular sprite from the cache
* Returns a regular sprite from the cache
* @param {string} key
* @returns {RegularSprite}
*/
@ -155,44 +157,34 @@ class LoaderImpl {
* @param {AtlasDefinition} atlas
* @param {HTMLImageElement} loadedImage
*/
internalParseAtlas(atlas, loadedImage) {
internalParseAtlas({ meta: { scale }, sourceData }, loadedImage) {
this.rawImages.push(loadedImage);
for (const spriteKey in atlas.sourceData) {
const spriteData = atlas.sourceData[spriteKey];
for (const spriteName in sourceData) {
const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName];
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteKey));
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteName));
if (!sprite) {
sprite = new AtlasSprite({
spriteName: spriteKey,
});
this.sprites.set(spriteKey, sprite);
sprite = new AtlasSprite(spriteName);
this.sprites.set(spriteName, sprite);
}
const link = new SpriteAtlasLink({
packedX: spriteData.frame.x,
packedY: spriteData.frame.y,
packedW: spriteData.frame.w,
packedH: spriteData.frame.h,
packOffsetX: spriteData.spriteSourceSize.x,
packOffsetY: spriteData.spriteSourceSize.y,
packedX: frame.x,
packedY: frame.y,
packedW: frame.w,
packedH: frame.h,
packOffsetX: spriteSourceSize.x,
packOffsetY: spriteSourceSize.y,
atlas: loadedImage,
w: spriteData.sourceSize.w,
h: spriteData.sourceSize.h,
w: sourceSize.w,
h: sourceSize.h,
});
sprite.linksByResolution[atlas.meta.scale] = link;
sprite.linksByResolution[scale] = link;
}
}
/**
* Creates the links for the sprites after the atlas has been loaded. Used so we
* don't have to store duplicate sprites.
*/
createAtlasLinks() {
// NOT USED
}
/**
* Makes the canvas which shows the question mark, shown when a sprite was not found
*/
@ -216,14 +208,9 @@ class LoaderImpl {
// @ts-ignore
canvas.src = "not-found";
const resolutions = ["0.1", "0.25", "0.5", "0.75", "1"];
const sprite = new AtlasSprite({
spriteName: "not-found",
});
for (let i = 0; i < resolutions.length; ++i) {
const res = resolutions[i];
const link = new SpriteAtlasLink({
const sprite = new AtlasSprite("not-found");
["0.1", "0.25", "0.5", "0.75", "1"].forEach(resolution => {
sprite.linksByResolution[resolution] = new SpriteAtlasLink({
packedX: 0,
packedY: 0,
w: dims,
@ -234,8 +221,8 @@ class LoaderImpl {
packedH: dims,
atlas: canvas,
});
sprite.linksByResolution[res] = link;
}
});
this.spriteNotFoundSprite = sprite;
}
}

View File

@ -2,7 +2,7 @@
import { Application } from "../application";
/* typehints:end */
import { sha1 } from "./sensitive_utils.encrypt";
import { sha1, CRC_PREFIX, computeCrc } from "./sensitive_utils.encrypt";
import { createLogger } from "./logging";
import { FILE_NOT_FOUND } from "../platform/storage";
import { accessNestedPropertyReverse } from "./utils";
@ -84,7 +84,7 @@ export class ReadWriteProxy {
*/
static serializeObject(obj) {
const jsonString = JSON.stringify(compressObject(obj));
const checksum = sha1(jsonString + salt);
const checksum = computeCrc(jsonString + salt);
return compressionPrefix + compressX64(checksum + jsonString);
}
@ -106,7 +106,11 @@ export class ReadWriteProxy {
// Compare stored checksum with actual checksum
const checksum = decompressed.substring(0, 40);
const jsonString = decompressed.substr(40);
const desiredChecksum = sha1(jsonString + salt);
const desiredChecksum = checksum.startsWith(CRC_PREFIX)
? computeCrc(jsonString + salt)
: sha1(jsonString + salt);
if (desiredChecksum !== checksum) {
// Checksum mismatch
throw new Error("bad-content / checksum-mismatch");
@ -119,7 +123,7 @@ export class ReadWriteProxy {
/**
* Writes the data asychronously, fails if verify() fails
* @returns {Promise<string>}
* @returns {Promise<void>}
*/
writeAsync() {
const verifyResult = this.internalVerifyEntry(this.currentData);
@ -128,35 +132,14 @@ export class ReadWriteProxy {
logger.error("Tried to write invalid data to", this.filename, "reason:", verifyResult.reason);
return Promise.reject(verifyResult.reason);
}
const jsonString = JSON.stringify(compressObject(this.currentData));
// if (!this.app.pageVisible || this.app.unloaded) {
// logger.log("Saving file sync because in unload handler");
// const checksum = sha1(jsonString + salt);
// let compressed = compressionPrefix + compressX64(checksum + jsonString);
// if (G_IS_DEV && IS_DEBUG) {
// compressed = jsonString;
// }
// if (!this.app.storage.writeFileSyncIfSupported(this.filename, compressed)) {
// return Promise.reject("Failed to write " + this.filename + " sync!");
// } else {
// logger.log("📄 Wrote (sync!)", this.filename);
// return Promise.resolve(compressed);
// }
// }
return asyncCompressor
.compressFileAsync(jsonString)
.compressObjectAsync(this.currentData)
.then(compressed => {
if (G_IS_DEV && IS_DEBUG) {
compressed = jsonString;
}
return this.app.storage.writeFileAsync(this.filename, compressed);
})
.then(() => {
logger.log("📄 Wrote", this.filename);
return jsonString;
})
.catch(err => {
logger.error("Failed to write", this.filename, ":", err);
@ -205,10 +188,16 @@ export class ReadWriteProxy {
// Compare stored checksum with actual checksum
const checksum = decompressed.substring(0, 40);
const jsonString = decompressed.substr(40);
const desiredChecksum = sha1(jsonString + salt);
const desiredChecksum = checksum.startsWith(CRC_PREFIX)
? computeCrc(jsonString + salt)
: sha1(jsonString + salt);
if (desiredChecksum !== checksum) {
// Checksum mismatch
return Promise.reject("bad-content / checksum-mismatch");
return Promise.reject(
"bad-content / checksum-mismatch: " + desiredChecksum + " vs " + checksum
);
}
return jsonString;
} else {

View File

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

View File

@ -1,5 +1,5 @@
import { createHash } from "rusha";
import crc32 from "crc/crc32";
import { decompressX64 } from "./lzstring";
export function sha1(str) {
@ -10,3 +10,14 @@ export function sha1(str) {
export function getNameOfProvider() {
return window[decompressX64("DYewxghgLgliB2Q")][decompressX64("BYewzgLgdghgtgUyA")];
}
// Distinguish legacy crc prefixes
export const CRC_PREFIX = "crc32".padEnd(32, "-");
/**
* Computes the crc for a given string
* @param {string} str
*/
export function computeCrc(str) {
return CRC_PREFIX + crc32(str).toString(16).padStart(8, "0");
}

View File

@ -1,6 +1,6 @@
import { DrawParameters } from "./draw_parameters";
import { Rectangle } from "./rectangle";
import { epsilonCompare, round3Digits } from "./utils";
import { round3Digits } from "./utils";
const floorSpriteCoordinates = false;
@ -63,10 +63,9 @@ export class SpriteAtlasLink {
export class AtlasSprite extends BaseSprite {
/**
*
* @param {object} param0
* @param {string} param0.spriteName
* @param {string} spriteName
*/
constructor({ spriteName = "sprite" }) {
constructor(spriteName = "sprite") {
super();
/** @type {Object.<string, SpriteAtlasLink>} */
this.linksByResolution = {};
@ -197,8 +196,6 @@ export class AtlasSprite extends BaseSprite {
destH = intersection.h;
}
// assert(epsilonCompare(scaleW, scaleH), "Sprite should be square for cached rendering");
if (floorSpriteCoordinates) {
parameters.context.drawImage(
link.atlas,

View File

@ -1,46 +1,7 @@
import { globalConfig, IS_DEBUG } from "./config";
import { Vector } from "./vector";
import { T } from "../translations";
// Constants
export const TOP = new Vector(0, -1);
export const RIGHT = new Vector(1, 0);
export const BOTTOM = new Vector(0, 1);
export const LEFT = new Vector(-1, 0);
export const ALL_DIRECTIONS = [TOP, RIGHT, BOTTOM, LEFT];
const bigNumberSuffixTranslationKeys = ["thousands", "millions", "billions", "trillions"];
/**
* Returns the build id
* @returns {string}
*/
export function getBuildId() {
if (G_IS_DEV && IS_DEBUG) {
return "local-dev";
} else if (G_IS_DEV) {
return "dev-" + getPlatformName() + "-" + G_BUILD_COMMIT_HASH;
} else {
return "prod-" + getPlatformName() + "-" + G_BUILD_COMMIT_HASH;
}
}
/**
* Returns the environment id (dev, prod, etc)
* @returns {string}
*/
export function getEnvironmentId() {
if (G_IS_DEV && IS_DEBUG) {
return "local-dev";
} else if (G_IS_DEV) {
return "dev-" + getPlatformName();
} else if (G_IS_RELEASE) {
return "release-" + getPlatformName();
} else {
return "staging-" + getPlatformName();
}
}
/**
* Returns if this platform is android
* @returns {boolean}
@ -66,7 +27,7 @@ export function isIos() {
/**
* Returns a platform name
* @returns {string}
* @returns {"android" | "browser" | "ios" | "standalone" | "unknown"}
*/
export function getPlatformName() {
if (G_IS_STANDALONE) {
@ -96,60 +57,13 @@ export function getIPCRenderer() {
return ipcRenderer;
}
/**
* Formats a sensitive token by only displaying the first digits of it. Use for
* stuff like savegame keys etc which should not appear in the log.
* @param {string} key
*/
export function formatSensitive(key) {
if (!key) {
return "<null>";
}
key = key || "";
return "[" + key.substr(0, 8) + "...]";
}
/**
* Creates a new 2D array with the given fill method
* @param {number} w Width
* @param {number} h Height
* @param {(function(number, number) : any) | number | boolean | string | null | undefined} filler Either Fill method, which should return the content for each cell, or static content
* @param {string=} context Optional context for memory tracking
* @returns {Array<Array<any>>}
*/
export function make2DArray(w, h, filler, context = null) {
if (typeof filler === "function") {
const tiles = new Array(w);
for (let x = 0; x < w; ++x) {
const row = new Array(h);
for (let y = 0; y < h; ++y) {
row[y] = filler(x, y);
}
tiles[x] = row;
}
return tiles;
} else {
const tiles = new Array(w);
const row = new Array(h);
for (let y = 0; y < h; ++y) {
row[y] = filler;
}
for (let x = 0; x < w; ++x) {
tiles[x] = row.slice();
}
return tiles;
}
}
/**
* Makes a new 2D array with undefined contents
* @param {number} w
* @param {number} h
* @param {string=} context
* @returns {Array<Array<any>>}
*/
export function make2DUndefinedArray(w, h, context = null) {
export function make2DUndefinedArray(w, h) {
const result = new Array(w);
for (let x = 0; x < w; ++x) {
result[x] = new Array(h);
@ -157,33 +71,6 @@ export function make2DUndefinedArray(w, h, context = null) {
return result;
}
/**
* Clears a given 2D array with the given fill method
* @param {Array<Array<any>>} array
* @param {number} w Width
* @param {number} h Height
* @param {(function(number, number) : any) | number | boolean | string | null | undefined} filler Either Fill method, which should return the content for each cell, or static content
*/
export function clear2DArray(array, w, h, filler) {
assert(array.length === w, "Array dims mismatch w");
assert(array[0].length === h, "Array dims mismatch h");
if (typeof filler === "function") {
for (let x = 0; x < w; ++x) {
const row = array[x];
for (let y = 0; y < h; ++y) {
row[y] = filler(x, y);
}
}
} else {
for (let x = 0; x < w; ++x) {
const row = array[x];
for (let y = 0; y < h; ++y) {
row[y] = filler;
}
}
}
}
/**
* Creates a new map (an empty object without any props)
*/
@ -215,7 +102,9 @@ export function accessNestedPropertyReverse(obj, keys) {
/**
* Chooses a random entry of an array
* @param {Array | string} arr
* @template T
* @param {T[]} arr
* @returns {T}
*/
export function randomChoice(arr) {
return arr[Math.floor(Math.random() * arr.length)];
@ -304,23 +193,6 @@ export function arrayDeleteValue(array, value) {
return arrayDelete(array, index);
}
// Converts a direction into a 0 .. 7 index
/**
* Converts a direction into a index from 0 .. 7, used for miners, zombies etc which have 8 sprites
* @param {Vector} offset direction
* @param {boolean} inverse if inverse, the direction is reversed
* @returns {number} in range [0, 7]
*/
export function angleToSpriteIndex(offset, inverse = false) {
const twoPi = 2.0 * Math.PI;
const factor = inverse ? -1 : 1;
const offs = inverse ? 2.5 : 3.5;
const angle = (factor * Math.atan2(offset.y, offset.x) + offs * Math.PI) % twoPi;
const index = Math.round((angle / twoPi) * 8) % 8;
return index;
}
/**
* Compare two floats for epsilon equality
* @param {number} a
@ -331,15 +203,6 @@ export function epsilonCompare(a, b, epsilon = 1e-5) {
return Math.abs(a - b) < epsilon;
}
/**
* Compare a float for epsilon equal to 0
* @param {number} a
* @returns {boolean}
*/
export function epsilonIsZero(a) {
return epsilonCompare(a, 0);
}
/**
* Interpolates two numbers
* @param {number} a
@ -399,17 +262,6 @@ export function findNiceIntegerValue(num) {
return Math.ceil(findNiceValue(num));
}
/**
* Smart rounding + fractional handling
* @param {number} n
*/
function roundSmart(n) {
if (n < 100) {
return n.toFixed(1);
}
return Math.round(n);
}
/**
* Formats a big number
* @param {number} num
@ -477,92 +329,12 @@ export function formatBigNumberFull(num, divider = T.global.thousandsDivider) {
return out.substring(0, out.length - 1);
}
/**
* Delayes a promise so that it will resolve after a *minimum* amount of time only
* @param {Promise<any>} promise The promise to delay
* @param {number} minTimeMs The time to make it run at least
* @returns {Promise<any>} The delayed promise
*/
export function artificialDelayedPromise(promise, minTimeMs = 500) {
if (G_IS_DEV && globalConfig.debug.noArtificialDelays) {
return promise;
}
const startTime = performance.now();
return promise.then(
result => {
const timeTaken = performance.now() - startTime;
const waitTime = Math.floor(minTimeMs - timeTaken);
if (waitTime > 0) {
return new Promise(resolve => {
setTimeout(() => {
resolve(result);
}, waitTime);
});
} else {
return result;
}
},
error => {
const timeTaken = performance.now() - startTime;
const waitTime = Math.floor(minTimeMs - timeTaken);
if (waitTime > 0) {
// @ts-ignore
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(error);
}, waitTime);
});
} else {
throw error;
}
}
);
}
/**
* Computes a sine-based animation which pulsates from 0 .. 1 .. 0
* @param {number} time Current time in seconds
* @param {number} duration Duration of the full pulse in seconds
* @param {number} seed Seed to offset the animation
*/
export function pulseAnimation(time, duration = 1.0, seed = 0.0) {
return Math.sin((time * Math.PI * 2.0) / duration + seed * 5642.86729349) * 0.5 + 0.5;
}
/**
* Returns the smallest angle between two angles
* @param {number} a
* @param {number} b
* @returns {number} 0 .. 2 PI
*/
export function smallestAngle(a, b) {
return safeMod(a - b + Math.PI, 2.0 * Math.PI) - Math.PI;
}
/**
* Modulo which works for negative numbers
* @param {number} n
* @param {number} m
*/
export function safeMod(n, m) {
return ((n % m) + m) % m;
}
/**
* Wraps an angle between 0 and 2 pi
* @param {number} angle
*/
export function wrapAngle(angle) {
return safeMod(angle, 2.0 * Math.PI);
}
/**
* Waits two frames so the ui is updated
* @returns {Promise<void>}
*/
export function waitNextFrame() {
return new Promise(function (resolve, reject) {
return new Promise(function (resolve) {
window.requestAnimationFrame(function () {
window.requestAnimationFrame(function () {
resolve();
@ -617,27 +389,13 @@ export function clamp(v, minimum = 0, maximum = 1) {
return Math.max(minimum, Math.min(maximum, v));
}
/**
* Measures how long a function took
* @param {string} name
* @param {function():void} target
*/
export function measure(name, target) {
const now = performance.now();
for (let i = 0; i < 25; ++i) {
target();
}
const dur = (performance.now() - now) / 25.0;
console.warn("->", name, "took", dur.toFixed(2), "ms");
}
/**
* Helper method to create a new div element
* @param {string=} id
* @param {Array<string>=} classes
* @param {string=} innerHTML
*/
export function makeDivElement(id = null, classes = [], innerHTML = "") {
function makeDivElement(id = null, classes = [], innerHTML = "") {
const div = document.createElement("div");
if (id) {
div.id = id;
@ -662,20 +420,6 @@ export function makeDiv(parent, id = null, classes = [], innerHTML = "") {
return div;
}
/**
* Helper method to create a new div and place before reference Node
* @param {Element} parent
* @param {Element} referenceNode
* @param {string=} id
* @param {Array<string>=} classes
* @param {string=} innerHTML
*/
export function makeDivBefore(parent, referenceNode, id = null, classes = [], innerHTML = "") {
const div = makeDivElement(id, classes, innerHTML);
parent.insertBefore(div, referenceNode);
return div;
}
/**
* Helper method to create a new button element
* @param {Array<string>=} classes
@ -703,19 +447,6 @@ export function makeButton(parent, classes = [], innerHTML = "") {
return element;
}
/**
* Helper method to create a new button and place before reference Node
* @param {Element} parent
* @param {Element} referenceNode
* @param {Array<string>=} classes
* @param {string=} innerHTML
*/
export function makeButtonBefore(parent, referenceNode, classes = [], innerHTML = "") {
const element = makeButtonElement(classes, innerHTML);
parent.insertBefore(element, referenceNode);
return element;
}
/**
* Removes all children of the given element
* @param {Element} elem
@ -728,20 +459,10 @@ export function removeAllChildren(elem) {
}
}
export function smartFadeNumber(current, newOne, minFade = 0.01, maxFade = 0.9) {
const tolerance = Math.min(current, newOne) * 0.5 + 10;
let fade = minFade;
if (Math.abs(current - newOne) < tolerance) {
fade = maxFade;
}
return current * fade + newOne * (1 - fade);
}
/**
* Fixes lockstep simulation by converting times like 34.0000000003 to 34.00.
* We use 3 digits of precision, this allows to store sufficient precision of 1 ms without
* the risk to simulation errors due to resync issues
* We use 3 digits of precision, this allows us to store precision of 1 ms without
* the risking simulation errors due to resync issues
* @param {number} value
*/
export function quantizeFloat(value) {
@ -840,37 +561,6 @@ export function isSupportedBrowser() {
}
}
/**
* Helper function to create a json schema object
* @param {any} properties
*/
export function schemaObject(properties) {
return {
type: "object",
required: Object.keys(properties).slice(),
additionalProperties: false,
properties,
};
}
/**
* Quickly
* @param {number} x
* @param {number} y
* @param {number} deg
* @returns {Vector}
*/
export function fastRotateMultipleOf90(x, y, deg) {
switch (deg) {
case 0: {
return new Vector(x, y);
}
case 90: {
return new Vector(x, y);
}
}
}
/**
* Formats an amount of seconds into something like "5s ago"
* @param {number} secs Seconds
@ -928,31 +618,6 @@ export function formatSeconds(secs) {
}
}
/**
* Generates a file download
* @param {string} filename
* @param {string} text
*/
export function generateFileDownload(filename, text) {
var element = document.createElement("a");
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
/**
* Capitalizes the first letter
* @param {string} str
*/
export function capitalizeFirstLetter(str) {
return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase();
}
/**
* Formats a number like 2.5 to "2.5 items / s"
* @param {number} speed

View File

@ -1,6 +1,5 @@
import { DrawParameters } from "../core/draw_parameters";
import { BasicSerializableObject } from "../savegame/serialization";
import { enumLayer } from "./root";
/** @enum {string} */
export const enumItemType = {
@ -13,15 +12,6 @@ export const enumItemType = {
genericEnergy: "genericEnergy",
};
/** @enum {enumLayer} */
export const enumItemTypeToLayer = {
[enumItemType.shape]: enumLayer.regular,
[enumItemType.color]: enumLayer.regular,
[enumItemType.positiveEnergy]: enumLayer.wires,
[enumItemType.negativeEnergy]: enumLayer.wires,
[enumItemType.genericEnergy]: enumLayer.wires,
};
/**
* Class for items on belts etc. Not an entity for performance reasons
*/

View File

@ -1,14 +1,14 @@
import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters";
import { gItemRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle";
import { epsilonCompare, round4Digits } from "../core/utils";
import { Vector } from "../core/vector";
import { enumDirection, enumDirectionToVector, Vector, enumInvertedDirections } from "../core/vector";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { BaseItem } from "./base_item";
import { Entity } from "./entity";
import { GameRoot, enumLayer } from "./root";
import { Rectangle } from "../core/rectangle";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { gItemRegistry } from "../core/global_registries";
const logger = createLogger("belt_path");
@ -115,22 +115,7 @@ export class BeltPath extends BasicSerializableObject {
* @returns {boolean}
*/
canAcceptItem() {
return this.spacingToFirstItem >= this.getItemSpacing();
}
/**
* Returns the spacing between items
*/
getItemSpacing() {
return globalConfig.beltItemSpacingByLayer[this.layer];
}
/**
* Returns the layer of the this path
* @returns {enumLayer}
*/
getLayer() {
return this.entityPath[0].layer;
return this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts;
}
/**
@ -138,15 +123,15 @@ export class BeltPath extends BasicSerializableObject {
* @param {BaseItem} item
*/
tryAcceptItem(item) {
if (this.spacingToFirstItem >= this.getItemSpacing()) {
if (this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts) {
// So, since we already need one tick to accept this item we will add this directly.
const beltProgressPerTick =
this.root.hubGoals.getBeltBaseSpeed(this.layer) *
this.root.hubGoals.getBeltBaseSpeed() *
this.root.dynamicTickrate.deltaSeconds *
this.getItemSpacing();
globalConfig.itemSpacingOnBelts;
// First, compute how much progress we can make *at max*
const maxProgress = Math.max(0, this.spacingToFirstItem - this.getItemSpacing());
const maxProgress = Math.max(0, this.spacingToFirstItem - globalConfig.itemSpacingOnBelts);
const initialProgress = Math.min(maxProgress, beltProgressPerTick);
this.items.unshift([this.spacingToFirstItem - initialProgress, item]);
@ -167,7 +152,7 @@ export class BeltPath extends BasicSerializableObject {
* @returns {BaseItem|null}
*/
findItemAtTile(tile) {
// TODO: This breaks color blind mode otherwise
// @TODO: This breaks color blind mode otherwise
return null;
}
@ -186,27 +171,81 @@ export class BeltPath extends BasicSerializableObject {
}
/**
* Updates all ejectors on the path, so that only the last ejector
* Recomputes cache variables once the path was changed
*/
onPathChanged() {
this.ejectorComp = this.entityPath[this.entityPath.length - 1].components.ItemEjector;
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;
const isLast = i === this.entityPath.length - 1;
ejectorComp.enabled = isLast;
/**
* Called by the belt system when the surroundings changed
*/
onSurroundingsChanged() {
this.onPathChanged();
}
// Clear all slots of non-end entities
if (!isLast) {
for (let k = 0; k < ejectorComp.slots.length; ++k) {
ejectorComp.slots[k].item = null;
ejectorComp.slots[k].progress = 0.0;
/**
* Finds the entity which accepts our items
* @return {{ entity: Entity, slot: number, direction?: enumDirection }}
*/
computeAcceptingEntityAndSlot() {
const lastEntity = this.entityPath[this.entityPath.length - 1];
const lastStatic = lastEntity.components.StaticMapEntity;
const lastBeltComp = lastEntity.components.Belt;
// Figure out where and into which direction we eject items
const ejectSlotWsTile = lastStatic.localTileToWorld(new Vector(0, 0));
const ejectSlotWsDirection = lastStatic.localDirectionToWorld(lastBeltComp.direction);
const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
// Try to find the given acceptor component to take the item
const targetEntity = this.root.map.getLayerContentXY(
ejectSlotTargetWsTile.x,
ejectSlotTargetWsTile.y,
enumLayer.regular
);
if (targetEntity) {
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
return;
}
const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection);
const matchingSlot = targetAcceptorComp.findMatchingSlot(
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
ejectingDirection
);
if (!matchingSlot) {
// No matching slot found
return;
}
return {
entity: targetEntity,
slot: matchingSlot.index,
direction: enumInvertedDirections[ejectingDirection],
};
}
}
// Following code will be compiled out outside of dev versions
@ -251,11 +290,6 @@ export class BeltPath extends BasicSerializableObject {
return fail("Reference to destroyed entity " + entity.uid);
}
const enabledState = i === this.entityPath.length - 1;
if (entity.components.ItemEjector.enabled !== enabledState) {
return fail("Item ejector enabled state is not synchronized (index =" + i + ")");
}
const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity);
if (!followUp) {
return fail(
@ -283,20 +317,6 @@ export class BeltPath extends BasicSerializableObject {
}
}
// Check for right ejector component and slot
if (this.ejectorComp !== this.entityPath[this.entityPath.length - 1].components.ItemEjector) {
return fail("Stale ejectorComp handle");
}
if (this.ejectorSlot !== this.ejectorComp.slots[0]) {
return fail("Stale ejector slot handle");
}
if (!this.ejectorComp) {
return fail("Ejector comp not set");
}
if (!this.ejectorSlot) {
return fail("Ejector slot not set");
}
// Check spacing
if (this.spacingToFirstItem > this.totalLength + 0.005) {
return fail(
@ -370,20 +390,12 @@ export class BeltPath extends BasicSerializableObject {
const beltComp = entity.components.Belt;
// If the last belt has something on its ejector, put that into the path first
const pendingItem = this.ejectorComp.takeSlotItem(0);
if (pendingItem) {
// Ok, so we have a pending item
DEBUG && logger.log("Taking pending item and putting it back on the path");
this.items.push([0, pendingItem]);
}
// Append the entity
this.entityPath.push(entity);
this.onPathChanged();
// Extend the path length
const additionalLength = beltComp.getEffectiveLengthTiles(entity.layer);
const additionalLength = beltComp.getEffectiveLengthTiles();
this.totalLength += additionalLength;
DEBUG && logger.log(" Extended total length by", additionalLength, "to", this.totalLength);
@ -426,7 +438,7 @@ export class BeltPath extends BasicSerializableObject {
// All items on that belt are simply lost (for now)
const length = beltComp.getEffectiveLengthTiles(entity.layer);
const length = beltComp.getEffectiveLengthTiles();
// Extend the length of this path
this.totalLength += length;
@ -478,7 +490,7 @@ export class BeltPath extends BasicSerializableObject {
const beltComp = entity.components.Belt;
beltComp.assignedPath = null;
const entityLength = beltComp.getEffectiveLengthTiles(entity.layer);
const entityLength = beltComp.getEffectiveLengthTiles();
assert(this.entityPath.indexOf(entity) >= 0, "Entity not contained for split");
assert(this.entityPath.indexOf(entity) !== 0, "Entity is first");
assert(this.entityPath.indexOf(entity) !== this.entityPath.length - 1, "Entity is last");
@ -496,7 +508,7 @@ export class BeltPath extends BasicSerializableObject {
++firstPathEntityCount;
firstPathEndEntity = otherEntity;
firstPathLength += otherEntity.components.Belt.getEffectiveLengthTiles(otherEntity.layer);
firstPathLength += otherEntity.components.Belt.getEffectiveLengthTiles();
}
DEBUG &&
@ -640,7 +652,7 @@ export class BeltPath extends BasicSerializableObject {
// Ok, first remove the entity
const beltComp = entity.components.Belt;
const beltLength = beltComp.getEffectiveLengthTiles(entity.layer);
const beltLength = beltComp.getEffectiveLengthTiles();
DEBUG &&
logger.log(
@ -749,7 +761,7 @@ export class BeltPath extends BasicSerializableObject {
// Ok, first remove the entity
const beltComp = entity.components.Belt;
const beltLength = beltComp.getEffectiveLengthTiles(entity.layer);
const beltLength = beltComp.getEffectiveLengthTiles();
DEBUG &&
logger.log(
@ -889,7 +901,7 @@ export class BeltPath extends BasicSerializableObject {
beltComp.assignedPath = this;
// Update our length
const additionalLength = beltComp.getEffectiveLengthTiles(entity.layer);
const additionalLength = beltComp.getEffectiveLengthTiles();
this.totalLength += additionalLength;
}
@ -947,7 +959,7 @@ export class BeltPath extends BasicSerializableObject {
let length = 0;
for (let i = 0; i < this.entityPath.length; ++i) {
const entity = this.entityPath[i];
length += entity.components.Belt.getEffectiveLengthTiles(this.layer);
length += entity.components.Belt.getEffectiveLengthTiles();
}
return length;
}
@ -962,15 +974,15 @@ export class BeltPath extends BasicSerializableObject {
// Divide by item spacing on belts since we use throughput and not speed
let beltSpeed =
this.root.hubGoals.getBeltBaseSpeed(this.layer) *
this.root.hubGoals.getBeltBaseSpeed() *
this.root.dynamicTickrate.deltaSeconds *
this.getItemSpacing();
globalConfig.itemSpacingOnBelts;
if (G_IS_DEV && globalConfig.debug.instantBelts) {
beltSpeed *= 100;
}
let minimumDistance = this.ejectorSlot.item ? this.getItemSpacing() : 0;
let minimumDistance = 0;
// Try to reduce spacing
let remainingAmount = beltSpeed;
@ -991,13 +1003,13 @@ export class BeltPath extends BasicSerializableObject {
break;
}
minimumDistance = this.getItemSpacing();
minimumDistance = globalConfig.itemSpacingOnBelts;
}
// Check if we have an item which is ready to be emitted
const lastItem = this.items[this.items.length - 1];
if (lastItem && lastItem[_nextDistance] === 0) {
// Take over
if (this.ejectorComp.tryEject(0, lastItem[_item])) {
if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
if (this.tryHandOverItem(lastItem[_item])) {
this.items.pop();
}
}
@ -1007,6 +1019,47 @@ export class BeltPath extends BasicSerializableObject {
}
}
/**
* Tries to hand over the item to the end entity
* @param {BaseItem} item
*/
tryHandOverItem(item) {
if (!this.acceptorTarget) {
return;
}
const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
// Check if the acceptor has a filter for example
if (targetAcceptorComp && !targetAcceptorComp.canAcceptItem(this.acceptorTarget.slot, item)) {
// Well, this item is not accepted
return false;
}
// Try to pass over
if (
this.root.systemMgr.systems.itemEjector.tryPassOverItem(
item,
this.acceptorTarget.entity,
this.acceptorTarget.slot
)
) {
// Trigger animation on the acceptor comp
const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
if (targetAcceptorComp) {
targetAcceptorComp.onItemAccepted(
this.acceptorTarget.slot,
this.acceptorTarget.direction,
item
);
}
return true;
}
return false;
}
/**
* Computes a world space position from the given progress
* @param {number} progress
@ -1020,14 +1073,14 @@ export class BeltPath extends BasicSerializableObject {
for (let i = 0; i < this.entityPath.length; ++i) {
const beltComp = this.entityPath[i].components.Belt;
const localLength = beltComp.getEffectiveLengthTiles(this.layer);
const localLength = beltComp.getEffectiveLengthTiles();
if (currentLength + localLength >= progress || i === this.entityPath.length - 1) {
// Min required here due to floating point issues
const localProgress = Math.min(1.0, progress - currentLength);
assert(localProgress >= 0.0, "Invalid local progress: " + localProgress);
const localSpace = beltComp.transformBeltToLocalSpace(localProgress, this.layer);
const localSpace = beltComp.transformBeltToLocalSpace(localProgress);
return this.entityPath[i].components.StaticMapEntity.localTileToWorld(localSpace);
}
currentLength += localLength;
@ -1045,11 +1098,6 @@ export class BeltPath extends BasicSerializableObject {
return;
}
if (this.entityPath[0].layer !== this.root.currentLayer) {
// Don't draw
return;
}
parameters.context.fillStyle = "#d79a25";
parameters.context.strokeStyle = "#d79a25";
parameters.context.beginPath();
@ -1131,10 +1179,10 @@ export class BeltPath extends BasicSerializableObject {
for (let i = 0; i < this.entityPath.length; ++i) {
const entity = this.entityPath[i];
const beltComp = entity.components.Belt;
const beltLength = beltComp.getEffectiveLengthTiles(this.layer);
const beltLength = beltComp.getEffectiveLengthTiles();
// Check if the current items are on the belt
while (trackPos + beltLength >= currentItemPos) {
while (trackPos + beltLength >= currentItemPos - 1e-51) {
// Its on the belt, render it now
const staticComp = entity.components.StaticMapEntity;
assert(
@ -1142,7 +1190,7 @@ export class BeltPath extends BasicSerializableObject {
"invalid track pos: " + currentItemPos + " vs " + trackPos + " (l =" + beltLength + ")"
);
const localPos = beltComp.transformBeltToLocalSpace(currentItemPos - trackPos, this.layer);
const localPos = beltComp.transformBeltToLocalSpace(currentItemPos - trackPos);
const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
const distanceAndItem = this.items[currentItemIndex];

View File

@ -92,13 +92,7 @@ export class Blueprint {
parameters.context.globalAlpha = 1;
}
staticComp.drawSpriteOnFullEntityBounds(
parameters,
staticComp.getBlueprintSprite(),
0,
true,
newPos
);
staticComp.drawSpriteOnFullEntityBounds(parameters, staticComp.getBlueprintSprite(), 0, newPos);
}
parameters.context.globalAlpha = 1;
}

View File

@ -1,5 +1,8 @@
import { MetaBuilding, defaultBuildingVariant } from "./meta_building";
/* typehints:start */
import { MetaBuilding } from "./meta_building";
import { AtlasSprite } from "../core/sprites";
import { Vector } from "../core/vector";
/* typehints:end */
/**
* @typedef {{
@ -7,6 +10,7 @@ import { AtlasSprite } from "../core/sprites";
* metaInstance?: MetaBuilding,
* variant?: string,
* rotationVariant?: number,
* tileSize?: Vector,
* sprite?: AtlasSprite,
* blueprintSprite?: AtlasSprite,
* silhouetteColor?: string
@ -22,18 +26,25 @@ export const gBuildingVariants = {
};
/**
*
* @param {*} id
* @param {*} meta
* @param {*} variant
* @param {*} rotationVariant
* Registers a new variant
* @param {number} id
* @param {typeof MetaBuilding} meta
* @param {string} variant
* @param {number} rotationVariant
*/
export function registerBuildingVariant(id, meta, variant = defaultBuildingVariant, rotationVariant = 0) {
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,
// @ts-ignore
tileSize: new meta().getDimensions(variant),
};
}

View File

@ -1,108 +0,0 @@
import { formatItemsPerSecond } from "../../core/utils";
import { enumDirection, Vector } from "../../core/vector";
import { T } from "../../translations";
import { enumItemType } from "../base_item";
import { EnergyConsumerComponent } from "../components/energy_consumer";
import { ItemAcceptorComponent } from "../components/item_acceptor";
import { ItemEjectorComponent } from "../components/item_ejector";
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building";
import { enumLayer, GameRoot } from "../root";
export class MetaAdvancedProcessorBuilding extends MetaBuilding {
constructor() {
super("advanced_processor");
}
getSilhouetteColor() {
return "#25d7b8";
}
getDimensions(variant) {
return new Vector(2, 2);
}
/**
* @param {GameRoot} root
* @param {string} variant
* @returns {Array<[string, string]>}
*/
getAdditionalStatistics(root, variant) {
const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.advancedProcessor);
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
}
/**
* @param {GameRoot} root
*/
getIsUnlocked(root) {
// TODO
return true;
// return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {
entity.addComponent(
new ItemProcessorComponent({
inputsPerCharge: 1,
processorType: enumItemProcessorTypes.advancedProcessor,
})
);
entity.addComponent(
new ItemEjectorComponent({
slots: [
{ pos: new Vector(1, 0), direction: enumDirection.right },
{ pos: new Vector(1, 0), direction: enumDirection.top, layer: enumLayer.wires },
],
})
);
entity.addComponent(
new EnergyConsumerComponent({
bufferSize: 3,
perCharge: 1,
batteryPosition: new Vector(0.63, 0.7),
acceptorSlotIndex: 1,
ejectorSlotIndex: 1,
})
);
entity.addComponent(
new WiredPinsComponent({
slots: [
{
pos: new Vector(0, 0),
direction: enumDirection.top,
type: enumPinSlotType.positiveEnergyAcceptor,
},
{
pos: new Vector(1, 0),
direction: enumDirection.top,
type: enumPinSlotType.negativeEnergyEjector,
},
],
})
);
entity.addComponent(
new ItemAcceptorComponent({
slots: [
{
pos: new Vector(0, 1),
directions: [enumDirection.left],
},
{
pos: new Vector(0, 0),
directions: [enumDirection.top],
filter: enumItemType.positiveEnergy,
layer: enumLayer.wires,
},
],
})
);
}
}

View File

@ -3,15 +3,35 @@ import { enumAngleToDirection, enumDirection, Vector } from "../../core/vector";
import { SOUNDS } from "../../platform/sound";
import { T } from "../../translations";
import { BeltComponent } from "../components/belt";
import { ItemAcceptorComponent } from "../components/item_acceptor";
import { ItemEjectorComponent } from "../components/item_ejector";
import { ReplaceableMapEntityComponent } from "../components/replaceable_map_entity";
import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building";
import { GameRoot, enumLayer } from "../root";
import { GameRoot } from "../root";
export const arrayBeltVariantToRotation = [enumDirection.top, enumDirection.left, enumDirection.right];
const overlayMatrices = {
[enumDirection.top]: {
0: [0, 1, 0, 0, 1, 0, 0, 1, 0],
90: [0, 0, 0, 1, 1, 1, 0, 0, 0],
180: [0, 1, 0, 0, 1, 0, 0, 1, 0],
270: [0, 0, 0, 1, 1, 1, 0, 0, 0],
},
[enumDirection.left]: {
0: [0, 0, 0, 1, 1, 0, 0, 1, 0],
90: [0, 1, 0, 1, 1, 0, 0, 0, 0],
180: [0, 1, 0, 0, 1, 1, 0, 0, 0],
270: [0, 0, 0, 0, 1, 1, 0, 1, 0],
},
[enumDirection.right]: {
0: [0, 0, 0, 0, 1, 1, 0, 1, 0],
90: [0, 0, 0, 1, 1, 0, 0, 1, 0],
180: [0, 1, 0, 1, 1, 0, 0, 0, 0],
270: [0, 1, 0, 0, 1, 1, 0, 0, 0],
},
};
export class MetaBeltBaseBuilding extends MetaBuilding {
getHasDirectionLockAvailable() {
return true;
@ -23,7 +43,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
* @returns {Array<[string, string]>}
*/
getAdditionalStatistics(root, variant) {
const beltSpeed = root.hubGoals.getBeltBaseSpeed(enumLayer.regular);
const beltSpeed = root.hubGoals.getBeltBaseSpeed();
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]];
}
@ -43,6 +63,21 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
return null;
}
getIsReplaceable() {
return true;
}
/**
*
* @param {number} rotation
* @param {number} rotationVariant
* @param {string} variant
* @param {Entity} entity
*/
getSpecialOverlayRenderMatrix(rotation, rotationVariant, variant, entity) {
return overlayMatrices[entity.components.Belt.direction][rotation];
}
/**
* Creates the entity at the given location
* @param {Entity} entity
@ -53,34 +88,6 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
direction: enumDirection.top, // updated later
})
);
// Make this entity replaceabel
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,
})
);
}
/**
@ -90,7 +97,6 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
*/
updateVariants(entity, rotationVariant) {
entity.components.Belt.direction = arrayBeltVariantToRotation[rotationVariant];
entity.components.ItemEjector.slots[0].direction = arrayBeltVariantToRotation[rotationVariant];
}
/**
@ -109,7 +115,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
const bottomDirection = enumAngleToDirection[(rotation + 180) % 360];
const leftDirection = enumAngleToDirection[(rotation + 270) % 360];
const { ejectors, acceptors } = root.logic.getEjectorsAndAcceptorsAtTile(tile, layer);
const { ejectors, acceptors } = root.logic.getEjectorsAndAcceptorsAtTile(tile);
let hasBottomEjector = false;
let hasRightEjector = false;

View File

@ -1,109 +0,0 @@
import { enumDirection, Vector } from "../../core/vector";
import { enumItemType } from "../base_item";
import { EnergyGeneratorComponent } from "../components/energy_generator";
import { ItemAcceptorComponent } from "../components/item_acceptor";
import { ItemEjectorComponent } from "../components/item_ejector";
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building";
import { enumLayer, GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals";
export class MetaEnergyGenerator extends MetaBuilding {
constructor() {
super("energy_generator");
}
getSilhouetteColor() {
return "#c425d7";
}
/**
* @param {GameRoot} root
* @param {string} variant
* @returns {Array<[string, string]>}
*/
getAdditionalStatistics(root, variant) {
// TODO
return [];
}
getDimensions(variant) {
return new Vector(2, 2);
}
/**
* @param {GameRoot} root
*/
getIsUnlocked(root) {
return true;
// return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {
entity.addComponent(
new ItemAcceptorComponent({
slots: [
{
pos: new Vector(0, 1),
directions: [enumDirection.bottom],
filter: enumItemType.shape,
},
{
pos: new Vector(1, 1),
directions: [enumDirection.bottom],
filter: enumItemType.shape,
},
{
pos: new Vector(1, 0),
directions: [enumDirection.top],
layer: enumLayer.wires,
filter: enumItemType.negativeEnergy,
},
],
})
);
entity.addComponent(
new ItemEjectorComponent({
slots: [
{
pos: new Vector(0, 0),
direction: enumDirection.top,
layer: enumLayer.wires,
},
],
instantEject: true,
})
);
entity.addComponent(
new EnergyGeneratorComponent({
// Set by the energy generator system later
requiredKey: null,
wasteAcceptorSlotIndex: 2,
})
);
entity.addComponent(
new WiredPinsComponent({
slots: [
{
pos: new Vector(0, 0),
type: enumPinSlotType.positiveEnergyEjector,
direction: enumDirection.top,
},
{
pos: new Vector(1, 0),
type: enumPinSlotType.negativeEnergyAcceptor,
direction: enumDirection.top,
},
],
})
);
}
}

View File

@ -3,9 +3,9 @@ import { enumItemType } from "../base_item";
import { HubComponent } from "../components/hub";
import { ItemAcceptorComponent } from "../components/item_acceptor";
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
import { UnremovableComponent } from "../components/unremovable";
import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building";
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
export class MetaHubBuilding extends MetaBuilding {
constructor() {
@ -33,6 +33,10 @@ export class MetaHubBuilding extends MetaBuilding {
return null;
}
getIsRemovable() {
return false;
}
/**
* Creates the entity at the given location
* @param {Entity} entity
@ -46,7 +50,18 @@ export class MetaHubBuilding extends MetaBuilding {
})
);
entity.addComponent(new UnremovableComponent());
entity.addComponent(
new WiredPinsComponent({
slots: [
{
pos: new Vector(0, 0),
type: enumPinSlotType.logicalEjector,
direction: enumDirection.top,
},
],
})
);
entity.addComponent(
new ItemAcceptorComponent({
slots: [

View File

@ -6,11 +6,13 @@ import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
import { GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals";
import { T } from "../../translations";
import { round1Digit, round2Digits, formatItemsPerSecond } from "../../core/utils";
import { formatItemsPerSecond } from "../../core/utils";
/** @enum {string} */
export const enumMinerVariants = { chainable: "chainable" };
const overlayMatrix = [1, 1, 1, 1, 0, 1, 1, 1, 1];
export class MetaMinerBuilding extends MetaBuilding {
constructor() {
super("miner");
@ -41,6 +43,16 @@ export class MetaMinerBuilding extends MetaBuilding {
return super.getAvailableVariants(root);
}
/**
* @param {number} rotation
* @param {number} rotationVariant
* @param {string} variant
* @param {Entity} entity
*/
getSpecialOverlayRenderMatrix(rotation, rotationVariant, variant, entity) {
return overlayMatrix;
}
/**
* Creates the entity at the given location
* @param {Entity} entity

View File

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

View File

@ -25,13 +25,31 @@ export const enumUndergroundBeltVariantToTier = {
[enumUndergroundBeltVariants.tier2]: 1,
};
const overlayMatrices = [
// Sender
{
0: [1, 1, 1, 0, 1, 0, 0, 1, 0],
90: [0, 0, 0, 1, 1, 1, 0, 0, 0],
180: [0, 1, 0, 0, 1, 0, 0, 1, 0],
270: [0, 0, 0, 1, 1, 1, 0, 0, 0],
},
// Receiver
{
0: [0, 1, 0, 0, 1, 0, 1, 1, 1],
90: [0, 1, 0, 1, 1, 0, 0, 0, 0],
180: [0, 1, 0, 0, 1, 1, 0, 0, 0],
270: [0, 0, 0, 0, 1, 1, 0, 1, 0],
},
];
export class MetaUndergroundBeltBuilding extends MetaBuilding {
constructor() {
super("underground_belt");
}
getSilhouetteColor() {
return "#555";
return "#222";
}
getFlipOrientationAfterPlacement() {
@ -42,6 +60,16 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
return true;
}
/**
* @param {number} rotation
* @param {number} rotationVariant
* @param {string} variant
* @param {Entity} entity
*/
getSpecialOverlayRenderMatrix(rotation, rotationVariant, variant, entity) {
return overlayMatrices[rotationVariant][rotation];
}
/**
* @param {GameRoot} root
* @param {string} variant

View File

@ -1,52 +0,0 @@
import { Loader } from "../../core/loader";
import { enumDirection } from "../../core/vector";
import { enumLayer } from "../root";
import { arrayBeltVariantToRotation, MetaBeltBaseBuilding } from "./belt_base";
export class MetaWireBaseBuilding extends MetaBeltBaseBuilding {
constructor() {
super("wire");
}
getSilhouetteColor() {
return "#c425d7";
}
getLayer() {
return enumLayer.wires;
}
getPreviewSprite(rotationVariant) {
switch (arrayBeltVariantToRotation[rotationVariant]) {
case enumDirection.top: {
return Loader.getSprite("sprites/buildings/wire_top.png");
}
case enumDirection.left: {
return Loader.getSprite("sprites/buildings/wire_left.png");
}
case enumDirection.right: {
return Loader.getSprite("sprites/buildings/wire_right.png");
}
default: {
assertAlways(false, "Invalid belt rotation variant");
}
}
}
getBlueprintSprite(rotationVariant) {
switch (arrayBeltVariantToRotation[rotationVariant]) {
case enumDirection.top: {
return Loader.getSprite("sprites/blueprints/wire_top.png");
}
case enumDirection.left: {
return Loader.getSprite("sprites/blueprints/wire_left.png");
}
case enumDirection.right: {
return Loader.getSprite("sprites/blueprints/wire_right.png");
}
default: {
assertAlways(false, "Invalid belt rotation variant");
}
}
}
}

View File

@ -1,120 +0,0 @@
import { enumDirection, Vector } from "../../core/vector";
import { enumItemType } from "../base_item";
import { ItemAcceptorComponent } from "../components/item_acceptor";
import { ItemEjectorComponent } from "../components/item_ejector";
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
import { Entity } from "../entity";
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
import { enumLayer, GameRoot } from "../root";
/** @enum {string} */
export const enumWireCrossingVariants = {
// Default = splitter
merger: "merger",
};
export class MetaWireCrossingsBuilding extends MetaBuilding {
constructor() {
super("wire_crossings");
}
getDimensions(variant) {
return new Vector(1, 1);
}
getSilhouetteColor() {
return "#c425d7";
}
getLayer() {
return enumLayer.wires;
}
/**
* @param {GameRoot} root
*/
getAvailableVariants(root) {
return [defaultBuildingVariant, enumWireCrossingVariants.merger];
}
/**
* @param {GameRoot} root
*/
getIsUnlocked(root) {
return true;
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {
entity.addComponent(
new ItemAcceptorComponent({
slots: [], // set later
})
);
entity.addComponent(
new ItemProcessorComponent({
inputsPerCharge: 1,
processorType: enumItemProcessorTypes.splitterWires,
})
);
entity.addComponent(
new ItemEjectorComponent({
slots: [], // set later
instantEject: true,
})
);
}
/**
*
* @param {Entity} entity
* @param {number} rotationVariant
* @param {string} variant
*/
updateVariants(entity, rotationVariant, variant) {
switch (variant) {
case defaultBuildingVariant: {
entity.components.ItemAcceptor.setSlots([
{
pos: new Vector(0, 0),
directions: [enumDirection.bottom],
layer: enumLayer.wires,
},
]);
entity.components.ItemEjector.setSlots([
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.wires },
{ pos: new Vector(0, 0), direction: enumDirection.right, layer: enumLayer.wires },
]);
break;
}
case enumWireCrossingVariants.merger: {
entity.components.ItemAcceptor.setSlots([
{
pos: new Vector(0, 0),
directions: [enumDirection.top],
layer: enumLayer.wires,
},
{
pos: new Vector(0, 0),
directions: [enumDirection.right],
layer: enumLayer.wires,
},
]);
entity.components.ItemEjector.setSlots([
{ pos: new Vector(0, 0), direction: enumDirection.bottom, layer: enumLayer.wires },
]);
break;
}
default:
assertAlways(false, "Unknown painter variant: " + variant);
}
}
}

View File

@ -10,8 +10,6 @@ export const enumColors = {
white: "white",
uncolored: "uncolored",
black: "black",
};
/** @enum {string} */
@ -26,8 +24,6 @@ export const enumColorToShortcode = {
[enumColors.white]: "w",
[enumColors.uncolored]: "u",
[enumColors.black]: "0",
};
/** @enum {enumColors} */
@ -54,27 +50,9 @@ export const enumColorsToHexCode = {
// blue + green + red
[enumColors.white]: "#ffffff",
[enumColors.black]: "#31383a",
[enumColors.uncolored]: "#aaaaaa",
};
/** @enum {enumColors} */
export const enumInvertedColors = {
[enumColors.red]: enumColors.cyan,
[enumColors.green]: enumColors.purple,
[enumColors.blue]: enumColors.yellow,
[enumColors.yellow]: enumColors.blue,
[enumColors.purple]: enumColors.green,
[enumColors.cyan]: enumColors.red,
[enumColors.white]: enumColors.black,
[enumColors.black]: enumColors.white,
[enumColors.uncolored]: enumColors.uncolored,
};
const c = enumColors;
/** @enum {Object.<string, string>} */
export const enumColorMixingResults = {
@ -88,7 +66,6 @@ export const enumColorMixingResults = {
[c.cyan]: c.white,
[c.white]: c.white,
[c.black]: c.red,
},
// 0, 255, 0
@ -100,7 +77,6 @@ export const enumColorMixingResults = {
[c.cyan]: c.cyan,
[c.white]: c.white,
[c.black]: c.green,
},
// 0, 255, 0
@ -110,20 +86,17 @@ export const enumColorMixingResults = {
[c.cyan]: c.cyan,
[c.white]: c.white,
[c.black]: c.blue,
},
// 255, 255, 0
[c.yellow]: {
[c.purple]: c.white,
[c.cyan]: c.white,
[c.black]: c.yellow,
},
// 255, 0, 255
[c.purple]: {
[c.cyan]: c.white,
[c.black]: c.purple,
},
// 0, 255, 255
@ -140,24 +113,12 @@ export const enumColorMixingResults = {
[c.uncolored]: {
// auto
},
[c.black]: {
// auto
[c.white]: c.uncolored,
[c.cyan]: c.cyan,
[c.uncolored]: c.uncolored,
},
};
// Create same color lookups
for (const color in enumColors) {
enumColorMixingResults[color][color] = color;
// Anything with white is white again, except for black which creates gray
if (color !== enumColors.black) {
enumColorMixingResults[color][c.white] = c.white;
}
enumColorMixingResults[color][c.white] = c.white;
// Anything with uncolored is the same color
enumColorMixingResults[color][c.uncolored] = color;
}

View File

@ -44,3 +44,9 @@ export class Component extends BasicSerializableObject {
}
/* dev:end */
}
/**
* TypeScript does not support Abstract Static methods (https://github.com/microsoft/TypeScript/issues/34516)
* One workaround is to declare the type of the component and reference that for static methods
* @typedef {typeof Component} StaticComponent
*/

View File

@ -5,14 +5,11 @@ import { ItemEjectorComponent } from "./components/item_ejector";
import { ItemAcceptorComponent } from "./components/item_acceptor";
import { MinerComponent } from "./components/miner";
import { ItemProcessorComponent } from "./components/item_processor";
import { ReplaceableMapEntityComponent } from "./components/replaceable_map_entity";
import { UndergroundBeltComponent } from "./components/underground_belt";
import { UnremovableComponent } from "./components/unremovable";
import { HubComponent } from "./components/hub";
import { StorageComponent } from "./components/storage";
import { EnergyGeneratorComponent } from "./components/energy_generator";
import { WiredPinsComponent } from "./components/wired_pins";
import { EnergyConsumerComponent } from "./components/energy_consumer";
import { BeltUnderlaysComponent } from "./components/belt_underlays";
export function initComponentRegistry() {
gComponentRegistry.register(StaticMapEntityComponent);
@ -21,14 +18,11 @@ export function initComponentRegistry() {
gComponentRegistry.register(ItemAcceptorComponent);
gComponentRegistry.register(MinerComponent);
gComponentRegistry.register(ItemProcessorComponent);
gComponentRegistry.register(ReplaceableMapEntityComponent);
gComponentRegistry.register(UndergroundBeltComponent);
gComponentRegistry.register(UnremovableComponent);
gComponentRegistry.register(HubComponent);
gComponentRegistry.register(StorageComponent);
gComponentRegistry.register(EnergyGeneratorComponent);
gComponentRegistry.register(WiredPinsComponent);
gComponentRegistry.register(EnergyConsumerComponent);
gComponentRegistry.register(BeltUnderlaysComponent);
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS

View File

@ -2,23 +2,44 @@ import { enumDirection, Vector } from "../../core/vector";
import { types } from "../../savegame/serialization";
import { BeltPath } from "../belt_path";
import { Component } from "../component";
import { Entity } from "../entity";
import { enumLayer } from "../root";
export const curvedBeltLength = /* Math.PI / 4 */ 0.78;
/** @type {import("./item_acceptor").ItemAcceptorSlot} */
export const FAKE_BELT_ACCEPTOR_SLOT = {
pos: new Vector(0, 0),
directions: [enumDirection.bottom],
};
/** @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,
progress: 0,
},
[enumDirection.right]: {
pos: new Vector(0, 0),
direction: enumDirection.right,
item: null,
progress: 0,
},
[enumDirection.left]: {
pos: new Vector(0, 0),
direction: enumDirection.left,
item: null,
progress: 0,
},
};
export class BeltComponent extends Component {
static getId() {
return "Belt";
}
static getSchema() {
// The followUpCache field is not serialized.
return {
direction: types.string,
};
}
duplicateWithoutContents() {
return new BeltComponent({ direction: this.direction });
}
@ -33,9 +54,6 @@ export class BeltComponent extends Component {
this.direction = direction;
/** @type {Entity} */
this.followUpCache = null;
/**
* The path this belt is contained in, not serialized
* @type {BeltPath}
@ -45,77 +63,59 @@ export class BeltComponent extends Component {
/**
* Returns the effective length of this belt in tile space
* @param {enumLayer} layer
* @returns {number}
*/
getEffectiveLengthTiles(layer) {
assert(layer, "no layer given");
if (layer === enumLayer.wires) {
return 1.0;
}
getEffectiveLengthTiles() {
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}
*/
getFakeEjectorSlot() {
assert(
FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction],
"Invalid belt direction: ",
this.direction
);
return FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction];
}
/**
* Converts from belt space (0 = start of belt ... 1 = end of belt) to the local
* belt coordinates (-0.5|-0.5 to 0.5|0.5)
* @param {number} progress
* @param {enumLayer} layer
* @returns {Vector}
*/
transformBeltToLocalSpace(progress, layer) {
transformBeltToLocalSpace(progress) {
assert(progress >= 0.0, "Invalid progress ( < 0): " + progress);
switch (this.direction) {
case enumDirection.top:
assert(progress <= 1.02, "Invalid progress: " + progress);
return new Vector(0, 0.5 - progress);
switch (layer) {
case enumLayer.regular: {
switch (this.direction) {
case enumDirection.top:
assert(progress <= 1.02, "Invalid progress: " + progress);
return new Vector(0, 0.5 - progress);
case enumDirection.right: {
assert(progress <= curvedBeltLength + 0.02, "Invalid progress 2: " + progress);
const arcProgress = (progress / curvedBeltLength) * 0.5 * Math.PI;
return new Vector(
0.5 - 0.5 * Math.cos(arcProgress),
0.5 - 0.5 * Math.sin(arcProgress)
);
}
case enumDirection.left: {
assert(progress <= curvedBeltLength + 0.02, "Invalid progress 3: " + progress);
const arcProgress = (progress / curvedBeltLength) * 0.5 * Math.PI;
return new Vector(
-0.5 + 0.5 * Math.cos(arcProgress),
0.5 - 0.5 * Math.sin(arcProgress)
);
}
default:
assertAlways(false, "Invalid belt direction: " + this.direction);
return new Vector(0, 0);
}
case enumDirection.right: {
assert(progress <= curvedBeltLength + 0.02, "Invalid progress 2: " + progress);
const arcProgress = (progress / curvedBeltLength) * 0.5 * Math.PI;
return new Vector(0.5 - 0.5 * Math.cos(arcProgress), 0.5 - 0.5 * Math.sin(arcProgress));
}
case enumLayer.wires: {
const pow = 0.5;
switch (this.direction) {
case enumDirection.top:
assert(progress <= 1.02, "Invalid progress: " + progress);
return new Vector(0, 0.5 - progress);
case enumDirection.right: {
assert(progress <= 1.02, "Invalid progress 2: " + progress);
return progress > 0.5 ? new Vector(progress - 0.5, 0) : new Vector(0, 0.5 - progress);
}
case enumDirection.left: {
assert(progress <= 1.02, "Invalid progress 3: " + progress);
return progress > 0.5
? new Vector(-progress + 0.5, 0)
: new Vector(0, 0.5 - progress);
}
default:
assertAlways(false, "Invalid belt direction: " + this.direction);
return new Vector(0, 0);
}
case enumDirection.left: {
assert(progress <= curvedBeltLength + 0.02, "Invalid progress 3: " + progress);
const arcProgress = (progress / curvedBeltLength) * 0.5 * Math.PI;
return new Vector(-0.5 + 0.5 * Math.cos(arcProgress), 0.5 - 0.5 * Math.sin(arcProgress));
}
default:
assertAlways(false, "Invalid belt direction: " + this.direction);
return new Vector(0, 0);
}
}
}

View File

@ -0,0 +1,33 @@
import { Component } from "../component";
import { types } from "../../savegame/serialization";
import { enumDirection, Vector } from "../../core/vector";
export class BeltUnderlaysComponent extends Component {
static getId() {
return "BeltUnderlays";
}
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

@ -1,133 +0,0 @@
import { Component } from "../component";
import { types } from "../../savegame/serialization";
import { Vector } from "../../core/vector";
import { BaseItem, enumItemTypeToLayer, enumItemType } from "../base_item";
export class EnergyConsumerComponent extends Component {
static getId() {
return "EnergyConsumer";
}
static getSchema() {
return {
bufferSize: types.float,
perCharge: types.float,
batteryPosition: types.vector,
energyType: types.enum(enumItemType),
wasteType: types.enum(enumItemType),
acceptorSlotIndex: types.uint,
ejectorSlotIndex: types.uint,
stored: types.float,
piledOutput: types.float,
};
}
duplicateWithoutContents() {
return new EnergyConsumerComponent({
bufferSize: this.bufferSize,
perCharge: this.perCharge,
batteryPosition: this.batteryPosition.copy(),
acceptorSlotIndex: this.acceptorSlotIndex,
ejectorSlotIndex: this.ejectorSlotIndex,
});
}
/**
*
* @param {object} param0
* @param {number} param0.bufferSize How much energy this consumer can store
* @param {number} param0.perCharge How much energy this consumer needs per charge
* @param {Vector} param0.batteryPosition world space render offset of the battery icon
* @param {number} param0.acceptorSlotIndex Which slot to accept energy on
* @param {number} param0.ejectorSlotIndex Which slot to eject energy off
*
*/
constructor({
bufferSize = 3,
perCharge = 1,
batteryPosition = new Vector(),
acceptorSlotIndex = 0,
ejectorSlotIndex = 0,
}) {
super();
this.bufferSize = bufferSize;
this.perCharge = perCharge;
this.batteryPosition = batteryPosition;
this.energyType = enumItemType.positiveEnergy;
this.wasteType = enumItemType.negativeEnergy;
this.acceptorSlotIndex = acceptorSlotIndex;
this.ejectorSlotIndex = ejectorSlotIndex;
/**
* How much energy we have stored right now
*/
this.stored = 0;
/**
* How much waste we have piled up so far
*/
this.piledOutput = 0;
}
/**
* Tries to accept a given item
* @param {BaseItem} item
* @param {number} slotIndex
*/
tryAcceptItem(item, slotIndex) {
if (slotIndex !== this.acceptorSlotIndex) {
// Wrong slot
return false;
}
if (item.getItemType() !== this.energyType) {
// Not the right type
return false;
}
if (this.stored >= this.bufferSize) {
// We are full
return false;
}
// All good, consume
this.stored = Math.min(this.stored + 1, this.bufferSize);
return true;
}
/**
* Tries to start the next charge
*/
tryStartNextCharge() {
if (this.hasTooMuchWastePiled()) {
// Too much waste remaining
return false;
}
if (this.stored < this.perCharge) {
// Not enough energy stored
return false;
}
this.stored -= this.perCharge;
this.piledOutput += this.perCharge;
return true;
}
/**
* Returns if there is too much waste piled
*/
hasTooMuchWastePiled() {
return this.piledOutput >= 1.0;
}
/**
* Reduces the waste by the given amount
* @param {number} amount
*/
reduceWaste(amount) {
this.piledOutput = Math.max(0, this.piledOutput - amount);
}
}

View File

@ -1,93 +0,0 @@
import { types } from "../../savegame/serialization";
import { BaseItem, enumItemType } from "../base_item";
import { Component } from "../component";
import { ShapeItem } from "../items/shape_item";
const maxQueueSize = 4;
export class EnergyGeneratorComponent extends Component {
static getId() {
return "EnergyGenerator";
}
static getSchema() {
return {
requiredKey: types.nullable(types.string),
itemsInQueue: types.uint,
wasteAcceptorSlotIndex: types.uint,
};
}
duplicateWithoutContents() {
return new EnergyGeneratorComponent({
requiredKey: null,
wasteAcceptorSlotIndex: this.wasteAcceptorSlotIndex,
});
}
/**
*
* @param {object} param0
* @param {string=} param0.requiredKey Which shape this generator needs, can be null if not computed yet
* @param {number} param0.wasteAcceptorSlotIndex Which slot accepts the waste
*/
constructor({ requiredKey, wasteAcceptorSlotIndex = 0 }) {
super();
this.requiredKey = requiredKey;
/**
* Stores how many items are ready to be converted to energy
* @type {number}
*/
this.itemsInQueue = 0;
/**
* Stores which slot accepts the waste
* @type {number}
*/
this.wasteAcceptorSlotIndex = wasteAcceptorSlotIndex;
}
/**
*
* @param {BaseItem} item
* @param {number} slot
*/
tryTakeItem(item, slot) {
if (slot === this.wasteAcceptorSlotIndex) {
// this is the acceptor slot on the wires layer
// just destroy it
return true;
} else {
if (item.getItemType() !== enumItemType.shape) {
// This shouldn't happen since we have a filter - still, it doesn't hurt
// to check either
assertAlways(
false,
"Energy generator took wrong item: " +
item.getItemType() +
" on slot " +
slot +
" (waste slot = " +
this.wasteAcceptorSlotIndex +
")"
);
return false;
}
if (/** @type {ShapeItem} */ (item).definition.getHash() !== this.requiredKey) {
// Not our shape
return false;
}
if (this.itemsInQueue >= maxQueueSize) {
// Queue is full
return false;
}
// Take item and put it into the queue
++this.itemsInQueue;
return true;
}
}
}

View File

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

View File

@ -2,12 +2,10 @@ import { enumDirection, enumInvertedDirections, Vector } from "../../core/vector
import { types } from "../../savegame/serialization";
import { BaseItem, enumItemType } from "../base_item";
import { Component } from "../component";
import { enumLayer } from "../root";
/** @typedef {{
* pos: Vector,
* directions: enumDirection[],
* layer: enumLayer,
* filter?: enumItemType
* }} ItemAcceptorSlot */
@ -22,7 +20,6 @@ import { enumLayer } from "../root";
/** @typedef {{
* pos: Vector,
* directions: enumDirection[],
* layer?: enumLayer,
* filter?: enumItemType
* }} ItemAcceptorSlotConfig */
@ -31,31 +28,6 @@ export class ItemAcceptorComponent extends Component {
return "ItemAcceptor";
}
static getSchema() {
return {
slots: types.array(
types.structured({
pos: types.vector,
directions: types.array(types.enum(enumDirection)),
filter: types.nullable(types.enum(enumItemType)),
// TODO: MIGRATE
layer: types.enum(enumLayer),
})
),
animated: types.bool,
beltUnderlays: types.array(
types.structured({
pos: types.vector,
direction: types.enum(enumDirection),
// TODO: MIGRATE
layer: types.enum(enumLayer),
})
),
};
}
duplicateWithoutContents() {
const slotsCopy = [];
for (let i = 0; i < this.slots.length; ++i) {
@ -64,24 +36,11 @@ export class ItemAcceptorComponent extends Component {
pos: slot.pos.copy(),
directions: slot.directions.slice(),
filter: slot.filter,
layer: slot.layer,
});
}
const beltUnderlaysCopy = [];
for (let i = 0; i < this.beltUnderlays.length; ++i) {
const underlay = this.beltUnderlays[i];
beltUnderlaysCopy.push({
pos: underlay.pos.copy(),
direction: underlay.direction,
layer: underlay.layer,
});
}
return new ItemAcceptorComponent({
slots: slotsCopy,
beltUnderlays: beltUnderlaysCopy,
animated: this.animated,
});
}
@ -89,23 +48,16 @@ export class ItemAcceptorComponent extends Component {
*
* @param {object} param0
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items
* @param {boolean=} param0.animated Whether to animate item consumption
* @param {Array<{pos: Vector, direction: enumDirection, layer: enumLayer}>=} param0.beltUnderlays Where to render belt underlays
*/
constructor({ slots = [], beltUnderlays = [], animated = true }) {
constructor({ slots = [] }) {
super();
this.animated = animated;
/**
* Fixes belt animations
* @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>}
*/
this.itemConsumptionAnimations = [];
/* Which belt underlays to render */
this.beltUnderlays = beltUnderlays;
this.setSlots(slots);
}
@ -121,7 +73,6 @@ export class ItemAcceptorComponent extends Component {
this.slots.push({
pos: slot.pos,
directions: slot.directions,
layer: slot.layer || enumLayer.regular,
// Which type of item to accept (shape | color | all) @see enumItemType
filter: slot.filter,
@ -164,24 +115,21 @@ export class ItemAcceptorComponent extends Component {
* @param {BaseItem} item
*/
onItemAccepted(slotIndex, direction, item) {
if (this.animated) {
this.itemConsumptionAnimations.push({
item,
slotIndex,
direction,
animProgress: 0.0,
});
}
this.itemConsumptionAnimations.push({
item,
slotIndex,
direction,
animProgress: 0.0,
});
}
/**
* Tries to find a slot which accepts the current item
* @param {Vector} targetLocalTile
* @param {enumDirection} fromLocalDirection
* @param {enumLayer} layer
* @returns {ItemAcceptorLocatedSlot|null}
*/
findMatchingSlot(targetLocalTile, fromLocalDirection, layer) {
findMatchingSlot(targetLocalTile, fromLocalDirection) {
// We need to invert our direction since the acceptor specifies *from* which direction
// it accepts items, but the ejector specifies *into* which direction it ejects items.
// E.g.: Ejector ejects into "right" direction but acceptor accepts from "left" direction.
@ -196,11 +144,6 @@ export class ItemAcceptorComponent extends Component {
continue;
}
// Make sure the layer matches
if (slot.layer !== layer) {
continue;
}
// Check if the acceptor slot accepts items from our direction
for (let i = 0; i < slot.directions.length; ++i) {
// const localDirection = targetStaticComp.localDirectionToWorld(slot.directions[l]);

View File

@ -5,15 +5,16 @@ import { types } from "../../savegame/serialization";
import { gItemRegistry } from "../../core/global_registries";
import { Entity } from "../entity";
import { enumLayer } from "../root";
import { BeltPath } from "../belt_path";
/**
* @typedef {{
* pos: Vector,
* direction: enumDirection,
* item: BaseItem,
* layer: enumLayer,
* progress: number?,
* cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot,
* cachedBeltPath?: BeltPath,
* cachedTargetEntity?: Entity
* }} ItemEjectorSlot
*/
@ -24,19 +25,12 @@ export class ItemEjectorComponent extends Component {
}
static getSchema() {
// The cachedDestSlot, cachedTargetEntity, and cachedConnectedSlots fields
// are not serialized.
// The cachedDestSlot, cachedTargetEntity fields are not serialized.
return {
instantEject: types.bool,
slots: types.array(
types.structured({
pos: types.vector,
direction: types.enum(enumDirection),
item: types.nullable(types.obj(gItemRegistry)),
progress: types.float,
// TODO: Migrate
layer: types.enum(enumLayer),
})
),
};
@ -49,33 +43,24 @@ export class ItemEjectorComponent extends Component {
slotsCopy.push({
pos: slot.pos.copy(),
direction: slot.direction,
layer: slot.layer,
});
}
return new ItemEjectorComponent({
slots: slotsCopy,
instantEject: this.instantEject,
});
}
/**
*
* @param {object} param0
* @param {Array<{pos: Vector, direction: enumDirection, layer?: enumLayer}>=} param0.slots The slots to eject on
* @param {boolean=} param0.instantEject If the ejection is instant
* @param {Array<{pos: Vector, direction: enumDirection }>=} param0.slots The slots to eject on
*/
constructor({ slots = [], instantEject = false }) {
constructor({ slots = [] }) {
super();
// How long items take to eject
this.instantEject = instantEject;
this.setSlots(slots);
/** @type {ItemEjectorSlot[]} */
this.cachedConnectedSlots = null;
/**
* Whether this ejector slot is enabled
*/
@ -83,7 +68,7 @@ export class ItemEjectorComponent extends Component {
}
/**
* @param {Array<{pos: Vector, direction: enumDirection, layer?: enumLayer}>} slots The slots to eject on
* @param {Array<{pos: Vector, direction: enumDirection }>} slots The slots to eject on
*/
setSlots(slots) {
/** @type {Array<ItemEjectorSlot>} */
@ -95,7 +80,6 @@ export class ItemEjectorComponent extends Component {
direction: slot.direction,
item: null,
progress: 0,
layer: slot.layer || enumLayer.regular,
cachedDestSlot: null,
cachedTargetEntity: null,
});
@ -104,11 +88,10 @@ export class ItemEjectorComponent extends Component {
/**
* Returns where this slot ejects to
* @param {number} index
* @param {ItemEjectorSlot} slot
* @returns {Vector}
*/
getSlotTargetLocalTile(index) {
const slot = this.slots[index];
getSlotTargetLocalTile(slot) {
const directionVector = enumDirectionToVector[slot.direction];
return slot.pos.add(directionVector);
}
@ -116,11 +99,10 @@ export class ItemEjectorComponent extends Component {
/**
* Returns whether any slot ejects to the given local tile
* @param {Vector} tile
* @param {enumLayer} layer
*/
anySlotEjectsToLocalTile(tile, layer) {
anySlotEjectsToLocalTile(tile) {
for (let i = 0; i < this.slots.length; ++i) {
if (this.getSlotTargetLocalTile(i).equals(tile) && this.slots[i].layer === layer) {
if (this.getSlotTargetLocalTile(this.slots[i]).equals(tile)) {
return true;
}
}
@ -139,12 +121,11 @@ export class ItemEjectorComponent extends Component {
/**
* Returns the first free slot on this ejector or null if there is none
* @param {enumLayer} layer
* @returns {number?}
*/
getFirstFreeSlot(layer) {
getFirstFreeSlot() {
for (let i = 0; i < this.slots.length; ++i) {
if (this.canEjectOnSlot(i) && this.slots[i].layer === layer) {
if (this.canEjectOnSlot(i)) {
return i;
}
}
@ -162,7 +143,7 @@ export class ItemEjectorComponent extends Component {
return false;
}
this.slots[slotIndex].item = item;
this.slots[slotIndex].progress = this.instantEject ? 1 : 0;
this.slots[slotIndex].progress = 0;
return true;
}

View File

@ -18,7 +18,6 @@ export const enumItemProcessorTypes = {
painter: "painter",
painterDouble: "painterDouble",
painterQuad: "painterQuad",
advancedProcessor: "advancedProcessor",
hub: "hub",
};
@ -30,9 +29,6 @@ export class ItemProcessorComponent extends Component {
static getSchema() {
return {
nextOutputSlot: types.uint,
type: types.enum(enumItemProcessorTypes),
inputsPerCharge: types.uint,
inputSlots: types.array(
types.structured({
item: types.obj(gItemRegistry),

View File

@ -15,7 +15,6 @@ export class MinerComponent extends Component {
// cachedMinedItem is not serialized.
return {
lastMiningTime: types.ufloat,
chainable: types.bool,
itemChainBuffer: types.array(types.obj(gItemRegistry)),
};
}

View File

@ -1,15 +0,0 @@
import { Component } from "../component";
/**
* Marks an entity as replaceable, so that when other buildings are placed above him it
* simply gets deleted
*/
export class ReplaceableMapEntityComponent extends Component {
static getId() {
return "ReplaceableMapEntity";
}
duplicateWithoutContents() {
return new ReplaceableMapEntityComponent();
}
}

View File

@ -6,6 +6,7 @@ import { enumDirection, Vector } from "../../core/vector";
import { types } from "../../savegame/serialization";
import { Component } from "../component";
import { getBuildingDataFromCode } from "../building_codes";
import { MetaBuilding } from "../meta_building";
export class StaticMapEntityComponent extends Component {
static getId() {
@ -15,7 +16,6 @@ export class StaticMapEntityComponent extends Component {
static getSchema() {
return {
origin: types.tileVector,
tileSize: types.tileVector,
rotation: types.float,
originalRotation: types.float,
@ -24,6 +24,14 @@ export class StaticMapEntityComponent extends Component {
};
}
/**
* Returns the effective tile size
* @returns {Vector}
*/
getTileSize() {
return getBuildingDataFromCode(this.code).tileSize;
}
/**
* Returns the sprite
* @returns {AtlasSprite}
@ -48,10 +56,17 @@ export class StaticMapEntityComponent extends Component {
return getBuildingDataFromCode(this.code).silhouetteColor;
}
/**
* Returns the meta building
* @returns {MetaBuilding}
*/
getMetaBuilding() {
return getBuildingDataFromCode(this.code).metaInstance;
}
duplicateWithoutContents() {
return new StaticMapEntityComponent({
origin: this.origin.copy(),
tileSize: this.tileSize.copy(),
rotation: this.rotation,
originalRotation: this.originalRotation,
code: this.code,
@ -81,7 +96,6 @@ export class StaticMapEntityComponent extends Component {
);
this.origin = origin;
this.tileSize = tileSize;
this.rotation = rotation;
this.code = code;
this.originalRotation = originalRotation;
@ -92,30 +106,16 @@ export class StaticMapEntityComponent extends Component {
* @returns {Rectangle}
*/
getTileSpaceBounds() {
const size = this.getTileSize();
switch (this.rotation) {
case 0:
return new Rectangle(this.origin.x, this.origin.y, this.tileSize.x, this.tileSize.y);
return new Rectangle(this.origin.x, this.origin.y, size.x, size.y);
case 90:
return new Rectangle(
this.origin.x - this.tileSize.y + 1,
this.origin.y,
this.tileSize.y,
this.tileSize.x
);
return new Rectangle(this.origin.x - size.y + 1, this.origin.y, size.y, size.x);
case 180:
return new Rectangle(
this.origin.x - this.tileSize.x + 1,
this.origin.y - this.tileSize.y + 1,
this.tileSize.x,
this.tileSize.y
);
return new Rectangle(this.origin.x - size.x + 1, this.origin.y - size.y + 1, size.x, size.y);
case 270:
return new Rectangle(
this.origin.x,
this.origin.y - this.tileSize.x + 1,
this.tileSize.y,
this.tileSize.x
);
return new Rectangle(this.origin.x, this.origin.y - size.x + 1, size.y, size.x);
default:
assert(false, "Invalid rotation");
}
@ -186,34 +186,35 @@ export class StaticMapEntityComponent extends Component {
let y = 0;
let w = 0;
let h = 0;
const size = this.getTileSize();
switch (this.rotation) {
case 0: {
x = this.origin.x;
y = this.origin.y;
w = this.tileSize.x;
h = this.tileSize.y;
w = size.x;
h = size.y;
break;
}
case 90: {
x = this.origin.x - this.tileSize.y + 1;
x = this.origin.x - size.y + 1;
y = this.origin.y;
w = this.tileSize.y;
h = this.tileSize.x;
w = size.y;
h = size.x;
break;
}
case 180: {
x = this.origin.x - this.tileSize.x + 1;
y = this.origin.y - this.tileSize.y + 1;
w = this.tileSize.x;
h = this.tileSize.y;
x = this.origin.x - size.x + 1;
y = this.origin.y - size.y + 1;
w = size.x;
h = size.y;
break;
}
case 270: {
x = this.origin.x;
y = this.origin.y - this.tileSize.x + 1;
w = this.tileSize.y;
h = this.tileSize.x;
y = this.origin.y - size.x + 1;
w = size.y;
h = size.x;
break;
}
default:
@ -233,19 +234,13 @@ export class StaticMapEntityComponent extends Component {
* @param {DrawParameters} parameters
* @param {AtlasSprite} sprite
* @param {number=} extrudePixels How many pixels to extrude the sprite
* @param {boolean=} clipping Whether to clip
* @param {Vector=} overridePosition Whether to drwa the entity at a different location
*/
drawSpriteOnFullEntityBounds(
parameters,
sprite,
extrudePixels = 0,
clipping = true,
overridePosition = null
) {
drawSpriteOnFullEntityBounds(parameters, sprite, extrudePixels = 0, overridePosition = null) {
if (!this.shouldBeDrawn(parameters) && !overridePosition) {
return;
}
const size = this.getTileSize();
let worldX = this.origin.x * globalConfig.tileSize;
let worldY = this.origin.y * globalConfig.tileSize;
@ -258,10 +253,10 @@ export class StaticMapEntityComponent extends Component {
// Early out, is faster
sprite.drawCached(
parameters,
worldX - extrudePixels * this.tileSize.x,
worldY - extrudePixels * this.tileSize.y,
globalConfig.tileSize * this.tileSize.x + 2 * extrudePixels * this.tileSize.x,
globalConfig.tileSize * this.tileSize.y + 2 * extrudePixels * this.tileSize.y,
worldX - extrudePixels * size.x,
worldY - extrudePixels * size.y,
globalConfig.tileSize * size.x + 2 * extrudePixels * size.x,
globalConfig.tileSize * size.y + 2 * extrudePixels * size.y,
false
);
} else {
@ -273,10 +268,10 @@ export class StaticMapEntityComponent extends Component {
sprite.drawCached(
parameters,
-globalConfig.halfTileSize - extrudePixels * this.tileSize.x,
-globalConfig.halfTileSize - extrudePixels * this.tileSize.y,
globalConfig.tileSize * this.tileSize.x + 2 * extrudePixels * this.tileSize.x,
globalConfig.tileSize * this.tileSize.y + 2 * extrudePixels * this.tileSize.y,
-globalConfig.halfTileSize - extrudePixels * size.x,
-globalConfig.halfTileSize - extrudePixels * size.y,
globalConfig.tileSize * size.x + 2 * extrudePixels * size.x,
globalConfig.tileSize * size.y + 2 * extrudePixels * size.y,
false
);

View File

@ -12,10 +12,8 @@ export class StorageComponent extends Component {
static getSchema() {
return {
maximumStorage: types.uint,
storedCount: types.uint,
storedItem: types.nullable(types.obj(gItemRegistry)),
overlayOpacity: types.ufloat,
};
}

View File

@ -26,9 +26,7 @@ export class UndergroundBeltComponent extends Component {
static getSchema() {
return {
mode: types.enum(enumUndergroundBeltMode),
pendingItems: types.array(types.pair(types.obj(gItemRegistry), types.float)),
tier: types.uint,
};
}
@ -103,8 +101,7 @@ export class UndergroundBeltComponent extends Component {
}
// Notice: We assume that for all items the travel distance is the same
const maxItemsInTunnel =
(2 + travelDistance) / globalConfig.beltItemSpacingByLayer[enumLayer.regular];
const maxItemsInTunnel = (2 + travelDistance) / globalConfig.itemSpacingOnBelts;
if (this.pendingItems.length >= maxItemsInTunnel) {
// Simulate a real belt which gets full at some point
return false;
@ -114,8 +111,7 @@ export class UndergroundBeltComponent extends Component {
// This corresponds to the item ejector - it needs 0.5 additional tiles to eject the item.
// So instead of adding 1 we add 0.5 only.
// Additionally it takes 1 tile for the acceptor which we just add on top.
const travelDuration =
(travelDistance + 1.5) / beltSpeed / globalConfig.beltItemSpacingByLayer[enumLayer.regular];
const travelDuration = (travelDistance + 1.5) / beltSpeed / globalConfig.itemSpacingOnBelts;
this.pendingItems.push([item, travelDuration]);

View File

@ -1,15 +0,0 @@
import { Component } from "../component";
export class UnremovableComponent extends Component {
static getId() {
return "Unremovable";
}
static getSchema() {
return {};
}
duplicateWithoutContents() {
return new UnremovableComponent();
}
}

View File

@ -8,6 +8,8 @@ export const enumPinSlotType = {
negativeEnergyEjector: "negativeEnergyEjector",
positiveEnergyAcceptor: "positiveEnergyAcceptor",
negativeEnergyAcceptor: "negativeEnergyAcceptor",
logicalEjector: "logicalEjector",
logicalAcceptor: "logicalAcceptor",
};
/** @typedef {{
@ -27,17 +29,6 @@ export class WiredPinsComponent extends Component {
return "WiredPins";
}
static getSchema() {
return {
slots: types.array(
types.structured({
pos: types.vector,
type: types.enum(enumPinSlotType),
})
),
};
}
/**
*
* @param {object} param0

View File

@ -349,8 +349,9 @@ export class GameCore {
// Gather context and save all state
const context = root.context;
context.save();
if (G_IS_DEV && globalConfig.debug.testClipping) {
context.clearRect(0, 0, window.innerWidth * 3, window.innerHeight * 3);
if (G_IS_DEV) {
context.fillStyle = "#a10000";
context.fillRect(0, 0, window.innerWidth * 3, window.innerHeight * 3);
}
// Compute optimal zoom level and atlas scale
@ -394,49 +395,36 @@ export class GameCore {
// -----
// BG / Map Resources / Belt Backgrounds
root.map.drawBackground(params);
if (!this.root.camera.getIsMapOverlayActive()) {
root.map.drawBackground(params);
// Underlays for splitters / balancers
systems.itemAcceptor.drawUnderlays(params, enumLayer.regular);
systems.beltUnderlays.drawUnderlays(params);
// Belt items
systems.belt.drawLayerBeltItems(params, enumLayer.regular);
systems.belt.drawBeltItems(params);
// Items being ejected / accepted currently (animations)
systems.itemEjector.drawLayer(params, enumLayer.regular);
systems.itemAcceptor.drawLayer(params, enumLayer.regular);
}
systems.itemEjector.draw(params);
systems.itemAcceptor.draw(params);
// Miner & Static map entities
root.map.drawForeground(params);
// Miner & Static map entities
root.map.drawForeground(params);
if (!this.root.camera.getIsMapOverlayActive()) {
// HUB Overlay
systems.hub.draw(params);
// Energy generator overlay
systems.energyGenerator.draw(params);
// Storage items
systems.storage.draw(params);
// Energy consumer (Battery icons)
systems.energyConsumer.draw(params);
// Green wires overlay
root.hud.parts.wiresOverlay.draw(params);
} else {
root.map.drawOverlay(params);
}
// Green wires overlay (not within the if because it can fade)
root.hud.parts.wiresOverlay.draw(params);
if (this.root.currentLayer === enumLayer.wires && !this.root.camera.getIsMapOverlayActive()) {
// Belt sprites & Static map entities
root.map.drawWiresLayer(params);
// Belt items as well as accepted / ejected items
systems.belt.drawLayerBeltItems(params, enumLayer.wires);
systems.itemEjector.drawLayer(params, enumLayer.wires);
systems.itemAcceptor.drawLayer(params, enumLayer.wires);
// Static map entities
root.map.drawWiresForegroundLayer(params);
// pins

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