Merge branch 'master' of https://github.com/tobspr/shapez.io into master
1
.github/workflows/ci.yml
vendored
@ -42,6 +42,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd gulp
|
cd gulp
|
||||||
yarn gulp translations.fullBuild
|
yarn gulp translations.fullBuild
|
||||||
|
yarn gulp localConfig.findOrCreate
|
||||||
cd ..
|
cd ..
|
||||||
yarn tslint
|
yarn tslint
|
||||||
|
|
||||||
|
7
.gitignore
vendored
@ -46,7 +46,14 @@ res_built
|
|||||||
|
|
||||||
gulp/runnable-texturepacker.jar
|
gulp/runnable-texturepacker.jar
|
||||||
tmp_standalone_files
|
tmp_standalone_files
|
||||||
|
tmp_standalone_files_china
|
||||||
|
|
||||||
# Local config
|
# Local config
|
||||||
config.local.js
|
config.local.js
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Editor artifacts
|
||||||
|
*.*.swp
|
||||||
|
*.*.swo
|
||||||
|
|
||||||
|
.history/
|
4
.gitpod.Dockerfile
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
FROM gitpod/workspace-full
|
||||||
|
|
||||||
|
RUN sudo apt-get update \
|
||||||
|
&& sudo apt install ffmpeg -yq
|
10
.gitpod.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
image:
|
||||||
|
file: .gitpod.Dockerfile
|
||||||
|
tasks:
|
||||||
|
- init: yarn && gp sync-done boot
|
||||||
|
- before: cd gulp
|
||||||
|
init: gp sync-await boot && yarn
|
||||||
|
command: yarn gulp
|
||||||
|
ports:
|
||||||
|
- port: 3005
|
||||||
|
onOpen: open-preview
|
4
.vscode/settings.json
vendored
@ -1,3 +1,5 @@
|
|||||||
{
|
{
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"editor.formatOnSave": true
|
||||||
}
|
}
|
10
README.md
@ -31,6 +31,16 @@ Your goal is to produce shapes by cutting, rotating, merging and painting parts
|
|||||||
|
|
||||||
**Notice**: This will produce a debug build with several debugging flags enabled. If you want to disable them, modify [`src/js/core/config.js`](src/js/core/config.js).
|
**Notice**: This will produce a debug build with several debugging flags enabled. If you want to disable them, modify [`src/js/core/config.js`](src/js/core/config.js).
|
||||||
|
|
||||||
|
## Build Online with one-click setup
|
||||||
|
|
||||||
|
You can use [Gitpod](https://www.gitpod.io/) (an Online Open Source VS Code-like IDE which is free for Open Source) for working on issues and making PRs to this project. With a single click it will start a workspace and automatically:
|
||||||
|
|
||||||
|
- clone the `shapez.io` repo.
|
||||||
|
- install all of the dependencies.
|
||||||
|
- start `gulp` in `gulp/` directory.
|
||||||
|
|
||||||
|
[](https://gitpod.io/from-referrer/)
|
||||||
|
|
||||||
## Helping translate
|
## Helping translate
|
||||||
|
|
||||||
Please checkout the [Translations readme](translations/).
|
Please checkout the [Translations readme](translations/).
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
/* eslint-disable quotes,no-undef */
|
/* eslint-disable quotes,no-undef */
|
||||||
|
|
||||||
const { app, BrowserWindow, Menu, MenuItem, session } = require("electron");
|
const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell } = require("electron");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
const childProcess = require("child_process");
|
|
||||||
const { ipcMain } = require("electron");
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const steam = require("./steam");
|
||||||
|
const asyncLock = require("async-lock");
|
||||||
|
|
||||||
const isDev = process.argv.indexOf("--dev") >= 0;
|
const isDev = process.argv.indexOf("--dev") >= 0;
|
||||||
const isLocal = process.argv.indexOf("--local") >= 0;
|
const isLocal = process.argv.indexOf("--local") >= 0;
|
||||||
|
|
||||||
@ -67,11 +68,7 @@ function createWindow() {
|
|||||||
|
|
||||||
win.webContents.on("new-window", (event, pth) => {
|
win.webContents.on("new-window", (event, pth) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (process.platform == "win32") {
|
shell.openExternal(pth);
|
||||||
childProcess.execSync("start " + pth);
|
|
||||||
} else if (process.platform == "linux") {
|
|
||||||
childProcess.execSync("xdg-open " + pth);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
win.on("closed", () => {
|
win.on("closed", () => {
|
||||||
@ -156,7 +153,82 @@ ipcMain.on("exit-app", (event, flag) => {
|
|||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
function performFsJob(job) {
|
let renameCounter = 1;
|
||||||
|
|
||||||
|
const fileLock = new asyncLock({
|
||||||
|
timeout: 30000,
|
||||||
|
maxPending: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
function niceFileName(filename) {
|
||||||
|
return filename.replace(storePath, "@");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeFileSafe(filename, contents) {
|
||||||
|
++renameCounter;
|
||||||
|
const prefix = "[ " + renameCounter + ":" + niceFileName(filename) + " ] ";
|
||||||
|
const transactionId = String(new Date().getTime()) + "." + renameCounter;
|
||||||
|
|
||||||
|
if (fileLock.isBusy()) {
|
||||||
|
console.warn(prefix, "Concurrent write process on", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileLock.acquire(filename, async () => {
|
||||||
|
console.log(prefix, "Starting write on", niceFileName(filename), "in transaction", transactionId);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filename)) {
|
||||||
|
// this one is easy
|
||||||
|
console.log(prefix, "Writing file instantly because it does not exist:", niceFileName(filename));
|
||||||
|
await fs.promises.writeFile(filename, contents, { encoding: "utf8" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first, write a temporary file (.tmp-XXX)
|
||||||
|
const tempName = filename + ".tmp-" + transactionId;
|
||||||
|
console.log(prefix, "Writing temporary file", niceFileName(tempName));
|
||||||
|
await fs.promises.writeFile(tempName, contents, { encoding: "utf8" });
|
||||||
|
|
||||||
|
// now, rename the original file to (.backup-XXX)
|
||||||
|
const oldTemporaryName = filename + ".backup-" + transactionId;
|
||||||
|
console.log(
|
||||||
|
prefix,
|
||||||
|
"Renaming old file",
|
||||||
|
niceFileName(filename),
|
||||||
|
"to",
|
||||||
|
niceFileName(oldTemporaryName)
|
||||||
|
);
|
||||||
|
await fs.promises.rename(filename, oldTemporaryName);
|
||||||
|
|
||||||
|
// now, rename the temporary file (.tmp-XXX) to the target
|
||||||
|
console.log(
|
||||||
|
prefix,
|
||||||
|
"Renaming the temporary file",
|
||||||
|
niceFileName(tempName),
|
||||||
|
"to the original",
|
||||||
|
niceFileName(filename)
|
||||||
|
);
|
||||||
|
await fs.promises.rename(tempName, filename);
|
||||||
|
|
||||||
|
// we are done now, try to create a backup, but don't fail if the backup fails
|
||||||
|
try {
|
||||||
|
// check if there is an old backup file
|
||||||
|
const backupFileName = filename + ".backup";
|
||||||
|
if (fs.existsSync(backupFileName)) {
|
||||||
|
console.log(prefix, "Deleting old backup file", niceFileName(backupFileName));
|
||||||
|
// delete the old backup
|
||||||
|
await fs.promises.unlink(backupFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename the old file to the new backup file
|
||||||
|
console.log(prefix, "Moving", niceFileName(oldTemporaryName), "to the backup file location");
|
||||||
|
await fs.promises.rename(oldTemporaryName, backupFileName);
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(prefix, "Failed to switch backup files:", ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function performFsJob(job) {
|
||||||
const fname = path.join(storePath, job.filename);
|
const fname = path.join(storePath, job.filename);
|
||||||
|
|
||||||
switch (job.type) {
|
switch (job.type) {
|
||||||
@ -168,38 +240,35 @@ function performFsJob(job) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let contents = "";
|
|
||||||
try {
|
try {
|
||||||
contents = fs.readFileSync(fname, { encoding: "utf8" });
|
const data = await fs.promises.readFile(fname, { encoding: "utf8" });
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
};
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return {
|
return {
|
||||||
error: ex,
|
error: ex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: contents,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
case "write": {
|
case "write": {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(fname, job.contents);
|
await writeFileSafe(fname, job.contents);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: job.contents,
|
||||||
|
};
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return {
|
return {
|
||||||
error: ex,
|
error: ex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: job.contents,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "delete": {
|
case "delete": {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(fname);
|
await fs.promises.unlink(fname);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return {
|
return {
|
||||||
error: ex,
|
error: ex,
|
||||||
@ -217,12 +286,7 @@ function performFsJob(job) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.on("fs-job", (event, arg) => {
|
ipcMain.handle("fs-job", (event, arg) => performFsJob(arg));
|
||||||
const result = performFsJob(arg);
|
|
||||||
event.reply("fs-response", { id: arg.id, result });
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on("fs-sync-job", (event, arg) => {
|
steam.init(isDev);
|
||||||
const result = performFsJob(arg);
|
steam.listen();
|
||||||
event.returnValue = result;
|
|
||||||
});
|
|
||||||
|
@ -10,7 +10,12 @@
|
|||||||
"start": "electron --disable-direct-composition --in-process-gpu ."
|
"start": "electron --disable-direct-composition --in-process-gpu ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "10.1.3"
|
"electron": "10.4.0"
|
||||||
},
|
},
|
||||||
"dependencies": {}
|
"optionalDependencies": {
|
||||||
|
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v85"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"async-lock": "^1.2.8"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
73
electron/steam.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { ipcMain } = require("electron");
|
||||||
|
|
||||||
|
let greenworks = null;
|
||||||
|
let appId = null;
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
greenworks = require("shapez.io-private-artifacts/steam/greenworks");
|
||||||
|
appId = parseInt(fs.readFileSync(path.join(__dirname, "steam_appid.txt"), "utf8"));
|
||||||
|
} catch (err) {
|
||||||
|
// greenworks is not installed
|
||||||
|
// throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init (isDev) {
|
||||||
|
if (!greenworks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDev) {
|
||||||
|
if (greenworks.restartAppIfNecessary(appId)) {
|
||||||
|
console.log("Restarting ...");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!greenworks.init()) {
|
||||||
|
console.log("Failed to initialize greenworks");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listen () {
|
||||||
|
ipcMain.handle("steam:is-initialized", isInitialized);
|
||||||
|
|
||||||
|
if (!greenworks || !initialized) {
|
||||||
|
console.log("Ignoring Steam IPC events");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle("steam:get-achievement-names", getAchievementNames);
|
||||||
|
ipcMain.handle("steam:activate-achievement", activateAchievement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInitialized(event) {
|
||||||
|
return Promise.resolve(initialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAchievementNames(event) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const achievements = greenworks.getAchievementNames()
|
||||||
|
resolve(achievements);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateAchievement(event, id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
greenworks.activateAchievement(id, () => resolve(), err => reject(err))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init,
|
||||||
|
listen
|
||||||
|
};
|
@ -1 +1 @@
|
|||||||
1134480
|
1318690
|
||||||
|
1154
electron/yarn.lock
@ -21,7 +21,6 @@ function gulptasksCSS($, gulp, buildFolder, browserSync) {
|
|||||||
const plugins = [postcssAssetsPlugin(cachebust)];
|
const plugins = [postcssAssetsPlugin(cachebust)];
|
||||||
if (prod) {
|
if (prod) {
|
||||||
plugins.unshift(
|
plugins.unshift(
|
||||||
$.postcssUnprefix(),
|
|
||||||
$.postcssPresetEnv({
|
$.postcssPresetEnv({
|
||||||
browsers: ["> 0.1%"],
|
browsers: ["> 0.1%"],
|
||||||
})
|
})
|
||||||
@ -62,7 +61,7 @@ function gulptasksCSS($, gulp, buildFolder, browserSync) {
|
|||||||
return gulp
|
return gulp
|
||||||
.src("../src/css/main.scss", { cwd: __dirname })
|
.src("../src/css/main.scss", { cwd: __dirname })
|
||||||
.pipe($.plumber())
|
.pipe($.plumber())
|
||||||
.pipe($.sass.sync().on("error", $.sass.logError))
|
.pipe($.dartSass.sync().on("error", $.dartSass.logError))
|
||||||
.pipe(
|
.pipe(
|
||||||
$.postcss([
|
$.postcss([
|
||||||
$.postcssCriticalSplit({
|
$.postcssCriticalSplit({
|
||||||
@ -95,7 +94,7 @@ function gulptasksCSS($, gulp, buildFolder, browserSync) {
|
|||||||
return gulp
|
return gulp
|
||||||
.src("../src/css/main.scss", { cwd: __dirname })
|
.src("../src/css/main.scss", { cwd: __dirname })
|
||||||
.pipe($.plumber())
|
.pipe($.plumber())
|
||||||
.pipe($.sass.sync().on("error", $.sass.logError))
|
.pipe($.dartSass.sync().on("error", $.dartSass.logError))
|
||||||
.pipe(
|
.pipe(
|
||||||
$.postcss([
|
$.postcss([
|
||||||
$.postcssCriticalSplit({
|
$.postcssCriticalSplit({
|
||||||
|
@ -50,6 +50,9 @@ css.gulptasksCSS($, gulp, buildFolder, browserSync);
|
|||||||
const sounds = require("./sounds");
|
const sounds = require("./sounds");
|
||||||
sounds.gulptasksSounds($, gulp, buildFolder);
|
sounds.gulptasksSounds($, gulp, buildFolder);
|
||||||
|
|
||||||
|
const localConfig = require("./local-config");
|
||||||
|
localConfig.gulptasksLocalConfig($, gulp);
|
||||||
|
|
||||||
const js = require("./js");
|
const js = require("./js");
|
||||||
js.gulptasksJS($, gulp, buildFolder, browserSync);
|
js.gulptasksJS($, gulp, buildFolder, browserSync);
|
||||||
|
|
||||||
@ -136,7 +139,7 @@ gulp.task("main.webserver", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function serve({ standalone }) {
|
function serve({ standalone, chineseVersion = false }) {
|
||||||
browserSync.init({
|
browserSync.init({
|
||||||
server: buildFolder,
|
server: buildFolder,
|
||||||
port: 3005,
|
port: 3005,
|
||||||
@ -200,7 +203,11 @@ function serve({ standalone }) {
|
|||||||
if (standalone) {
|
if (standalone) {
|
||||||
gulp.series("js.standalone-dev.watch")(() => true);
|
gulp.series("js.standalone-dev.watch")(() => true);
|
||||||
} else {
|
} else {
|
||||||
gulp.series("js.dev.watch")(() => true);
|
if (chineseVersion) {
|
||||||
|
gulp.series("china.js.dev.watch")(() => true);
|
||||||
|
} else {
|
||||||
|
gulp.series("js.dev.watch")(() => true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,6 +228,7 @@ gulp.task(
|
|||||||
gulp.series(
|
gulp.series(
|
||||||
"utils.cleanup",
|
"utils.cleanup",
|
||||||
"utils.copyAdditionalBuildFiles",
|
"utils.copyAdditionalBuildFiles",
|
||||||
|
"localConfig.findOrCreate",
|
||||||
"imgres.buildAtlas",
|
"imgres.buildAtlas",
|
||||||
"imgres.atlasToJson",
|
"imgres.atlasToJson",
|
||||||
"imgres.atlas",
|
"imgres.atlas",
|
||||||
@ -238,6 +246,7 @@ gulp.task(
|
|||||||
"build.standalone.dev",
|
"build.standalone.dev",
|
||||||
gulp.series(
|
gulp.series(
|
||||||
"utils.cleanup",
|
"utils.cleanup",
|
||||||
|
"localConfig.findOrCreate",
|
||||||
"imgres.buildAtlas",
|
"imgres.buildAtlas",
|
||||||
"imgres.atlasToJson",
|
"imgres.atlasToJson",
|
||||||
"imgres.atlas",
|
"imgres.atlas",
|
||||||
@ -284,30 +293,28 @@ gulp.task(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Builds everything (standalone-prod)
|
// Builds everything (standalone-prod)
|
||||||
gulp.task(
|
|
||||||
"step.standalone-prod.code",
|
|
||||||
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "js.standalone-prod")
|
|
||||||
);
|
|
||||||
gulp.task("step.standalone-prod.mainbuild", gulp.parallel("step.baseResources", "step.standalone-prod.code"));
|
|
||||||
gulp.task(
|
|
||||||
"step.standalone-prod.all",
|
|
||||||
gulp.series("step.standalone-prod.mainbuild", "css.prod-standalone", "html.standalone-prod")
|
|
||||||
);
|
|
||||||
gulp.task(
|
|
||||||
"build.standalone-prod",
|
|
||||||
gulp.series("utils.cleanup", "step.standalone-prod.all", "step.postbuild")
|
|
||||||
);
|
|
||||||
|
|
||||||
// OS X build and release upload
|
for (const prefix of ["", "china."]) {
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"build.darwin64-prod",
|
prefix + "step.standalone-prod.code",
|
||||||
gulp.series(
|
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", prefix + "js.standalone-prod")
|
||||||
"build.standalone-prod",
|
);
|
||||||
"standalone.prepare",
|
|
||||||
"standalone.package.prod.darwin64",
|
gulp.task(
|
||||||
"standalone.uploadRelease.darwin64"
|
prefix + "step.standalone-prod.mainbuild",
|
||||||
)
|
gulp.parallel("step.baseResources", prefix + "step.standalone-prod.code")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
gulp.task(
|
||||||
|
prefix + "step.standalone-prod.all",
|
||||||
|
gulp.series(prefix + "step.standalone-prod.mainbuild", "css.prod-standalone", "html.standalone-prod")
|
||||||
|
);
|
||||||
|
|
||||||
|
gulp.task(
|
||||||
|
prefix + "build.standalone-prod",
|
||||||
|
gulp.series("utils.cleanup", prefix + "step.standalone-prod.all", "step.postbuild")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Deploying!
|
// Deploying!
|
||||||
gulp.task(
|
gulp.task(
|
||||||
@ -320,7 +327,12 @@ gulp.task(
|
|||||||
);
|
);
|
||||||
gulp.task("main.deploy.prod", gulp.series("utils.requireCleanWorkingTree", "build.prod", "ftp.upload.prod"));
|
gulp.task("main.deploy.prod", gulp.series("utils.requireCleanWorkingTree", "build.prod", "ftp.upload.prod"));
|
||||||
gulp.task("main.deploy.all", gulp.series("main.deploy.staging", "main.deploy.prod"));
|
gulp.task("main.deploy.all", gulp.series("main.deploy.staging", "main.deploy.prod"));
|
||||||
gulp.task("main.standalone", gulp.series("build.standalone-prod", "standalone.package.prod"));
|
gulp.task("regular.main.standalone", gulp.series("build.standalone-prod", "standalone.package.prod"));
|
||||||
|
gulp.task(
|
||||||
|
"china.main.standalone",
|
||||||
|
gulp.series("china.build.standalone-prod", "china.standalone.package.prod")
|
||||||
|
);
|
||||||
|
gulp.task("standalone.all", gulp.series("regular.main.standalone", "china.main.standalone"));
|
||||||
|
|
||||||
// Live-development
|
// Live-development
|
||||||
gulp.task(
|
gulp.task(
|
||||||
@ -331,5 +343,9 @@ gulp.task(
|
|||||||
"main.serveStandalone",
|
"main.serveStandalone",
|
||||||
gulp.series("build.standalone.dev", () => serve({ standalone: true }))
|
gulp.series("build.standalone.dev", () => serve({ standalone: true }))
|
||||||
);
|
);
|
||||||
|
gulp.task(
|
||||||
|
"china.main.serveDev",
|
||||||
|
gulp.series("build.dev", () => serve({ standalone: false, chineseVersion: true }))
|
||||||
|
);
|
||||||
|
|
||||||
gulp.task("default", gulp.series("main.serveDev"));
|
gulp.task("default", gulp.series("main.serveDev"));
|
||||||
|
48
gulp/js.js
@ -6,7 +6,6 @@ function requireUncached(module) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gulptasksJS($, gulp, buildFolder, browserSync) {
|
function gulptasksJS($, gulp, buildFolder, browserSync) {
|
||||||
|
|
||||||
//// DEV
|
//// DEV
|
||||||
|
|
||||||
gulp.task("js.dev.watch", () => {
|
gulp.task("js.dev.watch", () => {
|
||||||
@ -30,6 +29,36 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
|
|||||||
.pipe(gulp.dest(buildFolder));
|
.pipe(gulp.dest(buildFolder));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//// DEV CHINA
|
||||||
|
|
||||||
|
gulp.task("china.js.dev.watch", () => {
|
||||||
|
return gulp
|
||||||
|
.src("../src/js/main.js")
|
||||||
|
.pipe(
|
||||||
|
$.webpackStream(
|
||||||
|
requireUncached("./webpack.config.js")({
|
||||||
|
watch: true,
|
||||||
|
chineseVersion: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest(buildFolder))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task("china.js.dev", () => {
|
||||||
|
return gulp
|
||||||
|
.src("../src/js/main.js")
|
||||||
|
.pipe(
|
||||||
|
$.webpackStream(
|
||||||
|
requireUncached("./webpack.config.js")({
|
||||||
|
chineseVersion: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest(buildFolder));
|
||||||
|
});
|
||||||
|
|
||||||
//// STAGING
|
//// STAGING
|
||||||
|
|
||||||
gulp.task("js.staging.transpiled", () => {
|
gulp.task("js.staging.transpiled", () => {
|
||||||
@ -162,6 +191,23 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
|
|||||||
)
|
)
|
||||||
.pipe(gulp.dest(buildFolder));
|
.pipe(gulp.dest(buildFolder));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task("china.js.standalone-prod", () => {
|
||||||
|
return gulp
|
||||||
|
.src("../src/js/main.js")
|
||||||
|
.pipe(
|
||||||
|
$.webpackStream(
|
||||||
|
requireUncached("./webpack.production.config.js")({
|
||||||
|
enableAssert: false,
|
||||||
|
environment: "prod",
|
||||||
|
es6: true,
|
||||||
|
standalone: true,
|
||||||
|
chineseVersion: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest(buildFolder));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
18
gulp/local-config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const fse = require("fs-extra");
|
||||||
|
|
||||||
|
const configTemplatePath = path.join(__dirname, "../src/js/core/config.local.template.js");
|
||||||
|
const configPath = path.join(__dirname, "../src/js/core/config.local.js");
|
||||||
|
|
||||||
|
function gulptasksLocalConfig($, gulp) {
|
||||||
|
gulp.task("localConfig.findOrCreate", cb => {
|
||||||
|
if (!fs.existsSync(configPath)) {
|
||||||
|
fse.copySync(configTemplatePath, configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { gulptasksLocalConfig };
|
@ -18,6 +18,7 @@
|
|||||||
"@types/node": "^12.7.5",
|
"@types/node": "^12.7.5",
|
||||||
"ajv": "^6.10.2",
|
"ajv": "^6.10.2",
|
||||||
"audiosprite": "^0.7.2",
|
"audiosprite": "^0.7.2",
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"browser-sync": "^2.26.10",
|
"browser-sync": "^2.26.10",
|
||||||
"circular-dependency-plugin": "^5.0.2",
|
"circular-dependency-plugin": "^5.0.2",
|
||||||
@ -33,6 +34,7 @@
|
|||||||
"fastdom": "^1.0.9",
|
"fastdom": "^1.0.9",
|
||||||
"flatted": "^2.0.1",
|
"flatted": "^2.0.1",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
|
"gifsicle": "^5.2.0",
|
||||||
"gulp-audiosprite": "^1.1.0",
|
"gulp-audiosprite": "^1.1.0",
|
||||||
"howler": "^2.1.2",
|
"howler": "^2.1.2",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
@ -41,10 +43,12 @@
|
|||||||
"markdown-loader": "^5.1.0",
|
"markdown-loader": "^5.1.0",
|
||||||
"node-sri": "^1.1.1",
|
"node-sri": "^1.1.1",
|
||||||
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
||||||
|
"postcss": ">=5.0.0",
|
||||||
"promise-polyfill": "^8.1.0",
|
"promise-polyfill": "^8.1.0",
|
||||||
"query-string": "^6.8.1",
|
"query-string": "^6.8.1",
|
||||||
"rusha": "^0.8.13",
|
"rusha": "^0.8.13",
|
||||||
"serialize-error": "^3.0.0",
|
"serialize-error": "^3.0.0",
|
||||||
|
"stream-browserify": "^3.0.0",
|
||||||
"strictdom": "^1.0.1",
|
"strictdom": "^1.0.1",
|
||||||
"string-replace-webpack-plugin": "^0.1.3",
|
"string-replace-webpack-plugin": "^0.1.3",
|
||||||
"strip-indent": "^3.0.0",
|
"strip-indent": "^3.0.0",
|
||||||
@ -58,7 +62,8 @@
|
|||||||
"webpack-plugin-replace": "^1.1.1",
|
"webpack-plugin-replace": "^1.1.1",
|
||||||
"webpack-strip-block": "^0.2.0",
|
"webpack-strip-block": "^0.2.0",
|
||||||
"whatwg-fetch": "^3.0.0",
|
"whatwg-fetch": "^3.0.0",
|
||||||
"worker-loader": "^2.0.0"
|
"worker-loader": "^2.0.0",
|
||||||
|
"yaml": "^1.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^9.4.3",
|
"autoprefixer": "^9.4.3",
|
||||||
@ -74,6 +79,7 @@
|
|||||||
"gulp-cache": "^1.1.3",
|
"gulp-cache": "^1.1.3",
|
||||||
"gulp-cached": "^1.1.1",
|
"gulp-cached": "^1.1.1",
|
||||||
"gulp-clean": "^0.4.0",
|
"gulp-clean": "^0.4.0",
|
||||||
|
"gulp-dart-sass": "^1.0.2",
|
||||||
"gulp-dom": "^1.0.0",
|
"gulp-dom": "^1.0.0",
|
||||||
"gulp-flatten": "^0.4.0",
|
"gulp-flatten": "^0.4.0",
|
||||||
"gulp-fluent-ffmpeg": "^2.0.0",
|
"gulp-fluent-ffmpeg": "^2.0.0",
|
||||||
@ -87,7 +93,6 @@
|
|||||||
"gulp-pngquant": "^1.0.13",
|
"gulp-pngquant": "^1.0.13",
|
||||||
"gulp-postcss": "^8.0.0",
|
"gulp-postcss": "^8.0.0",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
"gulp-sass": "^4.1.0",
|
|
||||||
"gulp-sass-lint": "^1.4.0",
|
"gulp-sass-lint": "^1.4.0",
|
||||||
"gulp-sftp": "git+https://git@github.com/webksde/gulp-sftp",
|
"gulp-sftp": "git+https://git@github.com/webksde/gulp-sftp",
|
||||||
"gulp-terser": "^1.2.0",
|
"gulp-terser": "^1.2.0",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
require("colors");
|
require("colors");
|
||||||
const packager = require("electron-packager");
|
const packager = require("electron-packager");
|
||||||
|
const pj = require("../electron/package.json");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { getVersion } = require("./buildutils");
|
const { getVersion } = require("./buildutils");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@ -9,227 +10,189 @@ const execSync = require("child_process").execSync;
|
|||||||
|
|
||||||
function gulptasksStandalone($, gulp) {
|
function gulptasksStandalone($, gulp) {
|
||||||
const electronBaseDir = path.join(__dirname, "..", "electron");
|
const electronBaseDir = path.join(__dirname, "..", "electron");
|
||||||
|
const targets = [
|
||||||
|
{
|
||||||
|
tempDestDir: path.join(__dirname, "..", "tmp_standalone_files"),
|
||||||
|
suffix: "",
|
||||||
|
taskPrefix: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tempDestDir: path.join(__dirname, "..", "tmp_standalone_files_china"),
|
||||||
|
suffix: "china",
|
||||||
|
taskPrefix: "china.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const tempDestDir = path.join(__dirname, "..", "tmp_standalone_files");
|
for (const { tempDestDir, suffix, taskPrefix } of targets) {
|
||||||
const tempDestBuildDir = path.join(tempDestDir, "built");
|
const tempDestBuildDir = path.join(tempDestDir, "built");
|
||||||
|
|
||||||
gulp.task("standalone.prepare.cleanup", () => {
|
gulp.task(taskPrefix + "standalone.prepare.cleanup", () => {
|
||||||
return gulp.src(tempDestDir, { read: false, allowEmpty: true }).pipe($.clean({ force: true }));
|
return gulp.src(tempDestDir, { read: false, allowEmpty: true }).pipe($.clean({ force: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("standalone.prepare.copyPrefab", () => {
|
gulp.task(taskPrefix + "standalone.prepare.copyPrefab", () => {
|
||||||
// const requiredFiles = $.glob.sync("../electron/");
|
const requiredFiles = [
|
||||||
const requiredFiles = [
|
path.join(electronBaseDir, "node_modules", "**", "*.*"),
|
||||||
path.join(electronBaseDir, "lib", "**", "*.node"),
|
path.join(electronBaseDir, "node_modules", "**", ".*"),
|
||||||
path.join(electronBaseDir, "node_modules", "**", "*.*"),
|
path.join(electronBaseDir, "steam_appid.txt"),
|
||||||
path.join(electronBaseDir, "node_modules", "**", ".*"),
|
path.join(electronBaseDir, "favicon*"),
|
||||||
path.join(electronBaseDir, "favicon*"),
|
|
||||||
|
|
||||||
// fails on platforms which support symlinks
|
// fails on platforms which support symlinks
|
||||||
// https://github.com/gulpjs/gulp/issues/1427
|
// https://github.com/gulpjs/gulp/issues/1427
|
||||||
// path.join(electronBaseDir, "node_modules", "**", "*"),
|
// path.join(electronBaseDir, "node_modules", "**", "*"),
|
||||||
];
|
];
|
||||||
return gulp.src(requiredFiles, { base: electronBaseDir }).pipe(gulp.dest(tempDestBuildDir));
|
return gulp.src(requiredFiles, { base: electronBaseDir }).pipe(gulp.dest(tempDestBuildDir));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("standalone.prepare.writePackageJson", cb => {
|
gulp.task(taskPrefix + "standalone.prepare.writePackageJson", cb => {
|
||||||
fs.writeFileSync(
|
const packageJsonString = JSON.stringify(
|
||||||
path.join(tempDestBuildDir, "package.json"),
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
{
|
||||||
devDependencies: {
|
scripts: {
|
||||||
electron: "6.1.12",
|
start: pj.scripts.start,
|
||||||
},
|
},
|
||||||
|
devDependencies: pj.devDependencies,
|
||||||
|
dependencies: pj.dependencies,
|
||||||
|
optionalDependencies: pj.optionalDependencies,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
4
|
4
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(tempDestBuildDir, "package.json"), packageJsonString);
|
||||||
|
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task(taskPrefix + "standalone.prepareVDF", cb => {
|
||||||
|
const hash = buildutils.getRevision();
|
||||||
|
|
||||||
|
const steampipeDir = path.join(__dirname, "steampipe", "scripts");
|
||||||
|
const templateContents = fs
|
||||||
|
.readFileSync(path.join(steampipeDir, "app.vdf.template"), { encoding: "utf-8" })
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
const convertedContents = templateContents.replace("$DESC$", "Commit " + hash);
|
||||||
|
fs.writeFileSync(path.join(steampipeDir, "app.vdf"), convertedContents);
|
||||||
|
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task(taskPrefix + "standalone.prepare.minifyCode", () => {
|
||||||
|
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task(taskPrefix + "standalone.prepare.copyGamefiles", () => {
|
||||||
|
return gulp.src("../build/**/*.*", { base: "../build" }).pipe(gulp.dest(tempDestBuildDir));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task(taskPrefix + "standalone.killRunningInstances", cb => {
|
||||||
|
try {
|
||||||
|
execSync("taskkill /F /IM shapezio.exe");
|
||||||
|
} catch (ex) {
|
||||||
|
console.warn("Failed to kill running instances, maybe none are up.");
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task(
|
||||||
|
taskPrefix + "standalone.prepare",
|
||||||
|
gulp.series(
|
||||||
|
taskPrefix + "standalone.killRunningInstances",
|
||||||
|
taskPrefix + "standalone.prepare.cleanup",
|
||||||
|
taskPrefix + "standalone.prepare.copyPrefab",
|
||||||
|
taskPrefix + "standalone.prepare.writePackageJson",
|
||||||
|
taskPrefix + "standalone.prepare.minifyCode",
|
||||||
|
taskPrefix + "standalone.prepare.copyGamefiles"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
cb();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("standalone.prepareVDF", cb => {
|
/**
|
||||||
const hash = buildutils.getRevision();
|
*
|
||||||
|
* @param {'win32'|'linux'} platform
|
||||||
|
* @param {'x64'|'ia32'} arch
|
||||||
|
* @param {function():void} cb
|
||||||
|
*/
|
||||||
|
function packageStandalone(platform, arch, cb) {
|
||||||
|
const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
|
||||||
|
const privateArtifactsPath = "node_modules/shapez.io-private-artifacts";
|
||||||
|
|
||||||
const steampipeDir = path.join(__dirname, "steampipe", "scripts");
|
let asar;
|
||||||
const templateContents = fs
|
if (fs.existsSync(path.join(tempDestBuildDir, privateArtifactsPath))) {
|
||||||
.readFileSync(path.join(steampipeDir, "app.vdf.template"), { encoding: "utf-8" })
|
asar = { unpackDir: privateArtifactsPath };
|
||||||
.toString();
|
} else {
|
||||||
|
asar = true;
|
||||||
const convertedContents = templateContents.replace("$DESC$", "Commit " + hash);
|
|
||||||
fs.writeFileSync(path.join(steampipeDir, "app.vdf"), convertedContents);
|
|
||||||
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("standalone.prepare.minifyCode", () => {
|
|
||||||
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("standalone.prepare.copyGamefiles", () => {
|
|
||||||
return gulp.src("../build/**/*.*", { base: "../build" }).pipe(gulp.dest(tempDestBuildDir));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("standalone.killRunningInstances", cb => {
|
|
||||||
try {
|
|
||||||
execSync("taskkill /F /IM shapezio.exe");
|
|
||||||
} catch (ex) {
|
|
||||||
console.warn("Failed to kill running instances, maybe none are up.");
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"standalone.prepare",
|
|
||||||
gulp.series(
|
|
||||||
"standalone.killRunningInstances",
|
|
||||||
"standalone.prepare.cleanup",
|
|
||||||
"standalone.prepare.copyPrefab",
|
|
||||||
"standalone.prepare.writePackageJson",
|
|
||||||
"standalone.prepare.minifyCode",
|
|
||||||
"standalone.prepare.copyGamefiles"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {'win32'|'linux'|'darwin'} platform
|
|
||||||
* @param {'x64'|'ia32'} arch
|
|
||||||
* @param {function():void} cb
|
|
||||||
* @param {boolean=} isRelease
|
|
||||||
*/
|
|
||||||
function packageStandalone(platform, arch, cb, isRelease = true) {
|
|
||||||
const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
|
|
||||||
|
|
||||||
packager({
|
|
||||||
dir: tempDestBuildDir,
|
|
||||||
appCopyright: "Tobias Springer",
|
|
||||||
appVersion: getVersion(),
|
|
||||||
buildVersion: "1.0.0",
|
|
||||||
arch,
|
|
||||||
platform,
|
|
||||||
asar: true,
|
|
||||||
executableName: "shapezio",
|
|
||||||
icon: path.join(electronBaseDir, "favicon"),
|
|
||||||
name: "shapez.io-standalone",
|
|
||||||
out: tempDestDir,
|
|
||||||
overwrite: true,
|
|
||||||
appBundleId: "io.shapez.standalone",
|
|
||||||
appCategoryType: "public.app-category.games",
|
|
||||||
...(isRelease &&
|
|
||||||
platform === "darwin" && {
|
|
||||||
osxSign: {
|
|
||||||
"identity": process.env.SHAPEZ_CLI_APPLE_CERT_NAME,
|
|
||||||
"hardened-runtime": true,
|
|
||||||
"hardenedRuntime": true,
|
|
||||||
"entitlements": "entitlements.plist",
|
|
||||||
"entitlements-inherit": "entitlements.plist",
|
|
||||||
"signature-flags": "library",
|
|
||||||
},
|
|
||||||
osxNotarize: {
|
|
||||||
appleId: process.env.SHAPEZ_CLI_APPLE_ID,
|
|
||||||
appleIdPassword: "@keychain:SHAPEZ_CLI_APPLE_ID",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}).then(
|
|
||||||
appPaths => {
|
|
||||||
console.log("Packages created:", appPaths);
|
|
||||||
appPaths.forEach(appPath => {
|
|
||||||
if (!fs.existsSync(appPath)) {
|
|
||||||
console.error("Bad app path gotten:", appPath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(appPath, "LICENSE"),
|
|
||||||
fs.readFileSync(path.join(__dirname, "..", "LICENSE"))
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(appPath, ".itch.toml"), tomlFile);
|
|
||||||
|
|
||||||
if (platform === "linux") {
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(appPath, "play.sh"),
|
|
||||||
'#!/usr/bin/env bash\n./shapezio --no-sandbox "$@"\n'
|
|
||||||
);
|
|
||||||
fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === "win32" && platform === "darwin") {
|
|
||||||
console.warn(
|
|
||||||
"Cross-building for macOS on Windows: dereferencing symlinks.\n".red +
|
|
||||||
"This will nearly double app size and make code signature invalid. Sorry!\n"
|
|
||||||
.red.bold +
|
|
||||||
"For more information, see " +
|
|
||||||
"https://github.com/electron/electron-packager/issues/71".underline
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clear up framework folders
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(appPath, "play.sh"),
|
|
||||||
'#!/usr/bin/env bash\n./shapez.io-standalone.app/Contents/MacOS/shapezio --no-sandbox "$@"\n'
|
|
||||||
);
|
|
||||||
fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
|
|
||||||
fs.chmodSync(
|
|
||||||
path.join(appPath, "shapez.io-standalone.app", "Contents", "MacOS", "shapezio"),
|
|
||||||
0o775
|
|
||||||
);
|
|
||||||
|
|
||||||
const finalPath = path.join(appPath, "shapez.io-standalone.app");
|
|
||||||
|
|
||||||
const frameworksDir = path.join(finalPath, "Contents", "Frameworks");
|
|
||||||
const frameworkFolders = fs
|
|
||||||
.readdirSync(frameworksDir)
|
|
||||||
.filter(fname => fname.endsWith(".framework"));
|
|
||||||
|
|
||||||
for (let i = 0; i < frameworkFolders.length; ++i) {
|
|
||||||
const folderName = frameworkFolders[i];
|
|
||||||
const frameworkFolder = path.join(frameworksDir, folderName);
|
|
||||||
console.log(" -> ", frameworkFolder);
|
|
||||||
|
|
||||||
const filesToDelete = fs
|
|
||||||
.readdirSync(frameworkFolder)
|
|
||||||
.filter(fname => fname.toLowerCase() !== "versions");
|
|
||||||
filesToDelete.forEach(fname => {
|
|
||||||
console.log(" -> Deleting", fname);
|
|
||||||
fs.unlinkSync(path.join(frameworkFolder, fname));
|
|
||||||
});
|
|
||||||
|
|
||||||
const frameworkSourceDir = path.join(frameworkFolder, "Versions", "A");
|
|
||||||
fse.copySync(frameworkSourceDir, frameworkFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
err => {
|
|
||||||
console.error("Packaging error:", err);
|
|
||||||
cb();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packager({
|
||||||
|
dir: tempDestBuildDir,
|
||||||
|
appCopyright: "Tobias Springer",
|
||||||
|
appVersion: getVersion(),
|
||||||
|
buildVersion: "1.0.0",
|
||||||
|
arch,
|
||||||
|
platform,
|
||||||
|
asar: asar,
|
||||||
|
executableName: "shapezio",
|
||||||
|
icon: path.join(electronBaseDir, "favicon"),
|
||||||
|
name: "shapez.io-standalone" + suffix,
|
||||||
|
out: tempDestDir,
|
||||||
|
overwrite: true,
|
||||||
|
appBundleId: "io.shapez.standalone",
|
||||||
|
appCategoryType: "public.app-category.games",
|
||||||
|
}).then(
|
||||||
|
appPaths => {
|
||||||
|
console.log("Packages created:", appPaths);
|
||||||
|
appPaths.forEach(appPath => {
|
||||||
|
if (!fs.existsSync(appPath)) {
|
||||||
|
console.error("Bad app path gotten:", appPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(appPath, "LICENSE"),
|
||||||
|
fs.readFileSync(path.join(__dirname, "..", "LICENSE"))
|
||||||
|
);
|
||||||
|
|
||||||
|
fse.copySync(
|
||||||
|
path.join(tempDestBuildDir, "steam_appid.txt"),
|
||||||
|
path.join(appPath, "steam_appid.txt")
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(appPath, ".itch.toml"), tomlFile);
|
||||||
|
|
||||||
|
if (platform === "linux") {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(appPath, "play.sh"),
|
||||||
|
'#!/usr/bin/env bash\n./shapezio --no-sandbox "$@"\n'
|
||||||
|
);
|
||||||
|
fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
console.error("Packaging error:", err);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task(taskPrefix + "standalone.package.prod.win64", cb => packageStandalone("win32", "x64", cb));
|
||||||
|
gulp.task(taskPrefix + "standalone.package.prod.linux64", cb =>
|
||||||
|
packageStandalone("linux", "x64", cb)
|
||||||
|
);
|
||||||
|
|
||||||
|
gulp.task(
|
||||||
|
taskPrefix + "standalone.package.prod",
|
||||||
|
gulp.series(
|
||||||
|
taskPrefix + "standalone.prepare",
|
||||||
|
gulp.parallel(
|
||||||
|
taskPrefix + "standalone.package.prod.win64",
|
||||||
|
taskPrefix + "standalone.package.prod.linux64"
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.darwin64.unsigned", cb =>
|
|
||||||
packageStandalone("darwin", "x64", cb, false)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"standalone.package.prod",
|
|
||||||
gulp.series(
|
|
||||||
"standalone.prepare",
|
|
||||||
gulp.parallel(
|
|
||||||
"standalone.package.prod.win64",
|
|
||||||
"standalone.package.prod.linux64",
|
|
||||||
"standalone.package.prod.darwin64"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { gulptasksStandalone };
|
module.exports = { gulptasksStandalone };
|
||||||
|
@ -2,14 +2,16 @@
|
|||||||
{
|
{
|
||||||
"appid" "1318690"
|
"appid" "1318690"
|
||||||
"desc" "$DESC$"
|
"desc" "$DESC$"
|
||||||
"buildoutput" "C:\work\shapez\shapez.io\gulp\steampipe\steamtemp"
|
"buildoutput" "C:\work\shapez.io\gulp\steampipe\steamtemp"
|
||||||
"contentroot" ""
|
"contentroot" ""
|
||||||
"setlive" ""
|
"setlive" ""
|
||||||
"preview" "0"
|
"preview" "0"
|
||||||
"local" ""
|
"local" ""
|
||||||
"depots"
|
"depots"
|
||||||
{
|
{
|
||||||
"1318691" "C:\work\shapez\shapez.io\gulp\steampipe\scripts\windows.vdf"
|
"1318691" "C:\work\shapez.io\gulp\steampipe\scripts\windows.vdf"
|
||||||
"1318692" "C:\work\shapez\shapez.io\gulp\steampipe\scripts\linux.vdf"
|
"1318694" "C:\work\shapez.io\gulp\steampipe\scripts\china-windows.vdf"
|
||||||
|
"1318692" "C:\work\shapez.io\gulp\steampipe\scripts\linux.vdf"
|
||||||
|
"1318695" "C:\work\shapez.io\gulp\steampipe\scripts\china-linux.vdf"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
gulp/steampipe/scripts/china-linux.vdf
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"DepotBuildConfig"
|
||||||
|
{
|
||||||
|
"DepotID" "1318695"
|
||||||
|
"contentroot" "C:\work\shapez.io\tmp_standalone_files_china\shapez.io-standalonechina-linux-x64"
|
||||||
|
"FileMapping"
|
||||||
|
{
|
||||||
|
"LocalPath" "*"
|
||||||
|
"DepotPath" "."
|
||||||
|
"recursive" "1"
|
||||||
|
}
|
||||||
|
"FileExclusion" "*.pdb"
|
||||||
|
}
|
12
gulp/steampipe/scripts/china-windows.vdf
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"DepotBuildConfig"
|
||||||
|
{
|
||||||
|
"DepotID" "1318694"
|
||||||
|
"contentroot" "C:\work\shapez.io\tmp_standalone_files_china\shapez.io-standalonechina-win32-x64"
|
||||||
|
"FileMapping"
|
||||||
|
{
|
||||||
|
"LocalPath" "*"
|
||||||
|
"DepotPath" "."
|
||||||
|
"recursive" "1"
|
||||||
|
}
|
||||||
|
"FileExclusion" "*.pdb"
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
"DepotBuildConfig"
|
"DepotBuildConfig"
|
||||||
{
|
{
|
||||||
"DepotID" "1318692"
|
"DepotID" "1318692"
|
||||||
"contentroot" "C:\work\shapez\shapez.io\tmp_standalone_files\shapez.io-standalone-linux-x64"
|
"contentroot" "C:\work\shapez.io\tmp_standalone_files\shapez.io-standalone-linux-x64"
|
||||||
"FileMapping"
|
"FileMapping"
|
||||||
{
|
{
|
||||||
"LocalPath" "*"
|
"LocalPath" "*"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"DepotBuildConfig"
|
"DepotBuildConfig"
|
||||||
{
|
{
|
||||||
"DepotID" "1318691"
|
"DepotID" "1318691"
|
||||||
"contentroot" "C:\work\shapez\shapez.io\tmp_standalone_files\shapez.io-standalone-win32-x64"
|
"contentroot" "C:\work\shapez.io\tmp_standalone_files\shapez.io-standalone-win32-x64"
|
||||||
"FileMapping"
|
"FileMapping"
|
||||||
{
|
{
|
||||||
"LocalPath" "*"
|
"LocalPath" "*"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@echo off
|
@echo off
|
||||||
cmd /c gulp standalone.prepareVDF
|
cmd /c yarn gulp standalone.prepareVDF
|
||||||
steamcmd +login %STEAM_UPLOAD_SHAPEZ_ID% %STEAM_UPLOAD_SHAPEZ_USER% +run_app_build %cd%/scripts/app.vdf +quit
|
steamcmd +login %STEAM_UPLOAD_SHAPEZ_ID% %STEAM_UPLOAD_SHAPEZ_USER% +run_app_build %cd%/scripts/app.vdf +quit
|
||||||
start https://partner.steamgames.com/apps/builds/1318690
|
start https://partner.steamgames.com/apps/builds/1318690
|
||||||
|
@ -25,6 +25,7 @@ function gulptasksTranslations($, gulp) {
|
|||||||
files
|
files
|
||||||
.filter(name => name.endsWith(".yaml"))
|
.filter(name => name.endsWith(".yaml"))
|
||||||
.forEach(fname => {
|
.forEach(fname => {
|
||||||
|
console.log("Loading", fname);
|
||||||
const languageName = fname.replace(".yaml", "");
|
const languageName = fname.replace(".yaml", "");
|
||||||
const abspath = path.join(translationsSourceDir, fname);
|
const abspath = path.join(translationsSourceDir, fname);
|
||||||
|
|
||||||
@ -40,39 +41,13 @@ function gulptasksTranslations($, gulp) {
|
|||||||
|
|
||||||
${storePage.intro.replace(/\n/gi, "\n\n")}
|
${storePage.intro.replace(/\n/gi, "\n\n")}
|
||||||
|
|
||||||
[h2]${storePage.title_advantages}[/h2]
|
[h2]${storePage.what_others_say}[/h2]
|
||||||
|
|
||||||
[list]
|
[list]
|
||||||
${storePage.advantages
|
[*] [i]${storePage.nothernlion_comment}[/i] [b]- Northernlion, YouTube[/b]
|
||||||
.map(x => "[*] " + x.replace(/<b>/, "[b]").replace(/<\/b>/, "[/b]"))
|
[*] [i]${storePage.notch_comment}[/i] [b]- Notch[/b]
|
||||||
.join("\n")}
|
[*] [i]${storePage.steam_review_comment}[/i] [b]- Steam User[/b]
|
||||||
[/list]
|
[/list]
|
||||||
|
|
||||||
[h2]${storePage.title_future}[/h2]
|
|
||||||
|
|
||||||
[list]
|
|
||||||
${storePage.planned
|
|
||||||
.map(x => "[*] " + x.replace(/<b>/, "[b]").replace(/<\/b>/, "[/b]"))
|
|
||||||
.join("\n")}
|
|
||||||
[/list]
|
|
||||||
|
|
||||||
[h2]${storePage.title_open_source}[/h2]
|
|
||||||
|
|
||||||
${storePage.text_open_source.replace(/\n/gi, "\n\n")}
|
|
||||||
|
|
||||||
[h2]${storePage.title_links}[/h2]
|
|
||||||
|
|
||||||
[list]
|
|
||||||
[*] [url=https://discord.com/invite/HN7EVzV]${storePage.links.discord}[/url]
|
|
||||||
[*] [url=https://trello.com/b/ISQncpJP/shapezio]${storePage.links.roadmap}[/url]
|
|
||||||
[*] [url=https://www.reddit.com/r/shapezio]${storePage.links.subreddit}[/url]
|
|
||||||
[*] [url=https://github.com/tobspr/shapez.io]${storePage.links.source_code}[/url]
|
|
||||||
[*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]${
|
|
||||||
storePage.links.translate
|
|
||||||
}[/url]
|
|
||||||
[/list]
|
|
||||||
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
fs.writeFileSync(destpath, trim(content.replace(/(\n[ \t\r]*)/gi, "\n")), {
|
fs.writeFileSync(destpath, trim(content.replace(/(\n[ \t\r]*)/gi, "\n")), {
|
||||||
|
@ -6,7 +6,7 @@ const { getRevision, getVersion, getAllResourceImages } = require("./buildutils"
|
|||||||
const lzString = require("lz-string");
|
const lzString = require("lz-string");
|
||||||
const CircularDependencyPlugin = require("circular-dependency-plugin");
|
const CircularDependencyPlugin = require("circular-dependency-plugin");
|
||||||
|
|
||||||
module.exports = ({ watch = false, standalone = false }) => {
|
module.exports = ({ watch = false, standalone = false, chineseVersion = false }) => {
|
||||||
return {
|
return {
|
||||||
mode: "development",
|
mode: "development",
|
||||||
devtool: "cheap-source-map",
|
devtool: "cheap-source-map",
|
||||||
@ -34,6 +34,7 @@ module.exports = ({ watch = false, standalone = false }) => {
|
|||||||
G_TRACKING_ENDPOINT: JSON.stringify(
|
G_TRACKING_ENDPOINT: JSON.stringify(
|
||||||
lzString.compressToEncodedURIComponent("http://localhost:10005/v1")
|
lzString.compressToEncodedURIComponent("http://localhost:10005/v1")
|
||||||
),
|
),
|
||||||
|
G_CHINA_VERSION: JSON.stringify(chineseVersion),
|
||||||
G_IS_DEV: "true",
|
G_IS_DEV: "true",
|
||||||
G_IS_RELEASE: "false",
|
G_IS_RELEASE: "false",
|
||||||
G_IS_MOBILE_APP: "false",
|
G_IS_MOBILE_APP: "false",
|
||||||
|
@ -16,12 +16,15 @@ module.exports = ({
|
|||||||
standalone = false,
|
standalone = false,
|
||||||
isBrowser = true,
|
isBrowser = true,
|
||||||
mobileApp = false,
|
mobileApp = false,
|
||||||
|
chineseVersion = false,
|
||||||
}) => {
|
}) => {
|
||||||
const globalDefs = {
|
const globalDefs = {
|
||||||
assert: enableAssert ? "window.assert" : "false && window.assert",
|
assert: enableAssert ? "window.assert" : "false && window.assert",
|
||||||
assertAlways: "window.assert",
|
assertAlways: "window.assert",
|
||||||
abstract: "window.assert(false, 'abstract method called');",
|
abstract: "window.assert(false, 'abstract method called');",
|
||||||
G_IS_DEV: "false",
|
G_IS_DEV: "false",
|
||||||
|
|
||||||
|
G_CHINA_VERSION: JSON.stringify(chineseVersion),
|
||||||
G_IS_RELEASE: environment === "prod" ? "true" : "false",
|
G_IS_RELEASE: environment === "prod" ? "true" : "false",
|
||||||
G_IS_STANDALONE: standalone ? "true" : "false",
|
G_IS_STANDALONE: standalone ? "true" : "false",
|
||||||
G_IS_BROWSER: isBrowser ? "true" : "false",
|
G_IS_BROWSER: isBrowser ? "true" : "false",
|
||||||
|
26753
gulp/yarn.lock
@ -8,6 +8,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cd gulp && yarn gulp main.serveDev",
|
"dev": "cd gulp && yarn gulp main.serveDev",
|
||||||
|
"devStandalone": "cd gulp && yarn gulp main.serveStandalone",
|
||||||
"tslint": "cd src/js && tsc",
|
"tslint": "cd src/js && tsc",
|
||||||
"lint": "eslint src/js",
|
"lint": "eslint src/js",
|
||||||
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*",
|
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"@types/cordova": "^0.0.34",
|
"@types/cordova": "^0.0.34",
|
||||||
"@types/filesystem": "^0.0.29",
|
"@types/filesystem": "^0.0.29",
|
||||||
"ajv": "^6.10.2",
|
"ajv": "^6.10.2",
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
"babel-loader": "^8.0.4",
|
"babel-loader": "^8.0.4",
|
||||||
"circular-dependency-plugin": "^5.0.2",
|
"circular-dependency-plugin": "^5.0.2",
|
||||||
"circular-json": "^0.5.9",
|
"circular-json": "^0.5.9",
|
||||||
@ -49,6 +51,7 @@
|
|||||||
"markdown-loader": "^4.0.0",
|
"markdown-loader": "^4.0.0",
|
||||||
"match-all": "^1.2.5",
|
"match-all": "^1.2.5",
|
||||||
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
||||||
|
"postcss": ">=5.0.0",
|
||||||
"promise-polyfill": "^8.1.0",
|
"promise-polyfill": "^8.1.0",
|
||||||
"query-string": "^6.8.1",
|
"query-string": "^6.8.1",
|
||||||
"rusha": "^0.8.13",
|
"rusha": "^0.8.13",
|
||||||
@ -71,7 +74,7 @@
|
|||||||
"yawn-yaml": "^1.5.0"
|
"yawn-yaml": "^1.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@octokit/rest": "^18.0.6",
|
"@octokit/rest": "^18.0.6",
|
||||||
"@typescript-eslint/eslint-plugin": "3.0.1",
|
"@typescript-eslint/eslint-plugin": "3.0.1",
|
||||||
"@typescript-eslint/parser": "3.0.1",
|
"@typescript-eslint/parser": "3.0.1",
|
||||||
"autoprefixer": "^9.4.3",
|
"autoprefixer": "^9.4.3",
|
||||||
|
BIN
res/logo_cn.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 44 KiB |
BIN
res/ui/changelog_skins/achievements.noinline.png
Normal file
After Width: | Height: | Size: 479 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 24 KiB |
BIN
res/ui/icons/advantage_achievements.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
res/ui/icons/unpin_shape.png
Normal file
After Width: | Height: | Size: 731 B |
BIN
res/ui/icons/waypoint_wires.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
38
res/ui/languages/fi.svg
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
|
<rect style="fill:#F5F5F5;" width="512" height="512"/>
|
||||||
|
<polygon style="fill:#41479B;" points="512,229.517 211.862,229.517 211.862,0 158.897,0 158.897,229.517 0,229.517 0,282.483
|
||||||
|
158.897,282.483 158.897,512 211.862,512 211.862,282.483 512,282.483 "/>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 786 B |
38
res/ui/languages/ro.svg
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
|
<rect style="fill:#41479B;" width="170.67" height="512"/>
|
||||||
|
<rect x="170.67" style="fill:#FFE15A;" width="170.67" height="512"/>
|
||||||
|
<rect x="341.33" style="fill:#FF4B55;" width="170.67" height="512"/>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 730 B |
BIN
res/ui/main_menu/opensea.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
res_raw/sprites/misc/waypoint_wires.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
@ -14,6 +14,12 @@
|
|||||||
"vetur.format.defaultFormatter.js": "vscode-typescript",
|
"vetur.format.defaultFormatter.js": "vscode-typescript",
|
||||||
"vetur.format.defaultFormatter.ts": "vscode-typescript",
|
"vetur.format.defaultFormatter.ts": "vscode-typescript",
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
"activityBar.background": "#163328",
|
||||||
|
"titleBar.activeBackground": "#1F4738",
|
||||||
|
"titleBar.activeForeground": "#F7FBFA"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,19 @@
|
|||||||
@include MakeAnimationWrappedEvenOdd(0.2s ease-in-out, "changeAnim") {
|
@each $animName in ("changeAnimEven", "changeAnimOdd") {
|
||||||
0% {
|
@keyframes #{$animName} {
|
||||||
transform: scale(1, 1);
|
0% {
|
||||||
|
transform: scale(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.03, 1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
.#{$animName} {
|
||||||
transform: scale(1.03, 1.03);
|
animation: $animName 0.2s ease-in-out;
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: scale(1, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/css/changelog_skins.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[data-changelog-skin="achievements"] {
|
||||||
|
background: #f8f8f8;
|
||||||
|
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
background: rgba(0, 10, 20, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include S(border-radius, 5px);
|
||||||
|
&::before {
|
||||||
|
content: " ";
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
background: uiResource("changelog_skins/achievements.noinline.png") center center / cover no-repeat !important;
|
||||||
|
@include S(height, 80px);
|
||||||
|
@include S(border-radius, 5px);
|
||||||
|
@include S(margin-bottom, 5px);
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,6 @@
|
|||||||
color: #333438;
|
color: #333438;
|
||||||
|
|
||||||
&.removable {
|
&.removable {
|
||||||
cursor: pointer;
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +85,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .unpinButton {
|
||||||
|
@include S(width, 8px);
|
||||||
|
@include S(height, 8px);
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.7;
|
||||||
|
@include S(top, 3px);
|
||||||
|
@include S(left, -7px);
|
||||||
|
@include DarkThemeInvert;
|
||||||
|
@include IncreasedClickArea(2px);
|
||||||
|
transition: opacity 0.12s ease-in-out;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
& {
|
||||||
|
/* @load-async */
|
||||||
|
background: uiResource("icons/unpin_shape.png") center center / 80% no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.goal,
|
&.goal,
|
||||||
&.blueprint {
|
&.blueprint {
|
||||||
.amountLabel::after {
|
.amountLabel::after {
|
||||||
|
@ -165,5 +165,15 @@
|
|||||||
color: #e72d2d;
|
color: #e72d2d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.achievements {
|
||||||
|
& {
|
||||||
|
/* @load-async */
|
||||||
|
background-image: uiResource("res/ui/icons/advantage_achievements.png");
|
||||||
|
}
|
||||||
|
> strong {
|
||||||
|
color: #ffac0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
display: flex;
|
overflow: auto;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
|
||||||
& {
|
& {
|
||||||
@ -33,7 +31,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 100vh;
|
|
||||||
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -61,6 +61,12 @@
|
|||||||
/* @load-async */
|
/* @load-async */
|
||||||
background: uiResource("icons/waypoint.png") left 50% / #{D(8px)} no-repeat;
|
background: uiResource("icons/waypoint.png") left 50% / #{D(8px)} no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.layer--wires {
|
||||||
|
/* @load-async */
|
||||||
|
background-image: uiResource("icons/waypoint_wires.png");
|
||||||
|
}
|
||||||
|
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
@include S(margin-bottom, 1px);
|
@include S(margin-bottom, 1px);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
@import "application_error";
|
@import "application_error";
|
||||||
@import "textual_game_state";
|
@import "textual_game_state";
|
||||||
@import "adinplay";
|
@import "adinplay";
|
||||||
|
@import "changelog_skins";
|
||||||
|
|
||||||
@import "states/preload";
|
@import "states/preload";
|
||||||
@import "states/main_menu";
|
@import "states/main_menu";
|
||||||
@ -56,8 +57,8 @@
|
|||||||
@import "ingame_hud/cat_memes";
|
@import "ingame_hud/cat_memes";
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
$elements:
|
$elements:
|
||||||
// Base
|
// Base
|
||||||
ingame_Canvas,
|
ingame_Canvas,
|
||||||
ingame_VignetteOverlay,
|
ingame_VignetteOverlay,
|
||||||
|
|
||||||
@ -119,11 +120,3 @@ body.uiHidden {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.modalDialogActive,
|
|
||||||
body.externalAdOpen,
|
|
||||||
body.ingameDialogOpen {
|
|
||||||
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs):not(.noBlur) {
|
|
||||||
// filter: blur(5px) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -67,7 +67,7 @@ $icons: notification_saved, notification_success, notification_upgrade;
|
|||||||
}
|
}
|
||||||
|
|
||||||
$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi,
|
$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi,
|
||||||
th, hu, pl, ja, kor, no, pt-PT;
|
th, hu, pl, ja, kor, no, pt-PT, fi, ro;
|
||||||
|
|
||||||
@each $language in $languages {
|
@each $language in $languages {
|
||||||
[data-languageicon="#{$language}"] {
|
[data-languageicon="#{$language}"] {
|
||||||
|
@ -43,9 +43,10 @@
|
|||||||
.languageChoose {
|
.languageChoose {
|
||||||
@include S(border-radius, 8px);
|
@include S(border-radius, 8px);
|
||||||
border: solid #222428;
|
border: solid #222428;
|
||||||
background-color: #fff;
|
|
||||||
@include S(border-width, 2px);
|
@include S(border-width, 2px);
|
||||||
background-size: cover;
|
background-color: #222428 !important;
|
||||||
|
background-size: contain !important;
|
||||||
|
background-position: center center !important;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,9 +95,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.standaloneBanner {
|
.standaloneBanner {
|
||||||
background: rgb(255, 234, 245);
|
background: rgb(255, 75, 84);
|
||||||
@include S(border-radius, $globalBorderRadius);
|
@include S(border-radius, $globalBorderRadius + 4);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border: solid rgba(#fff, 0.15);
|
||||||
|
@include S(border-width, 4px);
|
||||||
@include S(padding, 15px);
|
@include S(padding, 15px);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -110,13 +113,14 @@
|
|||||||
h3 {
|
h3 {
|
||||||
@include Heading;
|
@include Heading;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@include S(margin-bottom, 5px);
|
@include S(margin-bottom, 20px);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: $colorRedBright;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@include Text;
|
@include Text;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
@ -138,16 +142,14 @@
|
|||||||
display: block;
|
display: block;
|
||||||
text-indent: -999em;
|
text-indent: -999em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@include S(margin-top, 20px);
|
@include S(margin-top, 30px);
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
transition: all 0.12s ease-in;
|
transition: all 0.12s ease-in;
|
||||||
transition-property: opacity, transform;
|
transition-property: opacity, transform;
|
||||||
transform: skewX(-0.5deg);
|
|
||||||
|
|
||||||
@include S(border-radius, $globalBorderRadius);
|
@include S(border-radius, $globalBorderRadius);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.02);
|
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,9 +183,8 @@
|
|||||||
.updateLabel {
|
.updateLabel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateX(50%) rotate(-5deg);
|
transform: translateX(50%) rotate(-5deg);
|
||||||
color: $colorRedBright;
|
color: #3291e9;
|
||||||
@include Heading;
|
@include Heading;
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@include S(right, 40px);
|
@include S(right, 40px);
|
||||||
@include S(bottom, 20px);
|
@include S(bottom, 20px);
|
||||||
@ -449,6 +450,10 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@include S(grid-gap, 4px);
|
@include S(grid-gap, 4px);
|
||||||
|
|
||||||
|
&.china {
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.author {
|
.author {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: #eef1f4;
|
background: #eef1f4;
|
||||||
|
@include S(border-radius, 3px);
|
||||||
|
|
||||||
@include DarkThemeOverride {
|
@include DarkThemeOverride {
|
||||||
background: #424242;
|
background: #424242;
|
||||||
|
@ -12,6 +12,7 @@ import { getPlatformName, waitNextFrame } from "./core/utils";
|
|||||||
import { Vector } from "./core/vector";
|
import { Vector } from "./core/vector";
|
||||||
import { AdProviderInterface } from "./platform/ad_provider";
|
import { AdProviderInterface } from "./platform/ad_provider";
|
||||||
import { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
|
import { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
|
||||||
|
import { NoAchievementProvider } from "./platform/browser/no_achievement_provider";
|
||||||
import { AnalyticsInterface } from "./platform/analytics";
|
import { AnalyticsInterface } from "./platform/analytics";
|
||||||
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
|
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
|
||||||
import { SoundImplBrowser } from "./platform/browser/sound";
|
import { SoundImplBrowser } from "./platform/browser/sound";
|
||||||
@ -32,6 +33,7 @@ import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
|
|||||||
import { RestrictionManager } from "./core/restriction_manager";
|
import { RestrictionManager } from "./core/restriction_manager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
|
||||||
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
|
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
|
||||||
* @typedef {import("./platform/sound").SoundInterface} SoundInterface
|
* @typedef {import("./platform/sound").SoundInterface} SoundInterface
|
||||||
* @typedef {import("./platform/storage").StorageInterface} StorageInterface
|
* @typedef {import("./platform/storage").StorageInterface} StorageInterface
|
||||||
@ -85,6 +87,9 @@ export class Application {
|
|||||||
/** @type {PlatformWrapperInterface} */
|
/** @type {PlatformWrapperInterface} */
|
||||||
this.platformWrapper = null;
|
this.platformWrapper = null;
|
||||||
|
|
||||||
|
/** @type {AchievementProviderInterface} */
|
||||||
|
this.achievementProvider = null;
|
||||||
|
|
||||||
/** @type {AdProviderInterface} */
|
/** @type {AdProviderInterface} */
|
||||||
this.adProvider = null;
|
this.adProvider = null;
|
||||||
|
|
||||||
@ -137,6 +142,7 @@ export class Application {
|
|||||||
this.sound = new SoundImplBrowser(this);
|
this.sound = new SoundImplBrowser(this);
|
||||||
this.analytics = new GoogleAnalyticsImpl(this);
|
this.analytics = new GoogleAnalyticsImpl(this);
|
||||||
this.gameAnalytics = new ShapezGameAnalytics(this);
|
this.gameAnalytics = new ShapezGameAnalytics(this);
|
||||||
|
this.achievementProvider = new NoAchievementProvider(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,56 @@
|
|||||||
export const CHANGELOG = [
|
export const CHANGELOG = [
|
||||||
|
{
|
||||||
|
version: "1.3.1",
|
||||||
|
date: "beta",
|
||||||
|
entries: [
|
||||||
|
"Fixed savegames getting corrupt in rare conditions",
|
||||||
|
"Fixed game crashing sometimes since the achievements update",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "1.3.0",
|
||||||
|
date: "12.03.2020",
|
||||||
|
skin: "achievements",
|
||||||
|
entries: [
|
||||||
|
"There are now <strong>45 Steam Achievements!</strong>",
|
||||||
|
"Fixed constant signals being editable from the regular layer",
|
||||||
|
"Fixed items still overlapping sometimes between buildings and belts",
|
||||||
|
"The game is now available in finnish, italian, romanian and ukrainian! (Thanks to all contributors!)",
|
||||||
|
"Updated translations (Thanks to all contributors!)",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "1.2.2",
|
||||||
|
date: "07.12.2020",
|
||||||
|
entries: [
|
||||||
|
"Fix item readers and some other buildings slowing up belts, especially if they stalled (inspired by Keterr's fix)",
|
||||||
|
"Added the ability to edit constant signals by left clicking them",
|
||||||
|
"Prevent items from being rendered on each other when a belt stalls (inspired by Keterr)",
|
||||||
|
"You can now add markers in the wire layer (partially by daanbreur)",
|
||||||
|
"Allow to cycle backwards in the toolbar with SHIFT + Tab (idea by EmeraldBlock)",
|
||||||
|
"Allow to cycle variants backwards with SHIFT + T",
|
||||||
|
"Upgrade numbers now use roman numerals until tier 50 (by LeopoldTal)",
|
||||||
|
"Add button to unpin shapes from the left side (by artemisSystem)",
|
||||||
|
"Fix middle mouse button also placing blueprints (by Eiim)",
|
||||||
|
"Hide wires grid when using the 'Disable Grid' setting (by EmeraldBlock)",
|
||||||
|
"Fix UI using multiple different save icons",
|
||||||
|
"Updated translations (Thanks to all contributors!)",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "1.2.1",
|
||||||
|
date: "31.10.2020",
|
||||||
|
entries: [
|
||||||
|
"Fixed stacking bug for level 26 which required restarting the game",
|
||||||
|
"Fix reward notification being too long sometimes (by LeopoldTal)",
|
||||||
|
"Use locale decimal separator on belt reader display (by LeopoldTal)",
|
||||||
|
"Vastly improved performance when saving games (by LeopoldTal)",
|
||||||
|
"Prevent some antivirus programs blocking the opening of external links (by LeopoldTal)",
|
||||||
|
"Match tutorials to the correct painter variants (by LeopoldTal)",
|
||||||
|
"Prevent throughput goals containing fractional numbers (by CEbbinghaus)",
|
||||||
|
"Updated translations and added Hungarian",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: "1.2.0",
|
version: "1.2.0",
|
||||||
date: "09.10.2020",
|
date: "09.10.2020",
|
||||||
|
@ -13,7 +13,7 @@ import { cachebust } from "./cachebust";
|
|||||||
const logger = createLogger("background_loader");
|
const logger = createLogger("background_loader");
|
||||||
|
|
||||||
const essentialMainMenuSprites = [
|
const essentialMainMenuSprites = [
|
||||||
"logo.png",
|
G_CHINA_VERSION ? "logo_cn.png" : "logo.png",
|
||||||
...G_ALL_UI_IMAGES.filter(src => src.startsWith("ui/") && src.indexOf(".gif") < 0),
|
...G_ALL_UI_IMAGES.filter(src => src.startsWith("ui/") && src.indexOf(".gif") < 0),
|
||||||
];
|
];
|
||||||
const essentialMainMenuSounds = [
|
const essentialMainMenuSounds = [
|
||||||
|
@ -26,7 +26,8 @@ export const THIRDPARTY_URLS = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const A_B_TESTING_LINK_TYPE = Math.random() > 0.5 ? "steam_1_pr" : "steam_2_npr";
|
// export const A_B_TESTING_LINK_TYPE = Math.random() > 0.95 ? "steam_1_pr" : "steam_2_npr";
|
||||||
|
export const A_B_TESTING_LINK_TYPE = "steam_2_npr";
|
||||||
|
|
||||||
export const globalConfig = {
|
export const globalConfig = {
|
||||||
// Size of a single tile in Pixels.
|
// Size of a single tile in Pixels.
|
||||||
@ -39,6 +40,9 @@ export const globalConfig = {
|
|||||||
assetsSharpness: 1.5,
|
assetsSharpness: 1.5,
|
||||||
shapesSharpness: 1.4,
|
shapesSharpness: 1.4,
|
||||||
|
|
||||||
|
// Achievements
|
||||||
|
achievementSliceDuration: 10, // Seconds
|
||||||
|
|
||||||
// Production analytics
|
// Production analytics
|
||||||
statisticsGraphDpi: 2.5,
|
statisticsGraphDpi: 2.5,
|
||||||
statisticsGraphSlices: 100,
|
statisticsGraphSlices: 100,
|
||||||
|
@ -59,6 +59,9 @@ export default {
|
|||||||
// Enables ads in the local build (normally they are deactivated there)
|
// Enables ads in the local build (normally they are deactivated there)
|
||||||
// testAds: true,
|
// testAds: true,
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
|
// Allows unlocked achievements to be logged to console in the local build
|
||||||
|
// testAchievements: true,
|
||||||
|
// -----------------------------------------------------------------------------------
|
||||||
// Disables the automatic switch to an overview when zooming out
|
// Disables the automatic switch to an overview when zooming out
|
||||||
// disableMapOverview: true,
|
// disableMapOverview: true,
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
@ -558,7 +558,16 @@ export function formatSeconds(secs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a number like 2.5 to "2.5 items / s"
|
* Formats a number like 2.51 to "2.5"
|
||||||
|
* @param {number} speed
|
||||||
|
* @param {string=} separator The decimal separator for numbers like 50.1 (separator='.')
|
||||||
|
*/
|
||||||
|
export function round1DigitLocalized(speed, separator = T.global.decimalSeparator) {
|
||||||
|
return round1Digit(speed).toString().replace(".", separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a number like 2.51 to "2.51 items / s"
|
||||||
* @param {number} speed
|
* @param {number} speed
|
||||||
* @param {boolean=} double
|
* @param {boolean=} double
|
||||||
* @param {string=} separator The decimal separator for numbers like 50.1 (separator='.')
|
* @param {string=} separator The decimal separator for numbers like 50.1 (separator='.')
|
||||||
@ -752,29 +761,8 @@ export function startFileChoose(acceptedType = ".bin") {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const romanLiterals = [
|
const MAX_ROMAN_NUMBER = 49;
|
||||||
"0", // NULL
|
const romanLiteralsCache = ["0"];
|
||||||
"I",
|
|
||||||
"II",
|
|
||||||
"III",
|
|
||||||
"IV",
|
|
||||||
"V",
|
|
||||||
"VI",
|
|
||||||
"VII",
|
|
||||||
"VIII",
|
|
||||||
"IX",
|
|
||||||
"X",
|
|
||||||
"XI",
|
|
||||||
"XII",
|
|
||||||
"XIII",
|
|
||||||
"XIV",
|
|
||||||
"XV",
|
|
||||||
"XVI",
|
|
||||||
"XVII",
|
|
||||||
"XVIII",
|
|
||||||
"XIX",
|
|
||||||
"XX",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -783,8 +771,52 @@ const romanLiterals = [
|
|||||||
*/
|
*/
|
||||||
export function getRomanNumber(number) {
|
export function getRomanNumber(number) {
|
||||||
number = Math.max(0, Math.round(number));
|
number = Math.max(0, Math.round(number));
|
||||||
if (number < romanLiterals.length) {
|
if (romanLiteralsCache[number]) {
|
||||||
return romanLiterals[number];
|
return romanLiteralsCache[number];
|
||||||
}
|
}
|
||||||
return String(number);
|
|
||||||
|
if (number > MAX_ROMAN_NUMBER) {
|
||||||
|
return String(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDigit(digit, unit, quintuple, decuple) {
|
||||||
|
switch (digit) {
|
||||||
|
case 0:
|
||||||
|
return "";
|
||||||
|
case 1: // I
|
||||||
|
return unit;
|
||||||
|
case 2: // II
|
||||||
|
return unit + unit;
|
||||||
|
case 3: // III
|
||||||
|
return unit + unit + unit;
|
||||||
|
case 4: // IV
|
||||||
|
return unit + quintuple;
|
||||||
|
case 9: // IX
|
||||||
|
return unit + decuple;
|
||||||
|
default:
|
||||||
|
// V, VI, VII, VIII
|
||||||
|
return quintuple + formatDigit(digit - 5, unit, quintuple, decuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let thousands = Math.floor(number / 1000);
|
||||||
|
let thousandsPart = "";
|
||||||
|
while (thousands > 0) {
|
||||||
|
thousandsPart += "M";
|
||||||
|
thousands -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hundreds = Math.floor((number % 1000) / 100);
|
||||||
|
const hundredsPart = formatDigit(hundreds, "C", "D", "M");
|
||||||
|
|
||||||
|
const tens = Math.floor((number % 100) / 10);
|
||||||
|
const tensPart = formatDigit(tens, "X", "L", "C");
|
||||||
|
|
||||||
|
const units = number % 10;
|
||||||
|
const unitsPart = formatDigit(units, "I", "V", "X");
|
||||||
|
|
||||||
|
const formatted = thousandsPart + hundredsPart + tensPart + unitsPart;
|
||||||
|
|
||||||
|
romanLiteralsCache[number] = formatted;
|
||||||
|
return formatted;
|
||||||
}
|
}
|
||||||
|
148
src/js/game/achievement_proxy.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/* typehints:start */
|
||||||
|
import { Entity } from "./entity";
|
||||||
|
import { GameRoot } from "./root";
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
|
import { globalConfig } from "../core/config";
|
||||||
|
import { createLogger } from "../core/logging";
|
||||||
|
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
||||||
|
import { getBuildingDataFromCode } from "./building_codes";
|
||||||
|
|
||||||
|
const logger = createLogger("achievement_proxy");
|
||||||
|
|
||||||
|
const ROTATER = "rotater";
|
||||||
|
const DEFAULT = "default";
|
||||||
|
|
||||||
|
export class AchievementProxy {
|
||||||
|
/** @param {GameRoot} root */
|
||||||
|
constructor(root) {
|
||||||
|
this.root = root;
|
||||||
|
this.provider = this.root.app.achievementProvider;
|
||||||
|
this.disabled = true;
|
||||||
|
|
||||||
|
if (G_IS_DEV && globalConfig.debug.testAchievements) {
|
||||||
|
// still enable the proxy
|
||||||
|
} else if (!this.provider.hasAchievements()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sliceTime = 0;
|
||||||
|
|
||||||
|
this.root.signals.postLoadHook.add(this.onLoad, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.provider
|
||||||
|
.onLoad(this.root)
|
||||||
|
.then(() => {
|
||||||
|
this.disabled = false;
|
||||||
|
logger.log("Recieving achievement signals");
|
||||||
|
this.initialize();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.disabled = true;
|
||||||
|
logger.error("Ignoring achievement signals", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode, null);
|
||||||
|
|
||||||
|
if (this.has(ACHIEVEMENTS.mam)) {
|
||||||
|
this.root.signals.entityAdded.add(this.onMamFailure, this);
|
||||||
|
this.root.signals.entityDestroyed.add(this.onMamFailure, this);
|
||||||
|
this.root.signals.storyGoalCompleted.add(this.onStoryGoalCompleted, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.has(ACHIEVEMENTS.noInverseRotater)) {
|
||||||
|
this.root.signals.entityAdded.add(this.onEntityAdded, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
startSlice() {
|
||||||
|
this.sliceTime = this.root.time.now();
|
||||||
|
|
||||||
|
this.root.signals.bulkAchievementCheck.dispatch(
|
||||||
|
ACHIEVEMENTS.storeShape,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.throughputBp25,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.throughputBp50,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.throughputLogo25,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.throughputLogo50,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.throughputRocket10,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.throughputRocket20,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.play1h,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.play10h,
|
||||||
|
this.sliceTime,
|
||||||
|
ACHIEVEMENTS.play20h,
|
||||||
|
this.sliceTime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if (this.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.root.time.now() - this.sliceTime > globalConfig.achievementSliceDuration) {
|
||||||
|
this.startSlice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
has(key) {
|
||||||
|
if (!this.provider.collection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.provider.collection.map.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Entity} entity */
|
||||||
|
onEntityAdded(entity) {
|
||||||
|
if (!entity.components.StaticMapEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const building = getBuildingDataFromCode(entity.components.StaticMapEntity.code);
|
||||||
|
|
||||||
|
if (building.metaInstance.id !== ROTATER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (building.variant === DEFAULT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.root.savegame.currentData.stats.usedInverseRotater = true;
|
||||||
|
this.root.signals.entityAdded.remove(this.onEntityAdded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} level */
|
||||||
|
onStoryGoalCompleted(level) {
|
||||||
|
if (level > 26) {
|
||||||
|
this.root.signals.entityAdded.add(this.onMamFailure, this);
|
||||||
|
this.root.signals.entityDestroyed.add(this.onMamFailure, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.mam, null);
|
||||||
|
|
||||||
|
// reset on every level
|
||||||
|
this.root.savegame.currentData.stats.failedMam = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMamFailure() {
|
||||||
|
this.root.savegame.currentData.stats.failedMam = true;
|
||||||
|
}
|
||||||
|
}
|
@ -1086,6 +1086,9 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
// Reduce the spacing
|
// Reduce the spacing
|
||||||
nextDistanceAndItem[_nextDistance] -= clampedProgress;
|
nextDistanceAndItem[_nextDistance] -= clampedProgress;
|
||||||
|
|
||||||
|
// Advance all items behind by the progress we made
|
||||||
|
this.spacingToFirstItem += clampedProgress;
|
||||||
|
|
||||||
// If the last item can be ejected, eject it and reduce the spacing, because otherwise
|
// If the last item can be ejected, eject it and reduce the spacing, because otherwise
|
||||||
// we lose velocity
|
// we lose velocity
|
||||||
if (isFirstItemProcessed && nextDistanceAndItem[_nextDistance] < 1e-7) {
|
if (isFirstItemProcessed && nextDistanceAndItem[_nextDistance] < 1e-7) {
|
||||||
@ -1098,6 +1101,24 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
if (this.tryHandOverItem(nextDistanceAndItem[_item], excessVelocity)) {
|
if (this.tryHandOverItem(nextDistanceAndItem[_item], excessVelocity)) {
|
||||||
this.items.pop();
|
this.items.pop();
|
||||||
|
|
||||||
|
const itemBehind = this.items[lastItemProcessed - 1];
|
||||||
|
if (itemBehind && this.numCompressedItemsAfterFirstItem > 0) {
|
||||||
|
// So, with the next tick we will skip this item, but it actually has the potential
|
||||||
|
// to process farther -> If we don't advance here, we loose a tiny bit of progress
|
||||||
|
// every tick which causes the belt to be slower than it actually is.
|
||||||
|
// Also see #999
|
||||||
|
const fixupProgress = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(remainingVelocity, itemBehind[_nextDistance])
|
||||||
|
);
|
||||||
|
|
||||||
|
// See above
|
||||||
|
itemBehind[_nextDistance] -= fixupProgress;
|
||||||
|
remainingVelocity -= fixupProgress;
|
||||||
|
this.spacingToFirstItem += fixupProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce the number of compressed items since the first item no longer exists
|
||||||
this.numCompressedItemsAfterFirstItem = Math.max(
|
this.numCompressedItemsAfterFirstItem = Math.max(
|
||||||
0,
|
0,
|
||||||
this.numCompressedItemsAfterFirstItem - 1
|
this.numCompressedItemsAfterFirstItem - 1
|
||||||
@ -1111,7 +1132,6 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isFirstItemProcessed = false;
|
isFirstItemProcessed = false;
|
||||||
this.spacingToFirstItem += clampedProgress;
|
|
||||||
if (remainingVelocity < 1e-7) {
|
if (remainingVelocity < 1e-7) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { DrawParameters } from "../core/draw_parameters";
|
|||||||
import { findNiceIntegerValue } from "../core/utils";
|
import { findNiceIntegerValue } from "../core/utils";
|
||||||
import { Vector } from "../core/vector";
|
import { Vector } from "../core/vector";
|
||||||
import { Entity } from "./entity";
|
import { Entity } from "./entity";
|
||||||
|
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
|
|
||||||
export class Blueprint {
|
export class Blueprint {
|
||||||
@ -179,7 +180,7 @@ export class Blueprint {
|
|||||||
*/
|
*/
|
||||||
tryPlace(root, tile) {
|
tryPlace(root, tile) {
|
||||||
return root.logic.performBulkOperation(() => {
|
return root.logic.performBulkOperation(() => {
|
||||||
let anyPlaced = false;
|
let count = 0;
|
||||||
for (let i = 0; i < this.entities.length; ++i) {
|
for (let i = 0; i < this.entities.length; ++i) {
|
||||||
const entity = this.entities[i];
|
const entity = this.entities[i];
|
||||||
if (!root.logic.checkCanPlaceEntity(entity, tile)) {
|
if (!root.logic.checkCanPlaceEntity(entity, tile)) {
|
||||||
@ -191,9 +192,17 @@ export class Blueprint {
|
|||||||
root.logic.freeEntityAreaBeforeBuild(clone);
|
root.logic.freeEntityAreaBeforeBuild(clone);
|
||||||
root.map.placeStaticEntity(clone);
|
root.map.placeStaticEntity(clone);
|
||||||
root.entityMgr.registerEntity(clone);
|
root.entityMgr.registerEntity(clone);
|
||||||
anyPlaced = true;
|
count++;
|
||||||
}
|
}
|
||||||
return anyPlaced;
|
|
||||||
|
root.signals.bulkAchievementCheck.dispatch(
|
||||||
|
ACHIEVEMENTS.placeBlueprint,
|
||||||
|
count,
|
||||||
|
ACHIEVEMENTS.placeBp1000,
|
||||||
|
count
|
||||||
|
);
|
||||||
|
|
||||||
|
return count !== 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { generateMatrixRotations } from "../../core/utils";
|
import { generateMatrixRotations } from "../../core/utils";
|
||||||
import { enumDirection, Vector } from "../../core/vector";
|
import { enumDirection, Vector } from "../../core/vector";
|
||||||
|
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
@ -37,6 +38,25 @@ export class MetaTrashBuilding extends MetaBuilding {
|
|||||||
return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
|
return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addAchievementReceiver(entity) {
|
||||||
|
if (!entity.root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemProcessor = entity.components.ItemProcessor;
|
||||||
|
const tryTakeItem = itemProcessor.tryTakeItem.bind(itemProcessor);
|
||||||
|
|
||||||
|
itemProcessor.tryTakeItem = () => {
|
||||||
|
const taken = tryTakeItem(...arguments);
|
||||||
|
|
||||||
|
if (taken) {
|
||||||
|
entity.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.trash1000, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return taken;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the entity at the given location
|
* Creates the entity at the given location
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
@ -57,11 +77,14 @@ export class MetaTrashBuilding extends MetaBuilding {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
entity.addComponent(
|
entity.addComponent(
|
||||||
new ItemProcessorComponent({
|
new ItemProcessorComponent({
|
||||||
inputsPerCharge: 1,
|
inputsPerCharge: 1,
|
||||||
processorType: enumItemProcessorTypes.trash,
|
processorType: enumItemProcessorTypes.trash,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.addAchievementReceiver(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import { RegularGameMode } from "./modes/regular";
|
|||||||
import { ProductionAnalytics } from "./production_analytics";
|
import { ProductionAnalytics } from "./production_analytics";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||||
|
import { AchievementProxy } from "./achievement_proxy";
|
||||||
import { SoundProxy } from "./sound_proxy";
|
import { SoundProxy } from "./sound_proxy";
|
||||||
import { GameTime } from "./time/game_time";
|
import { GameTime } from "./time/game_time";
|
||||||
|
|
||||||
@ -111,6 +112,7 @@ export class GameCore {
|
|||||||
root.logic = new GameLogic(root);
|
root.logic = new GameLogic(root);
|
||||||
root.hud = new GameHUD(root);
|
root.hud = new GameHUD(root);
|
||||||
root.time = new GameTime(root);
|
root.time = new GameTime(root);
|
||||||
|
root.achievementProxy = new AchievementProxy(root);
|
||||||
root.automaticSave = new AutomaticSave(root);
|
root.automaticSave = new AutomaticSave(root);
|
||||||
root.soundProxy = new SoundProxy(root);
|
root.soundProxy = new SoundProxy(root);
|
||||||
|
|
||||||
@ -149,6 +151,9 @@ export class GameCore {
|
|||||||
|
|
||||||
// Update analytics
|
// Update analytics
|
||||||
root.productionAnalytics.update();
|
root.productionAnalytics.update();
|
||||||
|
|
||||||
|
// Check achievements
|
||||||
|
root.achievementProxy.update();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -274,6 +279,9 @@ export class GameCore {
|
|||||||
|
|
||||||
// Update analytics
|
// Update analytics
|
||||||
root.productionAnalytics.update();
|
root.productionAnalytics.update();
|
||||||
|
|
||||||
|
// Check achievements
|
||||||
|
root.achievementProxy.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update automatic save after everything finished
|
// Update automatic save after everything finished
|
||||||
|
@ -169,7 +169,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
getCurrentGoalDelivered() {
|
getCurrentGoalDelivered() {
|
||||||
if (this.currentGoal.throughputOnly) {
|
if (this.currentGoal.throughputOnly) {
|
||||||
return (
|
return (
|
||||||
this.root.productionAnalytics.getCurrentShapeRate(
|
this.root.productionAnalytics.getCurrentShapeRateRaw(
|
||||||
enumAnalyticsDataSource.delivered,
|
enumAnalyticsDataSource.delivered,
|
||||||
this.currentGoal.definition
|
this.currentGoal.definition
|
||||||
) / globalConfig.analyticsSliceDurationSeconds
|
) / globalConfig.analyticsSliceDurationSeconds
|
||||||
@ -238,7 +238,8 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const required = Math.min(200, 4 + (this.level - 27) * 0.25);
|
//Floor Required amount to remove confusion
|
||||||
|
const required = Math.min(200, Math.floor(4 + (this.level - 27) * 0.25));
|
||||||
this.currentGoal = {
|
this.currentGoal = {
|
||||||
definition: this.computeFreeplayShape(this.level),
|
definition: this.computeFreeplayShape(this.level),
|
||||||
required,
|
required,
|
||||||
|
@ -48,6 +48,7 @@ import { HUDBetaOverlay } from "./parts/beta_overlay";
|
|||||||
import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
|
import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
|
||||||
import { HUDCatMemes } from "./parts/cat_memes";
|
import { HUDCatMemes } from "./parts/cat_memes";
|
||||||
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
||||||
|
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
|
||||||
|
|
||||||
export class GameHUD {
|
export class GameHUD {
|
||||||
/**
|
/**
|
||||||
@ -86,6 +87,7 @@ export class GameHUD {
|
|||||||
waypoints: new HUDWaypoints(this.root),
|
waypoints: new HUDWaypoints(this.root),
|
||||||
wireInfo: new HUDWireInfo(this.root),
|
wireInfo: new HUDWireInfo(this.root),
|
||||||
leverToggle: new HUDLeverToggle(this.root),
|
leverToggle: new HUDLeverToggle(this.root),
|
||||||
|
constantSignalEdit: new HUDConstantSignalEdit(this.root),
|
||||||
|
|
||||||
// Must always exist
|
// Must always exist
|
||||||
pinnedShapes: new HUDPinnedShapes(this.root),
|
pinnedShapes: new HUDPinnedShapes(this.root),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
||||||
import { Signal, STOP_PROPAGATION } from "../../../core/signal";
|
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||||
import { makeDiv } from "../../../core/utils";
|
import { makeDiv, safeModulo } from "../../../core/utils";
|
||||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||||
import { MetaBuilding } from "../../meta_building";
|
import { MetaBuilding } from "../../meta_building";
|
||||||
import { GameRoot } from "../../root";
|
import { GameRoot } from "../../root";
|
||||||
@ -161,8 +161,12 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
|||||||
|
|
||||||
let newBuildingFound = false;
|
let newBuildingFound = false;
|
||||||
let newIndex = this.lastSelectedIndex;
|
let newIndex = this.lastSelectedIndex;
|
||||||
for (let i = 0; i < this.primaryBuildings.length; ++i, ++newIndex) {
|
const direction = this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed
|
||||||
newIndex %= this.primaryBuildings.length;
|
? -1
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
for (let i = 0; i <= this.primaryBuildings.length; ++i) {
|
||||||
|
newIndex = safeModulo(newIndex + direction, this.primaryBuildings.length);
|
||||||
const metaBuilding = gMetaBuildingRegistry.findByClass(this.primaryBuildings[newIndex]);
|
const metaBuilding = gMetaBuildingRegistry.findByClass(this.primaryBuildings[newIndex]);
|
||||||
const handle = this.buildingHandles[metaBuilding.id];
|
const handle = this.buildingHandles[metaBuilding.id];
|
||||||
if (!handle.selected && handle.unlocked) {
|
if (!handle.selected && handle.unlocked) {
|
||||||
|
@ -110,24 +110,25 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
|
|||||||
this.abortPlacement();
|
this.abortPlacement();
|
||||||
return STOP_PROPAGATION;
|
return STOP_PROPAGATION;
|
||||||
}
|
}
|
||||||
}
|
} else if (button === enumMouseButton.left) {
|
||||||
|
const blueprint = this.currentBlueprint.get();
|
||||||
|
if (!blueprint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const blueprint = this.currentBlueprint.get();
|
if (!blueprint.canAfford(this.root)) {
|
||||||
if (!blueprint) {
|
this.root.soundProxy.playUiError();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!blueprint.canAfford(this.root)) {
|
const worldPos = this.root.camera.screenToWorld(pos);
|
||||||
this.root.soundProxy.playUiError();
|
const tile = worldPos.toTileSpace();
|
||||||
return;
|
if (blueprint.tryPlace(this.root, tile)) {
|
||||||
}
|
const cost = blueprint.getCost();
|
||||||
|
this.root.hubGoals.takeShapeByKey(this.root.gameMode.getBlueprintShapeKey(), cost);
|
||||||
const worldPos = this.root.camera.screenToWorld(pos);
|
this.root.soundProxy.playUi(SOUNDS.placeBuilding);
|
||||||
const tile = worldPos.toTileSpace();
|
}
|
||||||
if (blueprint.tryPlace(this.root, tile)) {
|
return STOP_PROPAGATION;
|
||||||
const cost = blueprint.getCost();
|
|
||||||
this.root.hubGoals.takeShapeByKey(this.root.gameMode.getBlueprintShapeKey(), cost);
|
|
||||||
this.root.soundProxy.playUi(SOUNDS.placeBuilding);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,8 +216,8 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||||||
const dimensions = metaBuilding.getDimensions(variant);
|
const dimensions = metaBuilding.getDimensions(variant);
|
||||||
const sprite = metaBuilding.getPreviewSprite(0, variant);
|
const sprite = metaBuilding.getPreviewSprite(0, variant);
|
||||||
const spriteWrapper = makeDiv(element, null, ["iconWrap"]);
|
const spriteWrapper = makeDiv(element, null, ["iconWrap"]);
|
||||||
spriteWrapper.setAttribute("data-tile-w", dimensions.x);
|
spriteWrapper.setAttribute("data-tile-w", String(dimensions.x));
|
||||||
spriteWrapper.setAttribute("data-tile-h", dimensions.y);
|
spriteWrapper.setAttribute("data-tile-h", String(dimensions.y));
|
||||||
|
|
||||||
spriteWrapper.innerHTML = sprite.getAsHTML(iconSize * dimensions.x, iconSize * dimensions.y);
|
spriteWrapper.innerHTML = sprite.getAsHTML(iconSize * dimensions.x, iconSize * dimensions.y);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
|
|||||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||||
import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
|
import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
|
||||||
import { MetaHubBuilding } from "../../buildings/hub";
|
import { MetaHubBuilding } from "../../buildings/hub";
|
||||||
|
import { safeModulo } from "../../../core/utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains all logic for the building placer - this doesn't include the rendering
|
* Contains all logic for the building placer - this doesn't include the rendering
|
||||||
@ -109,6 +110,12 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
|||||||
// KEYBINDINGS
|
// KEYBINDINGS
|
||||||
const keyActionMapper = this.root.keyMapper;
|
const keyActionMapper = this.root.keyMapper;
|
||||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this);
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this);
|
||||||
|
|
||||||
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToUp).add(this.trySetRotate, this);
|
||||||
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToDown).add(this.trySetRotate, this);
|
||||||
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToRight).add(this.trySetRotate, this);
|
||||||
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToLeft).add(this.trySetRotate, this);
|
||||||
|
|
||||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this);
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this);
|
||||||
keyActionMapper
|
keyActionMapper
|
||||||
.getBinding(KEYMAPPINGS.placement.switchDirectionLockSide)
|
.getBinding(KEYMAPPINGS.placement.switchDirectionLockSide)
|
||||||
@ -289,6 +296,28 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
|||||||
staticComp.rotation = this.currentBaseRotation;
|
staticComp.rotation = this.currentBaseRotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the current building to the specified direction.
|
||||||
|
*/
|
||||||
|
trySetRotate() {
|
||||||
|
const selectedBuilding = this.currentMetaBuilding.get();
|
||||||
|
if (selectedBuilding) {
|
||||||
|
if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToUp).pressed) {
|
||||||
|
this.currentBaseRotation = 0;
|
||||||
|
} else if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToDown).pressed) {
|
||||||
|
this.currentBaseRotation = 180;
|
||||||
|
} else if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToRight).pressed) {
|
||||||
|
this.currentBaseRotation = 90;
|
||||||
|
} else if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToLeft).pressed) {
|
||||||
|
this.currentBaseRotation = 270;
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticComp = this.fakeEntity.components.StaticMapEntity;
|
||||||
|
staticComp.rotation = this.currentBaseRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to delete the building under the mouse
|
* Tries to delete the building under the mouse
|
||||||
*/
|
*/
|
||||||
@ -467,7 +496,12 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
|||||||
index = 0;
|
index = 0;
|
||||||
console.warn("Invalid variant selected:", this.currentVariant.get());
|
console.warn("Invalid variant selected:", this.currentVariant.get());
|
||||||
}
|
}
|
||||||
const newIndex = (index + 1) % availableVariants.length;
|
const direction = this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier)
|
||||||
|
.pressed
|
||||||
|
? -1
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
const newIndex = safeModulo(index + direction, availableVariants.length);
|
||||||
const newVariant = availableVariants[newIndex];
|
const newVariant = availableVariants[newIndex];
|
||||||
this.setVariant(newVariant);
|
this.setVariant(newVariant);
|
||||||
}
|
}
|
||||||
|
34
src/js/game/hud/parts/constant_signal_edit.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||||
|
import { Vector } from "../../../core/vector";
|
||||||
|
import { enumMouseButton } from "../../camera";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
|
export class HUDConstantSignalEdit extends BaseHUDPart {
|
||||||
|
initialize() {
|
||||||
|
this.root.camera.downPreHandler.add(this.downPreHandler, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Vector} pos
|
||||||
|
* @param {enumMouseButton} button
|
||||||
|
*/
|
||||||
|
downPreHandler(pos, button) {
|
||||||
|
if (this.root.currentLayer !== "wires") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tile = this.root.camera.screenToWorld(pos).toTileSpace();
|
||||||
|
const contents = this.root.map.getLayerContentXY(tile.x, tile.y, "wires");
|
||||||
|
if (contents) {
|
||||||
|
const constantComp = contents.components.ConstantSignal;
|
||||||
|
if (constantComp) {
|
||||||
|
if (button === enumMouseButton.left) {
|
||||||
|
this.root.systemMgr.systems.constantSignal.editConstantSignal(contents, {
|
||||||
|
deleteOnCancel: false,
|
||||||
|
});
|
||||||
|
return STOP_PROPAGATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,10 @@ import { createLogger } from "../../../core/logging";
|
|||||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||||
import { formatBigNumberFull } from "../../../core/utils";
|
import { formatBigNumberFull } from "../../../core/utils";
|
||||||
import { Vector } from "../../../core/vector";
|
import { Vector } from "../../../core/vector";
|
||||||
|
import { ACHIEVEMENTS } from "../../../platform/achievement_provider";
|
||||||
|
import { enumMouseButton } from "../../camera";
|
||||||
import { T } from "../../../translations";
|
import { T } from "../../../translations";
|
||||||
import { Blueprint } from "../../blueprint";
|
import { Blueprint } from "../../blueprint";
|
||||||
import { enumMouseButton } from "../../camera";
|
|
||||||
import { Entity } from "../../entity";
|
import { Entity } from "../../entity";
|
||||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||||
import { THEME } from "../../theme";
|
import { THEME } from "../../theme";
|
||||||
@ -101,13 +102,18 @@ export class HUDMassSelector extends BaseHUDPart {
|
|||||||
* @type {Map<number, Entity>}
|
* @type {Map<number, Entity>}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
this.root.logic.performBulkOperation(() => {
|
this.root.logic.performBulkOperation(() => {
|
||||||
const arr = [...this.selectedEntities.values()];
|
const arr = [...this.selectedEntities.values()];
|
||||||
for (let i = arr.length - 1; i >= 0; --i) {
|
for (let i = arr.length - 1; i >= 0; --i) {
|
||||||
if (!this.root.logic.tryDeleteBuilding(arr[i])) {
|
if (!this.root.logic.tryDeleteBuilding(arr[i])) {
|
||||||
logger.error("Error in mass delete, could not remove building");
|
logger.error("Error in mass delete, could not remove building");
|
||||||
|
} else {
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.destroy1000, count);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
|
@ -217,11 +217,14 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
|
|
||||||
let detector = null;
|
let detector = null;
|
||||||
if (canUnpin) {
|
if (canUnpin) {
|
||||||
|
const unpinButton = document.createElement("button");
|
||||||
|
unpinButton.classList.add("unpinButton");
|
||||||
|
element.appendChild(unpinButton);
|
||||||
element.classList.add("removable");
|
element.classList.add("removable");
|
||||||
detector = new ClickDetector(element, {
|
detector = new ClickDetector(unpinButton, {
|
||||||
consumeEvents: true,
|
consumeEvents: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
targetOnly: false,
|
targetOnly: true,
|
||||||
});
|
});
|
||||||
detector.click.add(() => this.unpinShape(key));
|
detector.click.add(() => this.unpinShape(key));
|
||||||
} else {
|
} else {
|
||||||
@ -270,7 +273,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
|
|
||||||
if (handle.throughputOnly) {
|
if (handle.throughputOnly) {
|
||||||
currentValue =
|
currentValue =
|
||||||
this.root.productionAnalytics.getCurrentShapeRate(
|
this.root.productionAnalytics.getCurrentShapeRateRaw(
|
||||||
enumAnalyticsDataSource.delivered,
|
enumAnalyticsDataSource.delivered,
|
||||||
handle.definition
|
handle.definition
|
||||||
) / globalConfig.analyticsSliceDurationSeconds;
|
) / globalConfig.analyticsSliceDurationSeconds;
|
||||||
|
@ -5,7 +5,7 @@ import { T } from "../../../translations";
|
|||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
|
|
||||||
const showIntervalSeconds = 30 * 60;
|
const showIntervalSeconds = 9 * 60;
|
||||||
|
|
||||||
export class HUDStandaloneAdvantages extends BaseHUDPart {
|
export class HUDStandaloneAdvantages extends BaseHUDPart {
|
||||||
createElements(parent) {
|
createElements(parent) {
|
||||||
@ -25,7 +25,7 @@ export class HUDStandaloneAdvantages extends BaseHUDPart {
|
|||||||
([key, trans]) => `
|
([key, trans]) => `
|
||||||
<div class="point ${key}">
|
<div class="point ${key}">
|
||||||
<strong>${trans.title}</strong>
|
<strong>${trans.title}</strong>
|
||||||
<p>${trans.desc}</p>
|
<p>${trans.desc}</p>
|
||||||
</div>`
|
</div>`
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
@ -60,7 +60,7 @@ export class HUDStandaloneAdvantages extends BaseHUDPart {
|
|||||||
this.inputReciever = new InputReceiver("standalone-advantages");
|
this.inputReciever = new InputReceiver("standalone-advantages");
|
||||||
this.close();
|
this.close();
|
||||||
|
|
||||||
this.lastShown = this.root.gameIsFresh ? this.root.time.now() : 0;
|
this.lastShown = -1e10;
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
|
@ -209,7 +209,9 @@ export class HUDStatistics extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
case enumAnalyticsDataSource.produced:
|
case enumAnalyticsDataSource.produced:
|
||||||
case enumAnalyticsDataSource.delivered: {
|
case enumAnalyticsDataSource.delivered: {
|
||||||
entries = Object.entries(this.root.productionAnalytics.getCurrentShapeRates(this.dataSource));
|
entries = Object.entries(
|
||||||
|
this.root.productionAnalytics.getCurrentShapeRatesRaw(this.dataSource)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ export class HUDShapeStatisticsHandle {
|
|||||||
case enumAnalyticsDataSource.delivered:
|
case enumAnalyticsDataSource.delivered:
|
||||||
case enumAnalyticsDataSource.produced: {
|
case enumAnalyticsDataSource.produced: {
|
||||||
let rate =
|
let rate =
|
||||||
this.root.productionAnalytics.getCurrentShapeRate(dataSource, this.definition) /
|
this.root.productionAnalytics.getCurrentShapeRateRaw(dataSource, this.definition) /
|
||||||
globalConfig.analyticsSliceDurationSeconds;
|
globalConfig.analyticsSliceDurationSeconds;
|
||||||
|
|
||||||
this.counter.innerText = T.ingame.statistics.shapesDisplayUnits[unit].replace(
|
this.counter.innerText = T.ingame.statistics.shapesDisplayUnits[unit].replace(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||||
import { globalConfig, THIRDPARTY_URLS } from "../../../core/config";
|
import { globalConfig, THIRDPARTY_URLS } from "../../../core/config";
|
||||||
import { DrawParameters } from "../../../core/draw_parameters";
|
import { DrawParameters } from "../../../core/draw_parameters";
|
||||||
|
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
||||||
import { Loader } from "../../../core/loader";
|
import { Loader } from "../../../core/loader";
|
||||||
import { DialogWithForm } from "../../../core/modal_dialog_elements";
|
import { DialogWithForm } from "../../../core/modal_dialog_elements";
|
||||||
import { FormElementInput } from "../../../core/modal_dialog_forms";
|
import { FormElementInput } from "../../../core/modal_dialog_forms";
|
||||||
@ -14,8 +15,10 @@ import {
|
|||||||
removeAllChildren,
|
removeAllChildren,
|
||||||
} from "../../../core/utils";
|
} from "../../../core/utils";
|
||||||
import { Vector } from "../../../core/vector";
|
import { Vector } from "../../../core/vector";
|
||||||
|
import { ACHIEVEMENTS } from "../../../platform/achievement_provider";
|
||||||
import { T } from "../../../translations";
|
import { T } from "../../../translations";
|
||||||
import { BaseItem } from "../../base_item";
|
import { BaseItem } from "../../base_item";
|
||||||
|
import { MetaHubBuilding } from "../../buildings/hub";
|
||||||
import { enumMouseButton } from "../../camera";
|
import { enumMouseButton } from "../../camera";
|
||||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||||
import { ShapeDefinition } from "../../shape_definition";
|
import { ShapeDefinition } from "../../shape_definition";
|
||||||
@ -26,7 +29,8 @@ import { enumNotificationType } from "./notifications";
|
|||||||
/** @typedef {{
|
/** @typedef {{
|
||||||
* label: string | null,
|
* label: string | null,
|
||||||
* center: { x: number, y: number },
|
* center: { x: number, y: number },
|
||||||
* zoomLevel: number
|
* zoomLevel: number,
|
||||||
|
* layer: Layer,
|
||||||
* }} Waypoint */
|
* }} Waypoint */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,7 +92,12 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
*/
|
*/
|
||||||
initialize() {
|
initialize() {
|
||||||
// Cache the sprite for the waypoints
|
// Cache the sprite for the waypoints
|
||||||
this.waypointSprite = Loader.getSprite("sprites/misc/waypoint.png");
|
|
||||||
|
this.waypointSprites = {
|
||||||
|
regular: Loader.getSprite("sprites/misc/waypoint.png"),
|
||||||
|
wires: Loader.getSprite("sprites/misc/waypoint_wires.png"),
|
||||||
|
};
|
||||||
|
|
||||||
this.directionIndicatorSprite = Loader.getSprite("sprites/misc/hub_direction_indicator.png");
|
this.directionIndicatorSprite = Loader.getSprite("sprites/misc/hub_direction_indicator.png");
|
||||||
|
|
||||||
/** @type {Array<Waypoint>}
|
/** @type {Array<Waypoint>}
|
||||||
@ -98,6 +107,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
label: null,
|
label: null,
|
||||||
center: { x: 0, y: 0 },
|
center: { x: 0, y: 0 },
|
||||||
zoomLevel: 3,
|
zoomLevel: 3,
|
||||||
|
layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -187,7 +197,10 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
const waypoint = this.waypoints[i];
|
const waypoint = this.waypoints[i];
|
||||||
const label = this.getWaypointLabel(waypoint);
|
const label = this.getWaypointLabel(waypoint);
|
||||||
|
|
||||||
const element = makeDiv(this.waypointsListElement, null, ["waypoint"]);
|
const element = makeDiv(this.waypointsListElement, null, [
|
||||||
|
"waypoint",
|
||||||
|
"layer--" + waypoint.layer,
|
||||||
|
]);
|
||||||
|
|
||||||
if (ShapeDefinition.isValidShortKey(label)) {
|
if (ShapeDefinition.isValidShortKey(label)) {
|
||||||
const canvas = this.getWaypointCanvas(waypoint);
|
const canvas = this.getWaypointCanvas(waypoint);
|
||||||
@ -228,6 +241,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
* @param {Waypoint} waypoint
|
* @param {Waypoint} waypoint
|
||||||
*/
|
*/
|
||||||
moveToWaypoint(waypoint) {
|
moveToWaypoint(waypoint) {
|
||||||
|
this.root.currentLayer = waypoint.layer;
|
||||||
this.root.camera.setDesiredCenter(new Vector(waypoint.center.x, waypoint.center.y));
|
this.root.camera.setDesiredCenter(new Vector(waypoint.center.x, waypoint.center.y));
|
||||||
this.root.camera.setDesiredZoom(waypoint.zoomLevel);
|
this.root.camera.setDesiredZoom(waypoint.zoomLevel);
|
||||||
}
|
}
|
||||||
@ -326,6 +340,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
label,
|
label,
|
||||||
center: { x: position.x, y: position.y },
|
center: { x: position.x, y: position.y },
|
||||||
zoomLevel: this.root.camera.zoomLevel,
|
zoomLevel: this.root.camera.zoomLevel,
|
||||||
|
layer: this.root.currentLayer,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sortWaypoints();
|
this.sortWaypoints();
|
||||||
@ -335,6 +350,10 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
T.ingame.waypoints.creationSuccessNotification,
|
T.ingame.waypoints.creationSuccessNotification,
|
||||||
enumNotificationType.success
|
enumNotificationType.success
|
||||||
);
|
);
|
||||||
|
this.root.signals.achievementCheck.dispatch(
|
||||||
|
ACHIEVEMENTS.mapMarkers15,
|
||||||
|
this.waypoints.length - 1 // Disregard HUB
|
||||||
|
);
|
||||||
|
|
||||||
// Re-render the list and thus add it
|
// Re-render the list and thus add it
|
||||||
this.rerenderWaypointList();
|
this.rerenderWaypointList();
|
||||||
@ -537,7 +556,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
const iconOpacity = 1 - this.currentCompassOpacity;
|
const iconOpacity = 1 - this.currentCompassOpacity;
|
||||||
if (iconOpacity > 0.01) {
|
if (iconOpacity > 0.01) {
|
||||||
context.globalAlpha = iconOpacity;
|
context.globalAlpha = iconOpacity;
|
||||||
this.waypointSprite.drawCentered(context, dims / 2, dims / 2, dims * 0.7);
|
this.waypointSprites.regular.drawCentered(context, dims / 2, dims / 2, dims * 0.7);
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,11 +635,11 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the small icon on the left
|
// Render the small icon on the left
|
||||||
this.waypointSprite.drawCentered(
|
this.waypointSprites[waypoint.layer].drawCentered(
|
||||||
parameters.context,
|
parameters.context,
|
||||||
bounds.x + contentPaddingX,
|
bounds.x + contentPaddingX,
|
||||||
bounds.y + bounds.h / 2,
|
bounds.y + bounds.h / 2,
|
||||||
bounds.h * 0.7
|
bounds.h * 0.6
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,8 @@ export class HUDWiresOverlay extends BaseHUDPart {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.cachedPatternBackground) {
|
const hasTileGrid = !this.root.app.settings.getAllSettings().disableTileGrid;
|
||||||
|
if (hasTileGrid && !this.cachedPatternBackground) {
|
||||||
this.cachedPatternBackground = parameters.context.createPattern(this.tilePatternCanvas, "repeat");
|
this.cachedPatternBackground = parameters.context.createPattern(this.tilePatternCanvas, "repeat");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +134,9 @@ export class HUDWiresOverlay extends BaseHUDPart {
|
|||||||
parameters.context.globalCompositeOperation = "source-over";
|
parameters.context.globalCompositeOperation = "source-over";
|
||||||
|
|
||||||
parameters.context.scale(scaleFactor, scaleFactor);
|
parameters.context.scale(scaleFactor, scaleFactor);
|
||||||
parameters.context.fillStyle = this.cachedPatternBackground;
|
parameters.context.fillStyle = hasTileGrid
|
||||||
|
? this.cachedPatternBackground
|
||||||
|
: "rgba(78, 137, 125, 0.75)";
|
||||||
parameters.context.fillRect(
|
parameters.context.fillRect(
|
||||||
bounds.x / scaleFactor,
|
bounds.x / scaleFactor,
|
||||||
bounds.y / scaleFactor,
|
bounds.y / scaleFactor,
|
||||||
|
@ -11,6 +11,11 @@ function key(str) {
|
|||||||
return str.toUpperCase().charCodeAt(0);
|
return str.toUpperCase().charCodeAt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KEYCODE_UP_ARROW = 38;
|
||||||
|
const KEYCODE_DOWN_ARROW = 40;
|
||||||
|
const KEYCODE_LEFT_ARROW = 37;
|
||||||
|
const KEYCODE_RIGHT_ARROW = 39;
|
||||||
|
|
||||||
export const KEYMAPPINGS = {
|
export const KEYMAPPINGS = {
|
||||||
general: {
|
general: {
|
||||||
confirm: { keyCode: 13 }, // enter
|
confirm: { keyCode: 13 }, // enter
|
||||||
@ -81,6 +86,10 @@ export const KEYMAPPINGS = {
|
|||||||
pipette: { keyCode: key("Q") },
|
pipette: { keyCode: key("Q") },
|
||||||
rotateWhilePlacing: { keyCode: key("R") },
|
rotateWhilePlacing: { keyCode: key("R") },
|
||||||
rotateInverseModifier: { keyCode: 16 }, // SHIFT
|
rotateInverseModifier: { keyCode: 16 }, // SHIFT
|
||||||
|
rotateToUp: { keyCode: KEYCODE_UP_ARROW },
|
||||||
|
rotateToDown: { keyCode: KEYCODE_DOWN_ARROW },
|
||||||
|
rotateToRight: { keyCode: KEYCODE_RIGHT_ARROW },
|
||||||
|
rotateToLeft: { keyCode: KEYCODE_LEFT_ARROW },
|
||||||
cycleBuildingVariants: { keyCode: key("T") },
|
cycleBuildingVariants: { keyCode: key("T") },
|
||||||
cycleBuildings: { keyCode: 9 }, // TAB
|
cycleBuildings: { keyCode: 9 }, // TAB
|
||||||
switchDirectionLockSide: { keyCode: key("R") },
|
switchDirectionLockSide: { keyCode: key("R") },
|
||||||
@ -162,13 +171,13 @@ export function getStringForKeyCode(code) {
|
|||||||
return "END";
|
return "END";
|
||||||
case 36:
|
case 36:
|
||||||
return "HOME";
|
return "HOME";
|
||||||
case 37:
|
case KEYCODE_LEFT_ARROW:
|
||||||
return "⬅";
|
return "⬅";
|
||||||
case 38:
|
case KEYCODE_UP_ARROW:
|
||||||
return "⬆";
|
return "⬆";
|
||||||
case 39:
|
case KEYCODE_RIGHT_ARROW:
|
||||||
return "➡";
|
return "➡";
|
||||||
case 40:
|
case KEYCODE_DOWN_ARROW:
|
||||||
return "⬇";
|
return "⬇";
|
||||||
case 44:
|
case 44:
|
||||||
return "PRNT";
|
return "PRNT";
|
||||||
|
@ -83,7 +83,7 @@ export class ProductionAnalytics extends BasicSerializableObject {
|
|||||||
* @param {enumAnalyticsDataSource} dataSource
|
* @param {enumAnalyticsDataSource} dataSource
|
||||||
* @param {ShapeDefinition} definition
|
* @param {ShapeDefinition} definition
|
||||||
*/
|
*/
|
||||||
getCurrentShapeRate(dataSource, definition) {
|
getCurrentShapeRateRaw(dataSource, definition) {
|
||||||
const slices = this.history[dataSource];
|
const slices = this.history[dataSource];
|
||||||
return slices[slices.length - 2][definition.getHash()] || 0;
|
return slices[slices.length - 2][definition.getHash()] || 0;
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ export class ProductionAnalytics extends BasicSerializableObject {
|
|||||||
* Returns the rates of all shapes
|
* Returns the rates of all shapes
|
||||||
* @param {enumAnalyticsDataSource} dataSource
|
* @param {enumAnalyticsDataSource} dataSource
|
||||||
*/
|
*/
|
||||||
getCurrentShapeRates(dataSource) {
|
getCurrentShapeRatesRaw(dataSource) {
|
||||||
const slices = this.history[dataSource];
|
const slices = this.history[dataSource];
|
||||||
|
|
||||||
// First, copy current slice
|
// First, copy current slice
|
||||||
|
@ -8,6 +8,7 @@ import { createLogger } from "../core/logging";
|
|||||||
import { GameTime } from "./time/game_time";
|
import { GameTime } from "./time/game_time";
|
||||||
import { EntityManager } from "./entity_manager";
|
import { EntityManager } from "./entity_manager";
|
||||||
import { GameSystemManager } from "./game_system_manager";
|
import { GameSystemManager } from "./game_system_manager";
|
||||||
|
import { AchievementProxy } from "./achievement_proxy";
|
||||||
import { GameHUD } from "./hud/hud";
|
import { GameHUD } from "./hud/hud";
|
||||||
import { MapView } from "./map_view";
|
import { MapView } from "./map_view";
|
||||||
import { Camera } from "./camera";
|
import { Camera } from "./camera";
|
||||||
@ -119,6 +120,9 @@ export class GameRoot {
|
|||||||
/** @type {SoundProxy} */
|
/** @type {SoundProxy} */
|
||||||
this.soundProxy = null;
|
this.soundProxy = null;
|
||||||
|
|
||||||
|
/** @type {AchievementProxy} */
|
||||||
|
this.achievementProxy = null;
|
||||||
|
|
||||||
/** @type {ShapeDefinitionManager} */
|
/** @type {ShapeDefinitionManager} */
|
||||||
this.shapeDefinitionMgr = null;
|
this.shapeDefinitionMgr = null;
|
||||||
|
|
||||||
@ -175,6 +179,10 @@ export class GameRoot {
|
|||||||
// Called before actually placing an entity, use to perform additional logic
|
// Called before actually placing an entity, use to perform additional logic
|
||||||
// for freeing space before actually placing.
|
// for freeing space before actually placing.
|
||||||
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
|
|
||||||
|
// Called with an achievement key and necessary args to validate it can be unlocked.
|
||||||
|
achievementCheck: /** @type {TypedSignal<[string, any]>} */ (new Signal()),
|
||||||
|
bulkAchievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// RNG's
|
// RNG's
|
||||||
|
@ -4,6 +4,7 @@ import { enumColors } from "./colors";
|
|||||||
import { ShapeItem } from "./items/shape_item";
|
import { ShapeItem } from "./items/shape_item";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
||||||
|
import { ACHIEVEMENTS } from "../platform/achievement_provider";
|
||||||
|
|
||||||
const logger = createLogger("shape_definition_manager");
|
const logger = createLogger("shape_definition_manager");
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
*/
|
*/
|
||||||
this.shapeKeyToItem = {};
|
this.shapeKeyToItem = {};
|
||||||
|
|
||||||
// Caches operations in the form of 'operation:def1[:def2]'
|
// Caches operations in the form of 'operation/def1[/def2]'
|
||||||
/** @type {Object.<string, Array<ShapeDefinition>|ShapeDefinition>} */
|
/** @type {Object.<string, Array<ShapeDefinition>|ShapeDefinition>} */
|
||||||
this.operationCache = {};
|
this.operationCache = {};
|
||||||
}
|
}
|
||||||
@ -89,13 +90,15 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {[ShapeDefinition, ShapeDefinition]}
|
* @returns {[ShapeDefinition, ShapeDefinition]}
|
||||||
*/
|
*/
|
||||||
shapeActionCutHalf(definition) {
|
shapeActionCutHalf(definition) {
|
||||||
const key = "cut:" + definition.getHash();
|
const key = "cut/" + definition.getHash();
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key]);
|
return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key]);
|
||||||
}
|
}
|
||||||
const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
|
const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
|
||||||
const leftSide = definition.cloneFilteredByQuadrants([0, 1]);
|
const leftSide = definition.cloneFilteredByQuadrants([0, 1]);
|
||||||
|
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.cutShape, null);
|
||||||
|
|
||||||
return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [
|
return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [
|
||||||
this.registerOrReturnHandle(rightSide),
|
this.registerOrReturnHandle(rightSide),
|
||||||
this.registerOrReturnHandle(leftSide),
|
this.registerOrReturnHandle(leftSide),
|
||||||
@ -108,7 +111,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]}
|
* @returns {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]}
|
||||||
*/
|
*/
|
||||||
shapeActionCutQuad(definition) {
|
shapeActionCutQuad(definition) {
|
||||||
const key = "cut-quad:" + definition.getHash();
|
const key = "cut-quad/" + definition.getHash();
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]} */ (this
|
return /** @type {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]} */ (this
|
||||||
.operationCache[key]);
|
.operationCache[key]);
|
||||||
@ -130,13 +133,15 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {ShapeDefinition}
|
* @returns {ShapeDefinition}
|
||||||
*/
|
*/
|
||||||
shapeActionRotateCW(definition) {
|
shapeActionRotateCW(definition) {
|
||||||
const key = "rotate-cw:" + definition.getHash();
|
const key = "rotate-cw/" + definition.getHash();
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rotated = definition.cloneRotateCW();
|
const rotated = definition.cloneRotateCW();
|
||||||
|
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.rotateShape, null);
|
||||||
|
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
|
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
|
||||||
rotated
|
rotated
|
||||||
));
|
));
|
||||||
@ -148,7 +153,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {ShapeDefinition}
|
* @returns {ShapeDefinition}
|
||||||
*/
|
*/
|
||||||
shapeActionRotateCCW(definition) {
|
shapeActionRotateCCW(definition) {
|
||||||
const key = "rotate-ccw:" + definition.getHash();
|
const key = "rotate-ccw/" + definition.getHash();
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
||||||
}
|
}
|
||||||
@ -166,7 +171,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {ShapeDefinition}
|
* @returns {ShapeDefinition}
|
||||||
*/
|
*/
|
||||||
shapeActionRotate180(definition) {
|
shapeActionRotate180(definition) {
|
||||||
const key = "rotate-fl:" + definition.getHash();
|
const key = "rotate-fl/" + definition.getHash();
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
||||||
}
|
}
|
||||||
@ -185,10 +190,13 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {ShapeDefinition}
|
* @returns {ShapeDefinition}
|
||||||
*/
|
*/
|
||||||
shapeActionStack(lowerDefinition, upperDefinition) {
|
shapeActionStack(lowerDefinition, upperDefinition) {
|
||||||
const key = "stack:" + lowerDefinition.getHash() + ":" + upperDefinition.getHash();
|
const key = "stack/" + lowerDefinition.getHash() + "/" + upperDefinition.getHash();
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.stackShape, null);
|
||||||
|
|
||||||
const stacked = lowerDefinition.cloneAndStackWith(upperDefinition);
|
const stacked = lowerDefinition.cloneAndStackWith(upperDefinition);
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
|
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
|
||||||
stacked
|
stacked
|
||||||
@ -202,10 +210,13 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {ShapeDefinition}
|
* @returns {ShapeDefinition}
|
||||||
*/
|
*/
|
||||||
shapeActionPaintWith(definition, color) {
|
shapeActionPaintWith(definition, color) {
|
||||||
const key = "paint:" + definition.getHash() + ":" + color;
|
const key = "paint/" + definition.getHash() + "/" + color;
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.paintShape, null);
|
||||||
|
|
||||||
const colorized = definition.cloneAndPaintWith(color);
|
const colorized = definition.cloneAndPaintWith(color);
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
|
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
|
||||||
colorized
|
colorized
|
||||||
@ -219,7 +230,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
|
|||||||
* @returns {ShapeDefinition}
|
* @returns {ShapeDefinition}
|
||||||
*/
|
*/
|
||||||
shapeActionPaintWith4Colors(definition, colors) {
|
shapeActionPaintWith4Colors(definition, colors) {
|
||||||
const key = "paint4:" + definition.getHash() + ":" + colors.join(",");
|
const key = "paint4/" + definition.getHash() + "/" + colors.join(",");
|
||||||
if (this.operationCache[key]) {
|
if (this.operationCache[key]) {
|
||||||
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export class BeltReaderSystem extends GameSystemWithFilter {
|
|||||||
throughput = 1 / (averageSpacing / averageSpacingNum);
|
throughput = 1 / (averageSpacing / averageSpacingNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
readerComp.lastThroughput = Math.min(30, throughput);
|
readerComp.lastThroughput = Math.min(globalConfig.beltSpeedItemsPerSecond * 23.9, throughput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,9 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
|||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root, [ConstantSignalComponent]);
|
super(root, [ConstantSignalComponent]);
|
||||||
|
|
||||||
this.root.signals.entityManuallyPlaced.add(this.querySigalValue, this);
|
this.root.signals.entityManuallyPlaced.add(entity =>
|
||||||
|
this.editConstantSignal(entity, { deleteOnCancel: true })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
@ -33,8 +35,10 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
|||||||
/**
|
/**
|
||||||
* Asks the entity to enter a valid signal code
|
* Asks the entity to enter a valid signal code
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
|
* @param {object} param0
|
||||||
|
* @param {boolean=} param0.deleteOnCancel
|
||||||
*/
|
*/
|
||||||
querySigalValue(entity) {
|
editConstantSignal(entity, { deleteOnCancel = true }) {
|
||||||
if (!entity.components.ConstantSignal) {
|
if (!entity.components.ConstantSignal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -110,26 +114,28 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
|||||||
dialog.valueChosen.add(closeHandler);
|
dialog.valueChosen.add(closeHandler);
|
||||||
|
|
||||||
// When cancelled, destroy the entity again
|
// When cancelled, destroy the entity again
|
||||||
dialog.buttonSignals.cancel.add(() => {
|
if (deleteOnCancel) {
|
||||||
if (!this.root || !this.root.entityMgr) {
|
dialog.buttonSignals.cancel.add(() => {
|
||||||
// Game got stopped
|
if (!this.root || !this.root.entityMgr) {
|
||||||
return;
|
// Game got stopped
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const entityRef = this.root.entityMgr.findByUid(uid, false);
|
const entityRef = this.root.entityMgr.findByUid(uid, false);
|
||||||
if (!entityRef) {
|
if (!entityRef) {
|
||||||
// outdated
|
// outdated
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const constantComp = entityRef.components.ConstantSignal;
|
const constantComp = entityRef.components.ConstantSignal;
|
||||||
if (!constantComp) {
|
if (!constantComp) {
|
||||||
// no longer interesting
|
// no longer interesting
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.root.logic.tryDeleteBuilding(entityRef);
|
this.root.logic.tryDeleteBuilding(entityRef);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,6 +5,7 @@ import { Rectangle } from "../../core/rectangle";
|
|||||||
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
||||||
import { dirInterval } from "../../core/utils";
|
import { dirInterval } from "../../core/utils";
|
||||||
import { enumDirection, enumDirectionToVector } from "../../core/vector";
|
import { enumDirection, enumDirectionToVector } from "../../core/vector";
|
||||||
|
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { BeltComponent } from "../components/belt";
|
import { BeltComponent } from "../components/belt";
|
||||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||||
@ -329,6 +330,64 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Limit the progress to the maximum available space on the next belt (also see #1000)
|
||||||
|
let progress = slot.progress;
|
||||||
|
const nextBeltPath = slot.cachedBeltPath;
|
||||||
|
if (nextBeltPath) {
|
||||||
|
/*
|
||||||
|
If you imagine the track between the center of the building and the center of the first belt as
|
||||||
|
a range from 0 to 1:
|
||||||
|
|
||||||
|
Building Belt
|
||||||
|
| X | X |
|
||||||
|
| 0...................1 |
|
||||||
|
|
||||||
|
And for example the first item on belt has a distance of 0.4 to the beginning of the belt:
|
||||||
|
|
||||||
|
Building Belt
|
||||||
|
| X | X |
|
||||||
|
| 0...................1 |
|
||||||
|
^ item
|
||||||
|
|
||||||
|
Then the space towards this first item is always 0.5 (the distance from the center of the building to the beginning of the belt)
|
||||||
|
PLUS the spacing to the item, so in this case 0.5 + 0.4 = 0.9:
|
||||||
|
|
||||||
|
Building Belt
|
||||||
|
| X | X |
|
||||||
|
| 0...................1 |
|
||||||
|
^ item @ 0.9
|
||||||
|
|
||||||
|
Since items must not get clashed, we need to substract some spacing (lets assume it is 0.6, exact value see globalConfig.itemSpacingOnBelts),
|
||||||
|
So we do 0.9 - globalConfig.itemSpacingOnBelts = 0.3
|
||||||
|
|
||||||
|
Building Belt
|
||||||
|
| X | X |
|
||||||
|
| 0...................1 |
|
||||||
|
^ ^ item @ 0.9
|
||||||
|
^ max progress = 0.3
|
||||||
|
|
||||||
|
Because now our range actually only goes to the end of the building, and not towards the center of the building, we need to multiply
|
||||||
|
all values by 2:
|
||||||
|
|
||||||
|
Building Belt
|
||||||
|
| X | X |
|
||||||
|
| 0.........1.........2 |
|
||||||
|
^ ^ item @ 1.8
|
||||||
|
^ max progress = 0.6
|
||||||
|
|
||||||
|
And that's it! If you summarize the calculations from above into a formula, you get the one below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const maxProgress =
|
||||||
|
(0.5 + nextBeltPath.spacingToFirstItem - globalConfig.itemSpacingOnBelts) * 2;
|
||||||
|
progress = Math.min(maxProgress, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if the item would barely be visible
|
||||||
|
if (progress < 0.05) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const realPosition = staticComp.localTileToWorld(slot.pos);
|
const realPosition = staticComp.localTileToWorld(slot.pos);
|
||||||
if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) {
|
if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) {
|
||||||
// Not within this chunk
|
// Not within this chunk
|
||||||
@ -338,8 +397,8 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
const realDirection = staticComp.localDirectionToWorld(slot.direction);
|
const realDirection = staticComp.localDirectionToWorld(slot.direction);
|
||||||
const realDirectionVector = enumDirectionToVector[realDirection];
|
const realDirectionVector = enumDirectionToVector[realDirection];
|
||||||
|
|
||||||
const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
|
const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * progress;
|
||||||
const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
|
const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * progress;
|
||||||
|
|
||||||
const worldX = tileX * globalConfig.tileSize;
|
const worldX = tileX * globalConfig.tileSize;
|
||||||
const worldY = tileY * globalConfig.tileSize;
|
const worldY = tileY * globalConfig.tileSize;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
import { globalConfig } from "../../core/config";
|
||||||
import { Loader } from "../../core/loader";
|
import { Loader } from "../../core/loader";
|
||||||
import { smoothPulse } from "../../core/utils";
|
import { round1DigitLocalized, smoothPulse } from "../../core/utils";
|
||||||
import { enumItemProcessorRequirements, enumItemProcessorTypes } from "../components/item_processor";
|
import { enumItemProcessorRequirements, enumItemProcessorTypes } from "../components/item_processor";
|
||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
import { GameSystem } from "../game_system";
|
import { GameSystem } from "../game_system";
|
||||||
@ -92,7 +92,7 @@ export class ItemProcessorOverlaysSystem extends GameSystem {
|
|||||||
parameters.context.textAlign = "center";
|
parameters.context.textAlign = "center";
|
||||||
parameters.context.font = "bold 10px GameFont";
|
parameters.context.font = "bold 10px GameFont";
|
||||||
parameters.context.fillText(
|
parameters.context.fillText(
|
||||||
"" + Math.round(readerComp.lastThroughput * 10) / 10,
|
round1DigitLocalized(readerComp.lastThroughput),
|
||||||
(staticComp.origin.x + 0.5) * globalConfig.tileSize,
|
(staticComp.origin.x + 0.5) * globalConfig.tileSize,
|
||||||
(staticComp.origin.y + 0.62) * globalConfig.tileSize
|
(staticComp.origin.y + 0.62) * globalConfig.tileSize
|
||||||
);
|
);
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
enumInvertedDirections,
|
enumInvertedDirections,
|
||||||
Vector,
|
Vector,
|
||||||
} from "../../core/vector";
|
} from "../../core/vector";
|
||||||
|
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { arrayWireRotationVariantToType, MetaWireBuilding } from "../buildings/wire";
|
import { arrayWireRotationVariantToType, MetaWireBuilding } from "../buildings/wire";
|
||||||
import { getCodeFromBuildingData } from "../building_codes";
|
import { getCodeFromBuildingData } from "../building_codes";
|
||||||
@ -697,6 +698,8 @@ export class WireSystem extends GameSystemWithFilter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.place5000Wires, entity);
|
||||||
|
|
||||||
// Invalidate affected area
|
// Invalidate affected area
|
||||||
const originalRect = staticComp.getTileSpaceBounds();
|
const originalRect = staticComp.getTileSpaceBounds();
|
||||||
const affectedArea = originalRect.expandedInAllDirections(1);
|
const affectedArea = originalRect.expandedInAllDirections(1);
|
||||||
|
2
src/js/globals.d.ts
vendored
@ -19,6 +19,8 @@ declare const G_BUILD_VERSION: string;
|
|||||||
declare const G_ALL_UI_IMAGES: Array<string>;
|
declare const G_ALL_UI_IMAGES: Array<string>;
|
||||||
declare const G_IS_RELEASE: boolean;
|
declare const G_IS_RELEASE: boolean;
|
||||||
|
|
||||||
|
declare const G_CHINA_VERSION: boolean;
|
||||||
|
|
||||||
// Polyfills
|
// Polyfills
|
||||||
declare interface String {
|
declare interface String {
|
||||||
replaceAll(search: string, replacement: string): string;
|
replaceAll(search: string, replacement: string): string;
|
||||||
|
@ -8,113 +8,180 @@ export const LANGUAGES = {
|
|||||||
code: "en",
|
code: "en",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"de": {
|
|
||||||
name: "Deutsch",
|
|
||||||
data: require("./built-temp/base-de.json"),
|
|
||||||
code: "de",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
name: "Français",
|
|
||||||
data: require("./built-temp/base-fr.json"),
|
|
||||||
code: "fr",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
name: "日本語",
|
|
||||||
data: require("./built-temp/base-ja.json"),
|
|
||||||
code: "ja",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"pt-PT": {
|
|
||||||
name: "Português (Portugal)",
|
|
||||||
data: require("./built-temp/base-pt-PT.json"),
|
|
||||||
code: "pt",
|
|
||||||
region: "PT",
|
|
||||||
},
|
|
||||||
"pt-BR": {
|
|
||||||
name: "Português (Brasil)",
|
|
||||||
data: require("./built-temp/base-pt-BR.json"),
|
|
||||||
code: "pt",
|
|
||||||
region: "BR",
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
name: "Русский",
|
|
||||||
data: require("./built-temp/base-ru.json"),
|
|
||||||
code: "ru",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"cs": {
|
|
||||||
name: "Čeština",
|
|
||||||
data: require("./built-temp/base-cz.json"),
|
|
||||||
code: "cs",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"es-419": {
|
|
||||||
name: "Español",
|
|
||||||
data: require("./built-temp/base-es.json"),
|
|
||||||
code: "es",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"pl": {
|
|
||||||
name: "Polski",
|
|
||||||
data: require("./built-temp/base-pl.json"),
|
|
||||||
code: "pl",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"kor": {
|
|
||||||
name: "한국어",
|
|
||||||
data: require("./built-temp/base-kor.json"),
|
|
||||||
code: "kor",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
name: "Nederlands",
|
|
||||||
data: require("./built-temp/base-nl.json"),
|
|
||||||
code: "nl",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
"no": {
|
|
||||||
name: "Norsk",
|
|
||||||
data: require("./built-temp/base-no.json"),
|
|
||||||
code: "no",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
"tr": {
|
|
||||||
name: "Türkçe",
|
|
||||||
data: require("./built-temp/base-tr.json"),
|
|
||||||
code: "tr",
|
|
||||||
region: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
"zh-CN": {
|
"zh-CN": {
|
||||||
// simplified
|
// simplified chinese
|
||||||
name: "中文简体",
|
name: "简体中文",
|
||||||
data: require("./built-temp/base-zh-CN.json"),
|
data: require("./built-temp/base-zh-CN.json"),
|
||||||
code: "zh",
|
code: "zh",
|
||||||
region: "CN",
|
region: "CN",
|
||||||
},
|
},
|
||||||
|
|
||||||
"zh-TW": {
|
"zh-TW": {
|
||||||
// traditional
|
// traditional chinese
|
||||||
name: "中文繁體",
|
name: "繁體中文",
|
||||||
data: require("./built-temp/base-zh-TW.json"),
|
data: require("./built-temp/base-zh-TW.json"),
|
||||||
code: "zh",
|
code: "zh",
|
||||||
region: "TW",
|
region: "TW",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"ja": {
|
||||||
|
// japanese
|
||||||
|
name: "日本語",
|
||||||
|
data: require("./built-temp/base-ja.json"),
|
||||||
|
code: "ja",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"kor": {
|
||||||
|
// korean
|
||||||
|
name: "한국어",
|
||||||
|
data: require("./built-temp/base-kor.json"),
|
||||||
|
code: "kor",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"cs": {
|
||||||
|
// czech
|
||||||
|
name: "Čeština",
|
||||||
|
data: require("./built-temp/base-cz.json"),
|
||||||
|
code: "cs",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"da": {
|
||||||
|
// danish
|
||||||
|
name: "Dansk",
|
||||||
|
data: require("./built-temp/base-da.json"),
|
||||||
|
code: "da",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"de": {
|
||||||
|
// german
|
||||||
|
name: "Deutsch",
|
||||||
|
data: require("./built-temp/base-de.json"),
|
||||||
|
code: "de",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"es-419": {
|
||||||
|
// spanish
|
||||||
|
name: "Español",
|
||||||
|
data: require("./built-temp/base-es.json"),
|
||||||
|
code: "es",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"fr": {
|
||||||
|
// french
|
||||||
|
name: "Français",
|
||||||
|
data: require("./built-temp/base-fr.json"),
|
||||||
|
code: "fr",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"it": {
|
||||||
|
// italian
|
||||||
|
name: "Italiano",
|
||||||
|
data: require("./built-temp/base-it.json"),
|
||||||
|
code: "it",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"hu": {
|
||||||
|
// hungarian
|
||||||
|
name: "Magyar",
|
||||||
|
data: require("./built-temp/base-hu.json"),
|
||||||
|
code: "hu",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"nl": {
|
||||||
|
// dutch
|
||||||
|
name: "Nederlands",
|
||||||
|
data: require("./built-temp/base-nl.json"),
|
||||||
|
code: "nl",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"no": {
|
||||||
|
// norwegian
|
||||||
|
name: "Norsk",
|
||||||
|
data: require("./built-temp/base-no.json"),
|
||||||
|
code: "no",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"pl": {
|
||||||
|
// polish
|
||||||
|
name: "Polski",
|
||||||
|
data: require("./built-temp/base-pl.json"),
|
||||||
|
code: "pl",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"pt-PT": {
|
||||||
|
// portuguese
|
||||||
|
name: "Português",
|
||||||
|
data: require("./built-temp/base-pt-PT.json"),
|
||||||
|
code: "pt",
|
||||||
|
region: "PT",
|
||||||
|
},
|
||||||
|
|
||||||
|
"pt-BR": {
|
||||||
|
// portuguese - brazil
|
||||||
|
name: "Português - Brasil",
|
||||||
|
data: require("./built-temp/base-pt-BR.json"),
|
||||||
|
code: "pt",
|
||||||
|
region: "BR",
|
||||||
|
},
|
||||||
|
|
||||||
|
"ro": {
|
||||||
|
// romanian
|
||||||
|
name: "Română",
|
||||||
|
data: require("./built-temp/base-ro.json"),
|
||||||
|
code: "pt",
|
||||||
|
region: "BR",
|
||||||
|
},
|
||||||
|
|
||||||
|
"ru": {
|
||||||
|
// russian
|
||||||
|
name: "Русский",
|
||||||
|
data: require("./built-temp/base-ru.json"),
|
||||||
|
code: "ru",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"fi": {
|
||||||
|
// finish
|
||||||
|
name: "Suomi",
|
||||||
|
data: require("./built-temp/base-fi.json"),
|
||||||
|
code: "fi",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
"sv": {
|
"sv": {
|
||||||
|
// swedish
|
||||||
name: "Svenska",
|
name: "Svenska",
|
||||||
data: require("./built-temp/base-sv.json"),
|
data: require("./built-temp/base-sv.json"),
|
||||||
code: "sv",
|
code: "sv",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
"da": {
|
"tr": {
|
||||||
name: "Dansk",
|
// turkish
|
||||||
data: require("./built-temp/base-da.json"),
|
name: "Türkçe",
|
||||||
code: "da",
|
data: require("./built-temp/base-tr.json"),
|
||||||
|
code: "tr",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
"uk": {
|
||||||
|
// ukrainian
|
||||||
|
name: "Українська",
|
||||||
|
data: require("./built-temp/base-uk.json"),
|
||||||
|
code: "uk",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
642
src/js/platform/achievement_provider.js
Normal file
@ -0,0 +1,642 @@
|
|||||||
|
/* typehints:start */
|
||||||
|
import { Application } from "../application";
|
||||||
|
import { Entity } from "../game/entity";
|
||||||
|
import { GameRoot } from "../game/root";
|
||||||
|
import { THEMES } from "../game/theme";
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
|
import { enumAnalyticsDataSource } from "../game/production_analytics";
|
||||||
|
import { ShapeDefinition } from "../game/shape_definition";
|
||||||
|
import { ShapeItem } from "../game/items/shape_item";
|
||||||
|
import { globalConfig } from "../core/config";
|
||||||
|
|
||||||
|
export const ACHIEVEMENTS = {
|
||||||
|
belt500Tiles: "belt500Tiles",
|
||||||
|
blueprint100k: "blueprint100k",
|
||||||
|
blueprint1m: "blueprint1m",
|
||||||
|
completeLvl26: "completeLvl26",
|
||||||
|
cutShape: "cutShape",
|
||||||
|
darkMode: "darkMode",
|
||||||
|
destroy1000: "destroy1000",
|
||||||
|
irrelevantShape: "irrelevantShape",
|
||||||
|
level100: "level100",
|
||||||
|
level50: "level50",
|
||||||
|
logoBefore18: "logoBefore18",
|
||||||
|
mam: "mam",
|
||||||
|
mapMarkers15: "mapMarkers15",
|
||||||
|
noBeltUpgradesUntilBp: "noBeltUpgradesUntilBp",
|
||||||
|
noInverseRotater: "noInverseRotater",
|
||||||
|
oldLevel17: "oldLevel17",
|
||||||
|
openWires: "openWires",
|
||||||
|
paintShape: "paintShape",
|
||||||
|
place5000Wires: "place5000Wires",
|
||||||
|
placeBlueprint: "placeBlueprint",
|
||||||
|
placeBp1000: "placeBp1000",
|
||||||
|
play1h: "play1h",
|
||||||
|
play10h: "play10h",
|
||||||
|
play20h: "play20h",
|
||||||
|
produceLogo: "produceLogo",
|
||||||
|
produceMsLogo: "produceMsLogo",
|
||||||
|
produceRocket: "produceRocket",
|
||||||
|
rotateShape: "rotateShape",
|
||||||
|
speedrunBp30: "speedrunBp30",
|
||||||
|
speedrunBp60: "speedrunBp60",
|
||||||
|
speedrunBp120: "speedrunBp120",
|
||||||
|
stack4Layers: "stack4Layers",
|
||||||
|
stackShape: "stackShape",
|
||||||
|
store100Unique: "store100Unique",
|
||||||
|
storeShape: "storeShape",
|
||||||
|
throughputBp25: "throughputBp25",
|
||||||
|
throughputBp50: "throughputBp50",
|
||||||
|
throughputLogo25: "throughputLogo25",
|
||||||
|
throughputLogo50: "throughputLogo50",
|
||||||
|
throughputRocket10: "throughputRocket10",
|
||||||
|
throughputRocket20: "throughputRocket20",
|
||||||
|
trash1000: "trash1000",
|
||||||
|
unlockWires: "unlockWires",
|
||||||
|
upgradesTier5: "upgradesTier5",
|
||||||
|
upgradesTier8: "upgradesTier8",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {keyof typeof THEMES} */
|
||||||
|
const DARK_MODE = "dark";
|
||||||
|
|
||||||
|
const HOUR_1 = 3600; // Seconds
|
||||||
|
const HOUR_10 = HOUR_1 * 10;
|
||||||
|
const HOUR_20 = HOUR_1 * 20;
|
||||||
|
const ITEM_SHAPE = ShapeItem.getId();
|
||||||
|
const MINUTE_30 = 1800; // Seconds
|
||||||
|
const MINUTE_60 = MINUTE_30 * 2;
|
||||||
|
const MINUTE_120 = MINUTE_30 * 4;
|
||||||
|
const ROTATER_CCW_CODE = 12;
|
||||||
|
const ROTATER_180_CODE = 13;
|
||||||
|
const SHAPE_BP = "CbCbCbRb:CwCwCwCw";
|
||||||
|
const SHAPE_LOGO = "RuCw--Cw:----Ru--";
|
||||||
|
const SHAPE_MS_LOGO = "RgRyRbRr";
|
||||||
|
const SHAPE_OLD_LEVEL_17 = "WrRgWrRg:CwCrCwCr:SgSgSgSg";
|
||||||
|
const SHAPE_ROCKET = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
||||||
|
|
||||||
|
/** @type {Layer} */
|
||||||
|
const WIRE_LAYER = "wires";
|
||||||
|
|
||||||
|
export class AchievementProviderInterface {
|
||||||
|
/* typehints:start */
|
||||||
|
collection = /** @type {AchievementCollection|undefined} */ (null);
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
|
/** @param {Application} app */
|
||||||
|
constructor(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the achievement provider.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
initialize() {
|
||||||
|
abstract;
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opportunity to do additional initialization work with the GameRoot.
|
||||||
|
* @param {GameRoot} root
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
onLoad(root) {
|
||||||
|
abstract;
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
|
hasLoaded() {
|
||||||
|
abstract;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call to activate an achievement with the provider
|
||||||
|
* @param {string} key - Maps to an Achievement
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
activate(key) {
|
||||||
|
abstract;
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if achievements are supported in the current build
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
hasAchievements() {
|
||||||
|
abstract;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Achievement {
|
||||||
|
/** @param {string} key - An ACHIEVEMENTS key */
|
||||||
|
constructor(key) {
|
||||||
|
this.key = key;
|
||||||
|
this.activate = null;
|
||||||
|
this.activatePromise = null;
|
||||||
|
this.receiver = null;
|
||||||
|
this.signal = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
|
||||||
|
isValid() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock() {
|
||||||
|
if (!this.activatePromise) {
|
||||||
|
this.activatePromise = this.activate(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activatePromise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AchievementCollection {
|
||||||
|
/**
|
||||||
|
* @param {function} activate - Resolves when provider activation is complete
|
||||||
|
*/
|
||||||
|
constructor(activate) {
|
||||||
|
this.map = new Map();
|
||||||
|
this.activate = activate;
|
||||||
|
|
||||||
|
this.add(ACHIEVEMENTS.belt500Tiles, {
|
||||||
|
isValid: this.isBelt500TilesValid,
|
||||||
|
signal: "entityAdded",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.blueprint100k, this.createBlueprintOptions(100000));
|
||||||
|
this.add(ACHIEVEMENTS.blueprint1m, this.createBlueprintOptions(1000000));
|
||||||
|
this.add(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26));
|
||||||
|
this.add(ACHIEVEMENTS.cutShape);
|
||||||
|
this.add(ACHIEVEMENTS.darkMode, {
|
||||||
|
isValid: this.isDarkModeValid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.destroy1000, {
|
||||||
|
isValid: this.isDestroy1000Valid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.irrelevantShape, {
|
||||||
|
isValid: this.isIrrelevantShapeValid,
|
||||||
|
signal: "shapeDelivered",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.level100, this.createLevelOptions(100));
|
||||||
|
this.add(ACHIEVEMENTS.level50, this.createLevelOptions(50));
|
||||||
|
this.add(ACHIEVEMENTS.logoBefore18, {
|
||||||
|
isValid: this.isLogoBefore18Valid,
|
||||||
|
signal: "itemProduced",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.mam, {
|
||||||
|
isValid: this.isMamValid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.mapMarkers15, {
|
||||||
|
isValid: this.isMapMarkers15Valid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.noBeltUpgradesUntilBp, {
|
||||||
|
isValid: this.isNoBeltUpgradesUntilBpValid,
|
||||||
|
signal: "storyGoalCompleted",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.noInverseRotater, {
|
||||||
|
init: this.initNoInverseRotater,
|
||||||
|
isValid: this.isNoInverseRotaterValid,
|
||||||
|
signal: "storyGoalCompleted",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.oldLevel17, this.createShapeOptions(SHAPE_OLD_LEVEL_17));
|
||||||
|
this.add(ACHIEVEMENTS.openWires, {
|
||||||
|
isValid: this.isOpenWiresValid,
|
||||||
|
signal: "editModeChanged",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.paintShape);
|
||||||
|
this.add(ACHIEVEMENTS.place5000Wires, {
|
||||||
|
isValid: this.isPlace5000WiresValid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.placeBlueprint, {
|
||||||
|
isValid: this.isPlaceBlueprintValid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.placeBp1000, {
|
||||||
|
isValid: this.isPlaceBp1000Valid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.play1h, this.createTimeOptions(HOUR_1));
|
||||||
|
this.add(ACHIEVEMENTS.play10h, this.createTimeOptions(HOUR_10));
|
||||||
|
this.add(ACHIEVEMENTS.play20h, this.createTimeOptions(HOUR_20));
|
||||||
|
this.add(ACHIEVEMENTS.produceLogo, this.createShapeOptions(SHAPE_LOGO));
|
||||||
|
this.add(ACHIEVEMENTS.produceRocket, this.createShapeOptions(SHAPE_ROCKET));
|
||||||
|
this.add(ACHIEVEMENTS.produceMsLogo, this.createShapeOptions(SHAPE_MS_LOGO));
|
||||||
|
this.add(ACHIEVEMENTS.rotateShape);
|
||||||
|
this.add(ACHIEVEMENTS.speedrunBp30, this.createSpeedOptions(12, MINUTE_30));
|
||||||
|
this.add(ACHIEVEMENTS.speedrunBp60, this.createSpeedOptions(12, MINUTE_60));
|
||||||
|
this.add(ACHIEVEMENTS.speedrunBp120, this.createSpeedOptions(12, MINUTE_120));
|
||||||
|
this.add(ACHIEVEMENTS.stack4Layers, {
|
||||||
|
isValid: this.isStack4LayersValid,
|
||||||
|
signal: "itemProduced",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.stackShape);
|
||||||
|
this.add(ACHIEVEMENTS.store100Unique, {
|
||||||
|
init: this.initStore100Unique,
|
||||||
|
isValid: this.isStore100UniqueValid,
|
||||||
|
signal: "shapeDelivered",
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.storeShape, {
|
||||||
|
init: this.initStoreShape,
|
||||||
|
isValid: this.isStoreShapeValid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.throughputBp25, this.createRateOptions(SHAPE_BP, 25));
|
||||||
|
this.add(ACHIEVEMENTS.throughputBp50, this.createRateOptions(SHAPE_BP, 50));
|
||||||
|
this.add(ACHIEVEMENTS.throughputLogo25, this.createRateOptions(SHAPE_LOGO, 25));
|
||||||
|
this.add(ACHIEVEMENTS.throughputLogo50, this.createRateOptions(SHAPE_LOGO, 50));
|
||||||
|
this.add(ACHIEVEMENTS.throughputRocket10, this.createRateOptions(SHAPE_ROCKET, 10));
|
||||||
|
this.add(ACHIEVEMENTS.throughputRocket20, this.createRateOptions(SHAPE_ROCKET, 20));
|
||||||
|
this.add(ACHIEVEMENTS.trash1000, {
|
||||||
|
init: this.initTrash1000,
|
||||||
|
isValid: this.isTrash1000Valid,
|
||||||
|
});
|
||||||
|
this.add(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20));
|
||||||
|
this.add(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5));
|
||||||
|
this.add(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {GameRoot} root */
|
||||||
|
initialize(root) {
|
||||||
|
this.root = root;
|
||||||
|
this.root.signals.achievementCheck.add(this.unlock, this);
|
||||||
|
this.root.signals.bulkAchievementCheck.add(this.bulkUnlock, this);
|
||||||
|
|
||||||
|
for (let [key, achievement] of this.map.entries()) {
|
||||||
|
if (achievement.signal) {
|
||||||
|
achievement.receiver = this.unlock.bind(this, key);
|
||||||
|
this.root.signals[achievement.signal].add(achievement.receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (achievement.init) {
|
||||||
|
achievement.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hasDefaultReceivers()) {
|
||||||
|
this.root.signals.achievementCheck.remove(this.unlock);
|
||||||
|
this.root.signals.bulkAchievementCheck.remove(this.bulkUnlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key - Maps to an Achievement
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {function} [options.init]
|
||||||
|
* @param {function} [options.isValid]
|
||||||
|
* @param {string} [options.signal]
|
||||||
|
*/
|
||||||
|
add(key, options = {}) {
|
||||||
|
if (G_IS_DEV) {
|
||||||
|
assert(ACHIEVEMENTS[key], "Achievement key not found: ", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const achievement = new Achievement(key);
|
||||||
|
|
||||||
|
achievement.activate = this.activate;
|
||||||
|
|
||||||
|
if (options.init) {
|
||||||
|
achievement.init = options.init.bind(this, achievement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.isValid) {
|
||||||
|
achievement.isValid = options.isValid.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.signal) {
|
||||||
|
achievement.signal = options.signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map.set(key, achievement);
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkUnlock() {
|
||||||
|
for (let i = 0; i < arguments.length; i += 2) {
|
||||||
|
this.unlock(arguments[i], arguments[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key - Maps to an Achievement
|
||||||
|
* @param {any} data - Data received from signal dispatches for validation
|
||||||
|
*/
|
||||||
|
unlock(key, data) {
|
||||||
|
if (!this.map.has(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const achievement = this.map.get(key);
|
||||||
|
|
||||||
|
if (!achievement.isValid(data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
achievement
|
||||||
|
.unlock()
|
||||||
|
.then(() => {
|
||||||
|
this.onActivate(null, key);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.onActivate(err, key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up after achievement activation attempt with the provider. Could
|
||||||
|
* utilize err to retry some number of times if needed.
|
||||||
|
* @param {?Error} err - Error is null if activation was successful
|
||||||
|
* @param {string} key - Maps to an Achievement
|
||||||
|
*/
|
||||||
|
onActivate(err, key) {
|
||||||
|
this.remove(key);
|
||||||
|
|
||||||
|
if (!this.hasDefaultReceivers()) {
|
||||||
|
this.root.signals.achievementCheck.remove(this.unlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} key - Maps to an Achievement */
|
||||||
|
remove(key) {
|
||||||
|
const achievement = this.map.get(key);
|
||||||
|
if (achievement) {
|
||||||
|
if (achievement.receiver) {
|
||||||
|
this.root.signals[achievement.signal].remove(achievement.receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the collection-level achievementCheck receivers are still
|
||||||
|
* necessary.
|
||||||
|
*/
|
||||||
|
hasDefaultReceivers() {
|
||||||
|
if (!this.map.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let achievement of this.map.values()) {
|
||||||
|
if (!achievement.signal) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remaining methods exist to extend Achievement instances within the
|
||||||
|
* collection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hasAllUpgradesAtLeastAtTier(tier) {
|
||||||
|
const upgrades = this.root.gameMode.getUpgrades();
|
||||||
|
|
||||||
|
for (let upgradeId in upgrades) {
|
||||||
|
if (this.root.hubGoals.getUpgradeLevel(upgradeId) < tier - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ShapeItem} item
|
||||||
|
* @param {string} shape
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isShape(item, shape) {
|
||||||
|
return item.getItemType() === ITEM_SHAPE && item.definition.getHash() === shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
createBlueprintOptions(count) {
|
||||||
|
return {
|
||||||
|
init: ({ key }) => this.unlock(key, ShapeDefinition.fromShortKey(SHAPE_BP)),
|
||||||
|
isValid: definition =>
|
||||||
|
definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= count,
|
||||||
|
signal: "shapeDelivered",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createLevelOptions(level) {
|
||||||
|
return {
|
||||||
|
init: ({ key }) => this.unlock(key, this.root.hubGoals.level),
|
||||||
|
isValid: currentLevel => currentLevel >= level,
|
||||||
|
signal: "storyGoalCompleted",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createRateOptions(shape, rate) {
|
||||||
|
return {
|
||||||
|
isValid: () => {
|
||||||
|
return (
|
||||||
|
this.root.productionAnalytics.getCurrentShapeRateRaw(
|
||||||
|
enumAnalyticsDataSource.delivered,
|
||||||
|
this.root.shapeDefinitionMgr.getShapeFromShortKey(shape)
|
||||||
|
) /
|
||||||
|
globalConfig.analyticsSliceDurationSeconds >=
|
||||||
|
rate
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createShapeOptions(shape) {
|
||||||
|
return {
|
||||||
|
isValid: item => this.isShape(item, shape),
|
||||||
|
signal: "itemProduced",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createSpeedOptions(level, time) {
|
||||||
|
return {
|
||||||
|
isValid: currentLevel => currentLevel >= level && this.root.time.now() < time,
|
||||||
|
signal: "storyGoalCompleted",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createTimeOptions(duration) {
|
||||||
|
return {
|
||||||
|
isValid: () => this.root.time.now() >= duration,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createUpgradeOptions(tier) {
|
||||||
|
return {
|
||||||
|
init: ({ key }) => this.unlock(key, null),
|
||||||
|
isValid: () => this.hasAllUpgradesAtLeastAtTier(tier),
|
||||||
|
signal: "upgradePurchased",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Entity} entity @returns {boolean} */
|
||||||
|
isBelt500TilesValid(entity) {
|
||||||
|
return entity.components.Belt && entity.components.Belt.assignedPath.totalLength >= 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
|
isDarkModeValid() {
|
||||||
|
return this.root.app.settings.currentData.settings.theme === DARK_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} count @returns {boolean} */
|
||||||
|
isDestroy1000Valid(count) {
|
||||||
|
return count >= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {ShapeDefinition} definition @returns {boolean} */
|
||||||
|
isIrrelevantShapeValid(definition) {
|
||||||
|
const levels = this.root.gameMode.getLevelDefinitions();
|
||||||
|
for (let i = 0; i < levels.length; i++) {
|
||||||
|
if (definition.cachedHash === levels[i].shape) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgrades = this.root.gameMode.getUpgrades();
|
||||||
|
for (let upgradeId in upgrades) {
|
||||||
|
for (const tier in upgrades[upgradeId]) {
|
||||||
|
const requiredShapes = upgrades[upgradeId][tier].required;
|
||||||
|
for (let i = 0; i < requiredShapes.length; i++) {
|
||||||
|
if (definition.cachedHash === requiredShapes[i].shape) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {ShapeItem} item @returns {boolean} */
|
||||||
|
isLogoBefore18Valid(item) {
|
||||||
|
return this.root.hubGoals.level < 18 && this.isShape(item, SHAPE_LOGO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
|
isMamValid() {
|
||||||
|
return this.root.hubGoals.level > 27 && !this.root.savegame.currentData.stats.failedMam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} count @returns {boolean} */
|
||||||
|
isMapMarkers15Valid(count) {
|
||||||
|
return count >= 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} level
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isNoBeltUpgradesUntilBpValid(level) {
|
||||||
|
return level >= 12 && this.root.hubGoals.upgradeLevels.belt === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
initNoInverseRotater() {
|
||||||
|
if (this.root.savegame.currentData.stats.usedInverseRotater === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entities = this.root.entityMgr.componentToEntity.StaticMapEntity;
|
||||||
|
|
||||||
|
let usedInverseRotater = false;
|
||||||
|
for (var i = 0; i < entities.length; i++) {
|
||||||
|
const entity = entities[i].components.StaticMapEntity;
|
||||||
|
|
||||||
|
if (entity.code === ROTATER_CCW_CODE || entity.code === ROTATER_180_CODE) {
|
||||||
|
usedInverseRotater = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.root.savegame.currentData.stats.usedInverseRotater = usedInverseRotater;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} level @returns {boolean} */
|
||||||
|
isNoInverseRotaterValid(level) {
|
||||||
|
return level >= 14 && !this.root.savegame.currentData.stats.usedInverseRotater;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} currentLayer @returns {boolean} */
|
||||||
|
isOpenWiresValid(currentLayer) {
|
||||||
|
return currentLayer === WIRE_LAYER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Entity} entity @returns {boolean} */
|
||||||
|
isPlace5000WiresValid(entity) {
|
||||||
|
return (
|
||||||
|
entity.components.Wire &&
|
||||||
|
entity.registered &&
|
||||||
|
entity.root.entityMgr.componentToEntity.Wire.length >= 5000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} count @returns {boolean} */
|
||||||
|
isPlaceBlueprintValid(count) {
|
||||||
|
return count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} count @returns {boolean} */
|
||||||
|
isPlaceBp1000Valid(count) {
|
||||||
|
return count >= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {ShapeItem} item @returns {boolean} */
|
||||||
|
isStack4LayersValid(item) {
|
||||||
|
return item.getItemType() === ITEM_SHAPE && item.definition.layers.length === 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Achievement} achievement */
|
||||||
|
initStore100Unique({ key }) {
|
||||||
|
this.unlock(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
|
isStore100UniqueValid() {
|
||||||
|
return Object.keys(this.root.hubGoals.storedShapes).length >= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Achievement} achievement */
|
||||||
|
initStoreShape({ key }) {
|
||||||
|
this.unlock(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
|
isStoreShapeValid() {
|
||||||
|
const entities = this.root.systemMgr.systems.storage.allEntities;
|
||||||
|
|
||||||
|
if (entities.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < entities.length; i++) {
|
||||||
|
if (entities[i].components.Storage.storedCount > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Achievement} achievement */
|
||||||
|
initTrash1000({ key }) {
|
||||||
|
if (Number(this.root.savegame.currentData.stats.trashedCount)) {
|
||||||
|
this.unlock(key, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.root.savegame.currentData.stats.trashedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} count @returns {boolean} */
|
||||||
|
isTrash1000Valid(count) {
|
||||||
|
this.root.savegame.currentData.stats.trashedCount += count;
|
||||||
|
|
||||||
|
return this.root.savegame.currentData.stats.trashedCount >= 1000;
|
||||||
|
}
|
||||||
|
}
|
23
src/js/platform/browser/no_achievement_provider.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { AchievementProviderInterface } from "../achievement_provider";
|
||||||
|
|
||||||
|
export class NoAchievementProvider extends AchievementProviderInterface {
|
||||||
|
hasAchievements() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasLoaded() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
return Promise.reject(new Error("No achievements to load"));
|
||||||
|
}
|
||||||
|
|
||||||
|
activate() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
@ -58,11 +58,6 @@ export class StorageImplBrowser extends StorageInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
window.localStorage.setItem(filename, contents);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
readFileAsync(filename) {
|
readFileAsync(filename) {
|
||||||
if (this.currentBusyFilename === filename) {
|
if (this.currentBusyFilename === filename) {
|
||||||
logger.warn("Attempt to read", filename, "while write progress on it is ongoing!");
|
logger.warn("Attempt to read", filename, "while write progress on it is ongoing!");
|
||||||
|
@ -94,12 +94,6 @@ export class StorageImplBrowserIndexedDB extends StorageInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
// Not supported
|
|
||||||
this.writeFileAsync(filename, contents);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
readFileAsync(filename) {
|
readFileAsync(filename) {
|
||||||
if (!this.database) {
|
if (!this.database) {
|
||||||
return Promise.reject("Storage not ready");
|
return Promise.reject("Storage not ready");
|
||||||
|
@ -4,7 +4,9 @@ import { queryParamOptions } from "../../core/query_parameters";
|
|||||||
import { clamp } from "../../core/utils";
|
import { clamp } from "../../core/utils";
|
||||||
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
|
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
|
||||||
import { NoAdProvider } from "../ad_providers/no_ad_provider";
|
import { NoAdProvider } from "../ad_providers/no_ad_provider";
|
||||||
|
import { SteamAchievementProvider } from "../electron/steam_achievement_provider";
|
||||||
import { PlatformWrapperInterface } from "../wrapper";
|
import { PlatformWrapperInterface } from "../wrapper";
|
||||||
|
import { NoAchievementProvider } from "./no_achievement_provider";
|
||||||
import { StorageImplBrowser } from "./storage";
|
import { StorageImplBrowser } from "./storage";
|
||||||
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
||||||
|
|
||||||
@ -71,6 +73,7 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
|||||||
|
|
||||||
return this.detectStorageImplementation()
|
return this.detectStorageImplementation()
|
||||||
.then(() => this.initializeAdProvider())
|
.then(() => this.initializeAdProvider())
|
||||||
|
.then(() => this.initializeAchievementProvider())
|
||||||
.then(() => super.initialize());
|
.then(() => super.initialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +199,20 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeAchievementProvider() {
|
||||||
|
if (G_IS_DEV && globalConfig.debug.testAchievements) {
|
||||||
|
this.app.achievementProvider = new SteamAchievementProvider(this.app);
|
||||||
|
|
||||||
|
return this.app.achievementProvider.initialize().catch(err => {
|
||||||
|
logger.error("Failed to initialize achievement provider, disabling:", err);
|
||||||
|
|
||||||
|
this.app.achievementProvider = new NoAchievementProvider(this.app);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.app.achievementProvider.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
exitApp() {
|
exitApp() {
|
||||||
// Can not exit app
|
// Can not exit app
|
||||||
}
|
}
|
||||||
|
143
src/js/platform/electron/steam_achievement_provider.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/* typehints:start */
|
||||||
|
import { Application } from "../../application";
|
||||||
|
import { GameRoot } from "../../game/root";
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
|
import { createLogger } from "../../core/logging";
|
||||||
|
import { getIPCRenderer } from "../../core/utils";
|
||||||
|
import { ACHIEVEMENTS, AchievementCollection, AchievementProviderInterface } from "../achievement_provider";
|
||||||
|
|
||||||
|
const logger = createLogger("achievements/steam");
|
||||||
|
|
||||||
|
const ACHIEVEMENT_IDS = {
|
||||||
|
[ACHIEVEMENTS.belt500Tiles]: "belt_500_tiles",
|
||||||
|
[ACHIEVEMENTS.blueprint100k]: "blueprint_100k",
|
||||||
|
[ACHIEVEMENTS.blueprint1m]: "blueprint_1m",
|
||||||
|
[ACHIEVEMENTS.completeLvl26]: "complete_lvl_26",
|
||||||
|
[ACHIEVEMENTS.cutShape]: "cut_shape",
|
||||||
|
[ACHIEVEMENTS.darkMode]: "dark_mode",
|
||||||
|
[ACHIEVEMENTS.destroy1000]: "destroy_1000",
|
||||||
|
[ACHIEVEMENTS.irrelevantShape]: "irrelevant_shape",
|
||||||
|
[ACHIEVEMENTS.level100]: "level_100",
|
||||||
|
[ACHIEVEMENTS.level50]: "level_50",
|
||||||
|
[ACHIEVEMENTS.logoBefore18]: "logo_before_18",
|
||||||
|
[ACHIEVEMENTS.mam]: "mam",
|
||||||
|
[ACHIEVEMENTS.mapMarkers15]: "map_markers_15",
|
||||||
|
[ACHIEVEMENTS.openWires]: "open_wires",
|
||||||
|
[ACHIEVEMENTS.oldLevel17]: "old_level_17",
|
||||||
|
[ACHIEVEMENTS.noBeltUpgradesUntilBp]: "no_belt_upgrades_until_bp",
|
||||||
|
[ACHIEVEMENTS.noInverseRotater]: "no_inverse_rotator", // [sic]
|
||||||
|
[ACHIEVEMENTS.paintShape]: "paint_shape",
|
||||||
|
[ACHIEVEMENTS.place5000Wires]: "place_5000_wires",
|
||||||
|
[ACHIEVEMENTS.placeBlueprint]: "place_blueprint",
|
||||||
|
[ACHIEVEMENTS.placeBp1000]: "place_bp_1000",
|
||||||
|
[ACHIEVEMENTS.play1h]: "play_1h",
|
||||||
|
[ACHIEVEMENTS.play10h]: "play_10h",
|
||||||
|
[ACHIEVEMENTS.play20h]: "play_20h",
|
||||||
|
[ACHIEVEMENTS.produceLogo]: "produce_logo",
|
||||||
|
[ACHIEVEMENTS.produceMsLogo]: "produce_ms_logo",
|
||||||
|
[ACHIEVEMENTS.produceRocket]: "produce_rocket",
|
||||||
|
[ACHIEVEMENTS.rotateShape]: "rotate_shape",
|
||||||
|
[ACHIEVEMENTS.speedrunBp30]: "speedrun_bp_30",
|
||||||
|
[ACHIEVEMENTS.speedrunBp60]: "speedrun_bp_60",
|
||||||
|
[ACHIEVEMENTS.speedrunBp120]: "speedrun_bp_120",
|
||||||
|
[ACHIEVEMENTS.stack4Layers]: "stack_4_layers",
|
||||||
|
[ACHIEVEMENTS.stackShape]: "stack_shape",
|
||||||
|
[ACHIEVEMENTS.store100Unique]: "store_100_unique",
|
||||||
|
[ACHIEVEMENTS.storeShape]: "store_shape",
|
||||||
|
[ACHIEVEMENTS.throughputBp25]: "throughput_bp_25",
|
||||||
|
[ACHIEVEMENTS.throughputBp50]: "throughput_bp_50",
|
||||||
|
[ACHIEVEMENTS.throughputLogo25]: "throughput_logo_25",
|
||||||
|
[ACHIEVEMENTS.throughputLogo50]: "throughput_logo_50",
|
||||||
|
[ACHIEVEMENTS.throughputRocket10]: "throughput_rocket_10",
|
||||||
|
[ACHIEVEMENTS.throughputRocket20]: "throughput_rocket_20",
|
||||||
|
[ACHIEVEMENTS.trash1000]: "trash_1000",
|
||||||
|
[ACHIEVEMENTS.unlockWires]: "unlock_wires",
|
||||||
|
[ACHIEVEMENTS.upgradesTier5]: "upgrades_tier_5",
|
||||||
|
[ACHIEVEMENTS.upgradesTier8]: "upgrades_tier_8",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SteamAchievementProvider extends AchievementProviderInterface {
|
||||||
|
/** @param {Application} app */
|
||||||
|
constructor(app) {
|
||||||
|
super(app);
|
||||||
|
|
||||||
|
this.initialized = false;
|
||||||
|
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||||
|
|
||||||
|
if (G_IS_DEV) {
|
||||||
|
for (let key in ACHIEVEMENT_IDS) {
|
||||||
|
assert(this.collection.map.has(key), "Key not found in collection: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("Collection created with", this.collection.map.size, "achievements");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
|
hasAchievements() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GameRoot} root
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
onLoad(root) {
|
||||||
|
this.root = root;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||||
|
this.collection.initialize(root);
|
||||||
|
|
||||||
|
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
||||||
|
return Promise.resolve();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Failed to initialize the collection");
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<void>} */
|
||||||
|
initialize() {
|
||||||
|
if (!G_IS_STANDALONE) {
|
||||||
|
logger.warn("Steam unavailable. Achievements won't sync.");
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ipc = getIPCRenderer();
|
||||||
|
|
||||||
|
return this.ipc.invoke("steam:is-initialized").then(initialized => {
|
||||||
|
this.initialized = initialized;
|
||||||
|
|
||||||
|
if (!this.initialized) {
|
||||||
|
logger.warn("Steam failed to intialize. Achievements won't sync.");
|
||||||
|
} else {
|
||||||
|
logger.log("Steam achievement provider initialized");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
activate(key) {
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
if (!this.initialized) {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
} else {
|
||||||
|
promise = this.ipc.invoke("steam:activate-achievement", ACHIEVEMENT_IDS[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then(() => {
|
||||||
|
logger.log("Achievement activated:", key);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error("Failed to activate achievement:", key, err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -7,24 +7,6 @@ const logger = createLogger("electron-storage");
|
|||||||
export class StorageImplElectron extends StorageInterface {
|
export class StorageImplElectron extends StorageInterface {
|
||||||
constructor(app) {
|
constructor(app) {
|
||||||
super(app);
|
super(app);
|
||||||
|
|
||||||
/** @type {Object.<number, {resolve:Function, reject: Function}>} */
|
|
||||||
this.jobs = {};
|
|
||||||
this.jobId = 0;
|
|
||||||
|
|
||||||
getIPCRenderer().on("fs-response", (event, arg) => {
|
|
||||||
const id = arg.id;
|
|
||||||
if (!this.jobs[id]) {
|
|
||||||
logger.warn("Got unhandled FS response, job not known:", id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { resolve, reject } = this.jobs[id];
|
|
||||||
if (arg.result.success) {
|
|
||||||
resolve(arg.result.data);
|
|
||||||
} else {
|
|
||||||
reject(arg.result.error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -33,51 +15,53 @@ export class StorageImplElectron extends StorageInterface {
|
|||||||
|
|
||||||
writeFileAsync(filename, contents) {
|
writeFileAsync(filename, contents) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// ipcMain
|
getIPCRenderer()
|
||||||
const jobId = ++this.jobId;
|
.invoke("fs-job", {
|
||||||
this.jobs[jobId] = { resolve, reject };
|
type: "write",
|
||||||
|
filename,
|
||||||
getIPCRenderer().send("fs-job", {
|
contents,
|
||||||
type: "write",
|
})
|
||||||
filename,
|
.then(result => {
|
||||||
contents,
|
if (result.success) {
|
||||||
id: jobId,
|
resolve(result.data);
|
||||||
});
|
} else {
|
||||||
});
|
reject(result.error);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
return getIPCRenderer().sendSync("fs-sync-job", {
|
|
||||||
type: "write",
|
|
||||||
filename,
|
|
||||||
contents,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
readFileAsync(filename) {
|
readFileAsync(filename) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// ipcMain
|
getIPCRenderer()
|
||||||
const jobId = ++this.jobId;
|
.invoke("fs-job", {
|
||||||
this.jobs[jobId] = { resolve, reject };
|
type: "read",
|
||||||
|
filename,
|
||||||
getIPCRenderer().send("fs-job", {
|
})
|
||||||
type: "read",
|
.then(result => {
|
||||||
filename,
|
if (result.success) {
|
||||||
id: jobId,
|
resolve(result.data);
|
||||||
});
|
} else {
|
||||||
|
reject(result.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteFileAsync(filename) {
|
deleteFileAsync(filename) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// ipcMain
|
getIPCRenderer()
|
||||||
const jobId = ++this.jobId;
|
.invoke("fs-job", {
|
||||||
this.jobs[jobId] = { resolve, reject };
|
type: "delete",
|
||||||
getIPCRenderer().send("fs-job", {
|
filename,
|
||||||
type: "delete",
|
})
|
||||||
filename,
|
.then(result => {
|
||||||
id: jobId,
|
if (result.success) {
|
||||||
});
|
resolve(result.data);
|
||||||
|
} else {
|
||||||
|
reject(result.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
|
import { NoAchievementProvider } from "../browser/no_achievement_provider";
|
||||||
import { PlatformWrapperImplBrowser } from "../browser/wrapper";
|
import { PlatformWrapperImplBrowser } from "../browser/wrapper";
|
||||||
import { getIPCRenderer } from "../../core/utils";
|
import { getIPCRenderer } from "../../core/utils";
|
||||||
import { createLogger } from "../../core/logging";
|
import { createLogger } from "../../core/logging";
|
||||||
import { StorageImplElectron } from "./storage";
|
import { StorageImplElectron } from "./storage";
|
||||||
|
import { SteamAchievementProvider } from "./steam_achievement_provider";
|
||||||
import { PlatformWrapperInterface } from "../wrapper";
|
import { PlatformWrapperInterface } from "../wrapper";
|
||||||
|
|
||||||
const logger = createLogger("electron-wrapper");
|
const logger = createLogger("electron-wrapper");
|
||||||
|
|
||||||
export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
||||||
initialize() {
|
initialize() {
|
||||||
|
this.steamOverlayCanvasFix = document.createElement("canvas");
|
||||||
|
this.steamOverlayCanvasFix.width = 1;
|
||||||
|
this.steamOverlayCanvasFix.height = 1;
|
||||||
|
this.steamOverlayCanvasFix.id = "steamOverlayCanvasFix";
|
||||||
|
|
||||||
|
this.steamOverlayContextFix = this.steamOverlayCanvasFix.getContext("2d");
|
||||||
|
document.documentElement.appendChild(this.steamOverlayCanvasFix);
|
||||||
|
|
||||||
|
this.app.ticker.frameEmitted.add(this.steamOverlayFixRedrawCanvas, this);
|
||||||
|
|
||||||
this.app.storage = new StorageImplElectron(this);
|
this.app.storage = new StorageImplElectron(this);
|
||||||
return PlatformWrapperInterface.prototype.initialize.call(this);
|
this.app.achievementProvider = new SteamAchievementProvider(this.app);
|
||||||
|
|
||||||
|
return this.initializeAchievementProvider().then(() =>
|
||||||
|
PlatformWrapperInterface.prototype.initialize.call(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
steamOverlayFixRedrawCanvas() {
|
||||||
|
this.steamOverlayContextFix.clearRect(0, 0, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() {
|
getId() {
|
||||||
@ -38,6 +58,14 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeAchievementProvider() {
|
||||||
|
return this.app.achievementProvider.initialize().catch(err => {
|
||||||
|
logger.error("Failed to initialize achievement provider, disabling:", err);
|
||||||
|
|
||||||
|
this.app.achievementProvider = new NoAchievementProvider(this.app);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getSupportsFullscreen() {
|
getSupportsFullscreen() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -30,16 +30,6 @@ export class StorageInterface {
|
|||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to write a file synchronously, used in unload handler
|
|
||||||
* @param {string} filename
|
|
||||||
* @param {string} contents
|
|
||||||
*/
|
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
abstract;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a string asynchronously. Returns Promise<FILE_NOT_FOUND> if file was not found.
|
* Reads a string asynchronously. Returns Promise<FILE_NOT_FOUND> if file was not found.
|
||||||
* @param {string} filename
|
* @param {string} filename
|
||||||
|
@ -12,6 +12,8 @@ import { SavegameInterface_V1003 } from "./schemas/1003";
|
|||||||
import { SavegameInterface_V1004 } from "./schemas/1004";
|
import { SavegameInterface_V1004 } from "./schemas/1004";
|
||||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||||
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
|
|
||||||
const logger = createLogger("savegame");
|
const logger = createLogger("savegame");
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static getCurrentVersion() {
|
static getCurrentVersion() {
|
||||||
return 1006;
|
return 1008;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,7 +79,11 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
return {
|
return {
|
||||||
version: this.getCurrentVersion(),
|
version: this.getCurrentVersion(),
|
||||||
dump: null,
|
dump: null,
|
||||||
stats: {},
|
stats: {
|
||||||
|
failedMam: false,
|
||||||
|
trashedCount: 0,
|
||||||
|
usedInverseRotater: false,
|
||||||
|
},
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -121,6 +127,16 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
data.version = 1006;
|
data.version = 1006;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.version === 1006) {
|
||||||
|
SavegameInterface_V1007.migrate1006to1007(data);
|
||||||
|
data.version = 1007;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.version === 1007) {
|
||||||
|
SavegameInterface_V1008.migrate1007to1008(data);
|
||||||
|
data.version = 1008;
|
||||||
|
}
|
||||||
|
|
||||||
return ExplainedResult.good();
|
return ExplainedResult.good();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,13 @@ if (G_IS_DEV) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compressObjectInternal(obj, keys = [], values = []) {
|
/**
|
||||||
|
* @param {any} obj
|
||||||
|
* @param {Map} keys
|
||||||
|
* @param {Map} values
|
||||||
|
* @returns {any[]|object|number|string}
|
||||||
|
*/
|
||||||
|
function compressObjectInternal(obj, keys, values) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
let result = [];
|
let result = [];
|
||||||
for (let i = 0; i < obj.length; ++i) {
|
for (let i = 0; i < obj.length; ++i) {
|
||||||
@ -69,37 +75,58 @@ function compressObjectInternal(obj, keys = [], values = []) {
|
|||||||
} else if (typeof obj === "object" && obj !== null) {
|
} else if (typeof obj === "object" && obj !== null) {
|
||||||
let result = {};
|
let result = {};
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
let index = keys.indexOf(key);
|
let index = keys.get(key);
|
||||||
if (index < 0) {
|
if (index === undefined) {
|
||||||
keys.push(key);
|
index = keys.size;
|
||||||
index = keys.length - 1;
|
keys.set(key, index);
|
||||||
}
|
}
|
||||||
const value = obj[key];
|
const value = obj[key];
|
||||||
result[compressInt(index)] = compressObjectInternal(value, keys, values);
|
result[compressInt(index)] = compressObjectInternal(value, keys, values);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else if (typeof obj === "string") {
|
} else if (typeof obj === "string") {
|
||||||
let index = values.indexOf(obj);
|
let index = values.get(obj);
|
||||||
if (index < 0) {
|
if (index === undefined) {
|
||||||
values.push(obj);
|
index = values.size;
|
||||||
index = values.length - 1;
|
values.set(obj, index);
|
||||||
}
|
}
|
||||||
return compressInt(index);
|
return compressInt(index);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Map} hashMap
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
function indexMapToArray(hashMap) {
|
||||||
|
const result = new Array(hashMap.size);
|
||||||
|
hashMap.forEach((index, key) => {
|
||||||
|
result[index] = key;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} obj
|
||||||
|
*/
|
||||||
export function compressObject(obj) {
|
export function compressObject(obj) {
|
||||||
const keys = [];
|
const keys = new Map();
|
||||||
const values = [];
|
const values = new Map();
|
||||||
const data = compressObjectInternal(obj, keys, values);
|
const data = compressObjectInternal(obj, keys, values);
|
||||||
return {
|
return {
|
||||||
keys,
|
keys: indexMapToArray(keys),
|
||||||
values,
|
values: indexMapToArray(values),
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string[]} keys
|
||||||
|
* @param {any[]} values
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
function decompressObjectInternal(obj, keys = [], values = []) {
|
function decompressObjectInternal(obj, keys = [], values = []) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
let result = [];
|
let result = [];
|
||||||
@ -122,6 +149,9 @@ function decompressObjectInternal(obj, keys = [], values = []) {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} obj
|
||||||
|
*/
|
||||||
export function decompressObject(obj) {
|
export function decompressObject(obj) {
|
||||||
if (obj.keys && obj.values && obj.data) {
|
if (obj.keys && obj.values && obj.data) {
|
||||||
const keys = obj.keys;
|
const keys = obj.keys;
|
||||||
|
@ -7,6 +7,8 @@ import { SavegameInterface_V1003 } from "./schemas/1003";
|
|||||||
import { SavegameInterface_V1004 } from "./schemas/1004";
|
import { SavegameInterface_V1004 } from "./schemas/1004";
|
||||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||||
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
|
|
||||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||||
export const savegameInterfaces = {
|
export const savegameInterfaces = {
|
||||||
@ -17,6 +19,8 @@ export const savegameInterfaces = {
|
|||||||
1004: SavegameInterface_V1004,
|
1004: SavegameInterface_V1004,
|
||||||
1005: SavegameInterface_V1005,
|
1005: SavegameInterface_V1005,
|
||||||
1006: SavegameInterface_V1006,
|
1006: SavegameInterface_V1006,
|
||||||
|
1007: SavegameInterface_V1007,
|
||||||
|
1008: SavegameInterface_V1008,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger("savegame_interface_registry");
|
const logger = createLogger("savegame_interface_registry");
|
||||||
|