diff --git a/shapez-io/electron_wegame/README.md b/shapez-io/electron_wegame/README.md
new file mode 100644
index 00000000..70736caf
--- /dev/null
+++ b/shapez-io/electron_wegame/README.md
@@ -0,0 +1 @@
+To build, place the lib64 folder from the wegame sdk for electron 13 in `wegame_sdk` and run the `wegame.main.standalone` gulp task.
diff --git a/shapez-io/electron_wegame/electron_wegame.code-workspace b/shapez-io/electron_wegame/electron_wegame.code-workspace
new file mode 100644
index 00000000..fc9ab864
--- /dev/null
+++ b/shapez-io/electron_wegame/electron_wegame.code-workspace
@@ -0,0 +1,13 @@
+{
+ "folders": [
+ {
+ "path": "."
+ }
+ ],
+ "settings": {
+ "files.exclude": {
+ "**/node_modules": true,
+ "**/typedefs_gen": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/favicon.icns b/shapez-io/electron_wegame/favicon.icns
new file mode 100644
index 00000000..79e141a5
Binary files /dev/null and b/shapez-io/electron_wegame/favicon.icns differ
diff --git a/shapez-io/electron_wegame/favicon.ico b/shapez-io/electron_wegame/favicon.ico
new file mode 100644
index 00000000..81a9aa5c
Binary files /dev/null and b/shapez-io/electron_wegame/favicon.ico differ
diff --git a/shapez-io/electron_wegame/favicon.png b/shapez-io/electron_wegame/favicon.png
new file mode 100644
index 00000000..c837c787
Binary files /dev/null and b/shapez-io/electron_wegame/favicon.png differ
diff --git a/shapez-io/electron_wegame/index.js b/shapez-io/electron_wegame/index.js
new file mode 100644
index 00000000..2c183f15
--- /dev/null
+++ b/shapez-io/electron_wegame/index.js
@@ -0,0 +1,253 @@
+/* eslint-disable quotes,no-undef */
+
+const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell } = require("electron");
+
+app.commandLine.appendSwitch("in-process-gpu");
+
+const path = require("path");
+const url = require("url");
+const fs = require("fs");
+const wegame = require("./wegame");
+const asyncLock = require("async-lock");
+
+const isDev = process.argv.indexOf("--dev") >= 0;
+const isLocal = process.argv.indexOf("--local") >= 0;
+
+const roamingFolder =
+ process.env.APPDATA ||
+ (process.platform == "darwin"
+ ? process.env.HOME + "/Library/Preferences"
+ : process.env.HOME + "/.local/share");
+let storePath = path.join(roamingFolder, "shapez.io", "saves");
+
+if (!fs.existsSync(storePath)) {
+ // No try-catch by design
+ fs.mkdirSync(storePath, { recursive: true });
+}
+
+/** @type {BrowserWindow} */
+let win = null;
+let menu = null;
+
+function createWindow() {
+ let faviconExtension = ".png";
+ if (process.platform === "win32") {
+ faviconExtension = ".ico";
+ }
+
+ win = new BrowserWindow({
+ width: 1280,
+ height: 800,
+ show: false,
+ backgroundColor: "#222428",
+ useContentSize: true,
+ minWidth: 800,
+ minHeight: 600,
+ title: "图形工厂",
+ transparent: false,
+ icon: path.join(__dirname, "favicon" + faviconExtension),
+ // fullscreen: true,
+ autoHideMenuBar: true,
+ webPreferences: {
+ nodeIntegration: false,
+ webSecurity: true,
+ sandbox: true,
+
+ contextIsolation: true,
+ preload: path.join(__dirname, "preload.js"),
+ },
+ allowRunningInsecureContent: false,
+ });
+
+ if (isLocal) {
+ win.loadURL("http://localhost:3005");
+ } else {
+ win.loadURL(
+ url.format({
+ pathname: path.join(__dirname, "index.html"),
+ protocol: "file:",
+ slashes: true,
+ })
+ );
+ }
+ win.webContents.session.clearCache(() => null);
+ win.webContents.session.clearStorageData();
+
+ win.webContents.on("new-window", (event, pth) => {
+ event.preventDefault();
+ shell.openExternal(pth);
+ });
+
+ win.on("closed", () => {
+ console.log("Window closed");
+ win = null;
+ });
+
+ if (isDev) {
+ menu = new Menu();
+
+ const mainItem = new MenuItem({
+ label: "Toggle Dev Tools",
+ click: () => win.webContents.toggleDevTools(),
+ accelerator: "F12",
+ });
+ menu.append(mainItem);
+
+ const reloadItem = new MenuItem({
+ label: "Restart",
+ click: () => win.reload(),
+ accelerator: "F5",
+ });
+ menu.append(reloadItem);
+
+ const fullscreenItem = new MenuItem({
+ label: "Fullscreen",
+ click: () => win.setFullScreen(!win.isFullScreen()),
+ accelerator: "F11",
+ });
+ menu.append(fullscreenItem);
+
+ Menu.setApplicationMenu(menu);
+ } else {
+ Menu.setApplicationMenu(null);
+ }
+
+ win.once("ready-to-show", () => {
+ win.show();
+ win.focus();
+ });
+}
+
+if (!app.requestSingleInstanceLock()) {
+ app.exit(0);
+} else {
+ app.on("second-instance", (event, commandLine, workingDirectory) => {
+ // Someone tried to run a second instance, we should focus
+ if (win) {
+ if (win.isMinimized()) {
+ win.restore();
+ }
+ win.focus();
+ }
+ });
+}
+
+app.on("ready", createWindow);
+
+app.on("window-all-closed", () => {
+ console.log("All windows closed");
+ app.quit();
+});
+
+ipcMain.on("set-fullscreen", (event, flag) => {
+ win.setFullScreen(flag);
+});
+
+ipcMain.on("exit-app", (event, flag) => {
+ win.close();
+ app.quit();
+});
+
+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, "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, "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);
+ }
+ });
+}
+
+ipcMain.handle("fs-job", async (event, job) => {
+ const filenameSafe = job.filename.replace(/[^a-z\.\-_0-9]/i, "");
+ const fname = path.join(storePath, filenameSafe);
+ switch (job.type) {
+ case "read": {
+ if (!fs.existsSync(fname)) {
+ // Special FILE_NOT_FOUND error code
+ return { error: "file_not_found" };
+ }
+ return await fs.promises.readFile(fname, "utf8");
+ }
+ case "write": {
+ await writeFileSafe(fname, job.contents);
+ return job.contents;
+ }
+
+ case "delete": {
+ await fs.promises.unlink(fname);
+ return;
+ }
+
+ default:
+ throw new Error("Unknown fs job: " + job.type);
+ }
+});
+
+wegame.init(isDev);
+wegame.listen();
diff --git a/shapez-io/electron_wegame/package.json b/shapez-io/electron_wegame/package.json
new file mode 100644
index 00000000..aba5bb6a
--- /dev/null
+++ b/shapez-io/electron_wegame/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "electron",
+ "version": "1.0.0",
+ "main": "index.js",
+ "license": "MIT",
+ "private": true,
+ "scripts": {
+ "startDev": "electron --disable-direct-composition --in-process-gpu . --dev --local",
+ "startDevGpu": "electron --enable-gpu-rasterization --enable-accelerated-2d-canvas --num-raster-threads=8 --enable-zero-copy . --dev --local",
+ "start": "electron --disable-direct-composition --in-process-gpu ."
+ },
+ "devDependencies": {
+ "electron": "^13.1.6"
+ },
+ "dependencies": {
+ "async-lock": "^1.2.8"
+ }
+}
diff --git a/shapez-io/electron_wegame/preloader/preloader.css b/shapez-io/electron_wegame/preloader/preloader.css
new file mode 100644
index 00000000..f6775f76
--- /dev/null
+++ b/shapez-io/electron_wegame/preloader/preloader.css
@@ -0,0 +1,262 @@
+* {
+ margin: 0;
+ padding: 0;
+ touch-action: pan-x pan-y !important;
+ pointer-events: none;
+ -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
+}
+
+html {
+ position: fixed;
+ -ms-touch-action: pan-x, pan-y;
+ touch-action: pan-x, pan-y;
+ -ms-content-zooming: none;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background: #dee1ea;
+}
+
+body {
+ color: #555;
+ user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ background: inherit !important;
+ text-transform: none;
+ white-space: normal;
+ word-break: normal;
+ word-spacing: normal;
+ word-wrap: break-word;
+ font-style: normal;
+ line-break: auto;
+ font-stretch: 100%;
+ text-rendering: optimizeLegibility;
+ text-decoration: none;
+ text-size-adjust: 100%;
+ letter-spacing: normal;
+ scrollbar-width: 6px;
+ -webkit-font-smoothing: antialiased;
+ -webkit-touch-callout: none;
+ /* prevent callout to copy image, etc when tap to hold */
+ -webkit-text-size-adjust: none;
+ /* prevent webkit from resizing text to fit */
+ scrollbar-face-color: #888;
+ scrollbar-track-color: rgba(255, 255, 255, 0.1);
+}
+
+#ll_fp {
+ font-family: "GameFont", Arial, sans-serif;
+ font-size: 14px;
+ position: fixed;
+ z-index: -1;
+ top: 0;
+ left: 0;
+ opacity: 0.05;
+}
+
+#ll_p {
+ display: grid;
+ position: fixed;
+ z-index: 999;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ justify-content: center;
+ justify-items: center;
+ align-items: center;
+ background: #d5d8de;
+ grid-template-rows: 1fr 200px;
+ grid-gap: 40px;
+ padding: 20px;
+ font-size: 14px;
+}
+
+#ll_p * {
+ line-height: 1em;
+}
+
+#ll_loader {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-self: end;
+ justify-content: center;
+}
+
+#ll_loader > .ll_text {
+ text-align: center;
+ color: #777a7f;
+ font-family: "GameFont", Arial, sans-serif;
+ font-size: 24px;
+ height: 30px;
+ line-height: 1.2em;
+}
+
+#ll_progressbar {
+ width: 80vw;
+ max-width: 800px;
+ margin-top: 40px;
+ height: 7px;
+ border-radius: 20px;
+ background: rgba(0, 10, 20, 0.08);
+
+ /* border: 5px solid transparent; */
+ display: flex;
+ position: relative;
+ align-items: flex-start;
+}
+
+@keyframes LL_LoadingAnimation {
+ 50% {
+ background-color: #34ae67;
+ }
+}
+
+#ll_progressbar > span {
+ border-radius: 20px;
+ position: absolute;
+ height: 190%;
+ width: 5%;
+ background: #fff;
+ transform: translateY(-50%);
+ top: 50%;
+ display: inline-flex;
+ background-color: #269fba;
+ animation: LL_LoadingAnimation 4s ease-in-out infinite;
+ position: relative;
+ z-index: 10;
+ border: 4px solid #d5d8de;
+ /* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); */
+ transition: width 0.5s ease-in-out;
+ min-width: 4%;
+}
+
+#ll_progressbar > #ll_loadinglabel {
+ position: absolute;
+ z-index: 20;
+ top: 50%;
+ text-transform: uppercase;
+ border-radius: 7px;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 16px;
+ color: #33373f;
+}
+
+@keyframes ShowStandaloneBannerAfterDelay {
+ 0% {
+ opacity: 0;
+ }
+ 95% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+#ll_standalone {
+ text-align: center;
+ color: #777a7f;
+ margin-top: 30px;
+ display: block;
+ font-size: 16px;
+ animation: ShowStandaloneBannerAfterDelay 60s linear;
+}
+
+#ll_standalone a {
+ color: #39f;
+ margin-left: 5px;
+ font-weight: bold;
+}
+
+#ll_logo {
+}
+
+#ll_logo > img {
+ width: 40vw;
+ max-width: 700px;
+ min-width: 150px;
+}
+
+#ll_loader > .ll_spinner {
+ width: 80px;
+ height: 80px;
+ display: inline-flex;
+ background: center center / contain no-repeat;
+ display: none;
+}
+
+#ll_preload_status {
+ position: absolute;
+ top: 40px;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 100;
+ opacity: 1 !important;
+ font-size: 18px;
+ color: rgba(0, 10, 20, 0.5);
+
+ font-family: "GameFont", Arial, sans-serif;
+ text-transform: uppercase;
+ text-align: center;
+}
+
+#ll_preload_error {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 999999;
+ background: #d5d8de;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+#ll_preload_error > .inner {
+ color: #fff;
+ font-family: Arial, "sans-serif";
+ font-size: 15px;
+ padding: 0;
+ text-align: center;
+}
+
+#ll_preload_error > .inner > .heading {
+ color: #ef5072;
+ margin-bottom: 40px;
+ font-size: 45px;
+}
+
+#ll_preload_error > .inner > .content {
+ color: #55585f;
+ font-family: monospace;
+ text-align: left;
+ background-color: #fff;
+ padding: 20px;
+ border-radius: 10px;
+
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
+}
+
+#ll_preload_error > .inner .discordLink {
+ color: #333;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ font-family: Arial;
+}
+
+#ll_preload_error > .inner .discordLink a {
+ color: #39f;
+}
+#ll_preload_error > .inner .discordLink strong {
+ font-weight: 900 !important;
+}
+
+#ll_preload_error > .inner .source {
+ color: #777;
+}
diff --git a/shapez-io/electron_wegame/preloader/preloader.html b/shapez-io/electron_wegame/preloader/preloader.html
new file mode 100644
index 00000000..48d3579c
--- /dev/null
+++ b/shapez-io/electron_wegame/preloader/preloader.html
@@ -0,0 +1,21 @@
+
_
+
+
+
+
+
+
diff --git a/shapez-io/electron_wegame/preloader/preloader.js b/shapez-io/electron_wegame/preloader/preloader.js
new file mode 100644
index 00000000..7e2fe518
--- /dev/null
+++ b/shapez-io/electron_wegame/preloader/preloader.js
@@ -0,0 +1,165 @@
+(function () {
+ var loadTimeout = null;
+ var callbackDone = false;
+
+ var searchString = window.location.search;
+ if (searchString.includes("steam_sso_auth_token=")) {
+ var pos = searchString.indexOf("steam_sso_auth_token");
+ const authToken = searchString.substring(pos + 21, pos + 57);
+ try {
+ window.localStorage.setItem("steam_sso_auth_token", authToken);
+ window.location.replace(window.location.protocol + "//" + window.location.host);
+ } catch (ex) {
+ alert("Failed to login via Steam SSO: " + ex);
+ window.location.replace("https://shapez.io");
+ }
+ return;
+ }
+
+ // Catch load errors
+
+ function errorHandler(event, source, lineno, colno, error) {
+ if (("" + event).indexOf("Script error.") >= 0) {
+ console.warn("Thirdparty script error:", event);
+ return;
+ }
+
+ if (("" + event).indexOf("NS_ERROR_FAILURE") >= 0) {
+ console.warn("Firefox NS_ERROR_FAILURE error:", event);
+ return;
+ }
+
+ if (("" + event).indexOf("Cannot read property 'postMessage' of null") >= 0) {
+ console.warn("Safari can not read post message error:", event);
+ return;
+ }
+
+ if (("" + event).indexOf("Possible side-effect in debug-evaluate") >= 0) {
+ console.warn("Chrome debug-evaluate error:", event);
+ return;
+ }
+
+ if (("" + source).indexOf("shapez.io") < 0) {
+ console.warn("Thirdparty error:", event);
+ return;
+ }
+
+ console.error("👀 App Error:", event, source, lineno, colno, error);
+ var element = document.createElement("div");
+ element.id = "ll_preload_error";
+
+ var inner = document.createElement("div");
+ inner.classList.add("inner");
+ element.appendChild(inner);
+
+ var heading = document.createElement("h3");
+ heading.classList.add("heading");
+ heading.innerText = "Fatal Error";
+ inner.appendChild(heading);
+
+ var content = document.createElement("p");
+ content.classList.add("content");
+ content.innerText = error || (event && event.message) || event || "Unknown Error";
+ inner.appendChild(content);
+
+ var discordLink = document.createElement("p");
+ discordLink.classList.add("discordLink");
+ discordLink.innerHTML =
+ "Please report this error in the #bugs channel of the official discord!";
+
+ inner.appendChild(discordLink);
+
+ if (source) {
+ var sourceElement = document.createElement("p");
+ sourceElement.classList.add("source");
+ sourceElement.innerText = source + ":" + lineno + ":" + colno;
+ inner.appendChild(sourceElement);
+ }
+
+ document.documentElement.appendChild(element);
+
+ window.APP_ERROR_OCCURED = true;
+ }
+
+ window.onerror = errorHandler;
+
+ function expectJsParsed() {
+ if (!callbackDone) {
+ console.error("👀 Got no core callback");
+ throw new Error("Core thread failed to respond within time.");
+ }
+ }
+
+ function onJsLoaded() {
+ console.log("👀 Core loaded at", Math.floor(performance.now()), "ms");
+ loadTimeout = setTimeout(expectJsParsed, 120000);
+ window.removeEventListener("unhandledrejection", errorHandler);
+ }
+
+ window.coreThreadLoadedCb = function () {
+ console.log("👀 Core responded at", Math.floor(performance.now()), "ms");
+ clearTimeout(loadTimeout);
+ loadTimeout = null;
+ callbackDone = true;
+ };
+
+ function progressHandler(progress) {
+ var progressElement = document.querySelector("#ll_preload_status");
+ if (progressElement) {
+ progressElement.innerText = "Downloading Bundle (" + Math.round(progress * 1200) + " / 1200 KB)";
+ }
+ var barElement = document.querySelector("#ll_progressbar span");
+ if (barElement) {
+ barElement.style.width = (5 + progress * 75.0).toFixed(2) + "%";
+ }
+ }
+
+ function startBundleDownload() {
+ var xhr = new XMLHttpRequest();
+ var notifiedNotComputable = false;
+
+ xhr.open("GET", bundleSrc, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onprogress = function (ev) {
+ if (ev.lengthComputable) {
+ progressHandler(ev.loaded / ev.total);
+ } else {
+ // Hardcoded length
+ progressHandler(Math.min(1, ev.loaded / 2349009));
+ }
+ };
+
+ xhr.onloadend = function () {
+ if (!xhr.status.toString().match(/^2/)) {
+ throw new Error("Failed to load bundle: " + xhr.status + " " + xhr.statusText);
+ } else {
+ if (!notifiedNotComputable) {
+ progressHandler(1);
+ }
+
+ var options = {};
+ var headers = xhr.getAllResponseHeaders();
+ var m = headers.match(/^Content-Type\:\s*(.*?)$/im);
+
+ if (m && m[1]) {
+ options.type = m[1];
+ }
+
+ var blob = new Blob([this.response], options);
+ var script = document.createElement("script");
+ script.addEventListener("load", onJsLoaded);
+ script.src = window.URL.createObjectURL(blob);
+ script.type = "text/javascript";
+ script.charset = "utf-8";
+ if (bundleIntegrity) {
+ script.setAttribute("integrity", bundleIntegrity);
+ }
+ document.head.appendChild(script);
+ }
+ };
+ xhr.send();
+ }
+
+ console.log("Start bundle download ...");
+ window.addEventListener("load", startBundleDownload);
+})();
diff --git a/shapez-io/electron_wegame/steampipe/templates/app-darwin-demo.vdf b/shapez-io/electron_wegame/steampipe/templates/app-darwin-demo.vdf
new file mode 100644
index 00000000..4bdb46b1
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/app-darwin-demo.vdf
@@ -0,0 +1,14 @@
+"appbuild"
+{
+ "appid" "1930750"
+ "desc" "$BUILD_DESC$"
+ "buildoutput" "$TMP_DIR$"
+ "contentroot" ""
+ "setlive" ""
+ "preview" "0"
+ "local" ""
+ "depots"
+ {
+ "1930756" "$VDF_DIR$/demo-darwin.vdf"
+ }
+}
diff --git a/shapez-io/electron_wegame/steampipe/templates/app-darwin.vdf b/shapez-io/electron_wegame/steampipe/templates/app-darwin.vdf
new file mode 100644
index 00000000..fa63b846
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/app-darwin.vdf
@@ -0,0 +1,14 @@
+"appbuild"
+{
+ "appid" "1318690"
+ "desc" "$BUILD_DESC$"
+ "buildoutput" "$TMP_DIR$"
+ "contentroot" ""
+ "setlive" ""
+ "preview" "0"
+ "local" ""
+ "depots"
+ {
+ "1318693" "$VDF_DIR$/standalone-darwin.vdf"
+ }
+}
diff --git a/shapez-io/electron_wegame/steampipe/templates/app-winlinux-demo.vdf b/shapez-io/electron_wegame/steampipe/templates/app-winlinux-demo.vdf
new file mode 100644
index 00000000..b4859b8b
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/app-winlinux-demo.vdf
@@ -0,0 +1,17 @@
+"appbuild"
+{
+ "appid" "1930750"
+ "desc" "$BUILD_DESC$"
+ "buildoutput" "$TMP_DIR$"
+ "contentroot" ""
+ "setlive" ""
+ "preview" "0"
+ "local" ""
+ "depots"
+ {
+ "1930753" "$VDF_DIR$/demo-windows.vdf"
+ "1930754" "$VDF_DIR$/demo-china-windows.vdf"
+ "1930752" "$VDF_DIR$/demo-linux.vdf"
+ "1930755" "$VDF_DIR$/demo-china-linux.vdf"
+ }
+}
diff --git a/shapez-io/electron_wegame/steampipe/templates/app-winlinux.vdf b/shapez-io/electron_wegame/steampipe/templates/app-winlinux.vdf
new file mode 100644
index 00000000..9fd7f9df
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/app-winlinux.vdf
@@ -0,0 +1,17 @@
+"appbuild"
+{
+ "appid" "1318690"
+ "desc" "$BUILD_DESC$"
+ "buildoutput" "$TMP_DIR$"
+ "contentroot" ""
+ "setlive" ""
+ "preview" "0"
+ "local" ""
+ "depots"
+ {
+ "1318691" "$VDF_DIR$\standalone-windows.vdf"
+ "1318694" "$VDF_DIR$\standalone-china-windows.vdf"
+ "1318692" "$VDF_DIR$\standalone-linux.vdf"
+ "1318695" "$VDF_DIR$\standalone-china-linux.vdf"
+ }
+}
diff --git a/shapez-io/electron_wegame/steampipe/templates/demo-china-linux.vdf b/shapez-io/electron_wegame/steampipe/templates/demo-china-linux.vdf
new file mode 100644
index 00000000..2ec63419
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/demo-china-linux.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1930755"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam-china-demo\shapez-linux-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/demo-china-windows.vdf b/shapez-io/electron_wegame/steampipe/templates/demo-china-windows.vdf
new file mode 100644
index 00000000..a06b4e9e
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/demo-china-windows.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1930754"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam-china-demo\shapez-win32-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/demo-darwin.vdf b/shapez-io/electron_wegame/steampipe/templates/demo-darwin.vdf
new file mode 100644
index 00000000..d0e8f1e2
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/demo-darwin.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1930756"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam-demo\shapez-darwin-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/demo-linux.vdf b/shapez-io/electron_wegame/steampipe/templates/demo-linux.vdf
new file mode 100644
index 00000000..4f2d274f
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/demo-linux.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1930752"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam-demo\shapez-linux-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/demo-windows.vdf b/shapez-io/electron_wegame/steampipe/templates/demo-windows.vdf
new file mode 100644
index 00000000..1b6cdbc7
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/demo-windows.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1930753"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam-demo\shapez-win32-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/standalone-china-linux.vdf b/shapez-io/electron_wegame/steampipe/templates/standalone-china-linux.vdf
new file mode 100644
index 00000000..56b9fe3c
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/standalone-china-linux.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1318695"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam-china\shapez-linux-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/standalone-china-windows.vdf b/shapez-io/electron_wegame/steampipe/templates/standalone-china-windows.vdf
new file mode 100644
index 00000000..469158db
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/standalone-china-windows.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1318694"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam-china\shapez-win32-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/standalone-darwin.vdf b/shapez-io/electron_wegame/steampipe/templates/standalone-darwin.vdf
new file mode 100644
index 00000000..026ab768
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/standalone-darwin.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1318693"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam\shapez-darwin-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/standalone-linux.vdf b/shapez-io/electron_wegame/steampipe/templates/standalone-linux.vdf
new file mode 100644
index 00000000..9edb1963
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/standalone-linux.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1318692"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam\shapez-linux-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/templates/standalone-windows.vdf b/shapez-io/electron_wegame/steampipe/templates/standalone-windows.vdf
new file mode 100644
index 00000000..6f7cb408
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/templates/standalone-windows.vdf
@@ -0,0 +1,12 @@
+"DepotBuildConfig"
+{
+ "DepotID" "1318691"
+ "contentroot" "$BUNDLE_DIR$\standalone-steam\shapez-win32-x64"
+ "FileMapping"
+ {
+ "LocalPath" "*"
+ "DepotPath" "."
+ "recursive" "1"
+ }
+ "FileExclusion" "*.pdb"
+}
\ No newline at end of file
diff --git a/shapez-io/electron_wegame/steampipe/upload-darwin-demo.sh b/shapez-io/electron_wegame/steampipe/upload-darwin-demo.sh
new file mode 100644
index 00000000..77bb29dc
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/upload-darwin-demo.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+yarn gulp standalone.prepareVDF
+steamcmd.sh +login $STEAM_UPLOAD_SHAPEZ_ID $STEAM_UPLOAD_SHAPEZ_USER +run_app_build $PWD/built_vdfs/app-darwin-demo.vdf +quit
diff --git a/shapez-io/electron_wegame/steampipe/upload-darwin.sh b/shapez-io/electron_wegame/steampipe/upload-darwin.sh
new file mode 100644
index 00000000..06412dcd
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/upload-darwin.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+yarn gulp standalone.prepareVDF
+steamcmd.sh +login $STEAM_UPLOAD_SHAPEZ_ID $STEAM_UPLOAD_SHAPEZ_USER +run_app_build $PWD/built_vdfs/app-darwin.vdf +quit
diff --git a/shapez-io/electron_wegame/steampipe/upload-winlinux-demo.bat b/shapez-io/electron_wegame/steampipe/upload-winlinux-demo.bat
new file mode 100644
index 00000000..e19f7f55
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/upload-winlinux-demo.bat
@@ -0,0 +1,3 @@
+@echo off
+cmd /c yarn gulp standalone.prepareVDF
+steamcmd +login %STEAM_UPLOAD_SHAPEZ_ID% %STEAM_UPLOAD_SHAPEZ_USER% +run_app_build %cd%/built_vdfs/app-winlinux-demo.vdf +quit
diff --git a/shapez-io/electron_wegame/steampipe/upload-winlinux.bat b/shapez-io/electron_wegame/steampipe/upload-winlinux.bat
new file mode 100644
index 00000000..1c9bdfe7
--- /dev/null
+++ b/shapez-io/electron_wegame/steampipe/upload-winlinux.bat
@@ -0,0 +1,3 @@
+@echo off
+cmd /c yarn gulp standalone.prepareVDF
+steamcmd +login %STEAM_UPLOAD_SHAPEZ_ID% %STEAM_UPLOAD_SHAPEZ_USER% +run_app_build %cd%/built_vdfs/app-winlinux.vdf +quit
diff --git a/shapez-io/electron_wegame/wegame.js b/shapez-io/electron_wegame/wegame.js
new file mode 100644
index 00000000..05a0e186
--- /dev/null
+++ b/shapez-io/electron_wegame/wegame.js
@@ -0,0 +1,63 @@
+const railsdk = require("./wegame_sdk/railsdk.js");
+const { dialog, app, remote, ipcMain } = require("electron");
+
+function init(isDev) {
+ console.log("Step 1: wegame: init");
+
+ try {
+ console.log("Step 2: Calling need restart app");
+ const need_restart = railsdk.RailNeedRestartAppForCheckingEnvironment(
+ 2001639,
+ [`--rail_render_pid=${process.pid}`] //,"--rail_debug_mode",
+ );
+ console.log("Step 3: Needs restart =", need_restart);
+ if (need_restart) {
+ console.error("Step 4: Need restart");
+ dialog.showErrorBox("加载RailSDK失败", "请先运行WeGame开发者版本");
+ return;
+ }
+ } catch (err) {
+ console.error("Rail SDK error:", err);
+ dialog.showErrorBox("加载RailSDK失败", err);
+ return;
+ }
+
+ console.log("Step 5: starting rail sdk");
+ if (railsdk.RailInitialize() === false) {
+ console.error("RailInitialize() = false");
+ dialog.showErrorBox("RailInitialize调用失败", "请先运行WeGame开发者版本");
+ return;
+ }
+
+ console.log("Initialize RailSDK success!");
+
+ railsdk.RailRegisterEvent(railsdk.RailEventID.kRailEventSystemStateChanged, event => {
+ console.log(event);
+ if (event.result === railsdk.RailResult.kSuccess) {
+ if (
+ event.state === railsdk.RailSystemState.kSystemStatePlatformOffline ||
+ event.state === railsdk.RailSystemState.kSystemStatePlatformExit ||
+ event.state === railsdk.RailSystemState.kSystemStateGameExitByAntiAddiction
+ ) {
+ app.exit();
+ }
+ }
+ });
+}
+
+function listen() {
+ console.log("wegame: listen");
+ ipcMain.handle("profanity-check", async (event, data) => {
+ if (data.length === 0) {
+ return "";
+ }
+ const result = railsdk.RailUtils.DirtyWordsFilter(data, true);
+ if (result.check_result.dirty_type !== 0 /** kRailDirtyWordsTypeNormalAllowWords */) {
+ return result.check_result.replace_string;
+ }
+
+ return data;
+ });
+}
+
+module.exports = { init, listen };
diff --git a/shapez-io/electron_wegame/yarn.lock b/shapez-io/electron_wegame/yarn.lock
new file mode 100644
index 00000000..69c595ea
--- /dev/null
+++ b/shapez-io/electron_wegame/yarn.lock
@@ -0,0 +1,578 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@electron/get@^1.0.1":
+ version "1.12.4"
+ resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.4.tgz#a5971113fc1bf8fa12a8789dc20152a7359f06ab"
+ integrity sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg==
+ dependencies:
+ debug "^4.1.1"
+ env-paths "^2.2.0"
+ fs-extra "^8.1.0"
+ got "^9.6.0"
+ progress "^2.0.3"
+ semver "^6.2.0"
+ sumchecker "^3.0.1"
+ optionalDependencies:
+ global-agent "^2.0.2"
+ global-tunnel-ng "^2.7.1"
+
+"@sindresorhus/is@^0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
+ integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
+
+"@szmarczak/http-timer@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
+ integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
+ dependencies:
+ defer-to-connect "^1.0.1"
+
+"@types/node@^14.6.2":
+ version "14.17.4"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.4.tgz#218712242446fc868d0e007af29a4408c7765bc0"
+ integrity sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==
+
+async-lock@^1.2.8:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.8.tgz#7b02bdfa2de603c0713acecd11184cf97bbc7c4c"
+ integrity sha512-G+26B2jc0Gw0EG/WN2M6IczuGepBsfR1+DtqLnyFSH4p2C668qkOCtEkGNVEaaNAVlYwEMazy1+/jnLxltBkIQ==
+
+boolean@^3.0.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.1.2.tgz#e30f210a26b02458482a8cc353ab06f262a780c2"
+ integrity sha512-YN6UmV0FfLlBVvRvNPx3pz5W/mUoYB24J4WSXOKP/OOJpi+Oq6WYqPaNTHzjI0QzwWtnvEd5CGYyQPgp1jFxnw==
+
+buffer-crc32@~0.2.3:
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+ integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
+
+buffer-from@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+ integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
+cacheable-request@^6.0.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
+ integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^3.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^4.1.0"
+ responselike "^1.0.2"
+
+clone-response@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
+ integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
+ dependencies:
+ mimic-response "^1.0.0"
+
+concat-stream@^1.6.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+ integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+config-chain@^1.1.11:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
+ integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
+ dependencies:
+ ini "^1.3.4"
+ proto-list "~1.2.1"
+
+core-js@^3.6.5:
+ version "3.15.2"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61"
+ integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+ integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+debug@^2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^4.1.0, debug@^4.1.1:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
+ integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
+ dependencies:
+ ms "2.1.2"
+
+decompress-response@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
+ integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
+ dependencies:
+ mimic-response "^1.0.0"
+
+defer-to-connect@^1.0.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
+ integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
+
+define-properties@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+ integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
+ dependencies:
+ object-keys "^1.0.12"
+
+detect-node@^2.0.4:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
+ integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
+
+duplexer3@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
+ integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
+
+electron@^13.1.6:
+ version "13.1.6"
+ resolved "https://registry.yarnpkg.com/electron/-/electron-13.1.6.tgz#6ecaf969255d62ce82cc0b5c948bf26e7dfb489b"
+ integrity sha512-XiB55/JTaQpDFQrD9pulYnOGwaWeMyRIub5ispvoE2bWBvM5zVMLptwMLb0m3KTMrfSkzhedZvOu7fwYvR7L7Q==
+ dependencies:
+ "@electron/get" "^1.0.1"
+ "@types/node" "^14.6.2"
+ extract-zip "^1.0.3"
+
+encodeurl@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+
+end-of-stream@^1.1.0:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+env-paths@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
+ integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
+
+es6-error@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
+ integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+extract-zip@^1.0.3:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
+ integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
+ dependencies:
+ concat-stream "^1.6.2"
+ debug "^2.6.9"
+ mkdirp "^0.5.4"
+ yauzl "^2.10.0"
+
+fd-slicer@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
+ integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+ dependencies:
+ pend "~1.2.0"
+
+fs-extra@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
+ integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
+get-stream@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+ integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+ dependencies:
+ pump "^3.0.0"
+
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
+global-agent@^2.0.2:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc"
+ integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==
+ dependencies:
+ boolean "^3.0.1"
+ core-js "^3.6.5"
+ es6-error "^4.1.1"
+ matcher "^3.0.0"
+ roarr "^2.15.3"
+ semver "^7.3.2"
+ serialize-error "^7.0.1"
+
+global-tunnel-ng@^2.7.1:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f"
+ integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==
+ dependencies:
+ encodeurl "^1.0.2"
+ lodash "^4.17.10"
+ npm-conf "^1.1.3"
+ tunnel "^0.0.6"
+
+globalthis@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b"
+ integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==
+ dependencies:
+ define-properties "^1.1.3"
+
+got@^9.6.0:
+ version "9.6.0"
+ resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
+ integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
+ dependencies:
+ "@sindresorhus/is" "^0.14.0"
+ "@szmarczak/http-timer" "^1.1.2"
+ cacheable-request "^6.0.0"
+ decompress-response "^3.3.0"
+ duplexer3 "^0.1.4"
+ get-stream "^4.1.0"
+ lowercase-keys "^1.0.1"
+ mimic-response "^1.0.1"
+ p-cancelable "^1.0.0"
+ to-readable-stream "^1.0.0"
+ url-parse-lax "^3.0.0"
+
+graceful-fs@^4.1.6, graceful-fs@^4.2.0:
+ version "4.2.6"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
+ integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
+
+http-cache-semantics@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
+ integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
+
+inherits@^2.0.3, inherits@~2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ini@^1.3.4:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+json-buffer@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
+ integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
+
+json-stringify-safe@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+ integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+ integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+keyv@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
+ integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
+ dependencies:
+ json-buffer "3.0.0"
+
+lodash@^4.17.10:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
+ integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
+
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
+matcher@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
+ integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
+ dependencies:
+ escape-string-regexp "^4.0.0"
+
+mimic-response@^1.0.0, mimic-response@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+mkdirp@^0.5.4:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+normalize-url@^4.1.0:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
+ integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
+
+npm-conf@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
+ integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==
+ dependencies:
+ config-chain "^1.1.11"
+ pify "^3.0.0"
+
+object-keys@^1.0.12:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+p-cancelable@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
+ integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
+
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+ integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
+
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+ integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+
+prepend-http@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
+ integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+progress@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
+ integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
+proto-list@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+ integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
+
+pump@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+ integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+readable-stream@^2.2.2:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+responselike@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
+ integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
+ dependencies:
+ lowercase-keys "^1.0.0"
+
+roarr@^2.15.3:
+ version "2.15.4"
+ resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
+ integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
+ dependencies:
+ boolean "^3.0.1"
+ detect-node "^2.0.4"
+ globalthis "^1.0.1"
+ json-stringify-safe "^5.0.1"
+ semver-compare "^1.0.0"
+ sprintf-js "^1.1.2"
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+semver-compare@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
+ integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
+
+semver@^6.2.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+semver@^7.3.2:
+ version "7.3.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+ integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+ dependencies:
+ lru-cache "^6.0.0"
+
+serialize-error@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
+ integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
+ dependencies:
+ type-fest "^0.13.1"
+
+sprintf-js@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
+ integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+sumchecker@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
+ integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==
+ dependencies:
+ debug "^4.1.0"
+
+to-readable-stream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
+ integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
+
+tunnel@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
+ integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
+
+type-fest@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
+ integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
+
+typedarray@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+ integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
+url-parse-lax@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
+ integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
+ dependencies:
+ prepend-http "^2.0.0"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yauzl@^2.10.0:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
+ integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
+ dependencies:
+ buffer-crc32 "~0.2.3"
+ fd-slicer "~1.1.0"
diff --git a/shapez-io/mod_examples/README.md b/shapez-io/mod_examples/README.md
new file mode 100644
index 00000000..5086bbdb
--- /dev/null
+++ b/shapez-io/mod_examples/README.md
@@ -0,0 +1,58 @@
+# shapez.io Modding
+
+## General Instructions
+
+Currently there are two options to develop mods for shapez.io:
+
+1. Writing single file mods, which doesn't require any additional tools and can be loaded directly in the game
+2. Using the [create-shapezio-mod](https://www.npmjs.com/package/create-shapezio-mod) package. This package is still in development but allows you to pack multiple files and images into a single mod file, so you don't have to base64 encode your images etc.
+
+## Mod Developer Discord
+
+A great place to get help with mod development is the official [shapez.io modloader discord](https://discord.gg/xq5v8uyMue).
+
+## Setting up your development environment
+
+The simplest way of developing mods is by just creating a `mymod.js` file and putting it in the `mods/` folder of the standalone (You can find the `mods/` folder by clicking "Open Mods Folder" in the shapez Standalone, be sure to select the 1.5.0-modloader branch on Steam).
+
+You can then add `--dev` to the launch options on Steam. This adds an application menu where you can click "Restart" to reload your mod, and will also show the developer console where you can see any potential errors.
+
+## Getting started
+
+To get into shapez.io modding, I highly recommend checking out all of the examples in this folder. Here's a list of examples and what features of the modloader they show:
+
+| Example | Description | Demonstrates |
+| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
+| [base.js](base.js) | The most basic mod | Base structure of a mod |
+| [class_extensions.js](class_extensions.js) | Shows how to extend multiple methods of one class at once, useful for overriding a lot of methods | Overriding and extending builtin methods |
+| [custom_css.js](custom_css.js) | Modifies the Main Menu State look | Modifying the UI styles with CSS |
+| [replace_builtin_sprites.js](replace_builtin_sprites.js) | Replaces all color sprites with icons | Replacing builtin sprites |
+| [translations.js](translations.js) | Shows how to replace and add new translations in multiple languages | Adding and replacing translations |
+| [add_building_basic.js](add_building_basic.js) | Shows how to add a new building | Registering a new building |
+| [add_building_flipper.js](add_building_flipper.js) | Adds a "flipper" building which mirrors shapes from top to bottom | Registering a new building, Adding a custom shape and item processing operation (flip) |
+| [custom_drawing.js](custom_drawing.js) | Displays a a small indicator on every item processing building whether it is currently working | Adding a new GameSystem and drawing overlays |
+| [custom_keybinding.js](custom_keybinding.js) | Adds a new customizable ingame keybinding (Shift+F) | Adding a new keybinding |
+| [custom_sub_shapes.js](custom_sub_shapes.js) | Adds a new type of sub-shape (Line) | Adding a new sub shape and drawing it, making it spawn on the map, modifying the builtin levels |
+| [modify_theme.js](modify_theme.js) | Modifies the default game themes | Modifying the builtin themes |
+| [custom_theme.js](custom_theme.js) | Adds a new UI and map theme | Adding a new game theme |
+| [mod_settings.js](mod_settings.js) | Shows a dialog counting how often the mod has been launched | Reading and storing mod settings |
+| [storing_data_in_savegame.js](storing_data_in_savegame.js) | Shows how to store custom (structured) data in the savegame | Storing custom data in savegame |
+| [modify_existing_building.js](modify_existing_building.js) | Makes the rotator building always unlocked and adds a new statistic to the building panel | Modifying a builtin building, replacing builtin methods |
+| [modify_ui.js](modify_ui.js) | Shows how to add custom UI elements to builtin game states (the Main Menu in this case) | Extending builtin UI states, Adding CSS |
+| [pasting.js](pasting.js) | Shows a dialog when pasting text in the game | Listening to paste events |
+| [smooth_zooming.js](smooth_zooming.js) | Allows to smoothly zoom in and out | Keybindings, overriding methods |
+| [sandbox.js](sandbox.js) | Makes blueprints free and always unlocked | Overriding builtin methods |
+
+### Advanced Examples
+
+| Example | Description | Demonstrates |
+| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [notification_blocks.js](notification_blocks.js) | Adds a notification block building, which shows a user defined notification when receiving a truthy signal | Adding a new Component, Adding a new GameSystem, Working with wire networks, Adding a new building, Adding a new HUD part, Using Input Dialogs, Adding Translations |
+| [usage_statistics.js](usage_statistics.js) | Displays a percentage on every building showing its utilization | Adding a new component, Adding a new GameSystem, Drawing within a GameSystem, Modifying builtin buildings, Adding custom game logic |
+| [new_item_type.js](new_item_type.js) | Adds a new type of items to the map (fluids) | Adding a new item type, modifying map generation |
+| [buildings_have_cost.js](buildings_have_cost.js) | Adds a new currency, and belts cost 1 of that currency | Extending and replacing builtin methods, Adding CSS and custom sprites |
+| [mirrored_cutter.js](mirrored_cutter.js) | Adds a mirrored variant of the cutter | Adding a new variant to existing buildings |
+
+### Creating new sprites
+
+If you want to add new buildings and create sprites for them, you can download the original Photoshop PSD files here: https://static.shapez.io/building-psds.zip
diff --git a/shapez-io/mod_examples/add_building_basic.js b/shapez-io/mod_examples/add_building_basic.js
new file mode 100644
index 00000000..6b92e769
--- /dev/null
+++ b/shapez-io/mod_examples/add_building_basic.js
@@ -0,0 +1,67 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Add new basic building",
+ version: "1",
+ id: "add-building-basic",
+ description: "Shows how to add a new basic building",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class MetaDemoModBuilding extends shapez.ModMetaBuilding {
+ constructor() {
+ super("demoModBuilding");
+ }
+
+ static getAllVariantCombinations() {
+ return [
+ {
+ variant: shapez.defaultBuildingVariant,
+ name: "A test name",
+ description: "A test building",
+
+ regularImageBase64: RESOURCES["demoBuilding.png"],
+ blueprintImageBase64: RESOURCES["demoBuildingBlueprint.png"],
+ tutorialImageBase64: RESOURCES["demoBuildingBlueprint.png"],
+ },
+ ];
+ }
+
+ getSilhouetteColor() {
+ return "red";
+ }
+
+ setupEntityComponents(entity) {
+ // Here you can add components, for example an ItemProcessorComponent.
+ // To get an idea what you can do with the builtin components, have a look
+ // at the builtin buildings in
+ }
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register the new building
+ this.modInterface.registerNewBuilding({
+ metaClass: MetaDemoModBuilding,
+ buildingIconBase64: RESOURCES["demoBuilding.png"],
+ });
+
+ // Add it to the regular toolbar
+ this.modInterface.addNewBuildingToToolbar({
+ toolbar: "regular",
+ location: "primary",
+ metaClass: MetaDemoModBuilding,
+ });
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+
+const RESOURCES = {
+ "demoBuilding.png":
+ "data:image/png;base64,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",
+
+ "demoBuildingBlueprint.png":
+ "data:image/png;base64,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",
+};
diff --git a/shapez-io/mod_examples/add_building_flipper.js b/shapez-io/mod_examples/add_building_flipper.js
new file mode 100644
index 00000000..03442499
--- /dev/null
+++ b/shapez-io/mod_examples/add_building_flipper.js
@@ -0,0 +1,130 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Add a flipper building",
+ version: "1",
+ id: "add-building-extended",
+ description:
+ "Shows how to add a new building with logic, in this case it flips/mirrors shapez from top to down",
+ minimumGameVersion: ">=1.5.0",
+};
+
+// Declare a new type of item processor
+shapez.enumItemProcessorTypes.flipper = "flipper";
+
+// For now, flipper always has the same speed
+shapez.MOD_ITEM_PROCESSOR_SPEEDS.flipper = () => 10;
+
+// Declare a handler for the processor so we define the "flip" operation
+shapez.MOD_ITEM_PROCESSOR_HANDLERS.flipper = function (payload) {
+ const shapeDefinition = payload.items.get(0).definition;
+
+ // Flip bottom with top on a new, cloned item (NEVER modify the incoming item!)
+ const newLayers = shapeDefinition.getClonedLayers();
+ newLayers.forEach(layer => {
+ const tr = layer[shapez.TOP_RIGHT];
+ const br = layer[shapez.BOTTOM_RIGHT];
+ const bl = layer[shapez.BOTTOM_LEFT];
+ const tl = layer[shapez.TOP_LEFT];
+
+ layer[shapez.BOTTOM_LEFT] = tl;
+ layer[shapez.BOTTOM_RIGHT] = tr;
+
+ layer[shapez.TOP_LEFT] = bl;
+ layer[shapez.TOP_RIGHT] = br;
+ });
+
+ const newDefinition = new shapez.ShapeDefinition({ layers: newLayers });
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(newDefinition),
+ });
+};
+
+// Create the building
+class MetaModFlipperBuilding extends shapez.ModMetaBuilding {
+ constructor() {
+ super("modFlipperBuilding");
+ }
+
+ static getAllVariantCombinations() {
+ return [
+ {
+ name: "Flipper",
+ description: "Flipps/Mirrors shapez from top to bottom",
+ variant: shapez.defaultBuildingVariant,
+
+ regularImageBase64: RESOURCES["flipper.png"],
+ blueprintImageBase64: RESOURCES["flipper.png"],
+ tutorialImageBase64: RESOURCES["flipper.png"],
+ },
+ ];
+ }
+
+ getSilhouetteColor() {
+ return "red";
+ }
+
+ getAdditionalStatistics(root) {
+ const speed = root.hubGoals.getProcessorBaseSpeed(shapez.enumItemProcessorTypes.flipper);
+ return [[shapez.T.ingame.buildingPlacement.infoTexts.speed, shapez.formatItemsPerSecond(speed)]];
+ }
+
+ getIsUnlocked(root) {
+ return true;
+ }
+
+ setupEntityComponents(entity) {
+ // Accept shapes from the bottom
+ entity.addComponent(
+ new shapez.ItemAcceptorComponent({
+ slots: [
+ {
+ pos: new shapez.Vector(0, 0),
+ direction: shapez.enumDirection.bottom,
+ filter: "shape",
+ },
+ ],
+ })
+ );
+
+ // Process those shapes with tye processor type "flipper" (which we added above)
+ entity.addComponent(
+ new shapez.ItemProcessorComponent({
+ inputsPerCharge: 1,
+ processorType: shapez.enumItemProcessorTypes.flipper,
+ })
+ );
+
+ // Eject the result to the top
+ entity.addComponent(
+ new shapez.ItemEjectorComponent({
+ slots: [{ pos: new shapez.Vector(0, 0), direction: shapez.enumDirection.top }],
+ })
+ );
+ }
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register the new building
+ this.modInterface.registerNewBuilding({
+ metaClass: MetaModFlipperBuilding,
+ buildingIconBase64: RESOURCES["flipper.png"],
+ });
+
+ // Add it to the regular toolbar
+ this.modInterface.addNewBuildingToToolbar({
+ toolbar: "regular",
+ location: "primary",
+ metaClass: MetaModFlipperBuilding,
+ });
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+
+const RESOURCES = {
+ "flipper.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMS1jMDAwIDc5LmRhYmFjYmIsIDIwMjEvMDQvMTQtMDA6Mzk6NDQgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCAyMy4wIChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkZDMkFGQkY5NkUyQTExRUM5QUY0OTQyNUQyODU2NURGIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkZDMkFGQkZBNkUyQTExRUM5QUY0OTQyNUQyODU2NURGIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RkMyQUZCRjc2RTJBMTFFQzlBRjQ5NDI1RDI4NTY1REYiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RkMyQUZCRjg2RTJBMTFFQzlBRjQ5NDI1RDI4NTY1REYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz43izVKAAATMklEQVR42uzd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",
+};
diff --git a/shapez-io/mod_examples/base.js b/shapez-io/mod_examples/base.js
new file mode 100644
index 00000000..f76ded9b
--- /dev/null
+++ b/shapez-io/mod_examples/base.js
@@ -0,0 +1,20 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Base",
+ version: "1",
+ id: "base",
+ description: "The most basic mod",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Start the modding here
+ }
+}
diff --git a/shapez-io/mod_examples/buildings_have_cost.js b/shapez-io/mod_examples/buildings_have_cost.js
new file mode 100644
index 00000000..3dae84ae
--- /dev/null
+++ b/shapez-io/mod_examples/buildings_have_cost.js
@@ -0,0 +1,89 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Patch Methods",
+ version: "1",
+ id: "patch-methods",
+ description: "Shows how to patch existing methods to change the game by making the belts cost shapes",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Define our currency
+ const CURRENCY = "CyCyCyCy:--------:CuCuCuCu";
+
+ // Make sure the currency is always pinned
+ this.modInterface.runAfterMethod(shapez.HUDPinnedShapes, "rerenderFull", function () {
+ this.internalPinShape({
+ key: CURRENCY,
+ canUnpin: false,
+ className: "currency",
+ });
+ });
+
+ // Style it
+ this.modInterface.registerCss(`
+ #ingame_HUD_PinnedShapes .shape.currency::after {
+ content: " ";
+ position: absolute;
+ display: inline-block;
+ width: $scaled(8px);
+ height: $scaled(8px);
+ top: $scaled(4px);
+ left: $scaled(-7px);
+ background: url('${RESOURCES["currency.png"]}') center center / contain no-repeat;
+ }
+
+ .currencyIcon {
+ display: inline-block;
+ vertical-align: middle;
+ background: url('${RESOURCES["currency.png"]}') center center / contain no-repeat;
+ }
+
+ .currencyIcon.small {
+ width: $scaled(11px);
+ height: $scaled(11px);
+ }
+ `);
+
+ // Make the player start with some currency
+ this.modInterface.runAfterMethod(shapez.GameCore, "initNewGame", function () {
+ this.root.hubGoals.storedShapes[CURRENCY] = 100;
+ });
+
+ // Make belts have a cost
+ this.modInterface.replaceMethod(shapez.MetaBeltBuilding, "getAdditionalStatistics", function (
+ $original,
+ [root, variant]
+ ) {
+ const oldStats = $original(root, variant);
+ oldStats.push(["Cost", "1 x "]);
+ return oldStats;
+ });
+
+ // Only allow placing an entity when there is enough currency
+ this.modInterface.replaceMethod(shapez.GameLogic, "checkCanPlaceEntity", function (
+ $original,
+ [entity, options]
+ ) {
+ const storedCurrency = this.root.hubGoals.storedShapes[CURRENCY] || 0;
+ return storedCurrency > 0 && $original(entity, options);
+ });
+
+ // Take shapes when placing a building
+ this.modInterface.replaceMethod(shapez.GameLogic, "tryPlaceBuilding", function ($original, args) {
+ const result = $original(...args);
+ if (result && result.components.Belt) {
+ this.root.hubGoals.storedShapes[CURRENCY]--;
+ }
+ return result;
+ });
+ }
+}
+
+const RESOURCES = {
+ "currency.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAIAAAACAAF+ftPjAAAFwWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNy4xLWMwMDAgNzkuZGFiYWNiYiwgMjAyMS8wNC8xNC0wMDozOTo0NCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIzLjAgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAxLTE2VDE2OjAzOjE1KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMS0xNlQxNjowNDowMyswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0wMS0xNlQxNjowNDowMyswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M2IxMTM4ZjEtNzdmMi00MzcyLTg4ZDktZTgzN2I4NzlkNGUwIiB4bXBNTTpEb2N1bWVudElE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",
+};
diff --git a/shapez-io/mod_examples/class_extensions.js b/shapez-io/mod_examples/class_extensions.js
new file mode 100644
index 00000000..ace5aae9
--- /dev/null
+++ b/shapez-io/mod_examples/class_extensions.js
@@ -0,0 +1,32 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Class Extensions",
+ version: "1",
+ id: "class-extensions",
+ description: "Shows how to extend builtin classes",
+ minimumGameVersion: ">=1.5.0",
+};
+
+const BeltExtension = ({ $super, $old }) => ({
+ getShowWiresLayerPreview() {
+ // Access the old method
+ return !$old.getShowWiresLayerPreview();
+ },
+
+ getIsReplaceable(variant, rotationVariant) {
+ // Instead of super, use $super
+ return $super.getIsReplaceable.call(this, variant, rotationVariant);
+ },
+
+ getIsRemoveable() {
+ return false;
+ },
+});
+
+class Mod extends shapez.Mod {
+ init() {
+ this.modInterface.extendClass(shapez.MetaBeltBuilding, BeltExtension);
+ }
+}
diff --git a/shapez-io/mod_examples/custom_css.js b/shapez-io/mod_examples/custom_css.js
new file mode 100644
index 00000000..0d28fda7
--- /dev/null
+++ b/shapez-io/mod_examples/custom_css.js
@@ -0,0 +1,44 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Add custom CSS",
+ version: "1",
+ id: "custom-css",
+ description: "Shows how to add custom css",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Notice that, since the UI is scaled dynamically, every pixel value
+ // should be wrapped in '$scaled()' (see below)
+
+ this.modInterface.registerCss(`
+ * {
+ font-family: "Comic Sans", "Comic Sans MS", "ComicSans", Tahoma !important;
+ }
+
+ #state_MainMenuState {
+ background: #9dc499 url('${RESOURCES["cat.png"]}') top left repeat !important;
+ }
+
+ #state_MainMenuState .fullscreenBackgroundVideo {
+ display: none !important;
+ }
+
+ #state_MainMenuState .mainContainer, #state_MainMenuState .modsOverview {
+ border: $scaled(5px) solid #000 !important;
+ }
+ `);
+ }
+}
+
+const RESOURCES = {
+ "cat.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAGxAAABsQFhmCgOAAAE8mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNy4xLWMwMDAgNzkuZGFiYWNiYiwgMjAyMS8wNC8xNC0wMDozOTo0NCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIzLjAgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAxLTE1VDEzOjI3OjU1KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMS0xNVQxMzozMDowMyswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0wMS0xNVQxMzozMDowMyswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTI2OGI2OWUtYTQ2Ni00YmNkLWJjNGYtM2VlNmUwOGI2NzA2IiB4bXBNTTpEb2N1bWVudElE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",
+};
diff --git a/shapez-io/mod_examples/custom_drawing.js b/shapez-io/mod_examples/custom_drawing.js
new file mode 100644
index 00000000..2dccab2d
--- /dev/null
+++ b/shapez-io/mod_examples/custom_drawing.js
@@ -0,0 +1,63 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: custom drawing",
+ version: "1",
+ id: "base",
+ description: "Displays an indicator on every item processing building when its working",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class ItemProcessorStatusGameSystem extends shapez.GameSystem {
+ drawChunk(parameters, chunk) {
+ const contents = chunk.containedEntitiesByLayer.regular;
+ for (let i = 0; i < contents.length; ++i) {
+ const entity = contents[i];
+ const processorComp = entity.components.ItemProcessor;
+ if (!processorComp) {
+ continue;
+ }
+
+ const staticComp = entity.components.StaticMapEntity;
+
+ const context = parameters.context;
+ const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
+
+ // Culling for better performance
+ if (parameters.visibleRect.containsCircle(center.x, center.y, 40)) {
+ // Circle
+ context.fillStyle = processorComp.ongoingCharges.length === 0 ? "#aaa" : "#53cf47";
+ context.strokeStyle = "#000";
+ context.lineWidth = 1;
+
+ context.beginCircle(center.x + 5, center.y + 5, 4);
+ context.fill();
+ context.stroke();
+ }
+ }
+ }
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register our game system
+ this.modInterface.registerGameSystem({
+ id: "item_processor_status",
+ systemClass: ItemProcessorStatusGameSystem,
+
+ // Specify at which point the update method will be called,
+ // in this case directly before the belt system. You can use
+ // before: "end" to make it the last system
+ before: "belt",
+
+ // Specify where our drawChunk method should be called, check out
+ // map_chunk_view
+ drawHooks: ["staticAfter"],
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/custom_keybinding.js b/shapez-io/mod_examples/custom_keybinding.js
new file mode 100644
index 00000000..0109833c
--- /dev/null
+++ b/shapez-io/mod_examples/custom_keybinding.js
@@ -0,0 +1,32 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Custom Keybindings",
+ version: "1",
+ id: "base",
+ description: "Shows how to add a new keybinding",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register keybinding
+ this.modInterface.registerIngameKeybinding({
+ id: "demo_mod_binding",
+ keyCode: shapez.keyToKeyCode("F"),
+ translation: "Do something (always with SHIFT)",
+ modifiers: {
+ shift: true,
+ },
+ handler: root => {
+ this.dialogs.showInfo("Mod Message", "It worked!");
+ return shapez.STOP_PROPAGATION;
+ },
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/custom_sub_shapes.js b/shapez-io/mod_examples/custom_sub_shapes.js
new file mode 100644
index 00000000..3aea03cf
--- /dev/null
+++ b/shapez-io/mod_examples/custom_sub_shapes.js
@@ -0,0 +1,46 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Custom Sub Shapes",
+ version: "1",
+ id: "custom-sub-shapes",
+ description: "Shows how to add custom sub shapes",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Add a new type of sub shape ("Line", short code "L")
+ this.modInterface.registerSubShapeType({
+ id: "line",
+ shortCode: "L",
+
+ // Make it spawn on the map
+ weightComputation: distanceToOriginInChunks =>
+ Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)),
+
+ // This defines how to draw it
+ draw: ({ context, quadrantSize, layerScale }) => {
+ const quadrantHalfSize = quadrantSize / 2;
+ context.beginPath();
+ context.moveTo(-quadrantHalfSize, quadrantHalfSize);
+ context.arc(
+ -quadrantHalfSize,
+ quadrantHalfSize,
+ quadrantSize * layerScale,
+ -Math.PI * 0.25,
+ 0
+ );
+ context.closePath();
+ context.fill();
+ context.stroke();
+ },
+ });
+
+ // Modify the goal of the first level to add our goal
+ this.signals.modifyLevelDefinitions.add(definitions => {
+ definitions[0].shape = "LuLuLuLu";
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/custom_theme.js b/shapez-io/mod_examples/custom_theme.js
new file mode 100644
index 00000000..a70d949f
--- /dev/null
+++ b/shapez-io/mod_examples/custom_theme.js
@@ -0,0 +1,99 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Custom Game Theme",
+ version: "1",
+ id: "custom-theme",
+ description: "Shows how to add a custom game theme",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ this.modInterface.registerGameTheme({
+ id: "my-theme",
+ name: "My fancy theme",
+ theme: RESOURCES["my-theme.json"],
+ });
+ }
+}
+
+const RESOURCES = {
+ "my-theme.json": {
+ map: {
+ background: "#abc",
+ grid: "#ccc",
+ gridLineWidth: 1,
+
+ selectionOverlay: "rgba(74, 163, 223, 0.7)",
+ selectionOutline: "rgba(74, 163, 223, 0.5)",
+ selectionBackground: "rgba(74, 163, 223, 0.2)",
+
+ chunkBorders: "rgba(0, 30, 50, 0.03)",
+
+ directionLock: {
+ regular: {
+ color: "rgb(74, 237, 134)",
+ background: "rgba(74, 237, 134, 0.2)",
+ },
+ wires: {
+ color: "rgb(74, 237, 134)",
+ background: "rgba(74, 237, 134, 0.2)",
+ },
+ error: {
+ color: "rgb(255, 137, 137)",
+ background: "rgba(255, 137, 137, 0.2)",
+ },
+ },
+
+ colorBlindPickerTile: "rgba(50, 50, 50, 0.4)",
+
+ resources: {
+ shape: "#eaebec",
+ red: "#ffbfc1",
+ green: "#cbffc4",
+ blue: "#bfdaff",
+ },
+
+ chunkOverview: {
+ empty: "#a6afbb",
+ filled: "#c5ccd6",
+ beltColor: "#777",
+ },
+
+ wires: {
+ overlayColor: "rgba(97, 161, 152, 0.75)",
+ previewColor: "rgb(97, 161, 152, 0.4)",
+ highlightColor: "rgba(72, 137, 255, 1)",
+ },
+
+ connectedMiners: {
+ overlay: "rgba(40, 50, 60, 0.5)",
+ textColor: "#fff",
+ textColorCapped: "#ef5072",
+ background: "rgba(40, 50, 60, 0.8)",
+ },
+
+ zone: {
+ borderSolid: "rgba(23, 192, 255, 1)",
+ outerColor: "rgba(240, 240, 255, 0.5)",
+ },
+ },
+
+ items: {
+ outline: "#55575a",
+ outlineWidth: 0.75,
+ circleBackground: "rgba(40, 50, 65, 0.1)",
+ },
+
+ shapeTooltip: {
+ background: "#dee1ea",
+ outline: "#54565e",
+ },
+ },
+};
diff --git a/shapez-io/mod_examples/mirrored_cutter.js b/shapez-io/mod_examples/mirrored_cutter.js
new file mode 100644
index 00000000..ae457a8c
--- /dev/null
+++ b/shapez-io/mod_examples/mirrored_cutter.js
@@ -0,0 +1,81 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Mirrored Cutter Variant",
+ version: "1",
+ id: "mirrored-cutter",
+ description: "Shows how to add new variants to existing buildings",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ shapez.enumCutterVariants.mirrored = "mirrored";
+
+ this.modInterface.addVariantToExistingBuilding(
+ shapez.MetaCutterBuilding,
+ shapez.enumCutterVariants.mirrored,
+ {
+ name: "Cutter (Mirrored)",
+ description: "A mirrored cutter",
+
+ tutorialImageBase64: RESOURCES["cutter-mirrored.png"],
+ regularSpriteBase64: RESOURCES["cutter-mirrored.png"],
+ blueprintSpriteBase64: RESOURCES["cutter-mirrored.png"],
+
+ dimensions: new shapez.Vector(2, 1),
+
+ additionalStatistics(root) {
+ const speed = root.hubGoals.getProcessorBaseSpeed(shapez.enumItemProcessorTypes.cutter);
+ return [
+ [
+ shapez.T.ingame.buildingPlacement.infoTexts.speed,
+ shapez.formatItemsPerSecond(speed),
+ ],
+ ];
+ },
+
+ isUnlocked(root) {
+ return true;
+ },
+ }
+ );
+
+ // Extend instance methods
+ this.modInterface.extendClass(shapez.MetaCutterBuilding, ({ $old }) => ({
+ updateVariants(entity, rotationVariant, variant) {
+ if (variant === shapez.enumCutterVariants.mirrored) {
+ entity.components.ItemEjector.setSlots([
+ { pos: new shapez.Vector(0, 0), direction: shapez.enumDirection.top },
+ { pos: new shapez.Vector(1, 0), direction: shapez.enumDirection.top },
+ ]);
+ entity.components.ItemProcessor.type = shapez.enumItemProcessorTypes.cutter;
+ entity.components.ItemAcceptor.setSlots([
+ {
+ pos: new shapez.Vector(1, 0),
+ direction: shapez.enumDirection.bottom,
+ filter: "shape",
+ },
+ ]);
+ } else {
+ // Since we are changing the ItemAcceptor slots, we should reset
+ // it to the regular slots when we are not using our mirrored variant
+ entity.components.ItemAcceptor.setSlots([
+ {
+ pos: new shapez.Vector(0, 0),
+ direction: shapez.enumDirection.bottom,
+ filter: "shape",
+ },
+ ]);
+ $old.updateVariants.bind(this)(entity, rotationVariant, variant);
+ }
+ },
+ }));
+ }
+}
+
+const RESOURCES = {
+ "cutter-mirrored.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAADACAYAAAAN6LRnAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4VpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMS1jMDAwIDc5LmRhYmFjYmIsIDIwMjEvMDQvMTQtMDA6Mzk6NDQgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NGQwZTY5MmYtOTRlNy00MDQyLWFjY2ItNmU3OGEzMGU1N2ZjIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIwOTY4MzA0N0I4QTExRUNCQzRERjNBOEI5ODYyRkJBIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIwOTY4MzAzN0I4QTExRUNCQzRERjNBOEI5ODYyRkJBIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmRmNmU1ODFjLTQ3ZDItYzE0OS05MmQzLWRhZDMyMTg5YTA3MiIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk3MDYxZWYyLTY2ZGMtZjI0Zi1iZTMy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",
+};
diff --git a/shapez-io/mod_examples/mod_settings.js b/shapez-io/mod_examples/mod_settings.js
new file mode 100644
index 00000000..b87c138b
--- /dev/null
+++ b/shapez-io/mod_examples/mod_settings.js
@@ -0,0 +1,32 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Mod Settings",
+ version: "1",
+ id: "mod-settings",
+ description: "Shows how to add settings to your mod",
+ minimumGameVersion: ">=1.5.0",
+
+ settings: {
+ timesLaunched: 0,
+ },
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Increment the setting every time we launch the mod
+ this.settings.timesLaunched++;
+ this.saveSettings();
+
+ // Show a dialog in the main menu with the settings
+ this.signals.stateEntered.add(state => {
+ if (state instanceof shapez.MainMenuState) {
+ this.dialogs.showInfo(
+ "Welcome back",
+ `You have launched this mod ${this.settings.timesLaunched} times`
+ );
+ }
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/modify_existing_building.js b/shapez-io/mod_examples/modify_existing_building.js
new file mode 100644
index 00000000..b09f5a20
--- /dev/null
+++ b/shapez-io/mod_examples/modify_existing_building.js
@@ -0,0 +1,27 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Modify existing building",
+ version: "1",
+ id: "modify-existing-building",
+ description: "Shows how to modify an existing building",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Make Rotator always unlocked
+ this.modInterface.replaceMethod(shapez.MetaRotaterBuilding, "getIsUnlocked", function () {
+ return true;
+ });
+
+ // Add some custom stats to the info panel when selecting the building
+ this.modInterface.replaceMethod(shapez.MetaRotaterBuilding, "getAdditionalStatistics", function (
+ root,
+ variant
+ ) {
+ return [["Awesomeness", 5]];
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/modify_theme.js b/shapez-io/mod_examples/modify_theme.js
new file mode 100644
index 00000000..de7f0ad2
--- /dev/null
+++ b/shapez-io/mod_examples/modify_theme.js
@@ -0,0 +1,24 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Modify Builtin Themes",
+ version: "1",
+ id: "modify-theme",
+ description: "Shows how to modify builtin themes",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ shapez.THEMES.light.map.background = "#eee";
+ shapez.THEMES.light.items.outline = "#000";
+
+ shapez.THEMES.dark.map.background = "#245";
+ shapez.THEMES.dark.items.outline = "#fff";
+ }
+}
diff --git a/shapez-io/mod_examples/modify_ui.js b/shapez-io/mod_examples/modify_ui.js
new file mode 100644
index 00000000..4beb403d
--- /dev/null
+++ b/shapez-io/mod_examples/modify_ui.js
@@ -0,0 +1,46 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Modify UI",
+ version: "1",
+ id: "modify-ui",
+ description: "Shows how to modify a builtin game state, in this case the main menu",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Add fancy sign to main menu
+ this.signals.stateEntered.add(state => {
+ if (state.key === "MainMenuState") {
+ const element = document.createElement("div");
+ element.id = "demo_mod_hello_world_element";
+ document.body.appendChild(element);
+
+ const button = document.createElement("button");
+ button.classList.add("styledButton");
+ button.innerText = "Hello!";
+ button.addEventListener("click", () => {
+ this.dialogs.showInfo("Mod Message", "Button clicked!");
+ });
+ element.appendChild(button);
+ }
+ });
+
+ this.modInterface.registerCss(`
+ #demo_mod_hello_world_element {
+ position: absolute;
+ top: calc(10px * var(--ui-scale));
+ left: calc(10px * var(--ui-scale));
+ color: red;
+ z-index: 0;
+ }
+
+ `);
+ }
+}
diff --git a/shapez-io/mod_examples/new_item_type.js b/shapez-io/mod_examples/new_item_type.js
new file mode 100644
index 00000000..104ef0a0
--- /dev/null
+++ b/shapez-io/mod_examples/new_item_type.js
@@ -0,0 +1,149 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: New Item Type (Fluids)",
+ version: "1",
+ id: "new-item-type",
+ description: "Shows how to add a new item type (fluid)",
+ minimumGameVersion: ">=1.5.0",
+};
+
+// Define which fluid types there are
+const enumFluidType = {
+ water: "water",
+ oil: "oil",
+};
+
+// Define which color they should have on the map
+const fluidColors = {
+ [enumFluidType.water]: "#477be7",
+ [enumFluidType.oil]: "#bc483a",
+};
+
+// The fluid item class (also see ColorItem and ShapeItem)
+class FluidItem extends shapez.BaseItem {
+ static getId() {
+ return "fluid";
+ }
+
+ static getSchema() {
+ return shapez.types.enum(enumFluidType);
+ }
+
+ serialize() {
+ return this.fluidType;
+ }
+
+ deserialize(data) {
+ this.fluidType = data;
+ }
+
+ getItemType() {
+ return "fluid";
+ }
+
+ /**
+ * @returns {string}
+ */
+ getAsCopyableKey() {
+ return this.fluidType;
+ }
+
+ /**
+ * @param {BaseItem} other
+ */
+ equalsImpl(other) {
+ return this.fluidType === /** @type {FluidItem} */ (other).fluidType;
+ }
+
+ /**
+ * @param {enumFluidType} fluidType
+ */
+ constructor(fluidType) {
+ super();
+ this.fluidType = fluidType;
+ }
+
+ getBackgroundColorAsResource() {
+ return fluidColors[this.fluidType];
+ }
+
+ /**
+ * Draws the item to a canvas
+ * @param {CanvasRenderingContext2D} context
+ * @param {number} size
+ */
+ drawFullSizeOnCanvas(context, size) {
+ if (!this.cachedSprite) {
+ this.cachedSprite = shapez.Loader.getSprite(`sprites/fluids/${this.fluidType}.png`);
+ }
+ this.cachedSprite.drawCentered(context, size / 2, size / 2, size);
+ }
+
+ /**
+ * @param {number} x
+ * @param {number} y
+ * @param {number} diameter
+ * @param {DrawParameters} parameters
+ */
+ drawItemCenteredClipped(x, y, parameters, diameter = shapez.globalConfig.defaultItemDiameter) {
+ const realDiameter = diameter * 0.6;
+ if (!this.cachedSprite) {
+ this.cachedSprite = shapez.Loader.getSprite(`sprites/fluids/${this.fluidType}.png`);
+ }
+ this.cachedSprite.drawCachedCentered(parameters, x, y, realDiameter);
+ }
+}
+
+/**
+ * Singleton instances.
+ *
+ * NOTICE: The game tries to instantiate as few instances as possible.
+ * Which means that if you have two types of fluids in this case, there should
+ * ONLY be 2 instances of FluidItem at *any* time.
+ *
+ * This works by having a map from fluid type to the FluidItem singleton.
+ * Additionally, all items are and should be immutable.
+ * @type {Object}
+ */
+const FLUID_ITEM_SINGLETONS = {};
+
+for (const fluidType in enumFluidType) {
+ FLUID_ITEM_SINGLETONS[fluidType] = new FluidItem(fluidType);
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register the sprites
+ this.modInterface.registerSprite("sprites/fluids/oil.png", RESOURCES["oil.png"]);
+ this.modInterface.registerSprite("sprites/fluids/water.png", RESOURCES["water.png"]);
+
+ // Make the item spawn on the map
+ this.modInterface.runAfterMethod(shapez.MapChunk, "generatePatches", function ({
+ rng,
+ chunkCenter,
+ distanceToOriginInChunks,
+ }) {
+ // Generate a simple patch
+ // ALWAYS use rng and NEVER use Math.random() otherwise the map will look different
+ // every time you resume the game
+ if (rng.next() > 0.8) {
+ const fluidType = rng.choice(Array.from(Object.keys(enumFluidType)));
+ this.internalGeneratePatch(rng, 4, FLUID_ITEM_SINGLETONS[fluidType]);
+ }
+ });
+
+ this.modInterface.registerItem(FluidItem, itemData => FLUID_ITEM_SINGLETONS[itemData]);
+ }
+}
+
+///////////////////////////////////////
+
+const RESOURCES = {
+ "oil.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAOxAAADsQH1g+1JAAAE8mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNy4xLWMwMDAgNzkuZGFiYWNiYiwgMjAyMS8wNC8xNC0wMDozOTo0NCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIzLjAgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAxLTE3VDEyOjIyOjUxKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMS0xN1QxMjoyMzozMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0wMS0xN1QxMjoyMzozMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Y2EzMGQxMDEtZWU5Yy00Mzc2LTgyOGEtZDM5ZmFkN2ViZTYyIiB4bXBNTTpEb2N1bWVudElE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",
+
+ "water.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAATEAAAExAE8zNSDAAAE8mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNy4xLWMwMDAgNzkuZGFiYWNiYiwgMjAyMS8wNC8xNC0wMDozOTo0NCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIzLjAgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAxLTE3VDEyOjI0OjA3KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMS0xN1QxMjoyNTowNiswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0wMS0xN1QxMjoyNTowNiswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NmU3YjM1ZDctOTAyNi00ZjNlLTkxNGItZTc0NjJhMzM3MGE4IiB4bXBNTTpEb2N1bWVudElE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",
+};
diff --git a/shapez-io/mod_examples/notification_blocks.js b/shapez-io/mod_examples/notification_blocks.js
new file mode 100644
index 00000000..23f95943
--- /dev/null
+++ b/shapez-io/mod_examples/notification_blocks.js
@@ -0,0 +1,314 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Notification Blocks",
+ version: "1",
+ id: "notification-blocks",
+ description:
+ "Adds a new building to the wires layer, 'Notification Blocks' which show a custom notification when they get a truthy signal.",
+
+ minimumGameVersion: ">=1.5.0",
+};
+
+////////////////////////////////////////////////////////////////////////
+// This is the component storing which text the block should show as
+// a notification.
+class NotificationBlockComponent extends shapez.Component {
+ static getId() {
+ return "NotificationBlock";
+ }
+
+ static getSchema() {
+ // Here you define which properties should be saved to the savegame
+ // and get automatically restored
+ return {
+ notificationText: shapez.types.string,
+ lastStoredInput: shapez.types.bool,
+ };
+ }
+
+ constructor() {
+ super();
+ this.notificationText = "Test";
+ this.lastStoredInput = false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// The game system to trigger notifications when the signal changes
+class NotificationBlocksSystem extends shapez.GameSystemWithFilter {
+ constructor(root) {
+ // By specifying the list of components, `this.allEntities` will only
+ // contain entities which have *all* of the specified components
+ super(root, [NotificationBlockComponent]);
+
+ // Ask for a notification text once an entity is placed
+ this.root.signals.entityManuallyPlaced.add(entity => {
+ const editorHud = this.root.hud.parts.notificationBlockEdit;
+ if (editorHud) {
+ editorHud.editNotificationText(entity, { deleteOnCancel: true });
+ }
+ });
+ }
+
+ update() {
+ if (!this.root.gameInitialized) {
+ // Do not start updating before the wires network was
+ // computed to avoid dispatching all notifications
+ return;
+ }
+
+ // Go over all notification blocks and check if the signal changed
+ for (let i = 0; i < this.allEntities.length; ++i) {
+ const entity = this.allEntities[i];
+
+ // Compute if the bottom pin currently has a truthy input
+ const pinsComp = entity.components.WiredPins;
+ const network = pinsComp.slots[0].linkedNetwork;
+
+ let currentInput = false;
+
+ if (network && network.hasValue()) {
+ const value = network.currentValue;
+ if (value && shapez.isTruthyItem(value)) {
+ currentInput = true;
+ }
+ }
+
+ // If the value changed, show the notification if its truthy
+ const notificationComp = entity.components.NotificationBlock;
+ if (currentInput !== notificationComp.lastStoredInput) {
+ notificationComp.lastStoredInput = currentInput;
+ if (currentInput) {
+ this.root.hud.signals.notification.dispatch(
+ notificationComp.notificationText,
+ shapez.enumNotificationType.info
+ );
+ }
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// The actual notification block building
+class MetaNotificationBlockBuilding extends shapez.ModMetaBuilding {
+ constructor() {
+ super("notification_block");
+ }
+
+ static getAllVariantCombinations() {
+ return [
+ {
+ variant: shapez.defaultBuildingVariant,
+ name: "Notification Block",
+ description: "Shows a predefined notification on screen when receiving a truthy signal",
+
+ regularImageBase64: RESOURCES["notification_block.png"],
+ blueprintImageBase64: RESOURCES["notification_block.png"],
+ tutorialImageBase64: RESOURCES["notification_block.png"],
+ },
+ ];
+ }
+
+ getSilhouetteColor() {
+ return "#daff89";
+ }
+
+ getIsUnlocked(root) {
+ return root.hubGoals.isRewardUnlocked(shapez.enumHubGoalRewards.reward_wires_painter_and_levers);
+ }
+
+ getLayer() {
+ return "wires";
+ }
+
+ getDimensions() {
+ return new shapez.Vector(1, 1);
+ }
+
+ getRenderPins() {
+ // Do not show pin overlays since it would hide our building icon
+ return false;
+ }
+
+ setupEntityComponents(entity) {
+ // Accept logical input from the bottom
+ entity.addComponent(
+ new shapez.WiredPinsComponent({
+ slots: [
+ {
+ pos: new shapez.Vector(0, 0),
+ direction: shapez.enumDirection.bottom,
+ type: shapez.enumPinSlotType.logicalAcceptor,
+ },
+ ],
+ })
+ );
+
+ // Add your notification component to identify the building as a notification block
+ entity.addComponent(new NotificationBlockComponent());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// HUD Component to be able to edit notification blocks by clicking them
+class HUDNotificationBlockEdit extends shapez.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 notificationComp = contents.components.NotificationBlock;
+ if (notificationComp) {
+ if (button === shapez.enumMouseButton.left) {
+ this.editNotificationText(contents, {
+ deleteOnCancel: false,
+ });
+ return shapez.STOP_PROPAGATION;
+ }
+ }
+ }
+ }
+
+ /**
+ * Asks the player to enter a notification text
+ * @param {Entity} entity
+ * @param {object} param0
+ * @param {boolean=} param0.deleteOnCancel
+ */
+ editNotificationText(entity, { deleteOnCancel = true }) {
+ const notificationComp = entity.components.NotificationBlock;
+ if (!notificationComp) {
+ return;
+ }
+
+ // save the uid because it could get stale
+ const uid = entity.uid;
+
+ // create an input field to query the text
+ const textInput = new shapez.FormElementInput({
+ id: "notificationText",
+ placeholder: "",
+ defaultValue: notificationComp.notificationText,
+ validator: val => val.length > 0,
+ });
+
+ // create the dialog & show it
+ const dialog = new shapez.DialogWithForm({
+ app: this.root.app,
+ title: shapez.T.mods.notificationBlocks.dialogTitle,
+ desc: shapez.T.mods.notificationBlocks.enterNotificationText,
+ formElements: [textInput],
+ buttons: ["cancel:bad:escape", "ok:good:enter"],
+ closeButton: false,
+ });
+ this.root.hud.parts.dialogs.internalShowDialog(dialog);
+
+ // When confirmed, set the text
+ dialog.buttonSignals.ok.add(() => {
+ if (!this.root || !this.root.entityMgr) {
+ // Game got stopped
+ return;
+ }
+
+ const entityRef = this.root.entityMgr.findByUid(uid, false);
+ if (!entityRef) {
+ // outdated
+ return;
+ }
+
+ const notificationComp = entityRef.components.NotificationBlock;
+ if (!notificationComp) {
+ // no longer interesting
+ return;
+ }
+
+ // set the text
+ notificationComp.notificationText = textInput.getValue();
+ });
+
+ // When cancelled, destroy the entity again
+ if (deleteOnCancel) {
+ dialog.buttonSignals.cancel.add(() => {
+ if (!this.root || !this.root.entityMgr) {
+ // Game got stopped
+ return;
+ }
+
+ const entityRef = this.root.entityMgr.findByUid(uid, false);
+ if (!entityRef) {
+ // outdated
+ return;
+ }
+
+ const notificationComp = entityRef.components.NotificationBlock;
+ if (!notificationComp) {
+ // no longer interesting
+ return;
+ }
+
+ this.root.logic.tryDeleteBuilding(entityRef);
+ });
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// The actual mod logic
+class Mod extends shapez.Mod {
+ init() {
+ // Register the component
+ this.modInterface.registerComponent(NotificationBlockComponent);
+
+ // Register the new building
+ this.modInterface.registerNewBuilding({
+ metaClass: MetaNotificationBlockBuilding,
+ buildingIconBase64: RESOURCES["notification_block.png"],
+ });
+
+ // Add it to the regular toolbar
+ this.modInterface.addNewBuildingToToolbar({
+ toolbar: "wires",
+ location: "secondary",
+ metaClass: MetaNotificationBlockBuilding,
+ });
+
+ // Register our game system so we can dispatch the notifications
+ this.modInterface.registerGameSystem({
+ id: "notificationBlocks",
+ systemClass: NotificationBlocksSystem,
+ before: "constantSignal",
+ });
+
+ // Register our hud element to be able to edit the notification texts
+ this.modInterface.registerHudElement("notificationBlockEdit", HUDNotificationBlockEdit);
+
+ // This mod also supports translations
+ this.modInterface.registerTranslations("en", {
+ mods: {
+ notificationBlocks: {
+ enterNotificationText:
+ "Enter the notification text to show once the signal switches from 0 to 1:",
+ },
+ },
+ });
+ }
+}
+
+const RESOURCES = {
+ "notification_block.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4VpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMS1jMDAwIDc5LmRhYmFjYmIsIDIwMjEvMDQvMTQtMDA6Mzk6NDQgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NGQwZTY5MmYtOTRlNy00MDQyLWFjY2ItNmU3OGEzMGU1N2ZjIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAyQTMyQTVGNzA1RTExRUNBQ0EzQTZCNEYxQjM5MkFBIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAyQTMyQTVFNzA1RTExRUNBQ0EzQTZCNEYxQjM5MkFBIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY3NDVhMzZlLTkyMWUtNDViYS04NDFjLWVjOWZjZTc0Y2MyNSIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk3MDYxZWYyLTY2ZGMtZjI0Zi1iZTMyLTVhNTdhOWI3YTQzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv8zaPsAABCFSURBVHja7J1pcFXlGcefuy/JTULInkAICFJRUbAqCrXW0c5Iq62lQ6c4/VS3sWpdpu30a2f6oYsdly6g1qWICGpdaatSdUQQlYiIllUIhKyQ3Cx3Pffe0/c53Lgk77nZ7nLuOf+f80zkJLnn5LzP/32f511tqqoSAFbFjlcAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAIAAAjIozVx98w0134O2CKfPQ2vvQAgAAAQBQjCFQjrChyAyPCgFkhyXClgtbIWypsCZhLviX4VGEtQvbJextYduEtUIAE+NCYdcL+4GwBvhSUcKVVEvaVqWvdQh7Vth6Ye8hBxgLv6w/CNsp7DY4v+loSJfrznQ5t0AAX7BG2EfC7oafWIK70+W9BgIgujXdLAbgF5YikC73W60sAG4SH4QvWJoH035guSSYm7/7M2ZSHhstON9HZ5zrpdpmFwUqHORwohfU6CQTKg0Fk9TdptChPVE68GGElFjGnlH2gz5hT1pFAJwA/VXvmzbRJi35ZiktuzpA/gDG6YoNrqQqqpyanbnUR5evKqcdW4ao9c1hUlO6v8b+sF3YESuEQLfqxfy+Ejut/nkVXbG6HM5vErgcuTxX31mllW+GnKAg+UC+vexC0unt4Rf1419U0+wzPfAaEzJ7gYfW/LI6U8XGfnGR2QWwRi/sufbGSppZ54SnmJjKWqdWzjb75PzDTAJYKbvIMf+sBaj5rQCXM5e3DteZWQBnC5s3+qLLbdMSXmAdLlkZ0Hr5JDQKO9+sAlguuzj3HC8SXovhK7VrXdyT8RMzCGCF7OL8xV54hAWZd443o5/YbLavmBkEsEx2sWGeG95gQerm6M5sv8CsLUCj7CKP8ALrkaHcG80oAG7DpFU9pjdYkwzl7qY8rvxD9gksDQQAIAAAjETrrp0IgYB1+WTv7gAEACxLSlV5UpgXAgBWhoeKbRAAsCq6XecQALAKLggAWBknBACsjB0CAAACAAACAAACAAACAAACAAACAAACAAACAGAKYC/CHKLEVeo+plDX0Th1ia/9PQmKhlLade3lu2zahrHlVQ6qaXJRfYub6ue4ye3FOmkIoEjp607Qh2+F6MShGHW1KeP+fLCXqPMo0b4PIp9fYzE0neGm8y4roaoGHIwJARQBR/8X0xz/4IeRaX9WT7uiWeubIWpZ5NWEgA3EIABDcmx/jN5/fZgO74nm5POPfBLVjFuEr18VgBAgAOPwxuYBzfnzQfuhuLBTtOgiP125poLcHuQJEECBCPYm6OW/91PHZ/G83/uTnWE6cThO37+lkqqbkB9AAPl2wHfD9OqG4HgHv2nY7Xby+nzk9/nJJ8ztcZPD4dSuM6qaokQiSUo8TpFohCLhEIXDYUqlUpkFeDJBj/6mh759fQUtXlGCQoEA8sOed8L07yf6x/05j8dDM2ZUUll5hfb/mXDzqle/n8qpQvs3i2FwcID6+/soEsmcUP9nfVDrUr3gilIUDgSQW/buGN/53W4PVVVXa84/UstPFpdQxMyqaqqcWUUDA0E62duTUQj/3TQg7mWjJZejJYAAchh3b3kss/NXCaetqa0TIU52drvmPfErKmZQuWhFeoUIurs6dX/29Y1BEpEVwqEpgKkQ4yW8It5+7clgRkdtaGikemHZcv7Rn19TU0uzZjeTPcPnvyqe8WSHggKDALLLOy8OUVwn4eUwZ3bzHC1kyTXcGrS0zCWnU97zo4pHfO2pARQYBJA9Pn0vrIU/ejUzO39ZWXnensfvL6E5LS1CBPLI9fiBGH2wdRgFBwFMn4FTSS2s0HX+2XMoECjL+3Nxd2rznLm64dCbzw7Sqc4EChACmB673xqmeFQe+nCyW1ZeXrBn8/v91NggP0kolVS1OUkAApg6wu8PfxzTDUOqq2sK/ogV2jiDXIR7d4S0FgxAAFOibX9Mt0elvr4hp8d2TgZ+Ftl4A7dc3IIBCGBK7NkmDyHKysrIX2KcvnYeeONBNxk8eQ5AAFPi8Mfyqc08Oms0KitnSq/zhLnIcAqFCQFMjs6jcWny63a7qaTEeHNutMl2IilGKwABZIV978vn3XCX51Tn9+QavbGIg7sjKFAIYHLo1ZqlpQHDPnOJzrO1H4qhQCGAiZNMqFoINBru9fH6jLsUkadcy0aHg71JGurHoBgEMEFCA/Kkkeff6M3BMQI8Cc/llh+n1dsRRcFCABMjPJzUEYDTsPH/CC4dgQ71J0lVVRQuBDA+sYiqU8Ma/zXpTcXmHq2Egu5QCGAC8DwaKbYi2IFB5xFTKRYApkVAABZGgQAgACujilaAWwIAAVg4vEMeAAFYuRVAAwABWFsAUAAEgBYAQAAAQAAAQAAAQAAAAgAAAgAAAgAAArAsZtxMauAkpkBAABOAz/fd+rR8d2W1CCaS6T3j7rei1HMcyyIhgHHY8cqQtiZYRiAQMPzzZ9qlevsr2B0CAsgAH3+ktw06b4dSXVNr+L+hvKKCZups3NXdlqDWN8IoaAhgLP09Cd1t0HkdMO+/WSzwrtV6i/fffn5IuuMFBGBx3v3XECUUeejDNb/HWzwns/Pi/foGfcHywd4AAvictn0x+ni7PDTg7QaNsA36ZBk5WE8Gb/q1dztCIQggjd5BEtrBd42zDLMN+mThQ/v0jlHC4RkQgAbPjz/QKu8d4VMZfT5f0f5tTpdLO7VSBucBp7rQLWp5Aejtm8nbDFbX1BT938ehUGmpfDfrE9gzFAJoPyjvEeH42WYzx2up0Dk84/hB9AZZXgBdx+RHIPEpjGaBWzMZfV04UNvyAtA7dN0Ki8dTWCAMAdQ1y3dTDkfM000Yjeoc9VTjoFgUibClBTBrvlwAA8EgpVLmmEHZ39cnF3+Lk6LhOLZKsbIA6lvcZHeM7edXlDh1dpwo+r+vt6ebwmF5n3/9nNNjBPEY9gy19EDYwqXyvv6+vlPUfvwYxePF11uSSCSoq7ODuro6dYTvpMCM08WeTGCtgNPKf/ziFX769D15zN/f30eDgwPayZB8QqTRR4VV8Z+iKBQOhbSveiy6+IueoST2C7W2AGYt8NCyqwO0Y8uQ9PvJZFITgVmYf56b5p3rRuaLEOgLVlxbRs0LPVn/XG4xpmPZhsOeFd/76hhHMZx8gxYgD6y+s4qeeeAUfbY3ewfK8VQEbSHNZHtabBx+9WtJbLaoanDQNTcGyOX5qrCcLggAAkiz6raZWij09guDWfm8UGiYGt1Tm1EaGh7K2t+1aJmHll8jH912e1D8eANfgvOBeed6afdbITp+MEanOqc+WMQ9SCdOHKemptmT+r3u7i4Kh6c3GFdWaae6OU466yIP1c6WF7GvBLkABCChpslFV605vaAkGkrR4b0h6utWKJUk3YPoTnUk6dBHY7tMeSDK4XBSXV3dhCbYneztoR4hABmN85zUtEDnrGKVl28SlZSfdvySssz3crocqP0hgPHxlthp0UUBioQVikf1uxZPHFKkAhhx6rAIh2pr68nnl4cisVhUOH43DQ3ph19nXeyhuWdPv9Z2uZ3kL0XtDwFMAp/fRS6RMLIQZGdtNZ7hoqVXeGnXVnkSzSHNkSOHp3z/hRdM3/ltdhv5RdjDtT+AACb/ooTjBMod2mmLPII6ehDp0u+6qLc9Rcf2Z3f0uKLaMab70uNzTfj37cLxHU47ujwhgOzADmV3O8hFY2vSH91VTU/8tpe62rIjAo7lr705QPZRt3IKh0ZNnqXyxCvILj/5dTUt/VbptD9n7jluWnVHGXn9NmlrBNACGJYrVpdT03w3bXthcNIL0LnW53ziaxfKR6fRfQkBFAVnLvHR/MVebS8etrb9UerRWYbJcT53X7LVNzvJ45f3t3IPDrovIYDiiS8dNpp9pkezS1YGKBKKUzw2tcE1DnvQfYkcoKjh8GUqIQz3+pQEPHiBaAGKHw5huDbnliChJHUXpdgd9vSIrQNdmBCAyZpdu4283Jc/if58gBAIAAgAAAgAAAgAAAgAAAgAAAgAAAgAAAgAAAgAACMJgHeHki6TSiawRbcVyVDucSUeV80mAKZDdnE4iA1archQUL41u6qqnWYNgbbLLp44jBMLrUjXUfnioFQqtdusAnhbdvHgR1F4gwU5/LG83BUlvsOsAtgmu/iZeBGRYYRBVoLL+8CH8kPKB4LBnWYVwF4W/hjFi3xn+ytD8AoLweWtxFRZ/N/10oub95hVAMwrsoutbw7T8QPIBawAbzrM5S0jkVBeyvfz5FsAG6SZv4iAXljXN+ktREBx0dedoBfW9mnlLQ1/BoKbzS4Aju/+KPtGeChFG37Xi5bAxDX/ht/3auWsk/z++fnnNu4yuwCYPwuTBv2RUIo2/ukkbd00oPuiQHHB5cjlufHek5nKdLi9/dgjhXi+QiyKPyLsFmHr9cKhXVuHac+2kLa51LxzvFTb7KJAhYMcThs8yuDwCC8PcnW3KVpX5/7WiDTh/TKh4aF7tr62pc0qAmCeFFYp7H69H+CXtndHWDNgXqLRyK+e2vDo5kLdv5CT4R4Q9jO4gLWdf/0TDz2U4UdSZhbASD5wvV5OAEzLsAh7bh7H+ZmE2QUwEg4tFnYv/ML8KEr8L0eOHPrGBMMexaw5gCwxvlvY0+kWYZWweriLOVBVtTuRUF4cCAY3Pf/Pja0T/TXSmUJvRgGM8F7abhe2RNhyYSuELRXWJAx7CRZBJS8cviOVSn3EE9sGBwfeffH5TVOZ3hBJi8BSAvgyrWn7vKfoH4+vLU+pqiGeedUPr1/ndDqvG329tDRAHo8v6/eLxSI0LDlAO5FIPPfMpvU3Sn+pAL3GWVrMwrF/XqYJF9XmuLFYjM8RLRNW8DOCHA7HJbLrLldu9vDX+1zxHMsVJW6mZXW8UiZvnSJFtSb4pzfezgU9mI/egUxcedXKhTabrU7ijGS350ab/Ln8+WMqeZut5sqrvrPQJM7P5TqYLue8UHTbo4+I4OF193vFV18hGvryihkXS1+mM7cpitPppmQyovc8+4o5T+aYX5Rt3ldH5UwAD629b1q/L2q28X6EXxbPnHOnk2Nnvlo0EY5cKr/uyvF9nSIMlN6Xn+exInP6VLrG565O7u1RRaVGphFAHmuOWNryRklJ6TLZ9UCgXBqmZAu3202KMrZr3OMhzkf6CJg7BzBICLaIJGMU7Pi5dP5x7lGXfi5glhZAVY3RsXHDTXeMvrRcJyzKy/PwfWR5QPq5PjHiO0QLYC6WFyL+n8B9lqNoIIB8sKLQLYBOi3kZytN6SXAh4P2Nmkdf7Os7WdCHSiaTvJ+OK98dAmgBrMc2Iz5UPB7biQoNAsiXAIy2YDkVDPa/S5gsCAHkAe5pucVIDxQOh+7Z8vJz+9LliTKFAHLOOmE3GaAlSAnnv2vD+kce/3KejOJBEpwvEbxDp7sfR9YtNOf6pqqqHueEl2N+DnvSNf/oMkUiDAHkLRxiW5v+97gTmJ547G8V6mRa3lFjWROY+owWAAIoGBmd8+F199vTIsnlEO1IHoCdxZADGA6Xye5T9NgwXyTHL/ir07pLhHnycFvOAUIoW7QAaAEAWgADtQD5TUhQtkiCC54VwwkRAgEAAQAAAQAAAQBgGP4vwABwNyC/lJLuDQAAAABJRU5ErkJggg==",
+};
diff --git a/shapez-io/mod_examples/pasting.js b/shapez-io/mod_examples/pasting.js
new file mode 100644
index 00000000..698edeff
--- /dev/null
+++ b/shapez-io/mod_examples/pasting.js
@@ -0,0 +1,23 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Pasting",
+ version: "1",
+ id: "pasting",
+ description: "Shows how to properly receive paste events ingame",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ this.signals.gameInitialized.add(root => {
+ root.gameState.inputReciever.paste.add(event => {
+ event.preventDefault();
+
+ const data = event.clipboardData.getData("text");
+ this.dialogs.showInfo("Pasted", `You pasted: '${data}'`);
+ });
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/replace_builtin_sprites.js b/shapez-io/mod_examples/replace_builtin_sprites.js
new file mode 100644
index 00000000..6e88b7fb
--- /dev/null
+++ b/shapez-io/mod_examples/replace_builtin_sprites.js
@@ -0,0 +1,48 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Replace builtin sprites",
+ version: "1",
+ id: "replace-builtin-sprites",
+ description: "Shows how to replace builtin sprites",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Replace a builtin sprite
+ ["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
+ this.modInterface.registerSprite(`sprites/colors/${color}.png`, RESOURCES[color + ".png"]);
+ });
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+
+const RESOURCES = {
+ "red.png":
+ "data:image/png;base64,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",
+
+ "green.png":
+ "data:image/png;base64,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",
+
+ "purple.png":
+ "data:image/png;base64,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",
+
+ "blue.png":
+ "data:image/png;base64,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",
+
+ "yellow.png":
+ "data:image/png;base64,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",
+
+ "cyan.png":
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAOxAAADsQH1g+1JAAAE8mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNy4xLWMwMDAgNzkuZGFiYWNiYiwgMjAyMS8wNC8xNC0wMDozOTo0NCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIzLjAgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAxLTE0VDEwOjMwOjMyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMS0xNFQxMDozMDo1MCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0wMS0xNFQxMDozMDo1MCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ZTFiZTU2MjEtMTU1ZC00YmFmLWFiNzgtYjM2Y2QzMmIzNTNkIiB4bXBNTTpEb2N1bWVudElE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",
+
+ "white.png":
+ "data:image/png;base64,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",
+};
diff --git a/shapez-io/mod_examples/sandbox.js b/shapez-io/mod_examples/sandbox.js
new file mode 100644
index 00000000..f405ab59
--- /dev/null
+++ b/shapez-io/mod_examples/sandbox.js
@@ -0,0 +1,21 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Sandbox",
+ version: "1",
+ id: "sandbox",
+ description: "Blueprints are always unlocked and cost no money, also all buildings are unlocked",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ this.modInterface.replaceMethod(shapez.Blueprint, "getCost", function () {
+ return 0;
+ });
+ this.modInterface.replaceMethod(shapez.HubGoals, "isRewardUnlocked", function () {
+ return true;
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/smooth_zooming.js b/shapez-io/mod_examples/smooth_zooming.js
new file mode 100644
index 00000000..94254f2d
--- /dev/null
+++ b/shapez-io/mod_examples/smooth_zooming.js
@@ -0,0 +1,58 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Smooth Zoom",
+ version: "1",
+ id: "smooth_zoom",
+ description: "Allows to zoom in and out smoothly, also disables map overview",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ this.modInterface.registerIngameKeybinding({
+ id: "smooth_zoom_zoom_in",
+ keyCode: shapez.keyToKeyCode("1"),
+ translation: "Zoom In (use with SHIFT)",
+ modifiers: {
+ shift: true,
+ },
+ handler: root => {
+ root.camera.setDesiredZoom(5);
+ return shapez.STOP_PROPAGATION;
+ },
+ });
+ this.modInterface.registerIngameKeybinding({
+ id: "smooth_zoom_zoom_out",
+ keyCode: shapez.keyToKeyCode("0"),
+ translation: "Zoom Out (use with SHIFT)",
+ modifiers: {
+ shift: true,
+ },
+ handler: root => {
+ root.camera.setDesiredZoom(0.1);
+ return shapez.STOP_PROPAGATION;
+ },
+ });
+
+ this.modInterface.extendClass(shapez.Camera, ({ $old }) => ({
+ internalUpdateZooming(now, dt) {
+ if (!this.currentlyPinching && this.desiredZoom !== null) {
+ const diff = this.zoomLevel - this.desiredZoom;
+ if (Math.abs(diff) > 0.0001) {
+ const speed = 0.0005;
+ let step = Math.sign(diff) * Math.min(speed, Math.abs(diff));
+ const pow = 1 / 2;
+ this.zoomLevel = Math.pow(Math.pow(this.zoomLevel, pow) - step, 1 / pow);
+ } else {
+ this.zoomLevel = this.desiredZoom;
+ this.desiredZoom = null;
+ }
+ }
+ },
+ }));
+
+ shapez.globalConfig.mapChunkOverviewMinZoom = -1;
+ }
+}
diff --git a/shapez-io/mod_examples/storing_data_in_savegame.js b/shapez-io/mod_examples/storing_data_in_savegame.js
new file mode 100644
index 00000000..92f7733b
--- /dev/null
+++ b/shapez-io/mod_examples/storing_data_in_savegame.js
@@ -0,0 +1,78 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Storing Data in Savegame",
+ version: "1",
+ id: "storing-savegame-data",
+ description: "Shows how to add custom data to a savegame",
+ minimumGameVersion: ">=1.5.0",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ ////////////////////////////////////////////////////////////////////
+ // Option 1: For simple data
+ this.signals.gameSerialized.add((root, data) => {
+ data.modExtraData["storing-savegame-data"] = Math.random();
+ });
+
+ this.signals.gameDeserialized.add((root, data) => {
+ alert("The value stored in the savegame was: " + data.modExtraData["storing-savegame-data"]);
+ });
+
+ ////////////////////////////////////////////////////////////////////
+ // Option 2: If you need a structured way of storing data
+
+ class SomeSerializableObject extends shapez.BasicSerializableObject {
+ static getId() {
+ return "SomeSerializableObject";
+ }
+
+ static getSchema() {
+ return {
+ someInt: shapez.types.int,
+ someString: shapez.types.string,
+ someVector: shapez.types.vector,
+
+ // this value is allowed to be null
+ nullableInt: shapez.types.nullable(shapez.types.int),
+
+ // There is a lot more .. be sure to checkout src/js/savegame/serialization.js
+ // You can have maps, classes, arrays etc..
+ // And if you need something specific you can always ask in the modding discord.
+ };
+ }
+
+ constructor() {
+ super();
+ this.someInt = 42;
+ this.someString = "Hello World";
+ this.someVector = new shapez.Vector(1, 2);
+
+ this.nullableInt = null;
+ }
+ }
+
+ // Store our object in the global game root
+ this.signals.gameInitialized.add(root => {
+ root.myObject = new SomeSerializableObject();
+ });
+
+ // Save it within the savegame
+ this.signals.gameSerialized.add((root, data) => {
+ data.modExtraData["storing-savegame-data-2"] = root.myObject.serialize();
+ });
+
+ // Restore it when the savegame is loaded
+ this.signals.gameDeserialized.add((root, data) => {
+ const errorText = root.myObject.deserialize(data.modExtraData["storing-savegame-data-2"]);
+ if (errorText) {
+ alert("Mod failed to deserialize from savegame: " + errorText);
+ }
+ alert("The other value stored in the savegame (option 2) was " + root.myObject.someInt);
+ });
+
+ ////////////////////////////////////////////////////////////////////
+ }
+}
diff --git a/shapez-io/mod_examples/translations.js b/shapez-io/mod_examples/translations.js
new file mode 100644
index 00000000..6b9c708e
--- /dev/null
+++ b/shapez-io/mod_examples/translations.js
@@ -0,0 +1,66 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Translations",
+ version: "1",
+ id: "translations",
+ description: "Shows how to add and modify translations",
+ minimumGameVersion: ">=1.5.0",
+
+ // You can specify this parameter if savegames will still work
+ // after your mod has been uninstalled
+ doesNotAffectSavegame: true,
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Replace an existing translation in the english language
+ this.modInterface.registerTranslations("en", {
+ ingame: {
+ interactiveTutorial: {
+ title: "Hello",
+ hints: {
+ "1_1_extractor": "World!",
+ },
+ },
+ },
+ });
+
+ // Replace an existing translation in german
+ this.modInterface.registerTranslations("de", {
+ ingame: {
+ interactiveTutorial: {
+ title: "Hallo",
+ hints: {
+ "1_1_extractor": "Welt!",
+ },
+ },
+ },
+ });
+
+ // Add an entirely new translation which is localized in german and english
+ this.modInterface.registerTranslations("en", {
+ mods: {
+ mymod: {
+ test: "Test Translation",
+ },
+ },
+ });
+ this.modInterface.registerTranslations("de", {
+ mods: {
+ mymod: {
+ test: "Test Übersetzung",
+ },
+ },
+ });
+
+ // Show a dialog in the main menu
+ this.signals.stateEntered.add(state => {
+ if (state instanceof shapez.MainMenuState) {
+ // Will show differently based on the selected language
+ this.dialogs.showInfo("My translation", shapez.T.mods.mymod.test);
+ }
+ });
+ }
+}
diff --git a/shapez-io/mod_examples/usage_statistics.js b/shapez-io/mod_examples/usage_statistics.js
new file mode 100644
index 00000000..80828d95
--- /dev/null
+++ b/shapez-io/mod_examples/usage_statistics.js
@@ -0,0 +1,148 @@
+// @ts-nocheck
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Usage Statistics",
+ version: "1",
+ id: "usage-statistics",
+ description:
+ "Shows how to add a new component to the game, how to save additional data and how to add custom logic and drawings",
+
+ minimumGameVersion: ">=1.5.0",
+};
+
+/**
+ * Quick info on how this mod works:
+ *
+ * It tracks how many ticks a building was idle within X seconds to compute
+ * the usage percentage.
+ *
+ * Every tick the logic checks if the building is idle, if so, it increases aggregatedIdleTime.
+ * Once X seconds are over, the aggregatedIdleTime is copied to computedUsage which
+ * is displayed on screen via the UsageStatisticsSystem
+ */
+
+const MEASURE_INTERVAL_SECONDS = 5;
+
+class UsageStatisticsComponent extends shapez.Component {
+ static getId() {
+ return "UsageStatistics";
+ }
+
+ static getSchema() {
+ // Here you define which properties should be saved to the savegame
+ // and get automatically restored
+ return {
+ lastTimestamp: shapez.types.float,
+ computedUsage: shapez.types.float,
+ aggregatedIdleTime: shapez.types.float,
+ };
+ }
+
+ constructor() {
+ super();
+ this.lastTimestamp = 0;
+ this.computedUsage = 0;
+ this.aggregatedIdleTime = 0;
+ }
+}
+
+class UsageStatisticsSystem extends shapez.GameSystemWithFilter {
+ constructor(root) {
+ // By specifying the list of components, `this.allEntities` will only
+ // contain entities which have *all* of the specified components
+ super(root, [UsageStatisticsComponent, shapez.ItemProcessorComponent]);
+ }
+
+ update() {
+ const now = this.root.time.now();
+ for (let i = 0; i < this.allEntities.length; ++i) {
+ const entity = this.allEntities[i];
+
+ const processorComp = entity.components.ItemProcessor;
+ const usageComp = entity.components.UsageStatistics;
+
+ if (now - usageComp.lastTimestamp > MEASURE_INTERVAL_SECONDS) {
+ usageComp.computedUsage = shapez.clamp(
+ 1 - usageComp.aggregatedIdleTime / MEASURE_INTERVAL_SECONDS
+ );
+ usageComp.aggregatedIdleTime = 0;
+ usageComp.lastTimestamp = now;
+ }
+
+ if (processorComp.ongoingCharges.length === 0) {
+ usageComp.aggregatedIdleTime += this.root.dynamicTickrate.deltaSeconds;
+ }
+ }
+ }
+
+ drawChunk(parameters, chunk) {
+ const contents = chunk.containedEntitiesByLayer.regular;
+ for (let i = 0; i < contents.length; ++i) {
+ const entity = contents[i];
+ const usageComp = entity.components.UsageStatistics;
+ if (!usageComp) {
+ continue;
+ }
+
+ const staticComp = entity.components.StaticMapEntity;
+ const context = parameters.context;
+ const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
+
+ // Culling for better performance
+ if (parameters.visibleRect.containsCircle(center.x, center.y, 40)) {
+ // Background badge
+ context.fillStyle = "rgba(250, 250, 250, 0.8)";
+ context.beginRoundedRect(center.x - 10, center.y + 3, 20, 8, 2);
+
+ context.fill();
+
+ // Text
+ const usage = usageComp.computedUsage * 100.0;
+ if (usage > 99.99) {
+ context.fillStyle = "green";
+ } else if (usage > 70) {
+ context.fillStyle = "orange";
+ } else {
+ context.fillStyle = "red";
+ }
+
+ context.textAlign = "center";
+ context.font = "7px GameFont";
+ context.fillText(Math.round(usage) + "%", center.x, center.y + 10);
+ }
+ }
+ }
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register the component
+ this.modInterface.registerComponent(UsageStatisticsComponent);
+
+ // Add our new component to all item processor buildings so we can see how many items it processed.
+ // You can also inspect the entity with F8
+ const buildings = [
+ shapez.MetaBalancerBuilding,
+ shapez.MetaCutterBuilding,
+ shapez.MetaRotaterBuilding,
+ shapez.MetaStackerBuilding,
+ shapez.MetaMixerBuilding,
+ shapez.MetaPainterBuilding,
+ ];
+
+ buildings.forEach(metaClass => {
+ this.modInterface.runAfterMethod(metaClass, "setupEntityComponents", function (entity) {
+ entity.addComponent(new UsageStatisticsComponent());
+ });
+ });
+
+ // Register our game system so we can update and draw stuff
+ this.modInterface.registerGameSystem({
+ id: "demo_mod",
+ systemClass: UsageStatisticsSystem,
+ before: "belt",
+ drawHooks: ["staticAfter"],
+ });
+ }
+}
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/1_1_extractor.gif b/shapez-io/res/ui/interactive_tutorial.noinline/1_1_extractor.gif
new file mode 100644
index 00000000..c7208ac2
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/1_1_extractor.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/1_2_conveyor.gif b/shapez-io/res/ui/interactive_tutorial.noinline/1_2_conveyor.gif
new file mode 100644
index 00000000..db59bfd4
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/1_2_conveyor.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/1_3_expand.gif b/shapez-io/res/ui/interactive_tutorial.noinline/1_3_expand.gif
new file mode 100644
index 00000000..9c655ab1
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/1_3_expand.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/21_1_place_quad_painter.gif b/shapez-io/res/ui/interactive_tutorial.noinline/21_1_place_quad_painter.gif
new file mode 100644
index 00000000..ea854cf2
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/21_1_place_quad_painter.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/21_2_switch_to_wires.gif b/shapez-io/res/ui/interactive_tutorial.noinline/21_2_switch_to_wires.gif
new file mode 100644
index 00000000..78ab6fd2
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/21_2_switch_to_wires.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/21_3_place_button.gif b/shapez-io/res/ui/interactive_tutorial.noinline/21_3_place_button.gif
new file mode 100644
index 00000000..52ffb076
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/21_3_place_button.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/21_4_press_button.gif b/shapez-io/res/ui/interactive_tutorial.noinline/21_4_press_button.gif
new file mode 100644
index 00000000..5d79f1e3
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/21_4_press_button.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/2_1_place_cutter.gif b/shapez-io/res/ui/interactive_tutorial.noinline/2_1_place_cutter.gif
new file mode 100644
index 00000000..1678c0b2
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/2_1_place_cutter.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/2_2_place_trash.gif b/shapez-io/res/ui/interactive_tutorial.noinline/2_2_place_trash.gif
new file mode 100644
index 00000000..0d60fa9f
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/2_2_place_trash.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/2_3_more_cutters.gif b/shapez-io/res/ui/interactive_tutorial.noinline/2_3_more_cutters.gif
new file mode 100644
index 00000000..50ce88f9
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/2_3_more_cutters.gif differ
diff --git a/shapez-io/res/ui/interactive_tutorial.noinline/3_1_rectangles.gif b/shapez-io/res/ui/interactive_tutorial.noinline/3_1_rectangles.gif
new file mode 100644
index 00000000..418d3123
Binary files /dev/null and b/shapez-io/res/ui/interactive_tutorial.noinline/3_1_rectangles.gif differ
diff --git a/shapez-io/res/ui/languages/ar.svg b/shapez-io/res/ui/languages/ar.svg
new file mode 100644
index 00000000..9b682077
--- /dev/null
+++ b/shapez-io/res/ui/languages/ar.svg
@@ -0,0 +1,62 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/cs.svg b/shapez-io/res/ui/languages/cs.svg
new file mode 100644
index 00000000..653b342e
--- /dev/null
+++ b/shapez-io/res/ui/languages/cs.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/da.svg b/shapez-io/res/ui/languages/da.svg
new file mode 100644
index 00000000..b6ace9ab
--- /dev/null
+++ b/shapez-io/res/ui/languages/da.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/de.svg b/shapez-io/res/ui/languages/de.svg
new file mode 100644
index 00000000..4585512c
--- /dev/null
+++ b/shapez-io/res/ui/languages/de.svg
@@ -0,0 +1,39 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/el.svg b/shapez-io/res/ui/languages/el.svg
new file mode 100644
index 00000000..90f83f5e
--- /dev/null
+++ b/shapez-io/res/ui/languages/el.svg
@@ -0,0 +1,46 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/en.svg b/shapez-io/res/ui/languages/en.svg
new file mode 100644
index 00000000..a0805a7d
--- /dev/null
+++ b/shapez-io/res/ui/languages/en.svg
@@ -0,0 +1,59 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/es-419.svg b/shapez-io/res/ui/languages/es-419.svg
new file mode 100644
index 00000000..5f671625
--- /dev/null
+++ b/shapez-io/res/ui/languages/es-419.svg
@@ -0,0 +1,107 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/et.svg b/shapez-io/res/ui/languages/et.svg
new file mode 100644
index 00000000..63ae7dd8
--- /dev/null
+++ b/shapez-io/res/ui/languages/et.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/fi.svg b/shapez-io/res/ui/languages/fi.svg
new file mode 100644
index 00000000..483fdde3
--- /dev/null
+++ b/shapez-io/res/ui/languages/fi.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/fr.svg b/shapez-io/res/ui/languages/fr.svg
new file mode 100644
index 00000000..632ffc1d
--- /dev/null
+++ b/shapez-io/res/ui/languages/fr.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/he.svg b/shapez-io/res/ui/languages/he.svg
new file mode 100644
index 00000000..aaa64e98
--- /dev/null
+++ b/shapez-io/res/ui/languages/he.svg
@@ -0,0 +1,42 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/hu.svg b/shapez-io/res/ui/languages/hu.svg
new file mode 100644
index 00000000..39298040
--- /dev/null
+++ b/shapez-io/res/ui/languages/hu.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/it.svg b/shapez-io/res/ui/languages/it.svg
new file mode 100644
index 00000000..a32bf996
--- /dev/null
+++ b/shapez-io/res/ui/languages/it.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/ja.svg b/shapez-io/res/ui/languages/ja.svg
new file mode 100644
index 00000000..6657b409
--- /dev/null
+++ b/shapez-io/res/ui/languages/ja.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/kor.svg b/shapez-io/res/ui/languages/kor.svg
new file mode 100644
index 00000000..6331281b
--- /dev/null
+++ b/shapez-io/res/ui/languages/kor.svg
@@ -0,0 +1,99 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/mt-MT.svg b/shapez-io/res/ui/languages/mt-MT.svg
new file mode 100644
index 00000000..59e6165a
--- /dev/null
+++ b/shapez-io/res/ui/languages/mt-MT.svg
@@ -0,0 +1,50 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/nb.svg b/shapez-io/res/ui/languages/nb.svg
new file mode 100644
index 00000000..64d2fa5e
--- /dev/null
+++ b/shapez-io/res/ui/languages/nb.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/nl.svg b/shapez-io/res/ui/languages/nl.svg
new file mode 100644
index 00000000..edd8b343
--- /dev/null
+++ b/shapez-io/res/ui/languages/nl.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/no.svg b/shapez-io/res/ui/languages/no.svg
new file mode 100644
index 00000000..5f5a4b6c
--- /dev/null
+++ b/shapez-io/res/ui/languages/no.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/pl.svg b/shapez-io/res/ui/languages/pl.svg
new file mode 100644
index 00000000..b471827f
--- /dev/null
+++ b/shapez-io/res/ui/languages/pl.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/pt-BR.svg b/shapez-io/res/ui/languages/pt-BR.svg
new file mode 100644
index 00000000..b1470d95
--- /dev/null
+++ b/shapez-io/res/ui/languages/pt-BR.svg
@@ -0,0 +1,51 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/pt-PT.svg b/shapez-io/res/ui/languages/pt-PT.svg
new file mode 100644
index 00000000..4daa9aeb
--- /dev/null
+++ b/shapez-io/res/ui/languages/pt-PT.svg
@@ -0,0 +1,64 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/ro.svg b/shapez-io/res/ui/languages/ro.svg
new file mode 100644
index 00000000..041ecc3f
--- /dev/null
+++ b/shapez-io/res/ui/languages/ro.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/ru.svg b/shapez-io/res/ui/languages/ru.svg
new file mode 100644
index 00000000..ecd327ac
--- /dev/null
+++ b/shapez-io/res/ui/languages/ru.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/sv.svg b/shapez-io/res/ui/languages/sv.svg
new file mode 100644
index 00000000..3754ad8c
--- /dev/null
+++ b/shapez-io/res/ui/languages/sv.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/th.svg b/shapez-io/res/ui/languages/th.svg
new file mode 100644
index 00000000..93a280b1
--- /dev/null
+++ b/shapez-io/res/ui/languages/th.svg
@@ -0,0 +1,43 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/tr.svg b/shapez-io/res/ui/languages/tr.svg
new file mode 100644
index 00000000..15d06a67
--- /dev/null
+++ b/shapez-io/res/ui/languages/tr.svg
@@ -0,0 +1,39 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/uk.svg b/shapez-io/res/ui/languages/uk.svg
new file mode 100644
index 00000000..4d7db7f1
--- /dev/null
+++ b/shapez-io/res/ui/languages/uk.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/vi.svg b/shapez-io/res/ui/languages/vi.svg
new file mode 100644
index 00000000..0aa76a0e
--- /dev/null
+++ b/shapez-io/res/ui/languages/vi.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/zh-CN.svg b/shapez-io/res/ui/languages/zh-CN.svg
new file mode 100644
index 00000000..f89219a0
--- /dev/null
+++ b/shapez-io/res/ui/languages/zh-CN.svg
@@ -0,0 +1,42 @@
+
+
+
diff --git a/shapez-io/res/ui/languages/zh-TW.svg b/shapez-io/res/ui/languages/zh-TW.svg
new file mode 100644
index 00000000..c3dab661
--- /dev/null
+++ b/shapez-io/res/ui/languages/zh-TW.svg
@@ -0,0 +1,43 @@
+
+
+