Merge branch 'master' into master
7
.github/workflows/ci.yml
vendored
@ -44,3 +44,10 @@ jobs:
|
||||
uses: ibiqlik/action-yamllint@v1.0.0
|
||||
with:
|
||||
file_or_dir: translations/*.yaml
|
||||
|
||||
- name: TSLint
|
||||
run: |
|
||||
cd gulp
|
||||
yarn gulp translations.fullBuild
|
||||
cd ..
|
||||
yarn tslint
|
||||
|
@ -50,7 +50,7 @@ If you want to add a new feature or in generally contribute I recommend to get i
|
||||
|
||||
### Code
|
||||
|
||||
The game is based on a custom engine which itself is based on the YORG.io 3 game egine (Actually it shares almost the same core).
|
||||
The game is based on a custom engine which itself is based on the YORG.io 3 game engine (Actually it shares almost the same core).
|
||||
The code within the engine is relatively clean with some code for the actual game on top being hacky.
|
||||
|
||||
This project is based on ES5. Some ES2015 features are used but most of them are too slow, especially when polyfilled. For example, `Array.prototype.forEach` is only used within non-critical loops since its slower than a plain for loop.
|
||||
|
@ -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
@ -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 = {
|
||||
|
@ -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))
|
||||
);
|
||||
});
|
||||
|
67
gulp/html.js
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -3,7 +3,6 @@
|
||||
"version": "1.0.0",
|
||||
"description": "builder",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"gulp": "gulp"
|
||||
},
|
||||
@ -41,21 +40,18 @@
|
||||
"lz-string": "^1.4.4",
|
||||
"markdown-loader": "^5.1.0",
|
||||
"node-sri": "^1.1.1",
|
||||
"obfuscator-loader": "^1.1.2",
|
||||
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
||||
"promise-polyfill": "^8.1.0",
|
||||
"query-string": "^6.8.1",
|
||||
"rusha": "^0.8.13",
|
||||
"serialize-error": "^3.0.0",
|
||||
"sloc": "^0.2.1",
|
||||
"strictdom": "^1.0.1",
|
||||
"string-replace-webpack-plugin": "^0.1.3",
|
||||
"terser-webpack-plugin": "^1.1.0",
|
||||
"through2": "^3.0.1",
|
||||
"uglify-template-string-loader": "^1.1.0",
|
||||
"unused-files-webpack-plugin": "^3.4.0",
|
||||
"webpack": "^4.31.0",
|
||||
"webpack-bundle-analyzer": "^3.0.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-deep-scope-plugin": "^1.6.0",
|
||||
"webpack-plugin-replace": "^1.1.1",
|
||||
@ -77,8 +73,6 @@
|
||||
"gulp-cache": "^1.1.3",
|
||||
"gulp-cached": "^1.1.1",
|
||||
"gulp-clean": "^0.4.0",
|
||||
"gulp-cssbeautify": "^2.0.1",
|
||||
"gulp-csslint": "^1.0.1",
|
||||
"gulp-dom": "^1.0.0",
|
||||
"gulp-flatten": "^0.4.0",
|
||||
"gulp-fluent-ffmpeg": "^2.0.0",
|
||||
@ -86,8 +80,6 @@
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-if": "^3.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-javascript-obfuscator": "^1.1.5",
|
||||
"gulp-jsbeautifier": "^3.0.0",
|
||||
"gulp-load-plugins": "^2.0.3",
|
||||
"gulp-phonegap-build": "^0.1.5",
|
||||
"gulp-plumber": "^1.2.1",
|
||||
@ -105,16 +97,14 @@
|
||||
"imagemin-pngquant": "^9.0.0",
|
||||
"jimp": "^0.6.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"onesky-fetch": "^0.0.7",
|
||||
"postcss-assets": "^5.0.0",
|
||||
"postcss-preset-env": "^6.5.0",
|
||||
"postcss-round-subpixels": "^1.2.0",
|
||||
"postcss-unprefix": "^2.1.3",
|
||||
"sass-unused": "^0.3.0",
|
||||
"speed-measure-webpack-plugin": "^1.3.1",
|
||||
"strip-json-comments": "^3.0.1",
|
||||
"trim": "^0.0.1",
|
||||
"webpack-stream": "^5.1.0",
|
||||
"webpack-stream": "^5.2.1",
|
||||
"yaml-loader": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -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"))
|
||||
|
@ -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: [
|
||||
|
@ -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: [
|
||||
|
1148
gulp/yarn.lock
10
package.json
@ -9,7 +9,7 @@
|
||||
"scripts": {
|
||||
"dev": "cd gulp && yarn gulp main.serveDev",
|
||||
"tslint": "cd src/js && tsc",
|
||||
"lint": "npx eslint src/js",
|
||||
"lint": "eslint src/js",
|
||||
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*",
|
||||
"publishOnItchWindows": "butler push tmp_standalone_files/shapez.io-standalone-win32-x64 tobspr/shapezio:windows --userversion-file version",
|
||||
"publishOnItchLinux": "butler push tmp_standalone_files/shapez.io-standalone-linux-x64 tobspr/shapezio:linux --userversion-file version",
|
||||
@ -46,20 +46,18 @@
|
||||
"lz-string": "^1.4.4",
|
||||
"markdown-loader": "^4.0.0",
|
||||
"match-all": "^1.2.5",
|
||||
"obfuscator-loader": "^1.1.2",
|
||||
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
||||
"promise-polyfill": "^8.1.0",
|
||||
"query-string": "^6.8.1",
|
||||
"rusha": "^0.8.13",
|
||||
"serialize-error": "^3.0.0",
|
||||
"sloc": "^0.2.1",
|
||||
"strictdom": "^1.0.1",
|
||||
"string-replace-webpack-plugin": "^0.1.3",
|
||||
"terser-webpack-plugin": "^1.1.0",
|
||||
"typescript": "3.9.3",
|
||||
"uglify-template-string-loader": "^1.1.0",
|
||||
"unused-files-webpack-plugin": "^3.4.0",
|
||||
"webpack": "^4.31.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-bundle-analyzer": "^3.0.3",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-deep-scope-plugin": "^1.6.0",
|
||||
@ -87,16 +85,14 @@
|
||||
"imagemin-pngquant": "^8.0.0",
|
||||
"jimp": "^0.6.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"onesky-fetch": "^0.0.7",
|
||||
"postcss-assets": "^5.0.0",
|
||||
"postcss-preset-env": "^6.5.0",
|
||||
"postcss-round-subpixels": "^1.2.0",
|
||||
"postcss-unprefix": "^2.1.3",
|
||||
"prettier": "^2.0.4",
|
||||
"sass-unused": "^0.3.0",
|
||||
"speed-measure-webpack-plugin": "^1.3.1",
|
||||
"strip-json-comments": "^3.0.1",
|
||||
"trim": "^0.0.1",
|
||||
"webpack-stream": "^5.1.0"
|
||||
"yarn": "^1.22.4"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
Before Width: | Height: | Size: 527 KiB After Width: | Height: | Size: 523 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
@ -449,6 +449,7 @@
|
||||
<key type="filename">sprites/blueprints/miner-chainable.png</key>
|
||||
<key type="filename">sprites/blueprints/miner.png</key>
|
||||
<key type="filename">sprites/blueprints/rotater-ccw.png</key>
|
||||
<key type="filename">sprites/blueprints/rotater-fl.png</key>
|
||||
<key type="filename">sprites/blueprints/rotater.png</key>
|
||||
<key type="filename">sprites/blueprints/splitter-compact-inverse.png</key>
|
||||
<key type="filename">sprites/blueprints/splitter-compact.png</key>
|
||||
@ -459,6 +460,7 @@
|
||||
<key type="filename">sprites/blueprints/underground_belt_exit.png</key>
|
||||
<key type="filename">sprites/buildings/miner-chainable.png</key>
|
||||
<key type="filename">sprites/buildings/rotater-ccw.png</key>
|
||||
<key type="filename">sprites/buildings/rotater-fl.png</key>
|
||||
<key type="filename">sprites/buildings/splitter-compact-inverse.png</key>
|
||||
<key type="filename">sprites/buildings/splitter-compact.png</key>
|
||||
<key type="filename">sprites/buildings/underground_belt_entry-tier2.png</key>
|
||||
@ -566,21 +568,6 @@
|
||||
<key>scale9FromFile</key>
|
||||
<false/>
|
||||
</struct>
|
||||
<key type="filename">sprites/misc/lock_direction_indicator.png</key>
|
||||
<struct type="IndividualSpriteSettings">
|
||||
<key>pivotPoint</key>
|
||||
<point_f>0.5,0.5</point_f>
|
||||
<key>spriteScale</key>
|
||||
<double>1</double>
|
||||
<key>scale9Enabled</key>
|
||||
<false/>
|
||||
<key>scale9Borders</key>
|
||||
<rect>12,12,24,24</rect>
|
||||
<key>scale9Paddings</key>
|
||||
<rect>12,12,24,24</rect>
|
||||
<key>scale9FromFile</key>
|
||||
<false/>
|
||||
</struct>
|
||||
<key type="filename">sprites/misc/storage_overlay.png</key>
|
||||
<struct type="IndividualSpriteSettings">
|
||||
<key>pivotPoint</key>
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.0 KiB |
@ -73,6 +73,9 @@ body {
|
||||
scrollbar-face-color: #888;
|
||||
scrollbar-track-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
// Firefox
|
||||
scrollbar-color: #cdd0d4 rgba(#000, 0.05);
|
||||
|
||||
overflow: hidden;
|
||||
@include Text;
|
||||
|
||||
@ -369,7 +372,7 @@ canvas {
|
||||
}
|
||||
|
||||
.pressed {
|
||||
transform: scale(0.95) !important;
|
||||
transform: scale(0.98) !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@
|
||||
.buildings {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
@include S(margin-bottom, 2px);
|
||||
|
||||
.building {
|
||||
color: $accentColorDark;
|
||||
@ -43,7 +42,7 @@
|
||||
@include S(width, 35px);
|
||||
@include S(height, 40px);
|
||||
|
||||
background: center center / 70% no-repeat;
|
||||
background: center center / 65% no-repeat;
|
||||
|
||||
&:not(.unlocked) {
|
||||
@include S(width, 20px);
|
||||
|
@ -55,6 +55,7 @@
|
||||
.dialogInner {
|
||||
opacity: 1;
|
||||
}
|
||||
backdrop-filter: blur(D(3px));
|
||||
}
|
||||
|
||||
.dialogInner {
|
||||
|
@ -1,6 +1,23 @@
|
||||
#state_AboutState {
|
||||
> .container .content {
|
||||
@include S(max-width, 600px);
|
||||
@include PlainText;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.head {
|
||||
@include S(padding, 20px);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
@include S(max-width, 200px);
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
@include S(margin, 10px);
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -1,5 +1,6 @@
|
||||
#state_ChangelogState {
|
||||
.content {
|
||||
@include S(max-width, 800px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@
|
||||
opacity: 0;
|
||||
display: none;
|
||||
transform: translate(50%, 50%);
|
||||
filter: blur(10px);
|
||||
filter: blur(D(3px));
|
||||
|
||||
$opacity: 0.2;
|
||||
&.loaded {
|
||||
@ -178,6 +178,10 @@
|
||||
transform: translateX(50%) rotate(-7deg) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
color: $colorBlueBright;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,33 +205,6 @@
|
||||
flex-grow: 1;
|
||||
@include S(margin-bottom, 10px);
|
||||
}
|
||||
|
||||
.contest {
|
||||
flex-grow: 1;
|
||||
background: rgb(32, 187, 166);
|
||||
@include S(padding, 15px);
|
||||
|
||||
h3 {
|
||||
@include Heading;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
@include S(margin-bottom, 5px);
|
||||
}
|
||||
p {
|
||||
color: #fff;
|
||||
@include Text;
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
@include S(margin-bottom, 5px);
|
||||
}
|
||||
|
||||
button {
|
||||
background: #fff;
|
||||
color: #333538;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainContainer {
|
||||
|
@ -1,113 +1,188 @@
|
||||
#state_SettingsState {
|
||||
.content {
|
||||
.versionbar {
|
||||
@include S(margin-top, 20px);
|
||||
@include SuperSmallText;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr auto;
|
||||
.buildVersion {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #aaadaf;
|
||||
$colorCategoryButton: #eee;
|
||||
$colorCategoryButtonSelected: #5f748b;
|
||||
|
||||
.container .content {
|
||||
display: flex;
|
||||
overflow-y: scroll;
|
||||
|
||||
.categoryContainer {
|
||||
width: 100%;
|
||||
|
||||
.category {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.setting {
|
||||
@include S(padding, 10px);
|
||||
background: #eeeff5;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(margin-bottom, 5px);
|
||||
|
||||
label {
|
||||
text-transform: uppercase;
|
||||
@include Text;
|
||||
}
|
||||
|
||||
.desc {
|
||||
@include S(margin-top, 5px);
|
||||
@include SuperSmallText;
|
||||
color: #aaadb2;
|
||||
}
|
||||
|
||||
> .row {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
// opacity: 0.3;
|
||||
pointer-events: none;
|
||||
* {
|
||||
pointer-events: none !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
position: relative;
|
||||
.standaloneOnlyHint {
|
||||
@include PlainText;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: all;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(#fff, 0.5);
|
||||
text-transform: uppercase;
|
||||
color: $colorRedBright;
|
||||
}
|
||||
}
|
||||
|
||||
.value.enum {
|
||||
background: #fff;
|
||||
@include PlainText;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
@include S(min-width, 100px);
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(padding, 4px);
|
||||
@include S(padding-right, 15px);
|
||||
|
||||
background: #fff uiResource("icons/enum_selector.png") calc(100% - #{D(5px)})
|
||||
calc(50% + #{D(1px)}) / #{D(15px)} no-repeat;
|
||||
|
||||
transition: background-color 0.12s ease-in-out;
|
||||
&:hover {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.about {
|
||||
background-color: $colorGreenBright;
|
||||
}
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@include S(min-width, 210px);
|
||||
@include S(max-width, 320px);
|
||||
width: 30%;
|
||||
height: 100%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@include S(margin-left, 20px);
|
||||
@include S(margin-right, 32px);
|
||||
|
||||
.setting {
|
||||
@include S(padding, 10px);
|
||||
background: #eeeff5;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(margin-bottom, 5px);
|
||||
|
||||
label {
|
||||
text-transform: uppercase;
|
||||
@include Text;
|
||||
.other {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.desc {
|
||||
@include S(margin-top, 5px);
|
||||
button {
|
||||
@include S(margin-top, 4px);
|
||||
width: calc(100% - #{D(20px)});
|
||||
text-align: start;
|
||||
|
||||
&::after {
|
||||
content: unset;
|
||||
}
|
||||
}
|
||||
|
||||
button.categoryButton,
|
||||
button.about {
|
||||
background-color: $colorCategoryButton;
|
||||
color: #777a7f;
|
||||
|
||||
&.active {
|
||||
background-color: $colorCategoryButtonSelected;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.pressed {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.versionbar {
|
||||
@include S(margin-top, 20px);
|
||||
@include SuperSmallText;
|
||||
color: #aaadb2;
|
||||
}
|
||||
|
||||
> .row {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
// opacity: 0.3;
|
||||
pointer-events: none;
|
||||
* {
|
||||
pointer-events: none !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
position: relative;
|
||||
.standaloneOnlyHint {
|
||||
@include PlainText;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: all;
|
||||
.buildVersion {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(#fff, 0.5);
|
||||
text-transform: uppercase;
|
||||
color: $colorRedBright;
|
||||
}
|
||||
}
|
||||
|
||||
.value.enum {
|
||||
background: #fff;
|
||||
@include PlainText;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
@include S(min-width, 100px);
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(padding, 4px);
|
||||
@include S(padding-right, 15px);
|
||||
|
||||
background: #fff uiResource("icons/enum_selector.png") calc(100% - #{D(5px)})
|
||||
calc(50% + #{D(1px)}) / #{D(15px)} no-repeat;
|
||||
|
||||
transition: background-color 0.12s ease-in-out;
|
||||
&:hover {
|
||||
background-color: #fafafa;
|
||||
flex-direction: column;
|
||||
color: #aaadaf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
.content {
|
||||
.setting {
|
||||
background: darken($darkModeGameBackground, 10);
|
||||
.container .content {
|
||||
.sidebar {
|
||||
button.categoryButton,
|
||||
button.about {
|
||||
background-color: #3f3f47;
|
||||
|
||||
.value.enum {
|
||||
// dirty but works
|
||||
filter: invert(0.85);
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.value.checkbox {
|
||||
background-color: #74767b;
|
||||
|
||||
&.checked {
|
||||
&.active {
|
||||
background-color: $colorBlueBright;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.categoryContainer {
|
||||
.category {
|
||||
.setting {
|
||||
background: darken($darkModeGameBackground, 10);
|
||||
|
||||
.value.enum {
|
||||
// dirty but works
|
||||
filter: invert(0.78) sepia(40%) hue-rotate(190deg);
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.value.checkbox {
|
||||
background-color: #74767b;
|
||||
|
||||
&.checked {
|
||||
background-color: $colorBlueBright;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,18 @@
|
||||
.gameState.textualState {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
$padding: 15px;
|
||||
|
||||
.headerBar,
|
||||
> .container .content {
|
||||
@include S(width, 500px);
|
||||
}
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
box-sizing: border-box;
|
||||
@include S(padding, 32px);
|
||||
height: 100vh;
|
||||
|
||||
.headerBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
pointer-events: all;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: center;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
@include SuperHeading;
|
||||
text-transform: uppercase;
|
||||
@ -39,11 +33,17 @@
|
||||
}
|
||||
|
||||
> .container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
> .content {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(padding, 10px);
|
||||
height: calc(80vh - #{D(60px)});
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
pointer-events: all;
|
||||
|
@ -14,13 +14,9 @@ import { AdProviderInterface } from "./platform/ad_provider";
|
||||
import { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
|
||||
import { AnalyticsInterface } from "./platform/analytics";
|
||||
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
|
||||
import { NoGameAnalytics } from "./platform/browser/no_game_analytics";
|
||||
import { SoundImplBrowser } from "./platform/browser/sound";
|
||||
import { PlatformWrapperImplBrowser } from "./platform/browser/wrapper";
|
||||
import { PlatformWrapperImplElectron } from "./platform/electron/wrapper";
|
||||
import { GameAnalyticsInterface } from "./platform/game_analytics";
|
||||
import { SoundInterface } from "./platform/sound";
|
||||
import { StorageInterface } from "./platform/storage";
|
||||
import { PlatformWrapperInterface } from "./platform/wrapper";
|
||||
import { ApplicationSettings } from "./profile/application_settings";
|
||||
import { SavegameManager } from "./savegame/savegame_manager";
|
||||
@ -34,6 +30,12 @@ import { PreloadState } from "./states/preload";
|
||||
import { SettingsState } from "./states/settings";
|
||||
import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
|
||||
|
||||
/**
|
||||
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
|
||||
* @typedef {import("./platform/sound").SoundInterface} SoundInterface
|
||||
* @typedef {import("./platform/storage").StorageInterface} StorageInterface
|
||||
*/
|
||||
|
||||
const logger = createLogger("application");
|
||||
|
||||
// Set the name of the hidden property and the change event for visibility
|
||||
@ -385,7 +387,7 @@ export class Application {
|
||||
}
|
||||
|
||||
const scale = this.getEffectiveUiScale();
|
||||
waitNextFrame().then(() => document.documentElement.style.setProperty("--ui-scale", scale));
|
||||
waitNextFrame().then(() => document.documentElement.style.setProperty("--ui-scale", `${scale}`));
|
||||
window.focus();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ export const CHANGELOG = [
|
||||
date: "unreleased",
|
||||
entries: [
|
||||
"WIRES",
|
||||
"Reworked menu UI design (by dengr1605)",
|
||||
"Allow holding ALT in belt planner to reverse direction (by jakobhellermann)",
|
||||
"Clear cursor when trying to pipette the same building twice (by hexy)",
|
||||
"Fixed level 18 stacker bug: If you experienced it already, you know it, if not, I don't want to spoiler (by hexy)",
|
||||
"Added keybinding to close menus (by isaisstillalive / Sandwichs-del)",
|
||||
"Fix rare crash regarding the buildings toolbar (by isaisstillalive)",
|
||||
"Fixed some phrases (by EnderDoom77)",
|
||||
@ -24,7 +26,7 @@ export const CHANGELOG = [
|
||||
version: "1.1.19",
|
||||
date: "02.07.2020",
|
||||
entries: [
|
||||
"There are now notifications every 15 minutes in the demo version to buy the full version (For further details and the reason, check the #surveys channel in the discord)",
|
||||
"There are now notifications every 15 minutes in the demo version to buy the full version (For further details and the reason, check the #surveys channel in the Discord)",
|
||||
"I'm still working on the wires update, I hope to release it mid july!",
|
||||
],
|
||||
},
|
||||
@ -151,7 +153,7 @@ export const CHANGELOG = [
|
||||
version: "1.1.10",
|
||||
date: "12.06.2020",
|
||||
entries: [
|
||||
"There are now linux builds on steam! Please report any issues in the discord!",
|
||||
"There are now linux builds on steam! Please report any issues in the Discord!",
|
||||
"Steam cloud saves are now available!",
|
||||
"Added and update more translations (Big thank you to all translators!)",
|
||||
"Prevent invalid connection if existing underground tunnel entrance exists (by jaysc)",
|
||||
@ -221,7 +223,7 @@ export const CHANGELOG = [
|
||||
entries: [
|
||||
"The official trailer is now ready! Check it out <a href='https://www.youtube.com/watch?v=KyorY1uIqiQ' target='_blank'>here</a>!",
|
||||
"The <a href='https://steam.shapez.io' target='_blank'>steam page</a> is now live!",
|
||||
"Experimental linux builds are now available! Please give me feedback on them in the discord",
|
||||
"Experimental linux builds are now available! Please give me feedback on them in the Discord",
|
||||
"Allow hovering pinned shapes to enlarge them",
|
||||
"Allow deselecting blueprints with right click and 'Q'",
|
||||
"Move default key for deleting from 'X' to 'DEL'",
|
||||
|
@ -14,12 +14,11 @@ export class AnimationFrame {
|
||||
this.frameEmitted = new Signal();
|
||||
this.bgFrameEmitted = new Signal();
|
||||
|
||||
this.lastTime = null;
|
||||
this.bgLastTime = null;
|
||||
this.lastTime = performance.now();
|
||||
this.bgLastTime = performance.now();
|
||||
|
||||
this.boundMethod = this.handleAnimationFrame.bind(this);
|
||||
|
||||
/** @type {Worker} */
|
||||
this.backgroundWorker = new BackgroundAnimationFrameEmitterWorker();
|
||||
this.backgroundWorker.addEventListener("error", err => {
|
||||
logger.error("Error in background fps worker:", err);
|
||||
@ -27,22 +26,16 @@ export class AnimationFrame {
|
||||
this.backgroundWorker.addEventListener("message", this.handleBackgroundTick.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {MessageEvent} event
|
||||
*/
|
||||
handleBackgroundTick(event) {
|
||||
handleBackgroundTick() {
|
||||
const time = performance.now();
|
||||
if (!this.bgLastTime) {
|
||||
// First update, first delta is always 16ms
|
||||
this.bgFrameEmitted.dispatch(1000 / 60);
|
||||
} else {
|
||||
let dt = time - this.bgLastTime;
|
||||
if (dt > maxDtMs) {
|
||||
dt = resetDtMs;
|
||||
}
|
||||
this.bgFrameEmitted.dispatch(dt);
|
||||
|
||||
let dt = time - this.bgLastTime;
|
||||
|
||||
if (dt > maxDtMs) {
|
||||
dt = resetDtMs;
|
||||
}
|
||||
|
||||
this.bgFrameEmitted.dispatch(dt);
|
||||
this.bgLastTime = time;
|
||||
}
|
||||
|
||||
@ -52,18 +45,15 @@ export class AnimationFrame {
|
||||
}
|
||||
|
||||
handleAnimationFrame(time) {
|
||||
if (!this.lastTime) {
|
||||
// First update, first delta is always 16ms
|
||||
this.frameEmitted.dispatch(1000 / 60);
|
||||
} else {
|
||||
let dt = time - this.lastTime;
|
||||
if (dt > maxDtMs) {
|
||||
// warn(this, "Clamping", dt, "to", resetDtMs);
|
||||
dt = resetDtMs;
|
||||
}
|
||||
this.frameEmitted.dispatch(dt);
|
||||
let dt = time - this.lastTime;
|
||||
|
||||
if (dt > maxDtMs) {
|
||||
dt = resetDtMs;
|
||||
}
|
||||
|
||||
this.frameEmitted.dispatch(dt);
|
||||
this.lastTime = time;
|
||||
|
||||
window.requestAnimationFrame(this.boundMethod);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
// @ts-ignore
|
||||
import CompressionWorker from "../webworkers/compression.worker";
|
||||
|
||||
import { createLogger } from "./logging";
|
||||
import { compressX64 } from "./lzstring";
|
||||
import { round2Digits } from "./utils";
|
||||
|
||||
const logger = createLogger("async_compression");
|
||||
|
||||
export let compressionPrefix = String.fromCodePoint(1);
|
||||
@ -35,7 +37,6 @@ if (!checkCryptPrefix(compressionPrefix)) {
|
||||
|
||||
class AsynCompression {
|
||||
constructor() {
|
||||
/** @type {Worker} */
|
||||
this.worker = new CompressionWorker();
|
||||
|
||||
this.currentJobId = 1000;
|
||||
@ -52,7 +53,7 @@ class AsynCompression {
|
||||
}
|
||||
|
||||
const duration = performance.now() - jobData.startTime;
|
||||
// log(this, "Got response from worker within", duration.toFixed(2), "ms");
|
||||
logger.log("Got job", jobId, "response within", round2Digits(duration), "ms");
|
||||
const resolver = jobData.resolver;
|
||||
delete this.currentJobs[jobId];
|
||||
resolver(result);
|
||||
@ -76,6 +77,7 @@ class AsynCompression {
|
||||
* @param {string} text
|
||||
*/
|
||||
compressFileAsync(text) {
|
||||
logger.log("Compressing", text.length, "bytes async");
|
||||
return this.internalQueueJob("compressFile", {
|
||||
text,
|
||||
compressionPrefix,
|
||||
@ -100,6 +102,8 @@ class AsynCompression {
|
||||
resolver: resolve,
|
||||
startTime: performance.now(),
|
||||
};
|
||||
|
||||
logger.log("Posting job", job, "/", jobId);
|
||||
this.worker.postMessage({ jobId, job, data });
|
||||
});
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -7,6 +7,7 @@ import { createLogger } from "./logging";
|
||||
import { Signal } from "./signal";
|
||||
import { SOUNDS, MUSIC } from "../platform/sound";
|
||||
import { AtlasDefinition, atlasFiles } from "./atlas_definitions";
|
||||
import { initBuildingCodesAfterResourcesLoaded } from "../game/meta_building_registry";
|
||||
|
||||
const logger = createLogger("background_loader");
|
||||
|
||||
@ -114,8 +115,8 @@ export class BackgroundResourcesLoader {
|
||||
})
|
||||
.then(() => {
|
||||
logger.log("⏰ Finish load: bare game");
|
||||
Loader.createAtlasLinks();
|
||||
this.bareGameReady = true;
|
||||
initBuildingCodesAfterResourcesLoaded();
|
||||
this.signalBareGameLoaded.dispatch();
|
||||
this.internalStartLoadingAdditionalGameAssets();
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Rectangle } from "./rectangle";
|
||||
import { globalConfig } from "./config";
|
||||
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../game/root";
|
||||
/* typehints:end */
|
||||
/**
|
||||
* @typedef {import("../game/root").GameRoot} GameRoot
|
||||
* @typedef {import("./rectangle").Rectangle} Rectangle
|
||||
*/
|
||||
|
||||
export class DrawParameters {
|
||||
constructor({ context, visibleRect, desiredAtlasScale, zoomLevel, root }) {
|
||||
|
@ -1,18 +1,13 @@
|
||||
/* typehints:start */
|
||||
import { AtlasSprite } from "./sprites";
|
||||
import { DrawParameters } from "./draw_parameters";
|
||||
/* typehints:end */
|
||||
|
||||
import { Vector } from "./vector";
|
||||
import { Rectangle } from "./rectangle";
|
||||
import { createLogger } from "./logging";
|
||||
|
||||
const logger = createLogger("draw_utils");
|
||||
/**
|
||||
* @typedef {import("./sprites").AtlasSprite} AtlasSprite
|
||||
* @typedef {import("./draw_parameters").DrawParameters} DrawParameters
|
||||
*/
|
||||
|
||||
export function initDrawUtils() {
|
||||
CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) {
|
||||
this.beginPath();
|
||||
|
||||
if (r < 0.05) {
|
||||
this.beginPath();
|
||||
this.rect(x, y, w, h);
|
||||
return;
|
||||
}
|
||||
@ -20,25 +15,26 @@ export function initDrawUtils() {
|
||||
if (w < 2 * r) {
|
||||
r = w / 2;
|
||||
}
|
||||
|
||||
if (h < 2 * r) {
|
||||
r = h / 2;
|
||||
}
|
||||
this.beginPath();
|
||||
|
||||
this.moveTo(x + r, y);
|
||||
this.arcTo(x + w, y, x + w, y + h, r);
|
||||
this.arcTo(x + w, y + h, x, y + h, r);
|
||||
this.arcTo(x, y + h, x, y, r);
|
||||
this.arcTo(x, y, x + w, y, r);
|
||||
// this.closePath();
|
||||
};
|
||||
|
||||
CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) {
|
||||
this.beginPath();
|
||||
|
||||
if (r < 0.05) {
|
||||
this.beginPath();
|
||||
this.rect(x, y, 1, 1);
|
||||
return;
|
||||
}
|
||||
this.beginPath();
|
||||
|
||||
this.arc(x, y, r, 0, 2.0 * Math.PI);
|
||||
};
|
||||
}
|
||||
@ -62,259 +58,3 @@ export function drawRotatedSprite({ parameters, sprite, x, y, angle, size, offse
|
||||
parameters.context.rotate(-angle);
|
||||
parameters.context.translate(-x, -y);
|
||||
}
|
||||
|
||||
export function drawLineFast(context, { x1, x2, y1, y2, color = null, lineSize = 1 }) {
|
||||
const dX = x2 - x1;
|
||||
const dY = y2 - y1;
|
||||
|
||||
const angle = Math.atan2(dY, dX) + 0.0 * Math.PI;
|
||||
const len = Math.hypot(dX, dY);
|
||||
|
||||
context.translate(x1, y1);
|
||||
context.rotate(angle);
|
||||
|
||||
if (color) {
|
||||
context.fillStyle = color;
|
||||
}
|
||||
|
||||
context.fillRect(0, -lineSize / 2, len, lineSize);
|
||||
|
||||
context.rotate(-angle);
|
||||
context.translate(-x1, -y1);
|
||||
}
|
||||
|
||||
const INSIDE = 0;
|
||||
const LEFT = 1;
|
||||
const RIGHT = 2;
|
||||
const BOTTOM = 4;
|
||||
const TOP = 8;
|
||||
|
||||
// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
|
||||
|
||||
function computeOutCode(x, y, xmin, xmax, ymin, ymax) {
|
||||
let code = INSIDE;
|
||||
|
||||
if (x < xmin)
|
||||
// to the left of clip window
|
||||
code |= LEFT;
|
||||
else if (x > xmax)
|
||||
// to the right of clip window
|
||||
code |= RIGHT;
|
||||
if (y < ymin)
|
||||
// below the clip window
|
||||
code |= BOTTOM;
|
||||
else if (y > ymax)
|
||||
// above the clip window
|
||||
code |= TOP;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
// Cohen–Sutherland clipping algorithm clips a line from
|
||||
// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
|
||||
// diagonal from (xmin, ymin) to (xmax, ymax).
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
*/
|
||||
export function drawLineFastClipped(context, rect, { x0, y0, x1, y1, color = null, lineSize = 1 }) {
|
||||
const xmin = rect.x;
|
||||
const ymin = rect.y;
|
||||
const xmax = rect.right();
|
||||
const ymax = rect.bottom();
|
||||
|
||||
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
|
||||
let outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
|
||||
let outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
|
||||
let accept = false;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (!(outcode0 | outcode1)) {
|
||||
// bitwise OR is 0: both points inside window; trivially accept and exit loop
|
||||
accept = true;
|
||||
break;
|
||||
} else if (outcode0 & outcode1) {
|
||||
// bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP,
|
||||
// or BOTTOM), so both must be outside window; exit loop (accept is false)
|
||||
break;
|
||||
} else {
|
||||
// failed both tests, so calculate the line segment to clip
|
||||
// from an outside point to an intersection with clip edge
|
||||
let x, y;
|
||||
|
||||
// At least one endpoint is outside the clip rectangle; pick it.
|
||||
let outcodeOut = outcode0 ? outcode0 : outcode1;
|
||||
|
||||
// Now find the intersection point;
|
||||
// use formulas:
|
||||
// slope = (y1 - y0) / (x1 - x0)
|
||||
// x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
|
||||
// y = y0 + slope * (xm - x0), where xm is xmin or xmax
|
||||
// No need to worry about divide-by-zero because, in each case, the
|
||||
// outcode bit being tested guarantees the denominator is non-zero
|
||||
if (outcodeOut & TOP) {
|
||||
// point is above the clip window
|
||||
x = x0 + ((x1 - x0) * (ymax - y0)) / (y1 - y0);
|
||||
y = ymax;
|
||||
} else if (outcodeOut & BOTTOM) {
|
||||
// point is below the clip window
|
||||
x = x0 + ((x1 - x0) * (ymin - y0)) / (y1 - y0);
|
||||
y = ymin;
|
||||
} else if (outcodeOut & RIGHT) {
|
||||
// point is to the right of clip window
|
||||
y = y0 + ((y1 - y0) * (xmax - x0)) / (x1 - x0);
|
||||
x = xmax;
|
||||
} else if (outcodeOut & LEFT) {
|
||||
// point is to the left of clip window
|
||||
y = y0 + ((y1 - y0) * (xmin - x0)) / (x1 - x0);
|
||||
x = xmin;
|
||||
}
|
||||
|
||||
// Now we move outside point to intersection point to clip
|
||||
// and get ready for next pass.
|
||||
if (outcodeOut == outcode0) {
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
|
||||
} else {
|
||||
x1 = x;
|
||||
y1 = y;
|
||||
outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accept) {
|
||||
// Following functions are left for implementation by user based on
|
||||
// their platform (OpenGL/graphics.h etc.)
|
||||
// DrawRectangle(xmin, ymin, xmax, ymax);
|
||||
// LineSegment(x0, y0, x1, y1);
|
||||
drawLineFast(context, {
|
||||
x1: x0,
|
||||
y1: y0,
|
||||
x2: x1,
|
||||
y2: y1,
|
||||
color,
|
||||
lineSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*
|
||||
* @param {number} h The hue
|
||||
* @param {number} s The saturation
|
||||
* @param {number} l The lightness
|
||||
* @return {Array} The RGB representation
|
||||
*/
|
||||
export function hslToRgb(h, s, l) {
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
const hue2rgb = function (p, q, t) {
|
||||
if (t < 0) {
|
||||
t += 1;
|
||||
}
|
||||
if (t > 1) {
|
||||
t -= 1;
|
||||
}
|
||||
if (t < 1 / 6) {
|
||||
return p + (q - p) * 6 * t;
|
||||
}
|
||||
if (t < 1 / 2) {
|
||||
return q;
|
||||
}
|
||||
if (t < 2 / 3) {
|
||||
return p + (q - p) * (2 / 3 - t) * 6;
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
let p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||
}
|
||||
|
||||
export function wrapText(context, text, x, y, maxWidth, lineHeight, stroke = false) {
|
||||
var words = text.split(" ");
|
||||
var line = "";
|
||||
|
||||
for (var n = 0; n < words.length; n++) {
|
||||
var testLine = line + words[n] + " ";
|
||||
var metrics = context.measureText(testLine);
|
||||
var testWidth = metrics.width;
|
||||
if (testWidth > maxWidth && n > 0) {
|
||||
if (stroke) {
|
||||
context.strokeText(line, x, y);
|
||||
} else {
|
||||
context.fillText(line, x, y);
|
||||
}
|
||||
line = words[n] + " ";
|
||||
y += lineHeight;
|
||||
} else {
|
||||
line = testLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (stroke) {
|
||||
context.strokeText(line, x, y);
|
||||
} else {
|
||||
context.fillText(line, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a rotated trapez, used for spotlight culling
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} w
|
||||
* @param {number} h
|
||||
* @param {number} leftHeight
|
||||
* @param {number} angle
|
||||
*/
|
||||
export function rotateTrapezRightFaced(x, y, w, h, leftHeight, angle) {
|
||||
const halfY = y + h / 2;
|
||||
const points = [
|
||||
new Vector(x, halfY - leftHeight / 2),
|
||||
new Vector(x + w, y),
|
||||
new Vector(x, halfY + leftHeight / 2),
|
||||
new Vector(x + w, y + h),
|
||||
];
|
||||
|
||||
return Rectangle.getAroundPointsRotated(points, angle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts values from 0 .. 255 to values like 07, 7f, 5d etc
|
||||
* @param {number} value
|
||||
* @returns {string}
|
||||
*/
|
||||
export function mapClampedColorValueToHex(value) {
|
||||
const hex = "0123456789abcdef";
|
||||
return hex[Math.floor(value / 16)] + hex[value % 16];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts rgb to a hex string
|
||||
* @param {number} r
|
||||
* @param {number} g
|
||||
* @param {number} b
|
||||
* @returns {string}
|
||||
*/
|
||||
export function rgbToHex(r, g, b) {
|
||||
return mapClampedColorValueToHex(r) + mapClampedColorValueToHex(g) + mapClampedColorValueToHex(b);
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { SingletonFactory } from "./singleton_factory";
|
||||
import { Factory } from "./factory";
|
||||
|
||||
/* typehints:start */
|
||||
import { BaseGameSpeed } from "../game/time/base_game_speed";
|
||||
import { Component } from "../game/component";
|
||||
import { BaseItem } from "../game/base_item";
|
||||
import { MetaBuilding } from "../game/meta_building";
|
||||
/* typehints:end */
|
||||
/**
|
||||
* @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed
|
||||
* @typedef {import("../game/component").Component} Component
|
||||
* @typedef {import("../game/base_item").BaseItem} BaseItem
|
||||
* @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding
|
||||
|
||||
|
||||
// These factories are here to remove circular dependencies
|
||||
|
||||
/** @type {SingletonFactoryTemplate<MetaBuilding>} */
|
||||
export let gMetaBuildingRegistry = new SingletonFactory();
|
||||
|
||||
/** @type {Object.<string, Array<typeof MetaBuilding>>} */
|
||||
/** @type {Object.<string, Array<Class<MetaBuilding>>>} */
|
||||
export let gBuildingsByCategory = null;
|
||||
|
||||
/** @type {FactoryTemplate<Component>} */
|
||||
@ -28,7 +28,7 @@ export let gItemRegistry = new Factory("item");
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* @param {Object.<string, Array<typeof MetaBuilding>>} buildings
|
||||
* @param {Object.<string, Array<Class<MetaBuilding>>>} buildings
|
||||
*/
|
||||
export function initBuildingsByCategory(buildings) {
|
||||
gBuildingsByCategory = buildings;
|
||||
|
@ -1,20 +1,19 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
import { AtlasDefinition } from "./atlas_definitions";
|
||||
import { makeOffscreenBuffer } from "./buffer_utils";
|
||||
import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites";
|
||||
import { cachebust } from "./cachebust";
|
||||
import { createLogger } from "./logging";
|
||||
|
||||
/**
|
||||
* @typedef {import("../application").Application} Application
|
||||
* @typedef {import("./atlas_definitions").AtlasDefinition} AtlasDefinition;
|
||||
*/
|
||||
|
||||
const logger = createLogger("loader");
|
||||
|
||||
const missingSpriteIds = {};
|
||||
|
||||
class LoaderImpl {
|
||||
constructor() {
|
||||
/** @type {Application} */
|
||||
this.app = null;
|
||||
|
||||
/** @type {Map<string, BaseSprite>} */
|
||||
@ -23,6 +22,9 @@ class LoaderImpl {
|
||||
this.rawImages = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Application} app
|
||||
*/
|
||||
linkAppAfterBoot(app) {
|
||||
this.app = app;
|
||||
this.makeSpriteNotFoundCanvas();
|
||||
@ -58,7 +60,7 @@ class LoaderImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retursn a regular sprite from the cache
|
||||
* Returns a regular sprite from the cache
|
||||
* @param {string} key
|
||||
* @returns {RegularSprite}
|
||||
*/
|
||||
@ -155,44 +157,34 @@ class LoaderImpl {
|
||||
* @param {AtlasDefinition} atlas
|
||||
* @param {HTMLImageElement} loadedImage
|
||||
*/
|
||||
internalParseAtlas(atlas, loadedImage) {
|
||||
internalParseAtlas({ meta: { scale }, sourceData }, loadedImage) {
|
||||
this.rawImages.push(loadedImage);
|
||||
|
||||
for (const spriteKey in atlas.sourceData) {
|
||||
const spriteData = atlas.sourceData[spriteKey];
|
||||
for (const spriteName in sourceData) {
|
||||
const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName];
|
||||
|
||||
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteKey));
|
||||
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteName));
|
||||
|
||||
if (!sprite) {
|
||||
sprite = new AtlasSprite({
|
||||
spriteName: spriteKey,
|
||||
});
|
||||
this.sprites.set(spriteKey, sprite);
|
||||
sprite = new AtlasSprite(spriteName);
|
||||
this.sprites.set(spriteName, sprite);
|
||||
}
|
||||
|
||||
const link = new SpriteAtlasLink({
|
||||
packedX: spriteData.frame.x,
|
||||
packedY: spriteData.frame.y,
|
||||
packedW: spriteData.frame.w,
|
||||
packedH: spriteData.frame.h,
|
||||
packOffsetX: spriteData.spriteSourceSize.x,
|
||||
packOffsetY: spriteData.spriteSourceSize.y,
|
||||
packedX: frame.x,
|
||||
packedY: frame.y,
|
||||
packedW: frame.w,
|
||||
packedH: frame.h,
|
||||
packOffsetX: spriteSourceSize.x,
|
||||
packOffsetY: spriteSourceSize.y,
|
||||
atlas: loadedImage,
|
||||
w: spriteData.sourceSize.w,
|
||||
h: spriteData.sourceSize.h,
|
||||
w: sourceSize.w,
|
||||
h: sourceSize.h,
|
||||
});
|
||||
sprite.linksByResolution[atlas.meta.scale] = link;
|
||||
sprite.linksByResolution[scale] = link;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the links for the sprites after the atlas has been loaded. Used so we
|
||||
* don't have to store duplicate sprites.
|
||||
*/
|
||||
createAtlasLinks() {
|
||||
// NOT USED
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the canvas which shows the question mark, shown when a sprite was not found
|
||||
*/
|
||||
@ -216,14 +208,9 @@ class LoaderImpl {
|
||||
// @ts-ignore
|
||||
canvas.src = "not-found";
|
||||
|
||||
const resolutions = ["0.1", "0.25", "0.5", "0.75", "1"];
|
||||
const sprite = new AtlasSprite({
|
||||
spriteName: "not-found",
|
||||
});
|
||||
|
||||
for (let i = 0; i < resolutions.length; ++i) {
|
||||
const res = resolutions[i];
|
||||
const link = new SpriteAtlasLink({
|
||||
const sprite = new AtlasSprite("not-found");
|
||||
["0.1", "0.25", "0.5", "0.75", "1"].forEach(resolution => {
|
||||
sprite.linksByResolution[resolution] = new SpriteAtlasLink({
|
||||
packedX: 0,
|
||||
packedY: 0,
|
||||
w: dims,
|
||||
@ -234,8 +221,8 @@ class LoaderImpl {
|
||||
packedH: dims,
|
||||
atlas: canvas,
|
||||
});
|
||||
sprite.linksByResolution[res] = link;
|
||||
}
|
||||
});
|
||||
|
||||
this.spriteNotFoundSprite = sprite;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { FILE_NOT_FOUND } from "../platform/storage";
|
||||
import { accessNestedPropertyReverse } from "./utils";
|
||||
import { IS_DEBUG, globalConfig } from "./config";
|
||||
import { ExplainedResult } from "./explained_result";
|
||||
import { decompressX64, compressX64 } from ".//lzstring";
|
||||
import { decompressX64, compressX64 } from "./lzstring";
|
||||
import { asyncCompressor, compressionPrefix } from "./async_compression";
|
||||
import { compressObject, decompressObject } from "../savegame/savegame_compressor";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { globalConfig } from "./config";
|
||||
import { clamp, epsilonCompare, round2Digits } from "./utils";
|
||||
import { epsilonCompare, round2Digits } from "./utils";
|
||||
import { Vector } from "./vector";
|
||||
|
||||
export class Rectangle {
|
||||
|
@ -1,19 +1,12 @@
|
||||
import { globalConfig } from "./config";
|
||||
import { decompressX64, compressX64 } from "./lzstring";
|
||||
import { createHash } from "rusha";
|
||||
|
||||
const Rusha = require("rusha");
|
||||
|
||||
const encryptKey = globalConfig.info.sgSalt;
|
||||
|
||||
export function decodeHashedString(s) {
|
||||
return decompressX64(s);
|
||||
}
|
||||
import { decompressX64 } from "./lzstring";
|
||||
|
||||
export function sha1(str) {
|
||||
return Rusha.createHash().update(str).digest("hex");
|
||||
return createHash().update(str).digest("hex");
|
||||
}
|
||||
|
||||
// Window.location.host
|
||||
export function getNameOfProvider() {
|
||||
return window[decodeHashedString("DYewxghgLgliB2Q")][decodeHashedString("BYewzgLgdghgtgUyA")];
|
||||
return window[decompressX64("DYewxghgLgliB2Q")][decompressX64("BYewzgLgdghgtgUyA")];
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DrawParameters } from "./draw_parameters";
|
||||
import { Rectangle } from "./rectangle";
|
||||
import { epsilonCompare, round3Digits } from "./utils";
|
||||
import { round3Digits } from "./utils";
|
||||
|
||||
const floorSpriteCoordinates = false;
|
||||
|
||||
@ -63,10 +63,9 @@ export class SpriteAtlasLink {
|
||||
export class AtlasSprite extends BaseSprite {
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {string} param0.spriteName
|
||||
* @param {string} spriteName
|
||||
*/
|
||||
constructor({ spriteName = "sprite" }) {
|
||||
constructor(spriteName = "sprite") {
|
||||
super();
|
||||
/** @type {Object.<string, SpriteAtlasLink>} */
|
||||
this.linksByResolution = {};
|
||||
@ -158,7 +157,9 @@ export class AtlasSprite extends BaseSprite {
|
||||
const scale = parameters.desiredAtlasScale;
|
||||
const link = this.linksByResolution[scale];
|
||||
|
||||
assert(link, "Link not known: " + scale + " (having " + Object.keys(this.linksByResolution) + ")");
|
||||
if (!link) {
|
||||
assert(false, `Link not known: ${scale} (having ${Object.keys(this.linksByResolution)})`);
|
||||
}
|
||||
|
||||
const scaleW = w / link.w;
|
||||
const scaleH = h / link.h;
|
||||
@ -195,8 +196,6 @@ export class AtlasSprite extends BaseSprite {
|
||||
destH = intersection.h;
|
||||
}
|
||||
|
||||
// assert(epsilonCompare(scaleW, scaleH), "Sprite should be square for cached rendering");
|
||||
|
||||
if (floorSpriteCoordinates) {
|
||||
parameters.context.drawImage(
|
||||
link.atlas,
|
||||
|
@ -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
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { gItemRegistry } from "../core/global_registries";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
import { epsilonCompare, round4Digits } from "../core/utils";
|
||||
import { Vector } from "../core/vector";
|
||||
import { enumDirection, enumDirectionToVector, Vector, enumInvertedDirections } from "../core/vector";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { Entity } from "./entity";
|
||||
import { GameRoot, enumLayer } from "./root";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { gItemRegistry } from "../core/global_registries";
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
const logger = createLogger("belt_path");
|
||||
|
||||
@ -125,14 +125,6 @@ export class BeltPath extends BasicSerializableObject {
|
||||
return globalConfig.beltItemSpacingByLayer[this.layer];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the layer of the this path
|
||||
* @returns {enumLayer}
|
||||
*/
|
||||
getLayer() {
|
||||
return this.entityPath[0].layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to accept the item
|
||||
* @param {BaseItem} item
|
||||
@ -167,7 +159,7 @@ export class BeltPath extends BasicSerializableObject {
|
||||
* @returns {BaseItem|null}
|
||||
*/
|
||||
findItemAtTile(tile) {
|
||||
// TODO: This breaks color blind mode otherwise
|
||||
// @TODO: This breaks color blind mode otherwise
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -186,27 +178,85 @@ export class BeltPath extends BasicSerializableObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all ejectors on the path, so that only the last ejector
|
||||
* Recomputes the layer of the path and the target acceptor
|
||||
*/
|
||||
onPathChanged() {
|
||||
this.ejectorComp = this.entityPath[this.entityPath.length - 1].components.ItemEjector;
|
||||
this.ejectorSlot = this.ejectorComp.slots[0];
|
||||
this.layer = this.entityPath[0].layer;
|
||||
this.acceptorTarget = this.computeAcceptingEntityAndSlot();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.entityPath.length; ++i) {
|
||||
const ejectorComp = this.entityPath[i].components.ItemEjector;
|
||||
const isLast = i === this.entityPath.length - 1;
|
||||
ejectorComp.enabled = isLast;
|
||||
/**
|
||||
* Called by the belt system when the surroundings changed
|
||||
*/
|
||||
onSurroundingsChanged() {
|
||||
this.onPathChanged();
|
||||
}
|
||||
|
||||
// Clear all slots of non-end entities
|
||||
if (!isLast) {
|
||||
for (let k = 0; k < ejectorComp.slots.length; ++k) {
|
||||
ejectorComp.slots[k].item = null;
|
||||
ejectorComp.slots[k].progress = 0.0;
|
||||
/**
|
||||
* Finds the entity which accepts our items
|
||||
* @return {{ entity: Entity, slot: number, direction?: enumDirection }}
|
||||
*/
|
||||
computeAcceptingEntityAndSlot() {
|
||||
const lastEntity = this.entityPath[this.entityPath.length - 1];
|
||||
const lastStatic = lastEntity.components.StaticMapEntity;
|
||||
const lastBeltComp = lastEntity.components.Belt;
|
||||
|
||||
// Figure out where and into which direction we eject items
|
||||
const ejectSlotWsTile = lastStatic.localTileToWorld(new Vector(0, 0));
|
||||
const ejectSlotWsDirection = lastStatic.localDirectionToWorld(lastBeltComp.direction);
|
||||
const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
|
||||
const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
|
||||
|
||||
// Try to find the given acceptor component to take the item
|
||||
// Since there can be cross layer dependencies, check on all layers
|
||||
const targetEntities = this.root.map.getLayersContentsMultipleXY(
|
||||
ejectSlotTargetWsTile.x,
|
||||
ejectSlotTargetWsTile.y
|
||||
);
|
||||
|
||||
for (let i = 0; i < targetEntities.length; ++i) {
|
||||
const targetEntity = targetEntities[i];
|
||||
|
||||
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
||||
const targetBeltComp = targetEntity.components.Belt;
|
||||
|
||||
// Check for belts (special case)
|
||||
if (targetBeltComp) {
|
||||
const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
|
||||
if (ejectSlotWsDirection === beltAcceptingDirection) {
|
||||
return {
|
||||
entity: targetEntity,
|
||||
direction: null,
|
||||
slot: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.layer = this.entityPath[0].layer;
|
||||
// Check for item acceptors
|
||||
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
|
||||
if (!targetAcceptorComp) {
|
||||
// Entity doesn't accept items
|
||||
continue;
|
||||
}
|
||||
|
||||
const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection);
|
||||
const matchingSlot = targetAcceptorComp.findMatchingSlot(
|
||||
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
|
||||
ejectingDirection,
|
||||
lastEntity.layer
|
||||
);
|
||||
|
||||
if (!matchingSlot) {
|
||||
// No matching slot found
|
||||
continue;
|
||||
}
|
||||
|
||||
return {
|
||||
entity: targetEntity,
|
||||
slot: matchingSlot.index,
|
||||
direction: enumInvertedDirections[ejectingDirection],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Following code will be compiled out outside of dev versions
|
||||
@ -251,11 +301,6 @@ export class BeltPath extends BasicSerializableObject {
|
||||
return fail("Reference to destroyed entity " + entity.uid);
|
||||
}
|
||||
|
||||
const enabledState = i === this.entityPath.length - 1;
|
||||
if (entity.components.ItemEjector.enabled !== enabledState) {
|
||||
return fail("Item ejector enabled state is not synchronized (index =" + i + ")");
|
||||
}
|
||||
|
||||
const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity);
|
||||
if (!followUp) {
|
||||
return fail(
|
||||
@ -283,20 +328,6 @@ export class BeltPath extends BasicSerializableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for right ejector component and slot
|
||||
if (this.ejectorComp !== this.entityPath[this.entityPath.length - 1].components.ItemEjector) {
|
||||
return fail("Stale ejectorComp handle");
|
||||
}
|
||||
if (this.ejectorSlot !== this.ejectorComp.slots[0]) {
|
||||
return fail("Stale ejector slot handle");
|
||||
}
|
||||
if (!this.ejectorComp) {
|
||||
return fail("Ejector comp not set");
|
||||
}
|
||||
if (!this.ejectorSlot) {
|
||||
return fail("Ejector slot not set");
|
||||
}
|
||||
|
||||
// Check spacing
|
||||
if (this.spacingToFirstItem > this.totalLength + 0.005) {
|
||||
return fail(
|
||||
@ -370,14 +401,6 @@ export class BeltPath extends BasicSerializableObject {
|
||||
|
||||
const beltComp = entity.components.Belt;
|
||||
|
||||
// If the last belt has something on its ejector, put that into the path first
|
||||
const pendingItem = this.ejectorComp.takeSlotItem(0);
|
||||
if (pendingItem) {
|
||||
// Ok, so we have a pending item
|
||||
DEBUG && logger.log("Taking pending item and putting it back on the path");
|
||||
this.items.push([0, pendingItem]);
|
||||
}
|
||||
|
||||
// Append the entity
|
||||
this.entityPath.push(entity);
|
||||
this.onPathChanged();
|
||||
@ -970,7 +993,7 @@ export class BeltPath extends BasicSerializableObject {
|
||||
beltSpeed *= 100;
|
||||
}
|
||||
|
||||
let minimumDistance = this.ejectorSlot.item ? this.getItemSpacing() : 0;
|
||||
let minimumDistance = 0;
|
||||
|
||||
// Try to reduce spacing
|
||||
let remainingAmount = beltSpeed;
|
||||
@ -994,11 +1017,28 @@ export class BeltPath extends BasicSerializableObject {
|
||||
minimumDistance = this.getItemSpacing();
|
||||
}
|
||||
|
||||
// Check if we have an item which is ready to be emitted
|
||||
const lastItem = this.items[this.items.length - 1];
|
||||
if (lastItem && lastItem[_nextDistance] === 0) {
|
||||
// Take over
|
||||
if (this.ejectorComp.tryEject(0, lastItem[_item])) {
|
||||
if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
|
||||
// Pass over the item
|
||||
if (
|
||||
this.root.systemMgr.systems.itemEjector.tryPassOverItem(
|
||||
lastItem[_item],
|
||||
this.acceptorTarget.entity,
|
||||
this.acceptorTarget.slot
|
||||
)
|
||||
) {
|
||||
this.items.pop();
|
||||
|
||||
// Also trigger animation
|
||||
const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
|
||||
if (targetAcceptorComp) {
|
||||
targetAcceptorComp.onItemAccepted(
|
||||
this.acceptorTarget.slot,
|
||||
this.acceptorTarget.direction,
|
||||
lastItem[_item]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1134,7 +1174,7 @@ export class BeltPath extends BasicSerializableObject {
|
||||
const beltLength = beltComp.getEffectiveLengthTiles(this.layer);
|
||||
|
||||
// Check if the current items are on the belt
|
||||
while (trackPos + beltLength >= currentItemPos) {
|
||||
while (trackPos + beltLength >= currentItemPos - 1e-51) {
|
||||
// Its on the belt, render it now
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
assert(
|
||||
|
@ -81,10 +81,6 @@ export class Blueprint {
|
||||
for (let i = 0; i < this.entities.length; ++i) {
|
||||
const entity = this.entities[i];
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
if (!staticComp.blueprintSpriteKey) {
|
||||
logger.warn("Blueprint entity without sprite!");
|
||||
return;
|
||||
}
|
||||
const newPos = staticComp.origin.add(tile);
|
||||
|
||||
const rect = staticComp.getTileSpaceBounds();
|
||||
@ -98,7 +94,7 @@ export class Blueprint {
|
||||
|
||||
staticComp.drawSpriteOnFullEntityBounds(
|
||||
parameters,
|
||||
Loader.getSprite(staticComp.blueprintSpriteKey),
|
||||
staticComp.getBlueprintSprite(),
|
||||
0,
|
||||
true,
|
||||
newPos
|
||||
|
79
src/js/game/building_codes.js
Normal file
@ -0,0 +1,79 @@
|
||||
/* typehints:start */
|
||||
import { MetaBuilding } from "./meta_building";
|
||||
import { AtlasSprite } from "../core/sprites";
|
||||
/* typehints:end */
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* metaClass: typeof MetaBuilding,
|
||||
* metaInstance?: MetaBuilding,
|
||||
* variant?: string,
|
||||
* rotationVariant?: number,
|
||||
* sprite?: AtlasSprite,
|
||||
* blueprintSprite?: AtlasSprite,
|
||||
* silhouetteColor?: string
|
||||
* }} BuildingVariantIdentifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores a lookup table for all building variants (for better performance)
|
||||
* @type {Object<number, BuildingVariantIdentifier>}
|
||||
*/
|
||||
export const gBuildingVariants = {
|
||||
// Set later
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new variant
|
||||
* @param {number} id
|
||||
* @param {typeof MetaBuilding} meta
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
*/
|
||||
export function registerBuildingVariant(
|
||||
id,
|
||||
meta,
|
||||
variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */,
|
||||
rotationVariant = 0
|
||||
) {
|
||||
assert(!gBuildingVariants[id], "Duplicate id: " + id);
|
||||
gBuildingVariants[id] = {
|
||||
metaClass: meta,
|
||||
variant,
|
||||
rotationVariant,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} code
|
||||
* @returns {BuildingVariantIdentifier}
|
||||
*/
|
||||
export function getBuildingDataFromCode(code) {
|
||||
assert(gBuildingVariants[code], "Invalid building code: " + code);
|
||||
return gBuildingVariants[code];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the code for a given variant
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
*/
|
||||
export function getCodeFromBuildingData(metaBuilding, variant, rotationVariant) {
|
||||
for (const key in gBuildingVariants) {
|
||||
const data = gBuildingVariants[key];
|
||||
if (
|
||||
data.metaInstance.getId() === metaBuilding.getId() &&
|
||||
data.variant === variant &&
|
||||
data.rotationVariant === rotationVariant
|
||||
) {
|
||||
return +key;
|
||||
}
|
||||
}
|
||||
assertAlways(
|
||||
false,
|
||||
"Building not found by data: " + metaBuilding.getId() + " / " + variant + " / " + rotationVariant
|
||||
);
|
||||
return 0;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import { Loader } from "../../core/loader";
|
||||
import { formatItemsPerSecond } from "../../core/utils";
|
||||
import { enumAngleToDirection, enumDirection, Vector } from "../../core/vector";
|
||||
import { SOUNDS } from "../../platform/sound";
|
||||
@ -40,6 +39,10 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
||||
return SOUNDS.placeBelt;
|
||||
}
|
||||
|
||||
getSprite() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
@ -50,34 +53,8 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
||||
direction: enumDirection.top, // updated later
|
||||
})
|
||||
);
|
||||
// Make this entity replaceabel
|
||||
// Make this entity replaceable
|
||||
entity.addComponent(new ReplaceableMapEntityComponent());
|
||||
|
||||
entity.addComponent(
|
||||
new ItemAcceptorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
layer: this.getLayer(),
|
||||
},
|
||||
],
|
||||
animated: false,
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemEjectorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.top, // updated later
|
||||
layer: this.getLayer(),
|
||||
},
|
||||
],
|
||||
instantEject: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,8 +64,6 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
||||
*/
|
||||
updateVariants(entity, rotationVariant) {
|
||||
entity.components.Belt.direction = arrayBeltVariantToRotation[rotationVariant];
|
||||
entity.components.ItemEjector.slots[0].direction = arrayBeltVariantToRotation[rotationVariant];
|
||||
entity.components.StaticMapEntity.spriteKey = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +77,6 @@ export class MetaEnergyGenerator extends MetaBuilding {
|
||||
layer: enumLayer.wires,
|
||||
},
|
||||
],
|
||||
instantEject: true,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/it
|
||||
import { UnremovableComponent } from "../components/unremovable";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
|
||||
|
||||
export class MetaHubBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
@ -28,6 +29,11 @@ export class MetaHubBuilding extends MetaBuilding {
|
||||
return null;
|
||||
}
|
||||
|
||||
getSprite() {
|
||||
// We render it ourself
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
@ -41,10 +47,20 @@ export class MetaHubBuilding extends MetaBuilding {
|
||||
})
|
||||
);
|
||||
|
||||
// We render the sprite ourself
|
||||
entity.components.StaticMapEntity.spriteKey = null;
|
||||
|
||||
entity.addComponent(new UnremovableComponent());
|
||||
|
||||
entity.addComponent(
|
||||
new WiredPinsComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
type: enumPinSlotType.logicalEjector,
|
||||
direction: enumDirection.top,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemAcceptorComponent({
|
||||
slots: [
|
||||
|
@ -6,7 +6,7 @@ import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
import { T } from "../../translations";
|
||||
import { round1Digit, round2Digits, formatItemsPerSecond } from "../../core/utils";
|
||||
import { formatItemsPerSecond } from "../../core/utils";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumMinerVariants = { chainable: "chainable" };
|
||||
|
@ -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;
|
||||
|
@ -71,6 +71,10 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
return super.getAvailableVariants(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} rotationVariant
|
||||
* @param {string} variant
|
||||
*/
|
||||
getPreviewSprite(rotationVariant, variant) {
|
||||
let suffix = "";
|
||||
if (variant !== defaultBuildingVariant) {
|
||||
@ -87,6 +91,10 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} rotationVariant
|
||||
* @param {string} variant
|
||||
*/
|
||||
getBlueprintSprite(rotationVariant, variant) {
|
||||
let suffix = "";
|
||||
if (variant !== defaultBuildingVariant) {
|
||||
@ -103,6 +111,14 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} rotationVariant
|
||||
* @param {string} variant
|
||||
*/
|
||||
getSprite(rotationVariant, variant) {
|
||||
return this.getPreviewSprite(rotationVariant, variant);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
@ -201,10 +217,6 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
*/
|
||||
updateVariants(entity, rotationVariant, variant) {
|
||||
entity.components.UndergroundBelt.tier = enumUndergroundBeltVariantToTier[variant];
|
||||
entity.components.StaticMapEntity.spriteKey = this.getPreviewSprite(
|
||||
rotationVariant,
|
||||
variant
|
||||
).spriteName;
|
||||
|
||||
switch (arrayUndergroundRotationVariantToMode[rotationVariant]) {
|
||||
case enumUndergroundBeltMode.sender: {
|
||||
|
@ -65,7 +65,6 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
|
||||
entity.addComponent(
|
||||
new ItemEjectorComponent({
|
||||
slots: [], // set later
|
||||
instantEject: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -88,7 +87,7 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
|
||||
]);
|
||||
|
||||
entity.components.ItemEjector.setSlots([
|
||||
{ pos: new Vector(0, 0), direction: enumDirection.left, layer: enumLayer.wires },
|
||||
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.wires },
|
||||
{ pos: new Vector(0, 0), direction: enumDirection.right, layer: enumLayer.wires },
|
||||
]);
|
||||
|
||||
@ -98,7 +97,7 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
directions: [enumDirection.top],
|
||||
layer: enumLayer.wires,
|
||||
},
|
||||
{
|
||||
@ -109,7 +108,7 @@ export class MetaWireCrossingsBuilding extends MetaBuilding {
|
||||
]);
|
||||
|
||||
entity.components.ItemEjector.setSlots([
|
||||
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.wires },
|
||||
{ pos: new Vector(0, 0), direction: enumDirection.bottom, layer: enumLayer.wires },
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage";
|
||||
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
||||
import { WiredPinsComponent } from "./components/wired_pins";
|
||||
import { EnergyConsumerComponent } from "./components/energy_consumer";
|
||||
import { BeltUnderlaysComponent } from "./components/belt_underlays";
|
||||
|
||||
export function initComponentRegistry() {
|
||||
gComponentRegistry.register(StaticMapEntityComponent);
|
||||
@ -29,6 +30,7 @@ export function initComponentRegistry() {
|
||||
gComponentRegistry.register(EnergyGeneratorComponent);
|
||||
gComponentRegistry.register(WiredPinsComponent);
|
||||
gComponentRegistry.register(EnergyConsumerComponent);
|
||||
gComponentRegistry.register(BeltUnderlaysComponent);
|
||||
|
||||
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
||||
|
||||
|
@ -7,6 +7,40 @@ import { enumLayer } from "../root";
|
||||
|
||||
export const curvedBeltLength = /* Math.PI / 4 */ 0.78;
|
||||
|
||||
/** @type {import("./item_acceptor").ItemAcceptorSlot} */
|
||||
export const FAKE_BELT_ACCEPTOR_SLOT = {
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
layer: enumLayer.regular,
|
||||
};
|
||||
|
||||
/** @type {Object<enumDirection, import("./item_ejector").ItemEjectorSlot>} */
|
||||
export const FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION = {
|
||||
[enumDirection.top]: {
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.top,
|
||||
item: null,
|
||||
layer: enumLayer.regular,
|
||||
progress: 0,
|
||||
},
|
||||
|
||||
[enumDirection.right]: {
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.right,
|
||||
item: null,
|
||||
layer: enumLayer.regular,
|
||||
progress: 0,
|
||||
},
|
||||
|
||||
[enumDirection.left]: {
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.left,
|
||||
item: null,
|
||||
layer: enumLayer.regular,
|
||||
progress: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export class BeltComponent extends Component {
|
||||
static getId() {
|
||||
return "Belt";
|
||||
@ -56,6 +90,27 @@ export class BeltComponent extends Component {
|
||||
return this.direction === enumDirection.top ? 1.0 : curvedBeltLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fake acceptor slot used for matching
|
||||
* @returns {import("./item_acceptor").ItemAcceptorSlot}
|
||||
*/
|
||||
getFakeAcceptorSlot() {
|
||||
return FAKE_BELT_ACCEPTOR_SLOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fake acceptor slot used for matching
|
||||
* @returns {import("./item_ejector").ItemEjectorSlot}
|
||||
*/
|
||||
getFakeEjectorSlots() {
|
||||
assert(
|
||||
FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction],
|
||||
"Invalid belt direction: ",
|
||||
this.direction
|
||||
);
|
||||
return FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from belt space (0 = start of belt ... 1 = end of belt) to the local
|
||||
* belt coordinates (-0.5|-0.5 to 0.5|0.5)
|
||||
|
44
src/js/game/components/belt_underlays.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { Component } from "../component";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
|
||||
export class BeltUnderlaysComponent extends Component {
|
||||
static getId() {
|
||||
return "BeltUnderlays";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
underlays: types.array(
|
||||
types.structured({
|
||||
pos: types.vector,
|
||||
direction: types.enum(enumDirection),
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
duplicateWithoutContents() {
|
||||
const beltUnderlaysCopy = [];
|
||||
for (let i = 0; i < this.underlays.length; ++i) {
|
||||
const underlay = this.underlays[i];
|
||||
beltUnderlaysCopy.push({
|
||||
pos: underlay.pos.copy(),
|
||||
direction: underlay.direction,
|
||||
});
|
||||
}
|
||||
|
||||
return new BeltUnderlaysComponent({
|
||||
underlays: beltUnderlaysCopy,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {Array<{pos: Vector, direction: enumDirection}>=} param0.underlays Where to render belt underlays
|
||||
*/
|
||||
constructor({ underlays }) {
|
||||
super();
|
||||
this.underlays = underlays;
|
||||
}
|
||||
}
|
@ -6,27 +6,4 @@ export class HubComponent extends Component {
|
||||
static getId() {
|
||||
return "Hub";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
definitionsToAnalyze: types.array(types.knownType(ShapeDefinition)),
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* Shape definitions in queue to be analyzed and counted towards the goal
|
||||
* @type {Array<ShapeDefinition>}
|
||||
*/
|
||||
this.definitionsToAnalyze = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ShapeDefinition} definition
|
||||
*/
|
||||
queueShapeDefinition(definition) {
|
||||
this.definitionsToAnalyze.push(definition);
|
||||
}
|
||||
}
|
||||
|
@ -39,16 +39,6 @@ export class ItemAcceptorComponent extends Component {
|
||||
directions: types.array(types.enum(enumDirection)),
|
||||
filter: types.nullable(types.enum(enumItemType)),
|
||||
|
||||
// TODO: MIGRATE
|
||||
layer: types.enum(enumLayer),
|
||||
})
|
||||
),
|
||||
animated: types.bool,
|
||||
beltUnderlays: types.array(
|
||||
types.structured({
|
||||
pos: types.vector,
|
||||
direction: types.enum(enumDirection),
|
||||
|
||||
// TODO: MIGRATE
|
||||
layer: types.enum(enumLayer),
|
||||
})
|
||||
@ -68,20 +58,8 @@ export class ItemAcceptorComponent extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
const beltUnderlaysCopy = [];
|
||||
for (let i = 0; i < this.beltUnderlays.length; ++i) {
|
||||
const underlay = this.beltUnderlays[i];
|
||||
beltUnderlaysCopy.push({
|
||||
pos: underlay.pos.copy(),
|
||||
direction: underlay.direction,
|
||||
layer: underlay.layer,
|
||||
});
|
||||
}
|
||||
|
||||
return new ItemAcceptorComponent({
|
||||
slots: slotsCopy,
|
||||
beltUnderlays: beltUnderlaysCopy,
|
||||
animated: this.animated,
|
||||
});
|
||||
}
|
||||
|
||||
@ -89,23 +67,16 @@ export class ItemAcceptorComponent extends Component {
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items
|
||||
* @param {boolean=} param0.animated Whether to animate item consumption
|
||||
* @param {Array<{pos: Vector, direction: enumDirection, layer: enumLayer}>=} param0.beltUnderlays Where to render belt underlays
|
||||
*/
|
||||
constructor({ slots = [], beltUnderlays = [], animated = true }) {
|
||||
constructor({ slots = [] }) {
|
||||
super();
|
||||
|
||||
this.animated = animated;
|
||||
|
||||
/**
|
||||
* Fixes belt animations
|
||||
* @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>}
|
||||
*/
|
||||
this.itemConsumptionAnimations = [];
|
||||
|
||||
/* Which belt underlays to render */
|
||||
this.beltUnderlays = beltUnderlays;
|
||||
|
||||
this.setSlots(slots);
|
||||
}
|
||||
|
||||
@ -164,14 +135,12 @@ export class ItemAcceptorComponent extends Component {
|
||||
* @param {BaseItem} item
|
||||
*/
|
||||
onItemAccepted(slotIndex, direction, item) {
|
||||
if (this.animated) {
|
||||
this.itemConsumptionAnimations.push({
|
||||
item,
|
||||
slotIndex,
|
||||
direction,
|
||||
animProgress: 0.0,
|
||||
});
|
||||
}
|
||||
this.itemConsumptionAnimations.push({
|
||||
item,
|
||||
slotIndex,
|
||||
direction,
|
||||
animProgress: 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,7 @@ import { types } from "../../savegame/serialization";
|
||||
import { gItemRegistry } from "../../core/global_registries";
|
||||
import { Entity } from "../entity";
|
||||
import { enumLayer } from "../root";
|
||||
import { BeltPath } from "../belt_path";
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
@ -14,6 +15,7 @@ import { enumLayer } from "../root";
|
||||
* layer: enumLayer,
|
||||
* progress: number?,
|
||||
* cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot,
|
||||
* cachedBeltPath?: BeltPath,
|
||||
* cachedTargetEntity?: Entity
|
||||
* }} ItemEjectorSlot
|
||||
*/
|
||||
@ -24,10 +26,8 @@ export class ItemEjectorComponent extends Component {
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
// The cachedDestSlot, cachedTargetEntity, and cachedConnectedSlots fields
|
||||
// are not serialized.
|
||||
// The cachedDestSlot, cachedTargetEntity fields are not serialized.
|
||||
return {
|
||||
instantEject: types.bool,
|
||||
slots: types.array(
|
||||
types.structured({
|
||||
pos: types.vector,
|
||||
@ -55,7 +55,6 @@ export class ItemEjectorComponent extends Component {
|
||||
|
||||
return new ItemEjectorComponent({
|
||||
slots: slotsCopy,
|
||||
instantEject: this.instantEject,
|
||||
});
|
||||
}
|
||||
|
||||
@ -63,19 +62,12 @@ export class ItemEjectorComponent extends Component {
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {Array<{pos: Vector, direction: enumDirection, layer?: enumLayer}>=} param0.slots The slots to eject on
|
||||
* @param {boolean=} param0.instantEject If the ejection is instant
|
||||
*/
|
||||
constructor({ slots = [], instantEject = false }) {
|
||||
constructor({ slots = [] }) {
|
||||
super();
|
||||
|
||||
// How long items take to eject
|
||||
this.instantEject = instantEject;
|
||||
|
||||
this.setSlots(slots);
|
||||
|
||||
/** @type {ItemEjectorSlot[]} */
|
||||
this.cachedConnectedSlots = null;
|
||||
|
||||
/**
|
||||
* Whether this ejector slot is enabled
|
||||
*/
|
||||
@ -162,7 +154,7 @@ export class ItemEjectorComponent extends Component {
|
||||
return false;
|
||||
}
|
||||
this.slots[slotIndex].item = item;
|
||||
this.slots[slotIndex].progress = this.instantEject ? 1 : 0;
|
||||
this.slots[slotIndex].progress = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { AtlasSprite } from "../../core/sprites";
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { Component } from "../component";
|
||||
import { getBuildingDataFromCode } from "../building_codes";
|
||||
|
||||
export class StaticMapEntityComponent extends Component {
|
||||
static getId() {
|
||||
@ -17,21 +18,43 @@ export class StaticMapEntityComponent extends Component {
|
||||
tileSize: types.tileVector,
|
||||
rotation: types.float,
|
||||
originalRotation: types.float,
|
||||
spriteKey: types.nullable(types.string),
|
||||
blueprintSpriteKey: types.string,
|
||||
silhouetteColor: types.nullable(types.string),
|
||||
|
||||
// See building_codes.js
|
||||
code: types.uint,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sprite
|
||||
* @returns {AtlasSprite}
|
||||
*/
|
||||
getSprite() {
|
||||
return getBuildingDataFromCode(this.code).sprite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blueprint sprite
|
||||
* @returns {AtlasSprite}
|
||||
*/
|
||||
getBlueprintSprite() {
|
||||
return getBuildingDataFromCode(this.code).blueprintSprite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the silhouette color
|
||||
* @returns {string}
|
||||
*/
|
||||
getSilhouetteColor() {
|
||||
return getBuildingDataFromCode(this.code).silhouetteColor;
|
||||
}
|
||||
|
||||
duplicateWithoutContents() {
|
||||
return new StaticMapEntityComponent({
|
||||
origin: this.origin.copy(),
|
||||
tileSize: this.tileSize.copy(),
|
||||
rotation: this.rotation,
|
||||
originalRotation: this.originalRotation,
|
||||
spriteKey: this.spriteKey,
|
||||
silhouetteColor: this.silhouetteColor,
|
||||
blueprintSpriteKey: this.blueprintSpriteKey,
|
||||
code: this.code,
|
||||
});
|
||||
}
|
||||
|
||||
@ -42,18 +65,14 @@ export class StaticMapEntityComponent extends Component {
|
||||
* @param {Vector=} param0.tileSize Size of the entity in tiles
|
||||
* @param {number=} param0.rotation Rotation in degrees. Must be multiple of 90
|
||||
* @param {number=} param0.originalRotation Original Rotation in degrees. Must be multiple of 90
|
||||
* @param {string=} param0.spriteKey Optional sprite
|
||||
* @param {string} param0.blueprintSpriteKey Blueprint sprite, required
|
||||
* @param {string=} param0.silhouetteColor Optional silhouette color override
|
||||
* @param {number=} param0.code Building code
|
||||
*/
|
||||
constructor({
|
||||
origin = new Vector(),
|
||||
tileSize = new Vector(1, 1),
|
||||
rotation = 0,
|
||||
originalRotation = 0,
|
||||
spriteKey = null,
|
||||
silhouetteColor = null,
|
||||
blueprintSpriteKey = null,
|
||||
code = 0,
|
||||
}) {
|
||||
super();
|
||||
assert(
|
||||
@ -63,11 +82,9 @@ export class StaticMapEntityComponent extends Component {
|
||||
|
||||
this.origin = origin;
|
||||
this.tileSize = tileSize;
|
||||
this.spriteKey = spriteKey;
|
||||
this.rotation = rotation;
|
||||
this.code = code;
|
||||
this.originalRotation = originalRotation;
|
||||
this.silhouetteColor = silhouetteColor;
|
||||
this.blueprintSpriteKey = blueprintSpriteKey;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,6 +8,8 @@ export const enumPinSlotType = {
|
||||
negativeEnergyEjector: "negativeEnergyEjector",
|
||||
positiveEnergyAcceptor: "positiveEnergyAcceptor",
|
||||
negativeEnergyAcceptor: "negativeEnergyAcceptor",
|
||||
logicalEjector: "logicalEjector",
|
||||
logicalAcceptor: "logicalAcceptor",
|
||||
};
|
||||
|
||||
/** @typedef {{
|
||||
|
@ -398,7 +398,7 @@ export class GameCore {
|
||||
|
||||
if (!this.root.camera.getIsMapOverlayActive()) {
|
||||
// Underlays for splitters / balancers
|
||||
systems.itemAcceptor.drawUnderlays(params, enumLayer.regular);
|
||||
systems.beltUnderlays.drawUnderlays(params, enumLayer.regular);
|
||||
|
||||
// Belt items
|
||||
systems.belt.drawLayerBeltItems(params, enumLayer.regular);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { GameRoot } from "./root";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { round3Digits } from "../core/utils";
|
||||
|
||||
const logger = createLogger("dynamic_tickrate");
|
||||
|
||||
|
@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage";
|
||||
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
||||
import { WiredPinsComponent } from "./components/wired_pins";
|
||||
import { EnergyConsumerComponent } from "./components/energy_consumer";
|
||||
import { BeltUnderlaysComponent } from "./components/belt_underlays";
|
||||
/* typehints:end */
|
||||
|
||||
/**
|
||||
@ -65,6 +66,9 @@ export class EntityComponentStorage {
|
||||
/** @type {EnergyConsumerComponent} */
|
||||
this.EnergyConsumer;
|
||||
|
||||
/** @type {BeltUnderlaysComponent} */
|
||||
this.BeltUnderlays;
|
||||
|
||||
/* typehints:end */
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import { StorageSystem } from "./systems/storage";
|
||||
import { EnergyGeneratorSystem } from "./systems/energy_generator";
|
||||
import { WiredPinsSystem } from "./systems/wired_pins";
|
||||
import { EnergyConsumerSystem } from "./systems/energy_consumer";
|
||||
import { BeltUnderlaysSystem } from "./systems/belt_underlays";
|
||||
|
||||
const logger = createLogger("game_system_manager");
|
||||
|
||||
@ -68,6 +69,9 @@ export class GameSystemManager {
|
||||
/** @type {EnergyConsumerSystem} */
|
||||
energyConsumer: null,
|
||||
|
||||
/** @type {BeltUnderlaysSystem} */
|
||||
beltUnderlays: null,
|
||||
|
||||
/* typehints:end */
|
||||
};
|
||||
this.systemUpdateOrder = [];
|
||||
@ -110,6 +114,8 @@ export class GameSystemManager {
|
||||
|
||||
add("energyConsumer", EnergyConsumerSystem);
|
||||
|
||||
add("beltUnderlays", BeltUnderlaysSystem);
|
||||
|
||||
// IMPORTANT: Must be after belt system since belt system can change the
|
||||
// orientation of an entity after it is placed -> the item acceptor cache
|
||||
// then would be invalid
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { queryParamOptions } from "../core/query_parameters";
|
||||
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { enumColors } from "./colors";
|
||||
@ -7,7 +6,7 @@ import { enumItemProcessorTypes } from "./components/item_processor";
|
||||
import { GameRoot, enumLayer } from "./root";
|
||||
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
||||
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
|
||||
import { UPGRADES, blueprintShape } from "./upgrades";
|
||||
import { UPGRADES } from "./upgrades";
|
||||
|
||||
export class HubGoals extends BasicSerializableObject {
|
||||
static getId() {
|
||||
@ -328,9 +327,7 @@ export class HubGoals extends BasicSerializableObject {
|
||||
/** @type {Array<import("./shape_definition").ShapeLayer>} */
|
||||
let layers = [];
|
||||
|
||||
// @ts-ignore
|
||||
const randomColor = () => randomChoice(Object.values(enumColors));
|
||||
// @ts-ignore
|
||||
const randomShape = () => randomChoice(Object.values(enumSubShape));
|
||||
|
||||
let anyIsMissingTwo = false;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ClickDetector } from "../../../core/click_detector";
|
||||
import { globalConfig, THIRDPARTY_URLS } from "../../../core/config";
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { drawRotatedSprite, rotateTrapezRightFaced } from "../../../core/draw_utils";
|
||||
import { drawRotatedSprite } from "../../../core/draw_utils";
|
||||
import { Loader } from "../../../core/loader";
|
||||
import { clamp, makeDiv, removeAllChildren } from "../../../core/utils";
|
||||
import {
|
||||
@ -18,6 +18,7 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { HUDBuildingPlacerLogic } from "./building_placer_logic";
|
||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||
import { enumLayer } from "../../root";
|
||||
import { getCodeFromBuildingData } from "../../building_codes";
|
||||
|
||||
export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
/**
|
||||
@ -84,9 +85,8 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
label: "lock-direction-indicator",
|
||||
});
|
||||
|
||||
// Loader.getSprite("sprites/misc/lock_direction_indicator.png").draw(context, 0, 0, 48, 48);
|
||||
context.fillStyle = THEME.map.directionLock[enumLayer.wires].color;
|
||||
context.strokeStyle = THEME.map.directionLock[enumLayer.wires].color;
|
||||
context.fillStyle = THEME.map.directionLock[layer].color;
|
||||
context.strokeStyle = THEME.map.directionLock[layer].color;
|
||||
context.lineWidth = 2;
|
||||
|
||||
const padding = 5;
|
||||
@ -313,12 +313,16 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
staticComp.rotation = rotation;
|
||||
staticComp.tileSize = metaBuilding.getDimensions(this.currentVariant.get());
|
||||
metaBuilding.updateVariants(this.fakeEntity, rotationVariant, this.currentVariant.get());
|
||||
staticComp.code = getCodeFromBuildingData(
|
||||
this.currentMetaBuilding.get(),
|
||||
this.currentVariant.get(),
|
||||
rotationVariant
|
||||
);
|
||||
|
||||
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
|
||||
|
||||
// Fade in / out
|
||||
parameters.context.lineWidth = 1;
|
||||
// parameters.context.globalAlpha = 0.3 + pulseAnimation(this.root.time.realtimeNow(), 0.9) * 0.7;
|
||||
|
||||
// Determine the bounds and visualize them
|
||||
const entityBounds = staticComp.getTileSpaceBounds();
|
||||
|
@ -13,6 +13,7 @@ import { SOUNDS } from "../../../platform/sound";
|
||||
import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
import { enumLayer } from "../../root";
|
||||
import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
|
||||
|
||||
/**
|
||||
* Contains all logic for the building placer - this doesn't include the rendering
|
||||
@ -338,109 +339,27 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
}
|
||||
|
||||
// Try to extract the building
|
||||
const extracted = this.hack_reconstructMetaBuildingAndVariantFromBuilding(contents);
|
||||
const buildingCode = contents.components.StaticMapEntity.code;
|
||||
const extracted = getBuildingDataFromCode(buildingCode);
|
||||
|
||||
// If the building we are picking is the same as the one we have, clear the cursor.
|
||||
if (
|
||||
!extracted ||
|
||||
(extracted.metaBuilding === this.currentMetaBuilding.get() &&
|
||||
extracted.variant === this.currentVariant.get())
|
||||
this.currentMetaBuilding.get() &&
|
||||
extracted.metaInstance.getId() === this.currentMetaBuilding.get().getId() &&
|
||||
extracted.variant === this.currentVariant.get()
|
||||
) {
|
||||
this.currentMetaBuilding.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentMetaBuilding.set(extracted.metaBuilding);
|
||||
this.currentMetaBuilding.set(extracted.metaInstance);
|
||||
this.currentVariant.set(extracted.variant);
|
||||
this.currentBaseRotation = contents.components.StaticMapEntity.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* HACK!
|
||||
*
|
||||
* This attempts to reconstruct the meta building and its variant from a given entity
|
||||
* @param {Entity} entity
|
||||
* @returns {{ metaBuilding: MetaBuilding, variant: string }}
|
||||
* Switches the side for the direction lock manually
|
||||
*/
|
||||
hack_reconstructMetaBuildingAndVariantFromBuilding(entity) {
|
||||
if (entity.components.Hub) {
|
||||
// Hub is not copyable
|
||||
return null;
|
||||
}
|
||||
|
||||
const matches = [];
|
||||
const metaBuildings = gMetaBuildingRegistry.entries;
|
||||
for (let i = 0; i < metaBuildings.length; ++i) {
|
||||
const metaBuilding = metaBuildings[i];
|
||||
const availableVariants = metaBuilding.getAvailableVariants(this.root);
|
||||
checkVariant: for (let k = 0; k < availableVariants.length; ++k) {
|
||||
const variant = availableVariants[k];
|
||||
let unplaced = metaBuilding.createEntity({
|
||||
root: this.root,
|
||||
variant,
|
||||
origin: new Vector(0, 0),
|
||||
rotation: 0,
|
||||
originalRotation: 0,
|
||||
rotationVariant: 0,
|
||||
});
|
||||
|
||||
// Compare if both entities share the same components
|
||||
for (let component in entity.components) {
|
||||
if ((entity.components[component] == null) !== (unplaced.components[component] == null)) {
|
||||
continue checkVariant;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for same item processor
|
||||
if (
|
||||
entity.components.ItemProcessor &&
|
||||
entity.components.ItemProcessor.type != unplaced.components.ItemProcessor.type
|
||||
) {
|
||||
continue checkVariant;
|
||||
}
|
||||
|
||||
// Check for underground belt
|
||||
if (
|
||||
entity.components.UndergroundBelt &&
|
||||
entity.components.UndergroundBelt.tier != unplaced.components.UndergroundBelt.tier
|
||||
) {
|
||||
continue checkVariant;
|
||||
}
|
||||
|
||||
// Check for same sprite key - except for underground belts
|
||||
// since the sprite may vary here
|
||||
if (
|
||||
!entity.components.UndergroundBelt &&
|
||||
entity.components.StaticMapEntity.spriteKey !=
|
||||
unplaced.components.StaticMapEntity.spriteKey
|
||||
) {
|
||||
continue checkVariant;
|
||||
}
|
||||
|
||||
if (metaBuilding.id === "wire" && entity.layer !== enumLayer.wires) {
|
||||
continue checkVariant;
|
||||
}
|
||||
|
||||
if (metaBuilding.id === "belt" && entity.layer !== enumLayer.regular) {
|
||||
continue checkVariant;
|
||||
}
|
||||
matches.push({ metaBuilding, variant });
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.length == 1) {
|
||||
const staticEntity = entity.components.StaticMapEntity;
|
||||
const key = staticEntity.spriteKey || staticEntity.blueprintSpriteKey;
|
||||
assert(
|
||||
key &&
|
||||
key.includes(matches[0].metaBuilding.id) &&
|
||||
(matches[0].variant === defaultBuildingVariant || key.includes(matches[0].variant))
|
||||
);
|
||||
return matches[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
switchDirectionLockSide() {
|
||||
this.currentDirectionLockSide = 1 - this.currentDirectionLockSide;
|
||||
}
|
||||
@ -673,7 +592,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
origin: new Vector(0, 0),
|
||||
rotation: 0,
|
||||
tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(),
|
||||
blueprintSpriteKey: "",
|
||||
code: getCodeFromBuildingData(metaBuilding, variant, 0),
|
||||
})
|
||||
);
|
||||
metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get());
|
||||
|
@ -88,8 +88,8 @@ export class HUDDebugInfo extends BaseHUDPart {
|
||||
const mouseTile = this.root.camera.screenToWorld(mousePos).toTileSpace();
|
||||
const cameraTile = this.root.camera.center.toTileSpace();
|
||||
|
||||
this.trackedMousePosition.set(`Pos: <code>${mouseTile.x}</code> / <code>${mouseTile.y}</code>`);
|
||||
this.trackedCameraPosition.set(`Center: <code>${cameraTile.x}</code> / <code>${cameraTile.y}</code>`);
|
||||
this.trackedMousePosition.set(`Mouse: <code>${mouseTile.x}</code> / <code>${mouseTile.y}</code>`);
|
||||
this.trackedCameraPosition.set(`Camera: <code>${cameraTile.x}</code> / <code>${cameraTile.y}</code>`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,7 +15,9 @@ export class HUDEntityDebugger extends BaseHUDPart {
|
||||
`
|
||||
);
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
this.mousePosElem = this.element.querySelector(".mousePos");
|
||||
/** @type {HTMLElement} */
|
||||
this.chunkPosElem = this.element.querySelector(".chunkPos");
|
||||
this.entityInfoElem = this.element.querySelector(".entityInfo");
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv, randomInt } from "../../../core/utils";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
import { enumNotificationType } from "./notifications";
|
||||
import { T } from "../../../translations";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { IS_DEMO } from "../../../core/config";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
|
||||
export class HUDGameMenu extends BaseHUDPart {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ClickDetector } from "../../../core/click_detector";
|
||||
import { formatBigNumber, makeDiv, arrayDelete, arrayDeleteValue } from "../../../core/utils";
|
||||
import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils";
|
||||
import { ShapeDefinition } from "../../shape_definition";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { blueprintShape, UPGRADES } from "../../upgrades";
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv, formatSeconds, formatBigNumberFull } from "../../../core/utils";
|
||||
import { makeDiv, formatBigNumberFull } from "../../../core/utils";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { InputReceiver } from "../../../core/input_receiver";
|
||||
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { T } from "../../../translations";
|
||||
import { StaticMapEntityComponent } from "../../components/static_map_entity";
|
||||
import { ItemProcessorComponent } from "../../components/item_processor";
|
||||
import { BeltComponent } from "../../components/belt";
|
||||
import { IS_DEMO } from "../../../core/config";
|
||||
|
||||
export class HUDSettingsMenu extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
@ -23,7 +21,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
|
||||
<strong>${T.ingame.settingsMenu.beltsPlaced}</strong><span class="beltsPlaced"></span>
|
||||
<strong>${T.ingame.settingsMenu.buildingsPlaced}</strong><span class="buildingsPlaced"></span>
|
||||
<strong>${T.ingame.settingsMenu.playtime}</strong><span class="playtime"></span>
|
||||
|
||||
|
||||
`
|
||||
);
|
||||
|
||||
@ -57,16 +55,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
|
||||
}
|
||||
|
||||
returnToMenu() {
|
||||
// if (IS_DEMO) {
|
||||
// const { cancel, deleteGame } = this.root.hud.parts.dialogs.showWarning(
|
||||
// T.dialogs.leaveNotPossibleInDemo.title,
|
||||
// T.dialogs.leaveNotPossibleInDemo.desc,
|
||||
// ["cancel:good", "deleteGame:bad"]
|
||||
// );
|
||||
// deleteGame.add(() => this.root.gameState.goBackToMenu());
|
||||
// } else {
|
||||
this.root.gameState.goBackToMenu();
|
||||
// }
|
||||
}
|
||||
|
||||
goToSettings() {
|
||||
@ -102,20 +91,25 @@ export class HUDSettingsMenu extends BaseHUDPart {
|
||||
show() {
|
||||
this.visible = true;
|
||||
document.body.classList.add("ingameDialogOpen");
|
||||
// this.background.classList.add("visible");
|
||||
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
|
||||
|
||||
const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60);
|
||||
this.statsElement.querySelector(".playtime").innerText = T.global.time.xMinutes.replace(
|
||||
"<x>",
|
||||
"" + totalMinutesPlayed
|
||||
);
|
||||
|
||||
this.statsElement.querySelector(".buildingsPlaced").innerText = formatBigNumberFull(
|
||||
/** @type {HTMLElement} */
|
||||
const playtimeElement = this.statsElement.querySelector(".playtime");
|
||||
/** @type {HTMLElement} */
|
||||
const buildingsPlacedElement = this.statsElement.querySelector(".buildingsPlaced");
|
||||
/** @type {HTMLElement} */
|
||||
const beltsPlacedElement = this.statsElement.querySelector(".beltsPlaced");
|
||||
|
||||
playtimeElement.innerText = T.global.time.xMinutes.replace("<x>", `${totalMinutesPlayed}`);
|
||||
|
||||
buildingsPlacedElement.innerText = formatBigNumberFull(
|
||||
this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent).length -
|
||||
this.root.entityMgr.getAllWithComponent(BeltComponent).length
|
||||
);
|
||||
this.statsElement.querySelector(".beltsPlaced").innerText = formatBigNumberFull(
|
||||
|
||||
beltsPlacedElement.innerText = formatBigNumberFull(
|
||||
this.root.entityMgr.getAllWithComponent(BeltComponent).length
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { InputReceiver } from "../../../core/input_receiver";
|
||||
import { makeButton, makeDiv, removeAllChildren, capitalizeFirstLetter } from "../../../core/utils";
|
||||
import { makeButton, makeDiv, removeAllChildren } from "../../../core/utils";
|
||||
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { enumAnalyticsDataSource } from "../../production_analytics";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
@ -7,6 +7,14 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle";
|
||||
import { T } from "../../../translations";
|
||||
|
||||
/**
|
||||
* Capitalizes the first letter
|
||||
* @param {string} str
|
||||
*/
|
||||
function capitalizeFirstLetter(str) {
|
||||
return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase();
|
||||
}
|
||||
|
||||
export class HUDStatistics extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.background = makeDiv(parent, "ingame_HUD_Statistics", ["ingameDialog"]);
|
||||
|