1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Merge branch 'master' into master

This commit is contained in:
EmeraldBlock 2020-09-28 11:04:58 -05:00 committed by GitHub
commit 1de6bdcf16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
124 changed files with 17755 additions and 13323 deletions

4
.gitattributes vendored
View File

@ -1,4 +0,0 @@
*.wav filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text

View File

@ -22,8 +22,6 @@ Your goal is to produce shapes by cutting, rotating, merging and painting parts
## Building ## Building
- Make sure git `git lfs` extension is on your path
- Run `git lfs pull` to download sound assets
- Make sure `ffmpeg` is on your path - Make sure `ffmpeg` is on your path
- Install Node.js and Yarn - Install Node.js and Yarn
- Run `yarn` in the root folder - Run `yarn` in the root folder

1
gulp/.gitattributes vendored
View File

@ -1 +0,0 @@
*.wav filter=lfs diff=lfs merge=lfs -text

View File

@ -25,6 +25,14 @@ module.exports = {
}); });
}, },
getTag() {
try {
return execSync("git describe --tag --exact-match").toString("ascii");
} catch (e) {
throw new Error('Current git HEAD is not a version tag');
}
},
getVersion() { getVersion() {
return trim(fs.readFileSync(path.join(__dirname, "..", "version")).toString()); return trim(fs.readFileSync(path.join(__dirname, "..", "version")).toString());
}, },

12
gulp/entitlements.plist Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>

View File

@ -42,6 +42,10 @@ const envVars = [
"SHAPEZ_CLI_STAGING_FTP_PW", "SHAPEZ_CLI_STAGING_FTP_PW",
"SHAPEZ_CLI_LIVE_FTP_USER", "SHAPEZ_CLI_LIVE_FTP_USER",
"SHAPEZ_CLI_LIVE_FTP_PW", "SHAPEZ_CLI_LIVE_FTP_PW",
"SHAPEZ_CLI_APPLE_ID",
"SHAPEZ_CLI_APPLE_CERT_NAME",
"SHAPEZ_CLI_GITHUB_USER",
"SHAPEZ_CLI_GITHUB_TOKEN",
]; ];
for (let i = 0; i < envVars.length; ++i) { for (let i = 0; i < envVars.length; ++i) {
@ -78,6 +82,9 @@ docs.gulptasksDocs($, gulp, buildFolder);
const standalone = require("./standalone"); const standalone = require("./standalone");
standalone.gulptasksStandalone($, gulp, buildFolder); standalone.gulptasksStandalone($, gulp, buildFolder);
const releaseUploader = require("./release-uploader");
releaseUploader.gulptasksReleaseUploader($, gulp, buildFolder);
const translations = require("./translations"); const translations = require("./translations");
translations.gulptasksTranslations($, gulp, buildFolder); translations.gulptasksTranslations($, gulp, buildFolder);
@ -299,6 +306,17 @@ gulp.task(
gulp.series("utils.cleanup", "step.standalone-prod.all", "step.postbuild") gulp.series("utils.cleanup", "step.standalone-prod.all", "step.postbuild")
); );
// OS X build and release upload
gulp.task(
"build.darwin64-prod",
gulp.series(
"build.standalone-prod",
"standalone.prepare",
"standalone.package.prod.darwin64",
"standalone.uploadRelease.darwin64"
)
);
// Deploying! // Deploying!
gulp.task( gulp.task(
"main.deploy.alpha", "main.deploy.alpha",

View File

@ -54,19 +54,19 @@ function gulptasksHTML($, gulp, buildFolder) {
document.head.appendChild(css); document.head.appendChild(css);
// Append async css // Append async css
const asyncCss = document.createElement("link"); // const asyncCss = document.createElement("link");
asyncCss.rel = "stylesheet"; // asyncCss.rel = "stylesheet";
asyncCss.type = "text/css"; // asyncCss.type = "text/css";
asyncCss.media = "none"; // asyncCss.media = "none";
asyncCss.setAttribute("onload", "this.media='all'"); // asyncCss.setAttribute("onload", "this.media='all'");
asyncCss.href = cachebust("async-resources.css"); // asyncCss.href = cachebust("async-resources.css");
if (integrity) { // if (integrity) {
asyncCss.setAttribute( // asyncCss.setAttribute(
"integrity", // "integrity",
computeIntegrityHash(path.join(buildFolder, "async-resources.css")) // computeIntegrityHash(path.join(buildFolder, "async-resources.css"))
); // );
} // }
document.head.appendChild(asyncCss); // document.head.appendChild(asyncCss);
if (app) { if (app) {
// Append cordova link // Append cordova link

66
gulp/release-uploader.js Normal file
View File

@ -0,0 +1,66 @@
const path = require("path");
const fs = require("fs");
const execSync = require("child_process").execSync;
const { Octokit } = require("@octokit/rest");
const buildutils = require("./buildutils");
function gulptasksReleaseUploader($, gulp, buildFolder) {
const standaloneDir = path.join(__dirname, "..", "tmp_standalone_files");
const darwinApp = path.join(standaloneDir, "shapez.io-standalone-darwin-x64", "shapez.io-standalone.app");
const dmgName = "shapez.io-standalone.dmg";
const dmgPath = path.join(standaloneDir, "shapez.io-standalone-darwin-x64", dmgName);
gulp.task("standalone.uploadRelease.darwin64.cleanup", () => {
return gulp.src(dmgPath, { read: false, allowEmpty: true }).pipe($.clean({ force: true }));
});
gulp.task("standalone.uploadRelease.darwin64.compress", cb => {
console.log("Packaging disk image", dmgPath);
execSync(`hdiutil create -format UDBZ -srcfolder ${darwinApp} ${dmgPath}`);
cb();
});
gulp.task("standalone.uploadRelease.darwin64.upload", async cb => {
const currentTag = buildutils.getTag();
const octokit = new Octokit({
auth: process.env.SHAPEZ_CLI_GITHUB_TOKEN
});
const createdRelease = await octokit.request("POST /repos/{owner}/{repo}/releases", {
owner: process.env.SHAPEZ_CLI_GITHUB_USER,
repo: "shapez.io",
tag_name: currentTag,
name: currentTag,
draft: true
});
const { data: { id, upload_url } } = createdRelease;
console.log(`Created release ${id} for tag ${currentTag}`);
const dmgContents = fs.readFileSync(dmgPath);
const dmgSize = fs.statSync(dmgPath).size;
console.log("Uploading", dmgContents.length / 1024 / 1024, "MB to", upload_url);
await octokit.request({
method: "POST",
url: upload_url,
headers: {
"content-type": "application/x-apple-diskimage"
},
name: dmgName,
data: dmgContents
});
cb();
});
gulp.task("standalone.uploadRelease.darwin64",
gulp.series(
"standalone.uploadRelease.darwin64.cleanup",
"standalone.uploadRelease.darwin64.compress",
"standalone.uploadRelease.darwin64.upload"
));
}
module.exports = { gulptasksReleaseUploader };

View File

@ -1,3 +1,4 @@
require('colors');
const packager = require("electron-packager"); const packager = require("electron-packager");
const path = require("path"); const path = require("path");
const { getVersion } = require("./buildutils"); const { getVersion } = require("./buildutils");
@ -80,8 +81,9 @@ function gulptasksStandalone($, gulp) {
* @param {'win32'|'linux'|'darwin'} platform * @param {'win32'|'linux'|'darwin'} platform
* @param {'x64'|'ia32'} arch * @param {'x64'|'ia32'} arch
* @param {function():void} cb * @param {function():void} cb
* @param {boolean=} isRelease
*/ */
function packageStandalone(platform, arch, cb) { function packageStandalone(platform, arch, cb, isRelease = true) {
const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml")); const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
packager({ packager({
@ -99,6 +101,20 @@ function gulptasksStandalone($, gulp) {
overwrite: true, overwrite: true,
appBundleId: "io.shapez.standalone", appBundleId: "io.shapez.standalone",
appCategoryType: "public.app-category.games", appCategoryType: "public.app-category.games",
...(isRelease && platform === "darwin" && {
osxSign: {
identity: process.env.SHAPEZ_CLI_APPLE_CERT_NAME,
"hardened-runtime": true,
hardenedRuntime: true,
entitlements: 'entitlements.plist',
'entitlements-inherit': 'entitlements.plist',
'signature-flags': 'library'
},
osxNotarize: {
appleId: process.env.SHAPEZ_CLI_APPLE_ID,
appleIdPassword: "@keychain:SHAPEZ_CLI_APPLE_ID"
}
})
}).then( }).then(
appPaths => { appPaths => {
console.log("Packages created:", appPaths); console.log("Packages created:", appPaths);
@ -123,7 +139,11 @@ function gulptasksStandalone($, gulp) {
fs.chmodSync(path.join(appPath, "play.sh"), 0o775); fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
} }
if (platform === "darwin") { if (process.platform === "win32" && platform === "darwin") {
console.warn("Cross-building for macOS on Windows: dereferencing symlinks.\n".red +
"This will nearly double app size and make code signature invalid. Sorry!\n".red.bold +
"For more information, see " + "https://github.com/electron/electron-packager/issues/71".underline);
// Clear up framework folders // Clear up framework folders
fs.writeFileSync( fs.writeFileSync(
path.join(appPath, "play.sh"), path.join(appPath, "play.sh"),
@ -175,6 +195,7 @@ function gulptasksStandalone($, gulp) {
gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb)); gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb));
gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb)); gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb));
gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb)); gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb));
gulp.task("standalone.package.prod.darwin64.unsigned", cb => packageStandalone("darwin", "x64", cb, false));
gulp.task( gulp.task(
"standalone.package.prod", "standalone.package.prod",

View File

@ -71,6 +71,7 @@
"yawn-yaml": "^1.5.0" "yawn-yaml": "^1.5.0"
}, },
"devDependencies": { "devDependencies": {
"@octokit/rest": "^18.0.6",
"@typescript-eslint/eslint-plugin": "3.0.1", "@typescript-eslint/eslint-plugin": "3.0.1",
"@typescript-eslint/parser": "3.0.1", "@typescript-eslint/parser": "3.0.1",
"autoprefixer": "^9.4.3", "autoprefixer": "^9.4.3",

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 283 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 KiB

After

Width:  |  Height:  |  Size: 706 KiB

View File

@ -118,7 +118,7 @@
<key>textureFormat</key> <key>textureFormat</key>
<enum type="SettingsBase::TextureFormat">png</enum> <enum type="SettingsBase::TextureFormat">png</enum>
<key>borderPadding</key> <key>borderPadding</key>
<uint>3</uint> <uint>4</uint>
<key>maxTextureSize</key> <key>maxTextureSize</key>
<QSize> <QSize>
<key>width</key> <key>width</key>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:df7487fb5e8cb34cecee2519b9c3162a5107d2d7b1301c4a550904cfb108a015
size 223361394

BIN
res_raw/sounds/sfx/copy.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,8 +1,24 @@
#ingame_HUD_BetaOverlay { #ingame_HUD_BetaOverlay {
position: fixed; position: fixed;
@include S(top, 10px); @include S(top, 10px);
@include S(right, 15px); left: 50%;
transform: translateX(-50%);
color: $colorRedBright; color: $colorRedBright;
@include Heading; @include Heading;
text-transform: uppercase; text-transform: uppercase;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
h2 {
@include PlainText;
}
span {
color: #555;
@include SuperSmallText;
}
} }

View File

@ -178,6 +178,27 @@
display: list-item; display: list-item;
} }
} }
.ingameItemChooser {
@include S(margin, 20px, 0);
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
@include S(grid-column-gap, 3px);
canvas {
pointer-events: all;
@include S(width, 25px);
@include S(height, 25px);
position: relative;
cursor: pointer;
@include IncreasedClickArea(3px);
&:hover {
opacity: 0.9;
}
}
}
} }
> .buttons { > .buttons {
@ -220,7 +241,7 @@
content: " "; content: " ";
display: inline-block; display: inline-block;
background: rgba(#fff, 0.6); background: rgba(#fff, 0.6);
@include InlineAnimation(5s linear) { @include InlineAnimation(3s linear) {
0% { 0% {
width: 100%; width: 100%;
} }

View File

@ -13,7 +13,7 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2,
cutter, cutter-quad, rotater, rotater-ccw, stacker, mixer, painter-double, painter-quad, trash, storage, cutter, cutter-quad, rotater, rotater-ccw, stacker, mixer, painter-double, painter-quad, trash, storage,
reader, rotater-rotate180, display, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not, reader, rotater-rotate180, display, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not,
logic_gate-xor, analyzer, virtual_processor-rotater, virtual_processor-unstacker, logic_gate-xor, analyzer, virtual_processor-rotater, virtual_processor-unstacker,
virtual_processor-stacker, virtual_processor-painter, wire-second, painter, painter-mirrored; virtual_processor-stacker, virtual_processor-painter, wire-second, painter, painter-mirrored, comparator;
@each $building in $buildingsAndVariants { @each $building in $buildingsAndVariants {
[data-icon="building_tutorials/#{$building}.png"] { [data-icon="building_tutorials/#{$building}.png"] {
/* @load-async */ /* @load-async */

View File

@ -1,43 +1,9 @@
export const CHANGELOG = [ export const CHANGELOG = [
{ {
version: "1.2.0", version: "1.2.0",
date: "unreleased", date: "09.10.2020",
entries: [ entries: [
"WIRES", "⚠This update is HUGE, view the full changelog <a href='https://shapez.io/wires/' target='_blank'>here</a>! ⚠️⚠️",
"Reworked menu UI design (by dengr1605)",
"Allow holding ALT in belt planner to reverse direction (by jakobhellermann)",
"Clear cursor when trying to pipette the same building twice (by hexy)",
"Fixed level 18 stacker bug: If you experienced it already, you know it, if not, I don't want to spoiler (by hexy)",
"Added keybinding to close menus (by isaisstillalive / Sandwichs-del)",
"Fix rare crash regarding the buildings toolbar (by isaisstillalive)",
"Fixed some phrases (by EnderDoom77)",
"Zoom towards mouse cursor (by Dimava)",
"Added multiple settings to optimize the performance",
"Updated the soundtrack again, it is now 40 minutes in total!",
"Added a button to the statistics dialog to disable the sorting (by squeek502)",
"Tier 2 tunnels are now 9 tiles wide, so the gap between is 8 tiles (double the tier 1 range)",
"Updated and added new translations (Thanks to all contributors!)",
"Show connected chained miners on hover",
"Added setting to be able to delete buildings while placing (inspired by hexy)",
"You can now adjust the sound and music volumes! (inspired by Yoshie2000)",
"Some hud elements now have reduced opacity when hovering, so you can see through (inspired by mvb005)",
"Mark pinned shapes in statistics dialog and show them first (inspired by davidburhans)",
"Added setting to show chunk borders",
"Quad painters have been reworked! They now are integrated with the wires, and only paint the shape when the value is 1 (inspired by dengr1605)",
"There are now compact 1x1 balancers available to be unlocked!",
"Replaced level completion sound to be less distracting",
"Allow editing waypoints (by isaisstillalive)",
"Show confirmation when cutting area which is too expensive to get pasted again (by isaisstillalive)",
"Show mouse and camera tile on debug overlay (F4) (by dengr)",
"Fix belt planner placing the belt when a dialog opens in the meantime",
"Added confirmation when deleting a savegame",
"Make chained mainer the default and only option after unlocking it",
"Fixed tunnels entrances connecting to exits sometimes when they shouldn't",
"You can now pan the map with your mouse by moving the cursor to the edges of the screen!",
"Added setting to auto select the extractor when pipetting a resource patch (by Exund)",
"You can now change the unit (seconds / minutes / hours) in the statistics dialog",
"The initial belt planner direction is now based on the cursor movement (by MizardX)",
"Fix preferred variant not getting saved when clicking on the hud (by Danacus)",
], ],
}, },
{ {

View File

@ -1,215 +1,232 @@
/* typehints:start */ /* typehints:start */
import { Application } from "../application"; import { Application } from "../application";
/* typehints:end */ /* typehints:end */
import { Loader } from "./loader"; import { Loader } from "./loader";
import { createLogger } from "./logging"; import { createLogger } from "./logging";
import { Signal } from "./signal"; import { Signal } from "./signal";
import { SOUNDS, MUSIC } from "../platform/sound"; import { SOUNDS, MUSIC } from "../platform/sound";
import { AtlasDefinition, atlasFiles } from "./atlas_definitions"; import { AtlasDefinition, atlasFiles } from "./atlas_definitions";
import { initBuildingCodesAfterResourcesLoaded } from "../game/meta_building_registry"; import { initBuildingCodesAfterResourcesLoaded } from "../game/meta_building_registry";
import { cachebust } from "./cachebust";
const logger = createLogger("background_loader");
const logger = createLogger("background_loader");
const essentialMainMenuSprites = [
"logo.png", const essentialMainMenuSprites = [
...G_ALL_UI_IMAGES.filter(src => src.startsWith("ui/") && src.indexOf(".gif") < 0), "logo.png",
]; ...G_ALL_UI_IMAGES.filter(src => src.startsWith("ui/") && src.indexOf(".gif") < 0),
const essentialMainMenuSounds = [ ];
SOUNDS.uiClick, const essentialMainMenuSounds = [
SOUNDS.uiError, SOUNDS.uiClick,
SOUNDS.dialogError, SOUNDS.uiError,
SOUNDS.dialogOk, SOUNDS.dialogError,
SOUNDS.swishShow, SOUNDS.dialogOk,
SOUNDS.swishHide, SOUNDS.swishShow,
]; SOUNDS.swishHide,
];
const essentialBareGameAtlases = atlasFiles;
const essentialBareGameSprites = G_ALL_UI_IMAGES.filter(src => src.indexOf(".gif") < 0); const essentialBareGameAtlases = atlasFiles;
const essentialBareGameSounds = [MUSIC.theme]; const essentialBareGameSprites = G_ALL_UI_IMAGES.filter(src => src.indexOf(".gif") < 0);
const essentialBareGameSounds = [MUSIC.theme];
const additionalGameSprites = [];
// @ts-ignore const additionalGameSprites = [];
const additionalGameSounds = [...Object.values(SOUNDS), ...Object.values(MUSIC)]; // @ts-ignore
const additionalGameSounds = [...Object.values(SOUNDS), ...Object.values(MUSIC)];
export class BackgroundResourcesLoader {
/** export class BackgroundResourcesLoader {
* /**
* @param {Application} app *
*/ * @param {Application} app
constructor(app) { */
this.app = app; constructor(app) {
this.app = app;
this.registerReady = false;
this.mainMenuReady = false; this.registerReady = false;
this.bareGameReady = false; this.mainMenuReady = false;
this.additionalReady = false; this.bareGameReady = false;
this.additionalReady = false;
this.signalMainMenuLoaded = new Signal();
this.signalBareGameLoaded = new Signal(); this.signalMainMenuLoaded = new Signal();
this.signalAdditionalLoaded = new Signal(); this.signalBareGameLoaded = new Signal();
this.signalAdditionalLoaded = new Signal();
this.numAssetsLoaded = 0;
this.numAssetsToLoadTotal = 0; this.numAssetsLoaded = 0;
this.numAssetsToLoadTotal = 0;
// Avoid loading stuff twice
this.spritesLoaded = []; // Avoid loading stuff twice
this.soundsLoaded = []; this.spritesLoaded = [];
} this.soundsLoaded = [];
}
getNumAssetsLoaded() {
return this.numAssetsLoaded; getNumAssetsLoaded() {
} return this.numAssetsLoaded;
}
getNumAssetsTotal() {
return this.numAssetsToLoadTotal; getNumAssetsTotal() {
} return this.numAssetsToLoadTotal;
}
getPromiseForMainMenu() {
if (this.mainMenuReady) { getPromiseForMainMenu() {
return Promise.resolve(); if (this.mainMenuReady) {
} return Promise.resolve();
}
return new Promise(resolve => {
this.signalMainMenuLoaded.add(resolve); return new Promise(resolve => {
}); this.signalMainMenuLoaded.add(resolve);
} });
}
getPromiseForBareGame() {
if (this.bareGameReady) { getPromiseForBareGame() {
return Promise.resolve(); if (this.bareGameReady) {
} return Promise.resolve();
}
return new Promise(resolve => {
this.signalBareGameLoaded.add(resolve); return new Promise(resolve => {
}); this.signalBareGameLoaded.add(resolve);
} });
}
startLoading() {
this.internalStartLoadingEssentialsForMainMenu(); startLoading() {
} this.internalStartLoadingEssentialsForMainMenu();
}
internalStartLoadingEssentialsForMainMenu() {
logger.log("⏰ Start load: main menu"); internalStartLoadingEssentialsForMainMenu() {
this.internalLoadSpritesAndSounds(essentialMainMenuSprites, essentialMainMenuSounds) logger.log("⏰ Start load: main menu");
.catch(err => { this.internalLoadSpritesAndSounds(essentialMainMenuSprites, essentialMainMenuSounds)
logger.warn("⏰ Failed to load essentials for main menu:", err); .catch(err => {
}) logger.warn("⏰ Failed to load essentials for main menu:", err);
.then(() => { })
logger.log("⏰ Finish load: main menu"); .then(() => {
this.mainMenuReady = true; logger.log("⏰ Finish load: main menu");
this.signalMainMenuLoaded.dispatch(); this.mainMenuReady = true;
this.internalStartLoadingEssentialsForBareGame(); this.signalMainMenuLoaded.dispatch();
}); this.internalStartLoadingEssentialsForBareGame();
} });
}
internalStartLoadingEssentialsForBareGame() {
logger.log("⏰ Start load: bare game"); internalStartLoadingEssentialsForBareGame() {
this.internalLoadSpritesAndSounds( logger.log("⏰ Start load: bare game");
essentialBareGameSprites, this.internalLoadSpritesAndSounds(
essentialBareGameSounds, essentialBareGameSprites,
essentialBareGameAtlases essentialBareGameSounds,
) essentialBareGameAtlases
.catch(err => { )
logger.warn("⏰ Failed to load essentials for bare game:", err); .then(() => this.internalPreloadCss("async-resources.scss"))
}) .catch(err => {
.then(() => { logger.warn("⏰ Failed to load essentials for bare game:", err);
logger.log("⏰ Finish load: bare game"); })
this.bareGameReady = true; .then(() => {
initBuildingCodesAfterResourcesLoaded(); logger.log("⏰ Finish load: bare game");
this.signalBareGameLoaded.dispatch(); this.bareGameReady = true;
this.internalStartLoadingAdditionalGameAssets(); initBuildingCodesAfterResourcesLoaded();
}); this.signalBareGameLoaded.dispatch();
} this.internalStartLoadingAdditionalGameAssets();
});
internalStartLoadingAdditionalGameAssets() { }
const additionalAtlases = [];
logger.log("⏰ Start load: additional assets (", additionalAtlases.length, "images)"); internalStartLoadingAdditionalGameAssets() {
this.internalLoadSpritesAndSounds(additionalGameSprites, additionalGameSounds, additionalAtlases) const additionalAtlases = [];
.catch(err => { logger.log("⏰ Start load: additional assets (", additionalAtlases.length, "images)");
logger.warn("⏰ Failed to load additional assets:", err); this.internalLoadSpritesAndSounds(additionalGameSprites, additionalGameSounds, additionalAtlases)
}) .catch(err => {
.then(() => { logger.warn("⏰ Failed to load additional assets:", err);
logger.log("⏰ Finish load: additional assets"); })
this.additionalReady = true; .then(() => {
this.signalAdditionalLoaded.dispatch(); logger.log("⏰ Finish load: additional assets");
}); this.additionalReady = true;
} this.signalAdditionalLoaded.dispatch();
});
/** }
* @param {Array<string>} sprites
* @param {Array<string>} sounds internalPreloadCss(name) {
* @param {Array<AtlasDefinition>} atlases return new Promise((resolve, reject) => {
* @returns {Promise<void>} const link = document.createElement("link");
*/
internalLoadSpritesAndSounds(sprites, sounds, atlases = []) { link.onload = resolve;
this.numAssetsToLoadTotal = sprites.length + sounds.length + atlases.length; link.onerror = reject;
this.numAssetsLoaded = 0;
link.setAttribute("rel", "stylesheet");
let promises = []; link.setAttribute("media", "all");
link.setAttribute("type", "text/css");
for (let i = 0; i < sounds.length; ++i) { link.setAttribute("href", cachebust("async-resources.css"));
if (this.soundsLoaded.indexOf(sounds[i]) >= 0) { document.head.appendChild(link);
// Already loaded });
continue; }
}
/**
this.soundsLoaded.push(sounds[i]); * @param {Array<string>} sprites
promises.push( * @param {Array<string>} sounds
this.app.sound * @param {Array<AtlasDefinition>} atlases
.loadSound(sounds[i]) * @returns {Promise<void>}
.catch(err => { */
logger.warn("Failed to load sound:", sounds[i]); internalLoadSpritesAndSounds(sprites, sounds, atlases = []) {
}) this.numAssetsToLoadTotal = sprites.length + sounds.length + atlases.length;
.then(() => { this.numAssetsLoaded = 0;
this.numAssetsLoaded++;
}) let promises = [];
);
} for (let i = 0; i < sounds.length; ++i) {
if (this.soundsLoaded.indexOf(sounds[i]) >= 0) {
for (let i = 0; i < sprites.length; ++i) { // Already loaded
if (this.spritesLoaded.indexOf(sprites[i]) >= 0) { continue;
// Already loaded }
continue;
} this.soundsLoaded.push(sounds[i]);
this.spritesLoaded.push(sprites[i]); promises.push(
promises.push( this.app.sound
Loader.preloadCSSSprite(sprites[i]) .loadSound(sounds[i])
.catch(err => { .catch(err => {
logger.warn("Failed to load css sprite:", sprites[i]); logger.warn("Failed to load sound:", sounds[i]);
}) })
.then(() => { .then(() => {
this.numAssetsLoaded++; this.numAssetsLoaded++;
}) })
); );
} }
for (let i = 0; i < atlases.length; ++i) { for (let i = 0; i < sprites.length; ++i) {
const atlas = atlases[i]; if (this.spritesLoaded.indexOf(sprites[i]) >= 0) {
promises.push( // Already loaded
Loader.preloadAtlas(atlas) continue;
.catch(err => { }
logger.warn("Failed to load atlas:", atlas.sourceFileName); this.spritesLoaded.push(sprites[i]);
}) promises.push(
.then(() => { Loader.preloadCSSSprite(sprites[i])
this.numAssetsLoaded++; .catch(err => {
}) logger.warn("Failed to load css sprite:", sprites[i]);
); })
} .then(() => {
this.numAssetsLoaded++;
return ( })
Promise.all(promises) );
}
// // Remove some pressure by waiting a bit
// .then(() => { for (let i = 0; i < atlases.length; ++i) {
// return new Promise(resolve => { const atlas = atlases[i];
// setTimeout(resolve, 200); promises.push(
// }); Loader.preloadAtlas(atlas)
// }) .catch(err => {
.then(() => { logger.warn("Failed to load atlas:", atlas.sourceFileName);
this.numAssetsToLoadTotal = 0; })
this.numAssetsLoaded = 0; .then(() => {
}) this.numAssetsLoaded++;
); })
} );
} }
return (
Promise.all(promises)
// // Remove some pressure by waiting a bit
// .then(() => {
// return new Promise(resolve => {
// setTimeout(resolve, 200);
// });
// })
.then(() => {
this.numAssetsToLoadTotal = 0;
this.numAssetsLoaded = 0;
})
);
}
}

View File

@ -20,6 +20,7 @@ export const THIRDPARTY_URLS = {
discord: "https://discord.gg/HN7EVzV", discord: "https://discord.gg/HN7EVzV",
github: "https://github.com/tobspr/shapez.io", github: "https://github.com/tobspr/shapez.io",
reddit: "https://www.reddit.com/r/shapezio", reddit: "https://www.reddit.com/r/shapezio",
shapeViewer: "https://viewer.shapez.io",
standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/", standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/",
}; };

View File

@ -60,6 +60,8 @@ export class Dialog {
this.buttonSignals[buttonId] = new Signal(); this.buttonSignals[buttonId] = new Signal();
} }
this.valueChosen = new Signal();
this.timeouts = []; this.timeouts = [];
this.clickDetectors = []; this.clickDetectors = [];
@ -164,7 +166,7 @@ export class Dialog {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
button.classList.remove("timedButton"); button.classList.remove("timedButton");
arrayDeleteValue(this.timeouts, timeout); arrayDeleteValue(this.timeouts, timeout);
}, 5000); }, 3000);
this.timeouts.push(timeout); this.timeouts.push(timeout);
} }
if (isEnter || isEscape) { if (isEnter || isEscape) {
@ -431,10 +433,12 @@ export class DialogWithForm extends Dialog {
for (let i = 0; i < this.formElements.length; ++i) { for (let i = 0; i < this.formElements.length; ++i) {
const elem = this.formElements[i]; const elem = this.formElements[i];
elem.bindEvents(div, this.clickDetectors); elem.bindEvents(div, this.clickDetectors);
elem.valueChosen.add(this.closeRequested.dispatch, this.closeRequested);
elem.valueChosen.add(this.valueChosen.dispatch, this.valueChosen);
} }
waitNextFrame().then(() => { waitNextFrame().then(() => {
this.formElements[0].focus(); this.formElements[this.formElements.length - 1].focus();
}); });
return div; return div;

View File

@ -1,150 +1,221 @@
import { ClickDetector } from "./click_detector"; import { BaseItem } from "../game/base_item";
import { ClickDetector } from "./click_detector";
export class FormElement { import { Signal } from "./signal";
constructor(id, label) {
this.id = id; export class FormElement {
this.label = label; constructor(id, label) {
} this.id = id;
this.label = label;
getHtml() {
abstract; this.valueChosen = new Signal();
return ""; }
}
getHtml() {
getFormElement(parent) { abstract;
return parent.querySelector("[data-formId='" + this.id + "']"); return "";
} }
bindEvents(parent, clickTrackers) { getFormElement(parent) {
abstract; return parent.querySelector("[data-formId='" + this.id + "']");
} }
focus() {} bindEvents(parent, clickTrackers) {
abstract;
isValid() { }
return true;
} focus() {}
/** @returns {any} */ isValid() {
getValue() { return true;
abstract; }
}
} /** @returns {any} */
getValue() {
export class FormElementInput extends FormElement { abstract;
constructor({ id, label = null, placeholder, defaultValue = "", inputType = "text", validator = null }) { }
super(id, label); }
this.placeholder = placeholder;
this.defaultValue = defaultValue; export class FormElementInput extends FormElement {
this.inputType = inputType; constructor({ id, label = null, placeholder, defaultValue = "", inputType = "text", validator = null }) {
this.validator = validator; super(id, label);
this.placeholder = placeholder;
this.element = null; this.defaultValue = defaultValue;
} this.inputType = inputType;
this.validator = validator;
getHtml() {
let classes = []; this.element = null;
let inputType = "text"; }
let maxlength = 256;
switch (this.inputType) { getHtml() {
case "text": { let classes = [];
classes.push("input-text"); let inputType = "text";
break; let maxlength = 256;
} switch (this.inputType) {
case "text": {
case "email": { classes.push("input-text");
classes.push("input-email"); break;
inputType = "email"; }
break;
} case "email": {
classes.push("input-email");
case "token": { inputType = "email";
classes.push("input-token"); break;
inputType = "text"; }
maxlength = 4;
break; case "token": {
} classes.push("input-token");
} inputType = "text";
maxlength = 4;
return ` break;
<div class="formElement input"> }
${this.label ? `<label>${this.label}</label>` : ""} }
<input
type="${inputType}" return `
value="${this.defaultValue.replace(/["\\]+/gi, "")}" <div class="formElement input">
maxlength="${maxlength}" ${this.label ? `<label>${this.label}</label>` : ""}
autocomplete="off" <input
autocorrect="off" type="${inputType}"
autocapitalize="off" value="${this.defaultValue.replace(/["\\]+/gi, "")}"
spellcheck="false" maxlength="${maxlength}"
class="${classes.join(" ")}" autocomplete="off"
placeholder="${this.placeholder.replace(/["\\]+/gi, "")}" autocorrect="off"
data-formId="${this.id}"> autocapitalize="off"
</div> spellcheck="false"
`; class="${classes.join(" ")}"
} placeholder="${this.placeholder.replace(/["\\]+/gi, "")}"
data-formId="${this.id}">
bindEvents(parent, clickTrackers) { </div>
this.element = this.getFormElement(parent); `;
this.element.addEventListener("input", event => this.updateErrorState()); }
this.updateErrorState();
} bindEvents(parent, clickTrackers) {
this.element = this.getFormElement(parent);
updateErrorState() { this.element.addEventListener("input", event => this.updateErrorState());
this.element.classList.toggle("errored", !this.isValid()); this.updateErrorState();
} }
isValid() { updateErrorState() {
return !this.validator || this.validator(this.element.value); this.element.classList.toggle("errored", !this.isValid());
} }
getValue() { isValid() {
return this.element.value; return !this.validator || this.validator(this.element.value);
} }
focus() { getValue() {
this.element.focus(); return this.element.value;
} }
}
focus() {
export class FormElementCheckbox extends FormElement { this.element.focus();
constructor({ id, label, defaultValue = true }) { }
super(id, label); }
this.defaultValue = defaultValue;
this.value = this.defaultValue; export class FormElementCheckbox extends FormElement {
constructor({ id, label, defaultValue = true }) {
this.element = null; super(id, label);
} this.defaultValue = defaultValue;
this.value = this.defaultValue;
getHtml() {
return ` this.element = null;
<div class="formElement checkBoxFormElem"> }
${this.label ? `<label>${this.label}</label>` : ""}
<div class="checkbox ${this.defaultValue ? "checked" : ""}" data-formId='${this.id}'> getHtml() {
<span class="knob"></span > return `
</div > <div class="formElement checkBoxFormElem">
</div> ${this.label ? `<label>${this.label}</label>` : ""}
`; <div class="checkbox ${this.defaultValue ? "checked" : ""}" data-formId='${this.id}'>
} <span class="knob"></span >
</div >
bindEvents(parent, clickTrackers) { </div>
this.element = this.getFormElement(parent); `;
const detector = new ClickDetector(this.element, { }
consumeEvents: false,
preventDefault: false, bindEvents(parent, clickTrackers) {
}); this.element = this.getFormElement(parent);
clickTrackers.push(detector); const detector = new ClickDetector(this.element, {
detector.click.add(this.toggle, this); consumeEvents: false,
} preventDefault: false,
});
getValue() { clickTrackers.push(detector);
return this.value; detector.click.add(this.toggle, this);
} }
toggle() { getValue() {
this.value = !this.value; return this.value;
this.element.classList.toggle("checked", this.value); }
}
toggle() {
focus(parent) {} this.value = !this.value;
} this.element.classList.toggle("checked", this.value);
}
focus(parent) {}
}
export class FormElementItemChooser extends FormElement {
/**
*
* @param {object} param0
* @param {string} param0.id
* @param {string=} param0.label
* @param {Array<BaseItem>} param0.items
*/
constructor({ id, label, items = [] }) {
super(id, label);
this.items = items;
this.element = null;
/**
* @type {BaseItem}
*/
this.chosenItem = null;
}
getHtml() {
let classes = [];
return `
<div class="formElement">
${this.label ? `<label>${this.label}</label>` : ""}
<div class="ingameItemChooser input" data-formId="${this.id}"></div>
</div>
`;
}
/**
* @param {HTMLElement} parent
* @param {Array<ClickDetector>} clickTrackers
*/
bindEvents(parent, clickTrackers) {
this.element = this.getFormElement(parent);
for (let i = 0; i < this.items.length; ++i) {
const item = this.items[i];
const canvas = document.createElement("canvas");
canvas.width = 128;
canvas.height = 128;
const context = canvas.getContext("2d");
item.drawFullSizeOnCanvas(context, 128);
this.element.appendChild(canvas);
const detector = new ClickDetector(canvas, {});
clickTrackers.push(detector);
detector.click.add(() => {
this.chosenItem = item;
this.valueChosen.dispatch(item);
});
}
}
isValid() {
return true;
}
getValue() {
return null;
}
focus() {}
}

View File

@ -667,3 +667,14 @@ export function safeModulo(n, m) {
export function smoothPulse(time) { export function smoothPulse(time) {
return Math.sin(time * 4) * 0.5 + 0.5; return Math.sin(time * 4) * 0.5 + 0.5;
} }
/**
* Fills in a <link> tag
* @param {string} translation
* @param {string} link
*/
export function fillInLinkIntoTranslation(translation, link) {
return translation
.replace("<link>", "<a href='" + link + "' target='_blank'>")
.replace("</link>", "</a>");
}

View File

@ -1,82 +1,100 @@
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters"; import { DrawParameters } from "../core/draw_parameters";
import { BasicSerializableObject } from "../savegame/serialization"; import { BasicSerializableObject } from "../savegame/serialization";
/** @type {ItemType[]} **/ /** @type {ItemType[]} **/
export const itemTypes = ["shape", "color", "boolean"]; export const itemTypes = ["shape", "color", "boolean"];
/** /**
* Class for items on belts etc. Not an entity for performance reasons * Class for items on belts etc. Not an entity for performance reasons
*/ */
export class BaseItem extends BasicSerializableObject { export class BaseItem extends BasicSerializableObject {
constructor() { constructor() {
super(); super();
} }
static getId() { static getId() {
return "base_item"; return "base_item";
} }
/** @returns {object} */ /** @returns {object} */
static getSchema() { static getSchema() {
return {}; return {};
} }
/** @returns {ItemType} **/ /** @returns {ItemType} **/
getItemType() { getItemType() {
abstract; abstract;
return "shape"; return "shape";
} }
/** /**
* Returns if the item equals the other itme * Returns a string id of the item
* @param {BaseItem} other * @returns {string}
* @returns {boolean} */
*/ getAsCopyableKey() {
equals(other) { abstract;
if (this.getItemType() !== other.getItemType()) { return "";
return false; }
}
return this.equalsImpl(other); /**
} * Returns if the item equals the other itme
* @param {BaseItem} other
/** * @returns {boolean}
* Override for custom comparison */
* @abstract equals(other) {
* @param {BaseItem} other if (this.getItemType() !== other.getItemType()) {
* @returns {boolean} return false;
*/ }
equalsImpl(other) { return this.equalsImpl(other);
abstract; }
return false;
} /**
* Override for custom comparison
/** * @abstract
* Draws the item at the given position * @param {BaseItem} other
* @param {number} x * @returns {boolean}
* @param {number} y */
* @param {DrawParameters} parameters equalsImpl(other) {
* @param {number=} diameter abstract;
*/ return false;
drawItemCenteredClipped(x, y, parameters, diameter = globalConfig.defaultItemDiameter) { }
if (parameters.visibleRect.containsCircle(x, y, diameter / 2)) {
this.drawItemCenteredImpl(x, y, parameters, diameter); /**
} * Draws the item to a canvas
} * @param {CanvasRenderingContext2D} context
* @param {number} size
/** */
* INTERNAL drawFullSizeOnCanvas(context, size) {
* @param {number} x abstract;
* @param {number} y }
* @param {DrawParameters} parameters
* @param {number=} diameter /**
*/ * Draws the item at the given position
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) { * @param {number} x
abstract; * @param {number} y
} * @param {DrawParameters} parameters
* @param {number=} diameter
getBackgroundColorAsResource() { */
abstract; drawItemCenteredClipped(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
return ""; if (parameters.visibleRect.containsCircle(x, y, diameter / 2)) {
} this.drawItemCenteredImpl(x, y, parameters, diameter);
} }
}
/**
* INTERNAL
* @param {number} x
* @param {number} y
* @param {DrawParameters} parameters
* @param {number=} diameter
*/
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
abstract;
}
getBackgroundColorAsResource() {
abstract;
return "";
}
}

View File

@ -1,12 +1,11 @@
import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters"; import { DrawParameters } from "../core/draw_parameters";
import { Loader } from "../core/loader";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { findNiceIntegerValue } from "../core/utils";
import { Vector } from "../core/vector"; import { Vector } from "../core/vector";
import { Entity } from "./entity"; import { Entity } from "./entity";
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { findNiceIntegerValue } from "../core/utils";
import { blueprintShape } from "./upgrades"; import { blueprintShape } from "./upgrades";
import { globalConfig } from "../core/config";
const logger = createLogger("blueprint"); const logger = createLogger("blueprint");

View File

@ -5,6 +5,7 @@ import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building"; import { MetaBuilding } from "../meta_building";
import { GameRoot } from "../root"; import { GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals";
const overlayMatrix = generateMatrixRotations([1, 1, 0, 1, 1, 1, 0, 1, 0]); const overlayMatrix = generateMatrixRotations([1, 1, 0, 1, 1, 1, 0, 1, 0]);
@ -21,8 +22,7 @@ export class MetaAnalyzerBuilding extends MetaBuilding {
* @param {GameRoot} root * @param {GameRoot} root
*/ */
getIsUnlocked(root) { getIsUnlocked(root) {
// @todo return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_virtual_processing);
return true;
} }
/** @returns {"wires"} **/ /** @returns {"wires"} **/

View File

@ -4,6 +4,7 @@ import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building"; import { MetaBuilding } from "../meta_building";
import { GameRoot } from "../root"; import { GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals";
export class MetaComparatorBuilding extends MetaBuilding { export class MetaComparatorBuilding extends MetaBuilding {
constructor() { constructor() {
@ -18,8 +19,7 @@ export class MetaComparatorBuilding extends MetaBuilding {
* @param {GameRoot} root * @param {GameRoot} root
*/ */
getIsUnlocked(root) { getIsUnlocked(root) {
// @todo return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_virtual_processing);
return true;
} }
/** @returns {"wires"} **/ /** @returns {"wires"} **/

View File

@ -5,6 +5,7 @@ import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
import { GameRoot } from "../root"; import { GameRoot } from "../root";
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate"; import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
import { generateMatrixRotations } from "../../core/utils"; import { generateMatrixRotations } from "../../core/utils";
import { enumHubGoalRewards } from "../tutorial_goals";
/** @enum {string} */ /** @enum {string} */
export const enumLogicGateVariants = { export const enumLogicGateVariants = {
@ -48,8 +49,7 @@ export class MetaLogicGateBuilding extends MetaBuilding {
* @param {GameRoot} root * @param {GameRoot} root
*/ */
getIsUnlocked(root) { getIsUnlocked(root) {
// @todo return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_logic_gates);
return true;
} }
/** @returns {"wires"} **/ /** @returns {"wires"} **/

View File

@ -5,6 +5,7 @@ import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { defaultBuildingVariant, MetaBuilding } from "../meta_building"; import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
import { GameRoot } from "../root"; import { GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals";
/** @enum {string} */ /** @enum {string} */
export const enumTransistorVariants = { export const enumTransistorVariants = {
@ -29,8 +30,7 @@ export class MetaTransistorBuilding extends MetaBuilding {
* @param {GameRoot} root * @param {GameRoot} root
*/ */
getIsUnlocked(root) { getIsUnlocked(root) {
// @todo return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_logic_gates);
return true;
} }
/** @returns {"wires"} **/ /** @returns {"wires"} **/

View File

@ -110,7 +110,12 @@ export class MetaVirtualProcessorBuilding extends MetaBuilding {
pinComp.setSlots([ pinComp.setSlots([
{ {
pos: new Vector(0, 0), pos: new Vector(0, 0),
direction: enumDirection.top, direction: enumDirection.left,
type: enumPinSlotType.logicalEjector,
},
{
pos: new Vector(0, 0),
direction: enumDirection.right,
type: enumPinSlotType.logicalEjector, type: enumPinSlotType.logicalEjector,
}, },
{ {

View File

@ -53,6 +53,6 @@ export class MetaWireTunnelBuilding extends MetaBuilding {
* @param {Entity} entity * @param {Entity} entity
*/ */
setupEntityComponents(entity) { setupEntityComponents(entity) {
entity.addComponent(new WireTunnelComponent({})); entity.addComponent(new WireTunnelComponent());
} }
} }

View File

@ -1,8 +1,10 @@
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { RandomNumberGenerator } from "../core/rng";
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils"; import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
import { BasicSerializableObject, types } from "../savegame/serialization"; import { BasicSerializableObject, types } from "../savegame/serialization";
import { enumColors } from "./colors"; import { enumColors } from "./colors";
import { enumItemProcessorTypes } from "./components/item_processor"; import { enumItemProcessorTypes } from "./components/item_processor";
import { enumAnalyticsDataSource } from "./production_analytics";
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { enumSubShape, ShapeDefinition } from "./shape_definition"; import { enumSubShape, ShapeDefinition } from "./shape_definition";
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals"; import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
@ -18,12 +20,6 @@ export class HubGoals extends BasicSerializableObject {
level: types.uint, level: types.uint,
storedShapes: types.keyValueMap(types.uint), storedShapes: types.keyValueMap(types.uint),
upgradeLevels: types.keyValueMap(types.uint), upgradeLevels: types.keyValueMap(types.uint),
currentGoal: types.structured({
definition: types.knownType(ShapeDefinition),
required: types.uint,
reward: types.nullable(types.enum(enumHubGoalRewards)),
}),
}; };
} }
@ -53,15 +49,7 @@ export class HubGoals extends BasicSerializableObject {
} }
// Compute current goal // Compute current goal
const goal = tutorialGoals[this.level - 1]; this.computeNextGoal();
if (goal) {
this.currentGoal = {
/** @type {ShapeDefinition} */
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(goal.shape),
required: goal.required,
reward: goal.reward,
};
}
} }
/** /**
@ -92,6 +80,11 @@ export class HubGoals extends BasicSerializableObject {
*/ */
this.upgradeLevels = {}; this.upgradeLevels = {};
// Reset levels
for (const key in UPGRADES) {
this.upgradeLevels[key] = 0;
}
/** /**
* Stores the improvements for all upgrades * Stores the improvements for all upgrades
* @type {Object<string, number>} * @type {Object<string, number>}
@ -101,7 +94,7 @@ export class HubGoals extends BasicSerializableObject {
this.upgradeImprovements[key] = 1; this.upgradeImprovements[key] = 1;
} }
this.createNextGoal(); this.computeNextGoal();
// Allow quickly switching goals in dev mode // Allow quickly switching goals in dev mode
if (G_IS_DEV) { if (G_IS_DEV) {
@ -150,6 +143,13 @@ export class HubGoals extends BasicSerializableObject {
* Returns how much of the current goal was already delivered * Returns how much of the current goal was already delivered
*/ */
getCurrentGoalDelivered() { getCurrentGoalDelivered() {
if (this.currentGoal.throughputOnly) {
return this.root.productionAnalytics.getCurrentShapeRate(
enumAnalyticsDataSource.delivered,
this.currentGoal.definition
);
}
return this.getShapesStored(this.currentGoal.definition); return this.getShapesStored(this.currentGoal.definition);
} }
@ -184,9 +184,8 @@ export class HubGoals extends BasicSerializableObject {
this.root.signals.shapeDelivered.dispatch(definition); this.root.signals.shapeDelivered.dispatch(definition);
// Check if we have enough for the next level // Check if we have enough for the next level
const targetHash = this.currentGoal.definition.getHash();
if ( if (
this.storedShapes[targetHash] >= this.currentGoal.required || this.getCurrentGoalDelivered() >= this.currentGoal.required ||
(G_IS_DEV && globalConfig.debug.rewardsInstant) (G_IS_DEV && globalConfig.debug.rewardsInstant)
) { ) {
this.onGoalCompleted(); this.onGoalCompleted();
@ -196,24 +195,28 @@ export class HubGoals extends BasicSerializableObject {
/** /**
* Creates the next goal * Creates the next goal
*/ */
createNextGoal() { computeNextGoal() {
const storyIndex = this.level - 1; const storyIndex = this.level - 1;
if (storyIndex < tutorialGoals.length) { if (storyIndex < tutorialGoals.length) {
const { shape, required, reward } = tutorialGoals[storyIndex]; const { shape, required, reward, throughputOnly } = tutorialGoals[storyIndex];
this.currentGoal = { this.currentGoal = {
/** @type {ShapeDefinition} */ /** @type {ShapeDefinition} */
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape), definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape),
required, required,
reward, reward,
throughputOnly,
}; };
return; return;
} }
const required = 4 + (this.level - 27) * 0.25;
this.currentGoal = { this.currentGoal = {
/** @type {ShapeDefinition} */ /** @type {ShapeDefinition} */
definition: this.createRandomShape(), definition: this.computeFreeplayShape(this.level),
required: findNiceIntegerValue(1000 + Math.pow(this.level * 2000, 0.8)), required,
reward: enumHubGoalRewards.no_reward_freeplay, reward: enumHubGoalRewards.no_reward_freeplay,
throughputOnly: true,
}; };
} }
@ -226,7 +229,7 @@ export class HubGoals extends BasicSerializableObject {
this.root.app.gameAnalytics.handleLevelCompleted(this.level); this.root.app.gameAnalytics.handleLevelCompleted(this.level);
++this.level; ++this.level;
this.createNextGoal(); this.computeNextGoal();
this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward); this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward);
} }
@ -320,15 +323,85 @@ export class HubGoals extends BasicSerializableObject {
} }
/** /**
* Picks random colors which are close to each other
* @param {RandomNumberGenerator} rng
*/
generateRandomColorSet(rng, allowUncolored = false) {
const colorWheel = [
enumColors.red,
enumColors.yellow,
enumColors.green,
enumColors.cyan,
enumColors.blue,
enumColors.purple,
enumColors.red,
enumColors.yellow,
];
const universalColors = [enumColors.white];
if (allowUncolored) {
universalColors.push(enumColors.uncolored);
}
const index = rng.nextIntRangeInclusive(0, colorWheel.length - 3);
const pickedColors = colorWheel.slice(index, index + 3);
pickedColors.push(rng.choice(universalColors));
return pickedColors;
}
/**
* Creates a (seeded) random shape
* @param {number} level
* @returns {ShapeDefinition} * @returns {ShapeDefinition}
*/ */
createRandomShape() { computeFreeplayShape(level) {
const layerCount = clamp(this.level / 25, 2, 4); const layerCount = clamp(this.level / 25, 2, 4);
/** @type {Array<import("./shape_definition").ShapeLayer>} */ /** @type {Array<import("./shape_definition").ShapeLayer>} */
let layers = []; let layers = [];
const randomColor = () => randomChoice(Object.values(enumColors)); const rng = new RandomNumberGenerator(this.root.map.seed + "/" + level);
const randomShape = () => randomChoice(Object.values(enumSubShape));
const colors = this.generateRandomColorSet(rng, level > 35);
let pickedSymmetry = null; // pairs of quadrants that must be the same
let availableShapes = [enumSubShape.rect, enumSubShape.circle, enumSubShape.star];
if (rng.next() < 0.5) {
pickedSymmetry = [
// radial symmetry
[0, 2],
[1, 3],
];
availableShapes.push(enumSubShape.windmill); // windmill looks good only in radial symmetry
} else {
const symmetries = [
[
// horizontal axis
[0, 3],
[1, 2],
],
[
// vertical axis
[0, 1],
[2, 3],
],
[
// diagonal axis
[0, 2],
[1],
[3],
],
[
// other diagonal axis
[1, 3],
[0],
[2],
],
];
pickedSymmetry = rng.choice(symmetries);
}
const randomColor = () => rng.choice(colors);
const randomShape = () => rng.choice(Object.values(enumSubShape));
let anyIsMissingTwo = false; let anyIsMissingTwo = false;
@ -336,23 +409,24 @@ export class HubGoals extends BasicSerializableObject {
/** @type {import("./shape_definition").ShapeLayer} */ /** @type {import("./shape_definition").ShapeLayer} */
const layer = [null, null, null, null]; const layer = [null, null, null, null];
for (let quad = 0; quad < 4; ++quad) { for (let j = 0; j < pickedSymmetry.length; ++j) {
layer[quad] = { const group = pickedSymmetry[j];
subShape: randomShape(), const shape = randomShape();
color: randomColor(), const color = randomColor();
}; for (let k = 0; k < group.length; ++k) {
} const quad = group[k];
layer[quad] = {
// Sometimes shapes are missing subShape: shape,
if (Math.random() > 0.85) { color,
layer[randomInt(0, 3)] = null; };
}
} }
// Sometimes they actually are missing *two* ones! // Sometimes they actually are missing *two* ones!
// Make sure at max only one layer is missing it though, otherwise we could // Make sure at max only one layer is missing it though, otherwise we could
// create an uncreateable shape // create an uncreateable shape
if (Math.random() > 0.95 && !anyIsMissingTwo) { if (level > 75 && rng.next() > 0.95 && !anyIsMissingTwo) {
layer[randomInt(0, 3)] = null; layer[rng.nextIntRange(0, 4)] = null;
anyIsMissingTwo = true; anyIsMissingTwo = true;
} }

View File

@ -44,6 +44,8 @@ import { HUDWireInfo } from "./parts/wire_info";
import { HUDLeverToggle } from "./parts/lever_toggle"; import { HUDLeverToggle } from "./parts/lever_toggle";
import { HUDLayerPreview } from "./parts/layer_preview"; import { HUDLayerPreview } from "./parts/layer_preview";
import { HUDMinerHighlight } from "./parts/miner_highlight"; import { HUDMinerHighlight } from "./parts/miner_highlight";
import { HUDBetaOverlay } from "./parts/beta_overlay";
import { HUDPerformanceWarning } from "./parts/performance_warning";
export class GameHUD { export class GameHUD {
/** /**
@ -75,7 +77,6 @@ export class GameHUD {
pinnedShapes: new HUDPinnedShapes(this.root), pinnedShapes: new HUDPinnedShapes(this.root),
notifications: new HUDNotifications(this.root), notifications: new HUDNotifications(this.root),
settingsMenu: new HUDSettingsMenu(this.root), settingsMenu: new HUDSettingsMenu(this.root),
// betaOverlay: new HUDBetaOverlay(this.root),
debugInfo: new HUDDebugInfo(this.root), debugInfo: new HUDDebugInfo(this.root),
dialogs: new HUDModalDialogs(this.root), dialogs: new HUDModalDialogs(this.root),
screenshotExporter: new HUDScreenshotExporter(this.root), screenshotExporter: new HUDScreenshotExporter(this.root),
@ -85,6 +86,7 @@ export class GameHUD {
layerPreview: new HUDLayerPreview(this.root), layerPreview: new HUDLayerPreview(this.root),
minerHighlight: new HUDMinerHighlight(this.root), minerHighlight: new HUDMinerHighlight(this.root),
performanceWarning: new HUDPerformanceWarning(this.root),
// Typing hints // Typing hints
/* typehints:start */ /* typehints:start */
@ -137,6 +139,10 @@ export class GameHUD {
this.parts.sandboxController = new HUDSandboxController(this.root); this.parts.sandboxController = new HUDSandboxController(this.root);
} }
if (!G_IS_RELEASE) {
this.parts.betaOverlay = new HUDBetaOverlay(this.root);
}
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
for (const key in this.parts) { for (const key in this.parts) {
this.parts[key].createElements(frag); this.parts[key].createElements(frag);

View File

@ -3,7 +3,12 @@ import { makeDiv } from "../../../core/utils";
export class HUDBetaOverlay extends BaseHUDPart { export class HUDBetaOverlay extends BaseHUDPart {
createElements(parent) { createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_BetaOverlay", [], "CLOSED BETA"); this.element = makeDiv(
parent,
"ingame_HUD_BetaOverlay",
[],
"<h2>CLOSED BETA VERSION</h2><span>This version is unstable, might crash and is not final!</span>"
);
} }
initialize() {} initialize() {}

View File

@ -334,7 +334,11 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
const tileBelow = this.root.map.getLowerLayerContentXY(tile.x, tile.y); const tileBelow = this.root.map.getLowerLayerContentXY(tile.x, tile.y);
// Check if there's a shape or color item below, if so select the miner // Check if there's a shape or color item below, if so select the miner
if (tileBelow && this.root.app.settings.getAllSettings().pickMinerOnPatch) { if (
tileBelow &&
this.root.app.settings.getAllSettings().pickMinerOnPatch &&
this.root.currentLayer === "regular"
) {
this.currentMetaBuilding.set(gMetaBuildingRegistry.findByClass(MetaMinerBuilding)); this.currentMetaBuilding.set(gMetaBuildingRegistry.findByClass(MetaMinerBuilding));
// Select chained miner if available, since that's always desired once unlocked // Select chained miner if available, since that's always desired once unlocked

View File

@ -0,0 +1,16 @@
import { T } from "../../../translations";
import { BaseHUDPart } from "../base_hud_part";
export class HUDPerformanceWarning extends BaseHUDPart {
initialize() {
this.warningShown = false;
this.root.signals.entityManuallyPlaced.add(this.checkAfterPlace, this);
}
checkAfterPlace() {
if (!this.warningShown && this.root.entityMgr.entities.length > 10000) {
this.root.hud.parts.dialogs.showInfo(T.dialogs.entityWarning.title, T.dialogs.entityWarning.desc);
this.warningShown = true;
}
}
}

View File

@ -4,6 +4,8 @@ import { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { blueprintShape, UPGRADES } from "../../upgrades"; import { blueprintShape, UPGRADES } from "../../upgrades";
import { enumHubGoalRewards } from "../../tutorial_goals"; import { enumHubGoalRewards } from "../../tutorial_goals";
import { enumAnalyticsDataSource } from "../../production_analytics";
import { T } from "../../../translations";
/** /**
* Manages the pinned shapes on the left side of the screen * Manages the pinned shapes on the left side of the screen
@ -22,11 +24,13 @@ export class HUDPinnedShapes extends BaseHUDPart {
* convenient. Also allows for cleaning up handles. * convenient. Also allows for cleaning up handles.
* @type {Array<{ * @type {Array<{
* key: string, * key: string,
* definition: ShapeDefinition,
* amountLabel: HTMLElement, * amountLabel: HTMLElement,
* lastRenderedValue: string, * lastRenderedValue: string,
* element: HTMLElement, * element: HTMLElement,
* detector?: ClickDetector, * detector?: ClickDetector,
* infoDetector?: ClickDetector * infoDetector?: ClickDetector,
* throughputOnly?: boolean
* }>} * }>}
*/ */
this.handles = []; this.handles = [];
@ -163,29 +167,40 @@ export class HUDPinnedShapes extends BaseHUDPart {
this.handles = []; this.handles = [];
// Pin story goal // Pin story goal
this.internalPinShape(currentKey, false, "goal"); this.internalPinShape({
key: currentKey,
canUnpin: false,
className: "goal",
throughputOnly: currentGoal.throughputOnly,
});
// Pin blueprint shape as well // Pin blueprint shape as well
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) { if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
this.internalPinShape(blueprintShape, false, "blueprint"); this.internalPinShape({
key: blueprintShape,
canUnpin: false,
className: "blueprint",
});
} }
// Pin manually pinned shapes // Pin manually pinned shapes
for (let i = 0; i < this.pinnedShapes.length; ++i) { for (let i = 0; i < this.pinnedShapes.length; ++i) {
const key = this.pinnedShapes[i]; const key = this.pinnedShapes[i];
if (key !== currentKey) { if (key !== currentKey) {
this.internalPinShape(key); this.internalPinShape({ key });
} }
} }
} }
/** /**
* Pins a new shape * Pins a new shape
* @param {string} key * @param {object} param0
* @param {boolean} canUnpin * @param {string} param0.key
* @param {string=} className * @param {boolean=} param0.canUnpin
* @param {string=} param0.className
* @param {boolean=} param0.throughputOnly
*/ */
internalPinShape(key, canUnpin = true, className = null) { internalPinShape({ key, canUnpin = true, className = null, throughputOnly = false }) {
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key); const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
const element = makeDiv(this.element, null, ["shape"]); const element = makeDiv(this.element, null, ["shape"]);
@ -229,11 +244,13 @@ export class HUDPinnedShapes extends BaseHUDPart {
this.handles.push({ this.handles.push({
key, key,
definition,
element, element,
amountLabel, amountLabel,
lastRenderedValue: "", lastRenderedValue: "",
detector, detector,
infoDetector, infoDetector,
throughputOnly,
}); });
} }
@ -244,8 +261,20 @@ export class HUDPinnedShapes extends BaseHUDPart {
for (let i = 0; i < this.handles.length; ++i) { for (let i = 0; i < this.handles.length; ++i) {
const handle = this.handles[i]; const handle = this.handles[i];
const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key); let currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
const currentValueFormatted = formatBigNumber(currentValue); let currentValueFormatted = formatBigNumber(currentValue);
if (handle.throughputOnly) {
currentValue = this.root.productionAnalytics.getCurrentShapeRate(
enumAnalyticsDataSource.delivered,
handle.definition
);
currentValueFormatted = T.ingame.statistics.shapesDisplayUnits.second.replace(
"<shapes>",
String(currentValue)
);
}
if (currentValueFormatted !== handle.lastRenderedValue) { if (currentValueFormatted !== handle.lastRenderedValue) {
handle.lastRenderedValue = currentValueFormatted; handle.lastRenderedValue = currentValueFormatted;
handle.amountLabel.innerText = currentValueFormatted; handle.amountLabel.innerText = currentValueFormatted;

View File

@ -113,7 +113,7 @@ export class HUDSandboxController extends BaseHUDPart {
modifyLevel(amount) { modifyLevel(amount) {
const hubGoals = this.root.hubGoals; const hubGoals = this.root.hubGoals;
hubGoals.level = Math.max(1, hubGoals.level + amount); hubGoals.level = Math.max(1, hubGoals.level + amount);
hubGoals.createNextGoal(); hubGoals.computeNextGoal();
// Clear all shapes of this level // Clear all shapes of this level
hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0; hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0;

View File

@ -90,17 +90,15 @@ export class HUDShop extends BaseHUDPart {
// Max level // Max level
handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace( handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace(
"<currentMult>", "<currentMult>",
currentTierMultiplier.toString() formatBigNumber(currentTierMultiplier)
); );
continue; continue;
} }
// Set description // Set description
handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description
.replace("<currentMult>", currentTierMultiplier.toString()) .replace("<currentMult>", formatBigNumber(currentTierMultiplier))
.replace("<newMult>", (currentTierMultiplier + tierHandle.improvement).toString()) .replace("<newMult>", formatBigNumber(currentTierMultiplier + tierHandle.improvement));
// Backwards compatibility
.replace("<gain>", (tierHandle.improvement * 100.0).toString());
tierHandle.required.forEach(({ shape, amount }) => { tierHandle.required.forEach(({ shape, amount }) => {
const container = makeDiv(handle.elemRequirements, null, ["requirement"]); const container = makeDiv(handle.elemRequirements, null, ["requirement"]);

View File

@ -4,11 +4,12 @@ import { makeDiv } from "../../../core/utils";
import { SOUNDS } from "../../../platform/sound"; import { SOUNDS } from "../../../platform/sound";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { defaultBuildingVariant } from "../../meta_building"; import { defaultBuildingVariant } from "../../meta_building";
import { enumHubGoalRewards } from "../../tutorial_goals"; import { enumHubGoalRewards, tutorialGoals } from "../../tutorial_goals";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach"; import { DynamicDomAttach } from "../dynamic_dom_attach";
import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings"; import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings";
import { InputReceiver } from "../../../core/input_receiver"; import { InputReceiver } from "../../../core/input_receiver";
import { enumNotificationType } from "./notifications";
export class HUDUnlockNotification extends BaseHUDPart { export class HUDUnlockNotification extends BaseHUDPart {
initialize() { initialize() {
@ -50,6 +51,14 @@ export class HUDUnlockNotification extends BaseHUDPart {
* @param {enumHubGoalRewards} reward * @param {enumHubGoalRewards} reward
*/ */
showForLevel(level, reward) { showForLevel(level, reward) {
if (level > tutorialGoals.length) {
this.root.hud.signals.notification.dispatch(
T.ingame.notifications.freeplayLevelComplete.replace("<level>", String(level)),
enumNotificationType.success
);
return;
}
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace( this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace(
"<level>", "<level>",

View File

@ -1,12 +1,18 @@
import { makeOffscreenBuffer } from "../../../core/buffer_utils"; import { makeOffscreenBuffer } from "../../../core/buffer_utils";
import { globalConfig, IS_DEMO } from "../../../core/config"; import { globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../../../core/config";
import { DrawParameters } from "../../../core/draw_parameters"; import { DrawParameters } from "../../../core/draw_parameters";
import { Loader } from "../../../core/loader"; import { Loader } from "../../../core/loader";
import { DialogWithForm } from "../../../core/modal_dialog_elements"; import { DialogWithForm } from "../../../core/modal_dialog_elements";
import { FormElementInput } from "../../../core/modal_dialog_forms"; import { FormElementInput } from "../../../core/modal_dialog_forms";
import { Rectangle } from "../../../core/rectangle"; import { Rectangle } from "../../../core/rectangle";
import { STOP_PROPAGATION } from "../../../core/signal"; import { STOP_PROPAGATION } from "../../../core/signal";
import { arrayDeleteValue, lerp, makeDiv, removeAllChildren } from "../../../core/utils"; import {
arrayDeleteValue,
fillInLinkIntoTranslation,
lerp,
makeDiv,
removeAllChildren,
} from "../../../core/utils";
import { Vector } from "../../../core/vector"; import { Vector } from "../../../core/vector";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { BaseItem } from "../../base_item"; import { BaseItem } from "../../base_item";
@ -272,7 +278,7 @@ export class HUDWaypoints extends BaseHUDPart {
const dialog = new DialogWithForm({ const dialog = new DialogWithForm({
app: this.root.app, app: this.root.app,
title: waypoint ? T.dialogs.createMarker.titleEdit : T.dialogs.createMarker.title, title: waypoint ? T.dialogs.createMarker.titleEdit : T.dialogs.createMarker.title,
desc: T.dialogs.createMarker.desc, desc: fillInLinkIntoTranslation(T.dialogs.createMarker.desc, THIRDPARTY_URLS.shapeViewer),
formElements: [markerNameInput], formElements: [markerNameInput],
buttons: waypoint ? ["delete:bad", "cancel", "ok:good"] : ["cancel", "ok:good"], buttons: waypoint ? ["delete:bad", "cancel", "ok:good"] : ["cancel", "ok:good"],
}); });

View File

@ -1,13 +1,14 @@
import { makeOffscreenBuffer } from "../../../core/buffer_utils"; import { makeOffscreenBuffer } from "../../../core/buffer_utils";
import { globalConfig } from "../../../core/config"; import { globalConfig } from "../../../core/config";
import { DrawParameters } from "../../../core/draw_parameters"; import { DrawParameters } from "../../../core/draw_parameters";
import { KEYMAPPINGS } from "../../key_action_mapper";
import { THEME } from "../../theme";
import { BaseHUDPart } from "../base_hud_part";
import { Loader } from "../../../core/loader"; import { Loader } from "../../../core/loader";
import { lerp } from "../../../core/utils"; import { lerp } from "../../../core/utils";
import { SOUNDS } from "../../../platform/sound";
import { KEYMAPPINGS } from "../../key_action_mapper";
import { enumHubGoalRewards } from "../../tutorial_goals"; import { enumHubGoalRewards } from "../../tutorial_goals";
import { BaseHUDPart } from "../base_hud_part";
const copy = require("clipboard-copy");
const wiresBackgroundDpi = 4; const wiresBackgroundDpi = 4;
export class HUDWiresOverlay extends BaseHUDPart { export class HUDWiresOverlay extends BaseHUDPart {
@ -16,6 +17,7 @@ export class HUDWiresOverlay extends BaseHUDPart {
initialize() { initialize() {
// Probably not the best location, but the one which makes most sense // Probably not the best location, but the one which makes most sense
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.switchLayers).add(this.switchLayers, this); this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.switchLayers).add(this.switchLayers, this);
this.root.keyMapper.getBinding(KEYMAPPINGS.placement.copyWireValue).add(this.copyWireValue, this);
this.generateTilePattern(); this.generateTilePattern();
@ -54,7 +56,53 @@ export class HUDWiresOverlay extends BaseHUDPart {
update() { update() {
const desiredAlpha = this.root.currentLayer === "wires" ? 1.0 : 0.0; const desiredAlpha = this.root.currentLayer === "wires" ? 1.0 : 0.0;
this.currentAlpha = lerp(this.currentAlpha, desiredAlpha, 0.12);
// On low performance, skip the fade
if (this.root.entityMgr.entities.length > 5000 || this.root.dynamicTickrate.averageFps < 50) {
this.currentAlpha = desiredAlpha;
} else {
this.currentAlpha = lerp(this.currentAlpha, desiredAlpha, 0.12);
}
}
/**
* Copies the wires value below the cursor
*/
copyWireValue() {
if (this.root.currentLayer !== "wires") {
return;
}
const mousePos = this.root.app.mousePosition;
if (!mousePos) {
return;
}
const tile = this.root.camera.screenToWorld(mousePos).toTileSpace();
const contents = this.root.map.getLayerContentXY(tile.x, tile.y, "wires");
if (!contents) {
return;
}
let value = null;
if (contents.components.Wire) {
const network = contents.components.Wire.linkedNetwork;
if (network && network.hasValue()) {
value = network.currentValue;
}
}
if (contents.components.ConstantSignal) {
value = contents.components.ConstantSignal.signal;
}
if (value) {
copy(value.getAsCopyableKey());
this.root.soundProxy.playUi(SOUNDS.copy);
} else {
copy("");
this.root.soundProxy.playUiError();
}
} }
/** /**

View File

@ -8,6 +8,9 @@ import { MetaVirtualProcessorBuilding } from "../../buildings/virtual_processor"
import { MetaTransistorBuilding } from "../../buildings/transistor"; import { MetaTransistorBuilding } from "../../buildings/transistor";
import { MetaAnalyzerBuilding } from "../../buildings/analyzer"; import { MetaAnalyzerBuilding } from "../../buildings/analyzer";
import { MetaComparatorBuilding } from "../../buildings/comparator"; import { MetaComparatorBuilding } from "../../buildings/comparator";
import { MetaReaderBuilding } from "../../buildings/reader";
import { MetaFilterBuilding } from "../../buildings/filter";
import { MetaDisplayBuilding } from "../../buildings/display";
export class HUDWiresToolbar extends HUDBaseToolbar { export class HUDWiresToolbar extends HUDBaseToolbar {
constructor(root) { constructor(root) {
@ -16,12 +19,17 @@ export class HUDWiresToolbar extends HUDBaseToolbar {
MetaWireBuilding, MetaWireBuilding,
MetaWireTunnelBuilding, MetaWireTunnelBuilding,
MetaConstantSignalBuilding, MetaConstantSignalBuilding,
MetaLeverBuilding,
MetaTransistorBuilding,
MetaLogicGateBuilding, MetaLogicGateBuilding,
MetaAnalyzerBuilding,
MetaVirtualProcessorBuilding, MetaVirtualProcessorBuilding,
MetaAnalyzerBuilding,
MetaComparatorBuilding, MetaComparatorBuilding,
MetaTransistorBuilding,
],
secondaryBuildings: [
MetaReaderBuilding,
MetaLeverBuilding,
MetaFilterBuilding,
MetaDisplayBuilding,
], ],
visibilityCondition: () => visibilityCondition: () =>
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires", !this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",

View File

@ -26,6 +26,13 @@ export class BooleanItem extends BaseItem {
return "boolean"; return "boolean";
} }
/**
* @returns {string}
*/
getAsCopyableKey() {
return this.value ? "1" : "0";
}
/** /**
* @param {number} value * @param {number} value
*/ */
@ -56,6 +63,21 @@ export class BooleanItem extends BaseItem {
} }
sprite.drawCachedCentered(parameters, x, y, diameter); sprite.drawCachedCentered(parameters, x, y, diameter);
} }
/**
* Draws the item to a canvas
* @param {CanvasRenderingContext2D} context
* @param {number} size
*/
drawFullSizeOnCanvas(context, size) {
let sprite;
if (this.value) {
sprite = Loader.getSprite("sprites/wires/boolean_true.png");
} else {
sprite = Loader.getSprite("sprites/wires/boolean_false.png");
}
sprite.drawCentered(context, size / 2, size / 2, size);
}
} }
export const BOOL_FALSE_SINGLETON = new BooleanItem(0); export const BOOL_FALSE_SINGLETON = new BooleanItem(0);

View File

@ -28,6 +28,13 @@ export class ColorItem extends BaseItem {
return "color"; return "color";
} }
/**
* @returns {string}
*/
getAsCopyableKey() {
return this.color;
}
/** /**
* @param {BaseItem} other * @param {BaseItem} other
*/ */
@ -47,6 +54,18 @@ export class ColorItem extends BaseItem {
return THEME.map.resources[this.color]; return THEME.map.resources[this.color];
} }
/**
* Draws the item to a canvas
* @param {CanvasRenderingContext2D} context
* @param {number} size
*/
drawFullSizeOnCanvas(context, size) {
if (!this.cachedSprite) {
this.cachedSprite = Loader.getSprite("sprites/colors/" + this.color + ".png");
}
this.cachedSprite.drawCentered(context, size / 2, size / 2, size);
}
/** /**
* @param {number} x * @param {number} x
* @param {number} y * @param {number} y

View File

@ -1,62 +1,78 @@
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { types } from "../../savegame/serialization"; import { types } from "../../savegame/serialization";
import { BaseItem } from "../base_item"; import { BaseItem } from "../base_item";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { THEME } from "../theme"; import { THEME } from "../theme";
import { globalConfig } from "../../core/config"; import { globalConfig } from "../../core/config";
export class ShapeItem extends BaseItem { export class ShapeItem extends BaseItem {
static getId() { static getId() {
return "shape"; return "shape";
} }
static getSchema() { static getSchema() {
return types.string; return types.string;
} }
serialize() { serialize() {
return this.definition.getHash(); return this.definition.getHash();
} }
deserialize(data) { deserialize(data) {
this.definition = ShapeDefinition.fromShortKey(data); this.definition = ShapeDefinition.fromShortKey(data);
} }
/** @returns {"shape"} **/ /** @returns {"shape"} **/
getItemType() { getItemType() {
return "shape"; return "shape";
} }
/** /**
* @param {BaseItem} other * @returns {string}
*/ */
equalsImpl(other) { getAsCopyableKey() {
return this.definition.getHash() === /** @type {ShapeItem} */ (other).definition.getHash(); return this.definition.getHash();
} }
/** /**
* @param {ShapeDefinition} definition * @param {BaseItem} other
*/ */
constructor(definition) { equalsImpl(other) {
super(); return this.definition.getHash() === /** @type {ShapeItem} */ (other).definition.getHash();
}
/**
* This property must not be modified on runtime, you have to clone the class in order to change the definition /**
*/ * @param {ShapeDefinition} definition
this.definition = definition; */
} constructor(definition) {
super();
getBackgroundColorAsResource() {
return THEME.map.resources.shape; /**
} * This property must not be modified on runtime, you have to clone the class in order to change the definition
*/
/** this.definition = definition;
* @param {number} x }
* @param {number} y
* @param {DrawParameters} parameters getBackgroundColorAsResource() {
* @param {number=} diameter return THEME.map.resources.shape;
*/ }
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
this.definition.drawCentered(x, y, parameters, diameter); /**
} * Draws the item to a canvas
} * @param {CanvasRenderingContext2D} context
* @param {number} size
*/
drawFullSizeOnCanvas(context, size) {
this.definition.drawFullSizeOnCanvas(context, size);
}
/**
* @param {number} x
* @param {number} y
* @param {DrawParameters} parameters
* @param {number=} diameter
*/
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
this.definition.drawCentered(x, y, parameters, diameter);
}
}

View File

@ -67,12 +67,11 @@ export const KEYMAPPINGS = {
wire: { keyCode: key("1") }, wire: { keyCode: key("1") },
wire_tunnel: { keyCode: key("2") }, wire_tunnel: { keyCode: key("2") },
constant_signal: { keyCode: key("3") }, constant_signal: { keyCode: key("3") },
lever_wires: { keyCode: key("4") }, logic_gate: { keyCode: key("4") },
logic_gate: { keyCode: key("5") }, virtual_processor: { keyCode: key("5") },
virtual_processor: { keyCode: key("6") }, analyzer: { keyCode: key("6") },
transistor: { keyCode: key("7") }, comparator: { keyCode: key("7") },
analyzer: { keyCode: key("8") }, transistor: { keyCode: key("8") },
comparator: { keyCode: key("9") },
}, },
placement: { placement: {
@ -82,6 +81,8 @@ export const KEYMAPPINGS = {
cycleBuildingVariants: { keyCode: key("T") }, cycleBuildingVariants: { keyCode: key("T") },
cycleBuildings: { keyCode: 9 }, // TAB cycleBuildings: { keyCode: 9 }, // TAB
switchDirectionLockSide: { keyCode: key("R") }, switchDirectionLockSide: { keyCode: key("R") },
copyWireValue: { keyCode: key("Z") },
}, },
massSelect: { massSelect: {

View File

@ -297,6 +297,15 @@ export class ShapeDefinition extends BasicSerializableObject {
parameters.context.drawImage(canvas, x - diameter / 2, y - diameter / 2, diameter, diameter); parameters.context.drawImage(canvas, x - diameter / 2, y - diameter / 2, diameter, diameter);
} }
/**
* Draws the item to a canvas
* @param {CanvasRenderingContext2D} context
* @param {number} size
*/
drawFullSizeOnCanvas(context, size) {
this.internalGenerateShapeBuffer(null, context, size, size, 1);
}
/** /**
* Generates this shape as a canvas * Generates this shape as a canvas
* @param {number} size * @param {number} size

View File

@ -1,6 +1,9 @@
import trim from "trim"; import trim from "trim";
import { THIRDPARTY_URLS } from "../../core/config";
import { DialogWithForm } from "../../core/modal_dialog_elements"; import { DialogWithForm } from "../../core/modal_dialog_elements";
import { FormElementInput } from "../../core/modal_dialog_forms"; import { FormElementInput, FormElementItemChooser } from "../../core/modal_dialog_forms";
import { fillInLinkIntoTranslation } from "../../core/utils";
import { T } from "../../translations";
import { BaseItem } from "../base_item"; import { BaseItem } from "../base_item";
import { enumColors } from "../colors"; import { enumColors } from "../colors";
import { ConstantSignalComponent } from "../components/constant_signal"; import { ConstantSignalComponent } from "../components/constant_signal";
@ -9,6 +12,7 @@ import { GameSystemWithFilter } from "../game_system_with_filter";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item"; import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../items/color_item"; import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { blueprintShape } from "../upgrades";
export class ConstantSignalSystem extends GameSystemWithFilter { export class ConstantSignalSystem extends GameSystemWithFilter {
constructor(root) { constructor(root) {
@ -41,23 +45,35 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
const signalValueInput = new FormElementInput({ const signalValueInput = new FormElementInput({
id: "signalValue", id: "signalValue",
label: null, label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
placeholder: "", placeholder: "",
defaultValue: "", defaultValue: "",
validator: val => this.parseSignalCode(val), validator: val => this.parseSignalCode(val),
}); });
const itemInput = new FormElementItemChooser({
id: "signalItem",
label: null,
items: [
BOOL_FALSE_SINGLETON,
BOOL_TRUE_SINGLETON,
...Object.values(COLOR_ITEM_SINGLETONS),
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(blueprintShape),
],
});
const dialog = new DialogWithForm({ const dialog = new DialogWithForm({
app: this.root.app, app: this.root.app,
title: "Set Signal", title: T.dialogs.editSignal.title,
desc: "Enter a shape code, color or '0' or '1'", desc: T.dialogs.editSignal.descItems,
formElements: [signalValueInput], formElements: [itemInput, signalValueInput],
buttons: ["cancel:bad:escape", "ok:good:enter"], buttons: ["cancel:bad:escape", "ok:good:enter"],
closeButton: false, closeButton: false,
}); });
this.root.hud.parts.dialogs.internalShowDialog(dialog); this.root.hud.parts.dialogs.internalShowDialog(dialog);
// When confirmed, set the signal // When confirmed, set the signal
dialog.buttonSignals.ok.add(() => { const closeHandler = () => {
if (!this.root || !this.root.entityMgr) { if (!this.root || !this.root.entityMgr) {
// Game got stopped // Game got stopped
return; return;
@ -75,8 +91,16 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
return; return;
} }
constantComp.signal = this.parseSignalCode(signalValueInput.getValue()); if (itemInput.chosenItem) {
}); console.log(itemInput.chosenItem);
constantComp.signal = itemInput.chosenItem;
} else {
constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
}
};
dialog.buttonSignals.ok.add(closeHandler);
dialog.valueChosen.add(closeHandler);
// When cancelled, destroy the entity again // When cancelled, destroy the entity again
dialog.buttonSignals.cancel.add(() => { dialog.buttonSignals.cancel.add(() => {

View File

@ -1,15 +1,15 @@
import { globalConfig } from "../../core/config";
import { smoothenDpi } from "../../core/dpi_manager";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { drawSpriteClipped } from "../../core/draw_utils";
import { Loader } from "../../core/loader"; import { Loader } from "../../core/loader";
import { Rectangle } from "../../core/rectangle";
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
import { formatBigNumber } from "../../core/utils"; import { formatBigNumber } from "../../core/utils";
import { T } from "../../translations"; import { T } from "../../translations";
import { HubComponent } from "../components/hub"; import { HubComponent } from "../components/hub";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { globalConfig } from "../../core/config";
import { smoothenDpi } from "../../core/dpi_manager";
import { drawSpriteClipped } from "../../core/draw_utils";
import { Rectangle } from "../../core/rectangle";
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
const HUB_SIZE_TILES = 4; const HUB_SIZE_TILES = 4;
const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize; const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize;
@ -73,23 +73,36 @@ export class HubSystem extends GameSystemWithFilter {
const textOffsetX = 70; const textOffsetX = 70;
const textOffsetY = 61; const textOffsetY = 61;
// Deliver count if (goals.throughputOnly) {
const delivered = this.root.hubGoals.getCurrentGoalDelivered(); // Throughput
const deliveredText = "" + formatBigNumber(delivered); const deliveredText = T.ingame.statistics.shapesDisplayUnits.second.replace(
"<shapes>",
formatBigNumber(goals.required)
);
if (delivered > 999) { context.font = "bold 12px GameFont";
context.font = "bold 16px GameFont"; context.fillStyle = "#64666e";
context.textAlign = "left";
context.fillText(deliveredText, textOffsetX, textOffsetY);
} else { } else {
context.font = "bold 25px GameFont"; // Deliver count
} const delivered = this.root.hubGoals.getCurrentGoalDelivered();
context.fillStyle = "#64666e"; const deliveredText = "" + formatBigNumber(delivered);
context.textAlign = "left";
context.fillText(deliveredText, textOffsetX, textOffsetY);
// Required if (delivered > 999) {
context.font = "13px GameFont"; context.font = "bold 16px GameFont";
context.fillStyle = "#a4a6b0"; } else {
context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13); context.font = "bold 25px GameFont";
}
context.fillStyle = "#64666e";
context.textAlign = "left";
context.fillText(deliveredText, textOffsetX, textOffsetY);
// Required
context.font = "13px GameFont";
context.fillStyle = "#a4a6b0";
context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
}
// Reward // Reward
const rewardText = T.storyRewards[goals.reward].title.toUpperCase(); const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
@ -167,4 +180,4 @@ export class HubSystem extends GameSystemWithFilter {
originalH: HUB_SIZE_PIXELS * dpi, originalH: HUB_SIZE_PIXELS * dpi,
}); });
} }
} }

View File

@ -3,8 +3,9 @@ import { enumColors } from "../colors";
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate"; import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
import { enumPinSlotType } from "../components/wired_pins"; import { enumPinSlotType } from "../components/wired_pins";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, isTruthyItem } from "../items/boolean_item"; import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, BooleanItem, isTruthyItem } from "../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../items/color_item"; import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeItem } from "../items/shape_item";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
export class LogicGateSystem extends GameSystemWithFilter { export class LogicGateSystem extends GameSystemWithFilter {
@ -153,18 +154,22 @@ export class LogicGateSystem extends GameSystemWithFilter {
/** /**
* @param {Array<BaseItem|null>} parameters * @param {Array<BaseItem|null>} parameters
* @returns {BaseItem} * @returns {[BaseItem, BaseItem]}
*/ */
compute_ROTATE(parameters) { compute_ROTATE(parameters) {
const item = parameters[0]; const item = parameters[0];
if (!item || item.getItemType() !== "shape") { if (!item || item.getItemType() !== "shape") {
// Not a shape // Not a shape
return null; return [null, null];
} }
const definition = /** @type {ShapeItem} */ (item).definition; const definition = /** @type {ShapeItem} */ (item).definition;
const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(definition); const rotatedDefinitionCCW = this.root.shapeDefinitionMgr.shapeActionRotateCCW(definition);
return this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition); const rotatedDefinitionCW = this.root.shapeDefinitionMgr.shapeActionRotateCW(definition);
return [
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinitionCCW),
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinitionCW),
];
} }
/** /**

View File

@ -43,7 +43,7 @@ export const tutorialGoals = [
// Circle // Circle
{ {
shape: "CuCuCuCu", // belts t1 shape: "CuCuCuCu", // belts t1
required: 40, required: 30,
reward: enumHubGoalRewards.reward_cutter_and_trash, reward: enumHubGoalRewards.reward_cutter_and_trash,
}, },
@ -59,14 +59,14 @@ export const tutorialGoals = [
// Rectangle // Rectangle
{ {
shape: "RuRuRuRu", // miners t1 shape: "RuRuRuRu", // miners t1
required: 85, required: 70,
reward: enumHubGoalRewards.reward_balancer, reward: enumHubGoalRewards.reward_balancer,
}, },
// 4 // 4
{ {
shape: "RuRu----", // processors t2 shape: "RuRu----", // processors t2
required: 100, required: 70,
reward: enumHubGoalRewards.reward_rotater, reward: enumHubGoalRewards.reward_rotater,
}, },
@ -74,14 +74,14 @@ export const tutorialGoals = [
// Rotater // Rotater
{ {
shape: "Cu----Cu", // belts t2 shape: "Cu----Cu", // belts t2
required: 175, required: 170,
reward: enumHubGoalRewards.reward_tunnel, reward: enumHubGoalRewards.reward_tunnel,
}, },
// 6 // 6
{ {
shape: "Cu------", // miners t2 shape: "Cu------", // miners t2
required: 250, required: 270,
reward: enumHubGoalRewards.reward_painter, reward: enumHubGoalRewards.reward_painter,
}, },
@ -89,14 +89,14 @@ export const tutorialGoals = [
// Painter // Painter
{ {
shape: "CrCrCrCr", // unused shape: "CrCrCrCr", // unused
required: 500, required: 300,
reward: enumHubGoalRewards.reward_rotater_ccw, reward: enumHubGoalRewards.reward_rotater_ccw,
}, },
// 8 // 8
{ {
shape: "RbRb----", // painter t2 shape: "RbRb----", // painter t2
required: 700, required: 480,
reward: enumHubGoalRewards.reward_mixer, reward: enumHubGoalRewards.reward_mixer,
}, },
@ -104,7 +104,7 @@ export const tutorialGoals = [
// Mixing (purple) // Mixing (purple)
{ {
shape: "CpCpCpCp", // belts t3 shape: "CpCpCpCp", // belts t3
required: 800, required: 600,
reward: enumHubGoalRewards.reward_merger, reward: enumHubGoalRewards.reward_merger,
}, },
@ -112,7 +112,7 @@ export const tutorialGoals = [
// STACKER: Star shape + cyan // STACKER: Star shape + cyan
{ {
shape: "ScScScSc", // miners t3 shape: "ScScScSc", // miners t3
required: 900, required: 800,
reward: enumHubGoalRewards.reward_stacker, reward: enumHubGoalRewards.reward_stacker,
}, },
@ -128,7 +128,7 @@ export const tutorialGoals = [
// Blueprints // Blueprints
{ {
shape: "CbCbCbRb:CwCwCwCw", shape: "CbCbCbRb:CwCwCwCw",
required: 1250, required: 1000,
reward: enumHubGoalRewards.reward_blueprints, reward: enumHubGoalRewards.reward_blueprints,
}, },
@ -136,24 +136,24 @@ export const tutorialGoals = [
// Tunnel Tier 2 // Tunnel Tier 2
{ {
shape: "RpRpRpRp:CwCwCwCw", // painting t3 shape: "RpRpRpRp:CwCwCwCw", // painting t3
required: 5000, required: 3800,
reward: enumHubGoalRewards.reward_underground_belt_tier_2, reward: enumHubGoalRewards.reward_underground_belt_tier_2,
}, },
// 14 // 14
// Belt reader // Belt reader
{ {
// @todo shape: "--Cg----:--Cr----", // unused
shape: "CuCuCuCu", required: 16, // Per second!
required: 7000,
reward: enumHubGoalRewards.reward_belt_reader, reward: enumHubGoalRewards.reward_belt_reader,
throughputOnly: true,
}, },
// 15 // 15
// Storage // Storage
{ {
shape: "SrSrSrSr:CyCyCyCy", // unused shape: "SrSrSrSr:CyCyCyCy", // unused
required: 7500, required: 10000,
reward: enumHubGoalRewards.reward_storage, reward: enumHubGoalRewards.reward_storage,
}, },
@ -161,7 +161,7 @@ export const tutorialGoals = [
// Quad Cutter // Quad Cutter
{ {
shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants) shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants)
required: 12500, required: 6000,
reward: enumHubGoalRewards.reward_cutter_quad, reward: enumHubGoalRewards.reward_cutter_quad,
}, },
@ -169,15 +169,14 @@ export const tutorialGoals = [
// Double painter // Double painter
{ {
shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants) shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
required: 15000, required: 20000,
reward: enumHubGoalRewards.reward_painter_double, reward: enumHubGoalRewards.reward_painter_double,
}, },
// 18 // 18
// Rotater (180deg) // Rotater (180deg)
{ {
// @TODO shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused
shape: "CuCuCuCu",
required: 20000, required: 20000,
reward: enumHubGoalRewards.reward_rotater_180, reward: enumHubGoalRewards.reward_rotater_180,
}, },
@ -185,8 +184,7 @@ export const tutorialGoals = [
// 19 // 19
// Compact splitter // Compact splitter
{ {
// @TODO shape: "CpRpCp--:SwSwSwSw",
shape: "CuCuCuCu",
required: 25000, required: 25000,
reward: enumHubGoalRewards.reward_splitter, reward: enumHubGoalRewards.reward_splitter,
}, },
@ -195,15 +193,14 @@ export const tutorialGoals = [
// WIRES // WIRES
{ {
shape: finalGameShape, shape: finalGameShape,
required: 50000, required: 25000,
reward: enumHubGoalRewards.reward_wires_filters_and_levers, reward: enumHubGoalRewards.reward_wires_filters_and_levers,
}, },
// 21 // 21
// Display // Display
{ {
// @TODO shape: "CrCrCrCr:CwCwCwCw:CrCrCrCr:CwCwCwCw",
shape: "CuCuCuCu",
required: 25000, required: 25000,
reward: enumHubGoalRewards.reward_display, reward: enumHubGoalRewards.reward_display,
}, },
@ -211,43 +208,37 @@ export const tutorialGoals = [
// 22 // 22
// Constant signal // Constant signal
{ {
// @TODO shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
shape: "CuCuCuCu", required: 25000,
required: 30000,
reward: enumHubGoalRewards.reward_constant_signal, reward: enumHubGoalRewards.reward_constant_signal,
}, },
// 23 // 23
// Quad Painter // Quad Painter
{ {
// @TODO shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
shape: "CuCuCuCu", required: 5000,
// shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
required: 35000,
reward: enumHubGoalRewards.reward_painter_quad, reward: enumHubGoalRewards.reward_painter_quad,
}, },
// 24 Logic gates // 24 Logic gates
{ {
// @TODO shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
shape: "CuCuCuCu", required: 10000,
required: 40000,
reward: enumHubGoalRewards.reward_logic_gates, reward: enumHubGoalRewards.reward_logic_gates,
}, },
// 25 Virtual Processing // 25 Virtual Processing
{ {
// @TODO shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg",
shape: "CuCuCuCu", required: 10000,
required: 45000,
reward: enumHubGoalRewards.reward_virtual_processing, reward: enumHubGoalRewards.reward_virtual_processing,
}, },
// 26 Freeplay // 26 Freeplay
{ {
// @TODO shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw",
shape: "CuCuCuCu", required: 10000,
required: 100000,
reward: enumHubGoalRewards.reward_freeplay, reward: enumHubGoalRewards.reward_freeplay,
}, },
]; ];

View File

@ -4,6 +4,7 @@ import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter"; import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
import { MetaDisplayBuilding } from "./buildings/display"; import { MetaDisplayBuilding } from "./buildings/display";
import { MetaLeverBuilding } from "./buildings/lever"; import { MetaLeverBuilding } from "./buildings/lever";
import { MetaLogicGateBuilding } from "./buildings/logic_gate";
import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner"; import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
import { MetaMixerBuilding } from "./buildings/mixer"; import { MetaMixerBuilding } from "./buildings/mixer";
import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter"; import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
@ -53,7 +54,7 @@ export const enumHubGoalRewardsToContentUnlocked = {
[enumHubGoalRewards.reward_constant_signal]: typed([ [enumHubGoalRewards.reward_constant_signal]: typed([
[MetaConstantSignalBuilding, defaultBuildingVariant], [MetaConstantSignalBuilding, defaultBuildingVariant],
]), ]),
[enumHubGoalRewards.reward_logic_gates]: null, // @TODO! [enumHubGoalRewards.reward_logic_gates]: typed([[MetaLogicGateBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_virtual_processing]: null, // @TODO! [enumHubGoalRewards.reward_virtual_processing]: null, // @TODO!
[enumHubGoalRewards.reward_wires_filters_and_levers]: typed([ [enumHubGoalRewards.reward_wires_filters_and_levers]: typed([

View File

@ -2,10 +2,28 @@ import { findNiceIntegerValue } from "../core/utils";
import { ShapeDefinition } from "./shape_definition"; import { ShapeDefinition } from "./shape_definition";
export const finalGameShape = "RuCw--Cw:----Ru--"; export const finalGameShape = "RuCw--Cw:----Ru--";
export const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
export const blueprintShape = "CbCbCbRb:CwCwCwCw"; export const blueprintShape = "CbCbCbRb:CwCwCwCw";
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 2]; const fixedImprovements = [0.5, 0.5, 1, 1, 2, 2];
const numEndgameUpgrades = G_IS_DEV || G_IS_STANDALONE ? 20 - fixedImprovements.length - 1 : 0;
function generateEndgameUpgrades() {
return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({
required: [
{ shape: blueprintShape, amount: 30000 + i * 10000 },
{ shape: finalGameShape, amount: 20000 + i * 5000 },
{ shape: rocketShape, amount: 20000 + i * 5000 },
],
excludePrevious: true,
}));
}
for (let i = 0; i < numEndgameUpgrades; ++i) {
fixedImprovements.push(0.1);
}
/** @typedef {{ /** @typedef {{
* shape: string, * shape: string,
* amount: number * amount: number
@ -23,95 +41,99 @@ const fixedImprovements = [0.5, 0.5, 1, 1, 2, 2];
export const UPGRADES = { export const UPGRADES = {
belt: [ belt: [
{ {
required: [{ shape: "CuCuCuCu", amount: 150 }], required: [{ shape: "CuCuCuCu", amount: 60 }],
}, },
{ {
required: [{ shape: "--CuCu--", amount: 1000 }], required: [{ shape: "--CuCu--", amount: 500 }],
}, },
{ {
required: [{ shape: "CpCpCpCp", amount: 5000 }], required: [{ shape: "CpCpCpCp", amount: 1000 }],
}, },
{ {
required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 12000 }], required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 6000 }],
}, },
{ {
required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 20000 }], required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 25000 }],
}, },
{ {
required: [{ shape: finalGameShape, amount: 50000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
miner: [ miner: [
{ {
required: [{ shape: "RuRuRuRu", amount: 400 }], required: [{ shape: "RuRuRuRu", amount: 300 }],
}, },
{ {
required: [{ shape: "Cu------", amount: 3000 }], required: [{ shape: "Cu------", amount: 800 }],
}, },
{ {
required: [{ shape: "ScScScSc", amount: 7000 }], required: [{ shape: "ScScScSc", amount: 3500 }],
}, },
{ {
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 15000 }], required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }],
}, },
{ {
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 30000 }], required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }],
}, },
{ {
required: [{ shape: finalGameShape, amount: 65000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
processors: [ processors: [
{ {
required: [{ shape: "SuSuSuSu", amount: 600 }], required: [{ shape: "SuSuSuSu", amount: 500 }],
}, },
{ {
required: [{ shape: "RuRu----", amount: 2000 }], required: [{ shape: "RuRu----", amount: 600 }],
}, },
{ {
required: [{ shape: "CgScScCg", amount: 15000 }], required: [{ shape: "CgScScCg", amount: 3500 }],
}, },
{ {
required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 20000 }], required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 25000 }],
}, },
{ {
required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 30000 }], required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 50000 }],
}, },
{ {
required: [{ shape: finalGameShape, amount: 75000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
painting: [ painting: [
{ {
required: [{ shape: "RbRb----", amount: 1000 }], required: [{ shape: "RbRb----", amount: 600 }],
}, },
{ {
required: [{ shape: "WrWrWrWr", amount: 3000 }], required: [{ shape: "WrWrWrWr", amount: 3800 }],
}, },
{ {
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 15000 }], required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }],
}, },
{ {
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 20000 }], required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }],
}, },
{ {
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 30000 }], required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 50000 }],
}, },
{ {
required: [{ shape: finalGameShape, amount: 100000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
}; };
// Tiers need % of the previous tier as requirement too // Tiers need % of the previous tier as requirement too
const tierGrowth = 1.8; const tierGrowth = 2.5;
// Automatically generate tier levels // Automatically generate tier levels
for (const upgradeId in UPGRADES) { for (const upgradeId in UPGRADES) {

3
src/js/globals.d.ts vendored
View File

@ -19,9 +19,6 @@ declare const G_BUILD_VERSION: string;
declare const G_ALL_UI_IMAGES: Array<string>; declare const G_ALL_UI_IMAGES: Array<string>;
declare const G_IS_RELEASE: boolean; declare const G_IS_RELEASE: boolean;
// Node require
declare function require(...args): any;
// Polyfills // Polyfills
declare interface String { declare interface String {
replaceAll(search: string, replacement: string): string; replaceAll(search: string, replacement: string): string;

View File

@ -25,6 +25,7 @@ export const SOUNDS = {
destroyBuilding: "destroy_building", destroyBuilding: "destroy_building",
placeBuilding: "place_building", placeBuilding: "place_building",
placeBelt: "place_belt", placeBelt: "place_belt",
copy: "copy",
}; };
export const MUSIC = { export const MUSIC = {

View File

@ -19,6 +19,7 @@ import { getCodeFromBuildingData } from "../../game/building_codes.js";
import { StaticMapEntityComponent } from "../../game/components/static_map_entity.js"; import { StaticMapEntityComponent } from "../../game/components/static_map_entity.js";
import { Entity } from "../../game/entity.js"; import { Entity } from "../../game/entity.js";
import { defaultBuildingVariant, MetaBuilding } from "../../game/meta_building.js"; import { defaultBuildingVariant, MetaBuilding } from "../../game/meta_building.js";
import { finalGameShape } from "../../game/upgrades.js";
import { SavegameInterface_V1005 } from "./1005.js"; import { SavegameInterface_V1005 } from "./1005.js";
const schema = require("./1006.json"); const schema = require("./1006.json");
@ -151,11 +152,26 @@ export class SavegameInterface_V1006 extends SavegameInterface_V1005 {
stored[shapeKey] = rebalance(stored[shapeKey]); stored[shapeKey] = rebalance(stored[shapeKey]);
} }
stored[finalGameShape] = 0;
// Reduce goals // Reduce goals
if (dump.hubGoals.currentGoal) { if (dump.hubGoals.currentGoal) {
dump.hubGoals.currentGoal.required = rebalance(dump.hubGoals.currentGoal.required); dump.hubGoals.currentGoal.required = rebalance(dump.hubGoals.currentGoal.required);
} }
let level = Math.min(19, dump.hubGoals.level);
const levelMapping = {
14: 15,
15: 16,
16: 17,
17: 18,
18: 19,
19: 20,
};
dump.hubGoals.level = levelMapping[level] || level;
// Update entities // Update entities
const entities = dump.entities; const entities = dump.entities;
for (let i = 0; i < entities.length; ++i) { for (let i = 0; i < entities.length; ++i) {

View File

@ -1,82 +1,80 @@
// Synchronizes all translations // Synchronizes all translations
const fs = require("fs"); const fs = require("fs");
const matchAll = require("match-all"); const matchAll = require("match-all");
const path = require("path"); const path = require("path");
const YAWN = require("yawn-yaml/cjs"); const YAML = require("yaml");
const YAML = require("yaml");
const files = fs
const files = fs .readdirSync(path.join(__dirname, "translations"))
.readdirSync(path.join(__dirname, "translations")) .filter(x => x.endsWith(".yaml"))
.filter(x => x.endsWith(".yaml")) .filter(x => x.indexOf("base-en") < 0);
.filter(x => x.indexOf("base-en") < 0);
const originalContents = fs
const originalContents = fs .readFileSync(path.join(__dirname, "translations", "base-en.yaml"))
.readFileSync(path.join(__dirname, "translations", "base-en.yaml")) .toString("utf-8");
.toString("utf-8");
const original = YAML.parse(originalContents);
const original = YAML.parse(originalContents);
const placeholderRegexp = /[[<]([a-zA-Z_0-9]+)[\]<]/gi;
const placeholderRegexp = /[[<]([a-zA-Z_0-9]+)[\]<]/gi;
function match(originalObj, translatedObj, path = "/") {
function match(originalObj, translatedObj, path = "/") { for (const key in originalObj) {
for (const key in originalObj) { if (!translatedObj.hasOwnProperty(key)) {
if (!translatedObj.hasOwnProperty(key)) { console.warn(" | Missing key", path + key);
console.warn(" | Missing key", path + key); translatedObj[key] = originalObj[key];
translatedObj[key] = originalObj[key]; continue;
continue; }
} const valueOriginal = originalObj[key];
const valueOriginal = originalObj[key]; const valueMatching = translatedObj[key];
const valueMatching = translatedObj[key]; if (typeof valueOriginal !== typeof valueMatching) {
if (typeof valueOriginal !== typeof valueMatching) { console.warn(" | MISMATCHING type (obj|non-obj) in", path + key);
console.warn(" | MISMATCHING type (obj|non-obj) in", path + key); continue;
continue; }
}
if (typeof valueOriginal === "object") {
if (typeof valueOriginal === "object") { match(valueOriginal, valueMatching, path + key + "/");
match(valueOriginal, valueMatching, path + key + "/"); } else if (typeof valueOriginal === "string") {
} else if (typeof valueOriginal === "string") { // todo
// todo const originalPlaceholders = matchAll(valueOriginal, placeholderRegexp).toArray();
const originalPlaceholders = matchAll(valueOriginal, placeholderRegexp).toArray(); const translatedPlaceholders = matchAll(valueMatching, placeholderRegexp).toArray();
const translatedPlaceholders = matchAll(valueMatching, placeholderRegexp).toArray();
if (originalPlaceholders.length !== translatedPlaceholders.length) {
if (originalPlaceholders.length !== translatedPlaceholders.length) { console.warn(
console.warn( " | Mismatching placeholders in",
" | Mismatching placeholders in", path + key,
path + key, "->",
"->", originalPlaceholders,
originalPlaceholders, "vs",
"vs", translatedPlaceholders
translatedPlaceholders );
); translatedObj[key] = originalObj[key];
translatedObj[key] = originalObj[key]; continue;
continue; }
} } else {
} else { console.warn(" | Unknown type: ", typeof valueOriginal);
console.warn(" | Unknown type: ", typeof valueOriginal); }
} }
// const matching = translatedObj[key]; for (const key in translatedObj) {
} if (!originalObj.hasOwnProperty(key)) {
console.warn(" | Obsolete key", path + key);
for (const key in translatedObj) { delete translatedObj[key];
if (!originalObj.hasOwnProperty(key)) { }
console.warn(" | Obsolete key", path + key); }
delete translatedObj[key]; }
}
} for (let i = 0; i < files.length; ++i) {
} const filePath = path.join(__dirname, "translations", files[i]);
console.log("Processing", files[i]);
for (let i = 0; i < files.length; ++i) { const translatedContents = fs.readFileSync(filePath).toString("utf-8");
const filePath = path.join(__dirname, "translations", files[i]);
console.log("Processing", files[i]); const json = YAML.parse(translatedContents);
const translatedContents = fs.readFileSync(filePath).toString("utf-8"); match(original, json, "/");
const translated = YAML.parse(translatedContents);
const handle = new YAWN(translatedContents); const stringified = YAML.stringify(json, {
indent: 4,
const json = handle.json; simpleKeys: true,
match(original, json, "/"); });
handle.json = json; fs.writeFileSync(filePath, stringified, "utf-8");
}
fs.writeFileSync(filePath, handle.yaml, "utf-8");
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -268,7 +268,13 @@ dialogs:
createMarker: createMarker:
title: New Marker title: New Marker
titleEdit: Edit Marker titleEdit: Edit Marker
desc: Give it a meaningful name, you can also include a <strong>short key</strong> of a shape (Which you can generate <a href="https://viewer.shapez.io" target="_blank">here</a>) desc: Give it a meaningful name, you can also include a <strong>short key</strong> of a shape (Which you can generate <link>here</link>)
editSignal:
title: Set Signal
descItems: >-
Choose a pre-defined item:
descShortKey: ... or enter the <strong>short key</strong> of a shape (Which you can generate <link>here</link>)
markerDemoLimit: markerDemoLimit:
desc: You can only create two custom markers in the demo. Get the standalone for unlimited markers! desc: You can only create two custom markers in the demo. Get the standalone for unlimited markers!
@ -281,6 +287,10 @@ dialogs:
title: Rename Savegame title: Rename Savegame
desc: You can rename your savegame here. desc: You can rename your savegame here.
entityWarning:
title: Performance Warning
desc: You have placed a lot of buildings, this is just a friendly reminder that the game can not handle an endless count of buildings - So try to keep your factories compact!
ingame: ingame:
# This is shown in the top left corner and displays useful keybindings in # This is shown in the top left corner and displays useful keybindings in
# every situation # every situation
@ -350,6 +360,7 @@ ingame:
notifications: notifications:
newUpgrade: A new upgrade is available! newUpgrade: A new upgrade is available!
gameSaved: Your game has been saved. gameSaved: Your game has been saved.
freeplayLevelComplete: Level <level> has been completed!
# The "Upgrades" window # The "Upgrades" window
shop: shop:
@ -360,7 +371,8 @@ ingame:
tier: Tier <x> tier: Tier <x>
# The roman number for each tier # The roman number for each tier
tierLabels: [I, II, III, IV, V, VI, VII, VIII, IX, X] tierLabels:
[I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX]
maximumLevel: MAXIMUM LEVEL (Speed x<currentMult>) maximumLevel: MAXIMUM LEVEL (Speed x<currentMult>)
@ -573,7 +585,7 @@ buildings:
wire_tunnel: wire_tunnel:
default: default:
name: &wire_tunnel Wire Tunnel name: &wire_tunnel Wire Crossing
description: Allows to cross two wires without connecting them. description: Allows to cross two wires without connecting them.
constant_signal: constant_signal:
@ -589,16 +601,16 @@ buildings:
logic_gate: logic_gate:
default: default:
name: AND Gate name: AND Gate
description: Emits a boolean "1" if both inputs are truthy. description: Emits a boolean "1" if both inputs are truthy. (Truthy means shape, color or boolean "1")
not: not:
name: NOT Gate name: NOT Gate
description: Emits a boolean "1" if the input is not truthy. description: Emits a boolean "1" if the input is not truthy. (Truthy means shape, color or boolean "1")
xor: xor:
name: XOR Gate name: XOR Gate
description: Emits a boolean "1" if one of the inputs is truthy, but not both. description: Emits a boolean "1" if one of the inputs is truthy, but not both. (Truthy means shape, color or boolean "1")
or: or:
name: OR Gate name: OR Gate
description: Emits a boolean "1" if one of the inputs is truthy. description: Emits a boolean "1" if one of the inputs is truthy. (Truthy means shape, color or boolean "1")
transistor: transistor:
default: default:
@ -622,7 +634,7 @@ buildings:
reader: reader:
default: default:
name: &reader Belt Reader name: &reader Belt Reader
description: Allows to measure belt throughput. Outputs the last read item on the wires layer (once unlocked). description: Allows to measure the average belt throughput. Outputs the last read item on the wires layer (once unlocked).
analyzer: analyzer:
default: default:
@ -632,20 +644,20 @@ buildings:
comparator: comparator:
default: default:
name: &comparator Compare name: &comparator Compare
description: Returns boolean "1" if both items are exactly equal. Can compare shapes, items and booleans. description: Returns boolean "1" if both signals are exactly equal. Can compare shapes, items and booleans.
virtual_processor: virtual_processor:
default: default:
name: &virtual_processor Virtual Cutter name: &virtual_processor Virtual Cutter
description: Computes description: Virtually cuts the shape into two halves.
rotater: rotater:
name: Virtual Rotater name: Virtual Rotater
description: Virtually rotates the shape by 90 degrees clockwise. description: Virtually rotates the shape, both clockwise and counter-clockwise.
unstacker: unstacker:
name: Virtual Unstacker name: Virtual Unstacker
description: Returns the topmost layer to the right, and the remaining ones on the left. description: Virtually extracts the topmost layer to the right output and the remaining ones to the left.
stacker: stacker:
name: Virtual Stacker name: Virtual Stacker
@ -753,7 +765,7 @@ storyRewards:
reward_display: reward_display:
title: Display title: Display
desc: >- desc: >-
You have unlocked the <strong>Display</strong>! Connect a wires signal to it to present a color, shape or boolean! You have unlocked the <strong>Display</strong> - Connect a signal on the wires layer to visualize its contents!
reward_constant_signal: reward_constant_signal:
title: Constant Signal title: Constant Signal
@ -765,17 +777,19 @@ storyRewards:
title: Logic Gates title: Logic Gates
desc: >- desc: >-
You unlocked <strong>logic gates</strong>! You don't have to be excited about this, but it's actually super cool!<br><br> You unlocked <strong>logic gates</strong>! You don't have to be excited about this, but it's actually super cool!<br><br>
With those gates you can now perform AND, OR, XOR and NOT boolean operations! With those gates you can now compute AND, OR, XOR and NOT operations.<br><br>
As a bonus on top I also just gave you a <strong>transistor</strong>!
reward_virtual_processing: reward_virtual_processing:
title: Virtual Processing title: Virtual Processing
desc: >- desc: >-
I just gave a whole bunch of new buildings which allow you to <strong>simulate the processing of shapes</strong>!<br><br> I just gave a whole bunch of new buildings which allow you to <strong>simulate the processing of shapes</strong>!<br><br>
You can now simulate a cutter, rotater, stacker and more on the wires layer!<br><br> You can now simulate a cutter, rotater, stacker and more on the wires layer!
With this you now have three options to continue the game:<br><br> With this you now have three options to continue the game:<br><br>
- Build an <strong>automated machine</strong> to create any possible shape requested by the hub (This is cool, I swear!).<br><br> - Build an <strong>automated machine</strong> to create any possible shape requested by the HUB (I recommend to try it!).<br><br>
- Build something cool with wires.<br><br> - Build something cool with wires.<br><br>
- Continue to play regulary. - Continue to play regulary.<br><br>
Whatever you choose, remember to have fun!
# Special reward, which is shown when there is no reward actually # Special reward, which is shown when there is no reward actually
no_reward: no_reward:
@ -786,13 +800,14 @@ storyRewards:
no_reward_freeplay: no_reward_freeplay:
title: Next level title: Next level
desc: >- desc: >-
Congratulations! By the way, more content is planned for the standalone! Congratulations!
reward_freeplay: reward_freeplay:
title: Freeplay title: Freeplay
desc: >- desc: >-
You did it! You unlocked the <strong>free-play mode</strong>! This means that shapes are now <strong>randomly</strong> generated!<br><br> You did it! You unlocked the <strong>free-play mode</strong>! This means that shapes are now <strong>randomly</strong> generated!<br><br>
Since the hub will only require low quantities, I highly recommend to build a machine which automatically delivers the requested shape! Since the hub will require a <strong>throughput</strong> from now on, I highly recommend to build a machine which automatically delivers the requested shape!<br><br>
The HUB outputs the requested shape on the wires layer, so all you have to do is to analyze it and automatically configure your factory based on that.
settings: settings:
title: Settings title: Settings
@ -1036,8 +1051,7 @@ keybindings:
wire: *wire wire: *wire
constant_signal: *constant_signal constant_signal: *constant_signal
logic_gate: Logic Gate logic_gate: Logic Gate
lever: Switch (regular) lever: *lever
lever_wires: Switch (wires)
filter: *filter filter: *filter
wire_tunnel: *wire_tunnel wire_tunnel: *wire_tunnel
display: *display display: *display
@ -1059,7 +1073,8 @@ keybindings:
lockBeltDirection: Enable belt planner lockBeltDirection: Enable belt planner
switchDirectionLockSide: >- switchDirectionLockSide: >-
Planner: Switch side Planner: Switch side
copyWireValue: >-
Wires: Copy value below cursor
massSelectStart: Hold and drag to start massSelectStart: Hold and drag to start
massSelectSelectMultiple: Select multiple areas massSelectSelectMultiple: Select multiple areas
massSelectCopy: Copy area massSelectCopy: Copy area
@ -1128,7 +1143,7 @@ tips:
- Organization is important. Try not to cross conveyors too much. - Organization is important. Try not to cross conveyors too much.
- Plan in advance, or it will be a huge chaos! - Plan in advance, or it will be a huge chaos!
- Don't remove your old factories! You'll need them to unlock upgrades. - Don't remove your old factories! You'll need them to unlock upgrades.
- Try beating level 18 on your own before seeking for help! - Try beating level 20 on your own before seeking for help!
- Don't complicate things, try to stay simple and you'll go far. - Don't complicate things, try to stay simple and you'll go far.
- You may need to re-use factories later in the game. Plan your factories to be re-usable. - You may need to re-use factories later in the game. Plan your factories to be re-usable.
- Sometimes, you can find a needed shape in the map without creating it with stackers. - Sometimes, you can find a needed shape in the map without creating it with stackers.
@ -1149,3 +1164,5 @@ tips:
- This game has a lot of settings, be sure to check them out! - This game has a lot of settings, be sure to check them out!
- The marker to your hub has a small compass to indicate its direction! - The marker to your hub has a small compass to indicate its direction!
- To clear belts, cut the area and then paste it at the same location. - To clear belts, cut the area and then paste it at the same location.
- Press F4 to show your FPS and Tick Rate.
- Press F4 twice to show the tile of your mouse and camera.

File diff suppressed because it is too large Load Diff

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