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

Performance optimizations (#1154)

* 1.3.1 preparations

* Minor fixes, update translations

* Fix achievements not working

* Lots of belt optimizations, ~15% performance boost
This commit is contained in:
tobspr 2021-04-29 11:16:56 +02:00 committed by GitHub
parent fa25deb761
commit 3318d869b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 8903 additions and 7783 deletions

View File

@ -74,20 +74,8 @@ function createWindow() {
win.on("closed", () => {
console.log("Window closed");
win = null;
app.quit();
});
function handleWindowBeforeunload(event) {
const confirmed = dialog.showMessageBox(remote.getCurrentWindow(), options) === 1;
if (confirmed) {
remote.getCurrentWindow().close();
} else {
event.returnValue = false;
}
}
win.on("", handleWindowBeforeunload);
if (isDev) {
menu = new Menu();

View File

@ -10,10 +10,10 @@
"start": "electron --disable-direct-composition --in-process-gpu ."
},
"devDependencies": {
"electron": "10.4.0"
"electron": "10.4.3"
},
"optionalDependencies": {
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v85"
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v82"
},
"dependencies": {
"async-lock": "^1.2.8"

View File

@ -1,5 +1,5 @@
const fs = require('fs');
const path = require('path');
const fs = require("fs");
const path = require("path");
const { ipcMain } = require("electron");
let greenworks = null;
@ -11,7 +11,7 @@ try {
appId = parseInt(fs.readFileSync(path.join(__dirname, "steam_appid.txt"), "utf8"));
} catch (err) {
// greenworks is not installed
// throw err;
console.warn("Failed to load steam api:", err);
}
function init(isDev) {
@ -37,8 +37,13 @@ function init (isDev) {
function listen() {
ipcMain.handle("steam:is-initialized", isInitialized);
if (!greenworks || !initialized) {
console.log("Ignoring Steam IPC events");
if (!initialized) {
console.warn("Steam not initialized, won't be able to listen");
return;
}
if (!greenworks) {
console.warn("Greenworks not loaded, won't be able to listen");
return;
}
@ -53,7 +58,7 @@ function isInitialized(event) {
function getAchievementNames(event) {
return new Promise((resolve, reject) => {
try {
const achievements = greenworks.getAchievementNames()
const achievements = greenworks.getAchievementNames();
resolve(achievements);
} catch (err) {
reject(err);
@ -63,11 +68,15 @@ function getAchievementNames(event) {
function activateAchievement(event, id) {
return new Promise((resolve, reject) => {
greenworks.activateAchievement(id, () => resolve(), err => reject(err))
greenworks.activateAchievement(
id,
() => resolve(),
err => reject(err)
);
});
}
module.exports = {
init,
listen
listen,
};

View File

@ -146,10 +146,10 @@ duplexer3@^0.1.4:
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
electron@10.4.0:
version "10.4.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-10.4.0.tgz#018385914474b56110a5a43087a53c114b67c08d"
integrity sha512-qK8OOCWuNvEFWThmjkukkqDwIpBqULlDuMXVC9MC/2P4UaWJEjIYvBmBuTyxtFcKoE3kWvcWyeRDUuvzVxxXjA==
electron@10.4.3:
version "10.4.3"
resolved "https://registry.yarnpkg.com/electron/-/electron-10.4.3.tgz#8d1c0f5e562d1b78dcec8074c0d59e58137fd508"
integrity sha512-qL8XZBII9KQHr1+YmVMj1AqyTR2I8/lxozvKEWoKKSkF8Hl6GzzxrLXRfISP7aDAvsJEyyhc6b2/42ME8hG5JA==
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^12.0.12"
@ -503,9 +503,9 @@ serialize-error@^7.0.1:
dependencies:
type-fest "^0.13.1"
"shapez.io-private-artifacts@github:tobspr/shapez.io-private-artifacts#abi-v85":
"shapez.io-private-artifacts@github:tobspr/shapez.io-private-artifacts#abi-v82":
version "0.1.0"
resolved "git+ssh://git@github.com/tobspr/shapez.io-private-artifacts.git#63adf7e0ea4b90c2a29053ce1f0ec9d573b3ac0a"
resolved "git+ssh://git@github.com/tobspr/shapez.io-private-artifacts.git#8aa3bfd3b569eb5695fc8a585a3f2ee3ed2db290"
sprintf-js@^1.1.2:
version "1.1.2"

View File

@ -17,7 +17,7 @@
@include S(border-radius, 3px);
@include DarkThemeOverride {
background: #424242;
background: #33343c;
}
.version {

View File

@ -2,7 +2,18 @@ export const CHANGELOG = [
{
version: "1.3.1",
date: "beta",
entries: [
entries: G_CHINA_VERSION
? [
"第13关的交付目标更改为中国古代指南针。感谢玩家凯风入心 创作并提供",
"第17关的交付目标更改为永乐通宝。感谢玩家金天赐 创作并提供",
"第22关的交付目标更改为凤凰。感谢玩家我没得眼镜 创作并提供",
"第23关的交付目标更改为古代车轮。感谢玩家我没得眼镜 创作并提供",
"第24关的交付目标更改为大熊猫。感谢玩家窝囸倪现任 创作并提供",
"修复了一些特定情况下偶尔会发生的存档损坏问题",
"修复了成就更新后有时候游戏崩溃的问题",
]
: [
"Fixed savegames getting corrupt in rare conditions",
"Fixed game crashing sometimes since the achievements update",
],

View File

@ -11,6 +11,7 @@ export const itemTypes = ["shape", "color", "boolean"];
export class BaseItem extends BasicSerializableObject {
constructor() {
super();
this._type = this.getItemType();
}
static getId() {

View File

@ -13,8 +13,6 @@ import { GameRoot } from "./root";
const logger = createLogger("belt_path");
// Helpers for more semantic access into interleaved arrays
const _nextDistance = 0;
const _item = 1;
const DEBUG = G_IS_DEV && false;
@ -174,7 +172,7 @@ export class BeltPath extends BasicSerializableObject {
* Recomputes cache variables once the path was changed
*/
onPathChanged() {
this.acceptorTarget = this.computeAcceptingEntityAndSlot();
this.boundAcceptor = this.computeAcceptingEntityAndSlot();
/**
* How many items past the first item are compressed
@ -192,7 +190,7 @@ export class BeltPath extends BasicSerializableObject {
/**
* Finds the entity which accepts our items
* @param {boolean=} debug_Silent Whether debug output should be silent
* @return {{ entity: Entity, slot: number, direction?: enumDirection }}
* @return { (BaseItem, number) => boolean }
*/
computeAcceptingEntityAndSlot(debug_Silent = false) {
DEBUG && !debug_Silent && logger.log("Recomputing acceptor target");
@ -214,7 +212,12 @@ export class BeltPath extends BasicSerializableObject {
"regular"
);
if (targetEntity) {
if (!targetEntity) {
return;
}
const noSimplifiedBelts = !this.root.app.settings.getAllSettings().simplifiedBelts;
DEBUG && !debug_Silent && logger.log(" Found target entity", targetEntity.uid);
const targetStaticComp = targetEntity.components.StaticMapEntity;
const targetBeltComp = targetEntity.components.Belt;
@ -233,10 +236,10 @@ export class BeltPath extends BasicSerializableObject {
targetStaticComp.rotation
);
if (ejectSlotWsDirection === beltAcceptingDirection) {
return {
entity: targetEntity,
direction: null,
slot: 0,
return item => {
const path = targetBeltComp.assignedPath;
assert(path, "belt has no path");
return path.tryAcceptItem(item);
};
}
}
@ -259,10 +262,92 @@ export class BeltPath extends BasicSerializableObject {
return;
}
return {
entity: targetEntity,
slot: matchingSlot.index,
direction: enumInvertedDirections[ejectingDirection],
const matchingSlotIndex = matchingSlot.index;
const passOver = this.computePassOverFunctionWithoutBelts(targetEntity, matchingSlotIndex);
if (!passOver) {
return;
}
const matchingDirection = enumInvertedDirections[ejectingDirection];
const filter = matchingSlot.slot.filter;
return function (item, remainingProgress = 0.0) {
// Check if the acceptor has a filter
if (filter && item._type !== filter) {
return false;
}
// Try to pass over
if (passOver(item, matchingSlotIndex)) {
// Trigger animation on the acceptor comp
if (noSimplifiedBelts) {
targetAcceptorComp.onItemAccepted(
matchingSlotIndex,
matchingDirection,
item,
remainingProgress
);
}
return true;
}
return false;
};
}
/**
* Computes a method to pass over the item to the entity
* @param {Entity} entity
* @param {number} matchingSlotIndex
* @returns {(item: BaseItem, slotIndex: number) => boolean | void}
*/
computePassOverFunctionWithoutBelts(entity, matchingSlotIndex) {
const systems = this.root.systemMgr.systems;
const hubGoals = this.root.hubGoals;
// NOTICE: THIS IS COPIED FROM THE ITEM EJECTOR SYSTEM FOR PEROFMANCE REASONS
const itemProcessorComp = entity.components.ItemProcessor;
if (itemProcessorComp) {
// Its an item processor ..
return function (item) {
// Check for potential filters
if (!systems.itemProcessor.checkRequirements(entity, item, matchingSlotIndex)) {
return;
}
return itemProcessorComp.tryTakeItem(item, matchingSlotIndex);
};
}
const undergroundBeltComp = entity.components.UndergroundBelt;
if (undergroundBeltComp) {
// Its an underground belt. yay.
return function (item) {
return undergroundBeltComp.tryAcceptExternalItem(
item,
hubGoals.getUndergroundBeltBaseSpeed()
);
};
}
const storageComp = entity.components.Storage;
if (storageComp) {
// It's a storage
return function (item) {
if (storageComp.canAcceptItem(item)) {
storageComp.takeItem(item);
return true;
}
};
}
const filterComp = entity.components.Filter;
if (filterComp) {
// It's a filter! Unfortunately the filter has to know a lot about it's
// surrounding state and components, so it can't be within the component itself.
return function (item) {
if (systems.filter.tryAcceptItem(entity, matchingSlotIndex, item)) {
return true;
}
};
}
}
@ -365,17 +450,17 @@ export class BeltPath extends BasicSerializableObject {
for (let i = 0; i < this.items.length; ++i) {
const item = this.items[i];
if (item[_nextDistance] < 0 || item[_nextDistance] > this.totalLength + 0.02) {
if (item[0 /* nextDistance */] < 0 || item[0 /* nextDistance */] > this.totalLength + 0.02) {
return fail(
"Item has invalid offset to next item: ",
item[_nextDistance],
item[0 /* nextDistance */],
"(total length:",
this.totalLength,
")"
);
}
currentPos += item[_nextDistance];
currentPos += item[0 /* nextDistance */];
}
// Check the total sum matches
@ -387,7 +472,7 @@ export class BeltPath extends BasicSerializableObject {
this.spacingToFirstItem,
") and items does not match total length (",
this.totalLength,
") -> items: " + this.items.map(i => i[_nextDistance]).join("|")
") -> items: " + this.items.map(i => i[0 /* nextDistance */]).join("|")
);
}
@ -399,43 +484,14 @@ export class BeltPath extends BasicSerializableObject {
// Check acceptor
const acceptor = this.computeAcceptingEntityAndSlot(true);
if (!!acceptor !== !!this.acceptorTarget) {
return fail("Acceptor target mismatch, acceptor", !!acceptor, "vs stored", !!this.acceptorTarget);
}
if (acceptor) {
if (this.acceptorTarget.entity !== acceptor.entity) {
return fail(
"Mismatching entity on acceptor target:",
acceptor.entity.uid,
"vs",
this.acceptorTarget.entity.uid
);
}
if (this.acceptorTarget.slot !== acceptor.slot) {
return fail(
"Mismatching entity on acceptor target:",
acceptor.slot,
"vs stored",
this.acceptorTarget.slot
);
}
if (this.acceptorTarget.direction !== acceptor.direction) {
return fail(
"Mismatching direction on acceptor target:",
acceptor.direction,
"vs stored",
this.acceptorTarget.direction
);
}
if (!!acceptor !== !!this.boundAcceptor) {
return fail("Acceptor target mismatch, acceptor", !!acceptor, "vs stored", !!this.boundAcceptor);
}
// Check first nonzero offset
let firstNonzero = 0;
for (let i = this.items.length - 2; i >= 0; --i) {
if (this.items[i][_nextDistance] < globalConfig.itemSpacingOnBelts + 1e-5) {
if (this.items[i][0 /* nextDistance */] < globalConfig.itemSpacingOnBelts + 1e-5) {
++firstNonzero;
} else {
break;
@ -483,11 +539,11 @@ export class BeltPath extends BasicSerializableObject {
DEBUG &&
logger.log(
" Extended spacing of last item from",
lastItem[_nextDistance],
lastItem[0 /* nextDistance */],
"to",
lastItem[_nextDistance] + additionalLength
lastItem[0 /* nextDistance */] + additionalLength
);
lastItem[_nextDistance] += additionalLength;
lastItem[0 /* nextDistance */] += additionalLength;
}
// Assign reference
@ -618,7 +674,7 @@ export class BeltPath extends BasicSerializableObject {
DEBUG &&
logger.log(
"Old items are",
this.items.map(i => i[_nextDistance])
this.items.map(i => i[0 /* nextDistance */])
);
// Create second path
@ -628,7 +684,7 @@ export class BeltPath extends BasicSerializableObject {
let itemPos = this.spacingToFirstItem;
for (let i = 0; i < this.items.length; ++i) {
const item = this.items[i];
const distanceToNext = item[_nextDistance];
const distanceToNext = item[0 /* nextDistance */];
DEBUG && logger.log(" Checking item at", itemPos, "with distance of", distanceToNext, "to next");
@ -643,7 +699,7 @@ export class BeltPath extends BasicSerializableObject {
// Check if its on the second path (otherwise its on the removed belt and simply lost)
if (itemPos >= secondPathStart) {
// Put item on second path
secondPath.items.push([distanceToNext, item[_item]]);
secondPath.items.push([distanceToNext, item[1 /* item */]]);
DEBUG &&
logger.log(
" Put item to second path @",
@ -672,7 +728,7 @@ export class BeltPath extends BasicSerializableObject {
"to",
clampedDistanceToNext
);
item[_nextDistance] = clampedDistanceToNext;
item[0 /* nextDistance */] = clampedDistanceToNext;
}
}
@ -683,13 +739,13 @@ export class BeltPath extends BasicSerializableObject {
DEBUG &&
logger.log(
"New items are",
this.items.map(i => i[_nextDistance])
this.items.map(i => i[0 /* nextDistance */])
);
DEBUG &&
logger.log(
"And second path items are",
secondPath.items.map(i => i[_nextDistance])
secondPath.items.map(i => i[0 /* nextDistance */])
);
// Adjust our total length
@ -776,9 +832,17 @@ export class BeltPath extends BasicSerializableObject {
continue;
}
DEBUG && logger.log("Item", i, "is at", itemOffset, "with next offset", item[_nextDistance]);
DEBUG &&
logger.log(
"Item",
i,
"is at",
itemOffset,
"with next offset",
item[0 /* nextDistance */]
);
lastItemOffset = itemOffset;
itemOffset += item[_nextDistance];
itemOffset += item[0 /* nextDistance */];
}
// If we still have an item, make sure the last item matches
@ -805,7 +869,7 @@ export class BeltPath extends BasicSerializableObject {
this.totalLength,
")"
);
this.items[this.items.length - 1][_nextDistance] = lastDistance;
this.items[this.items.length - 1][0 /* nextDistance */] = lastDistance;
} else {
DEBUG && logger.log(" Removed all items so we'll update spacing to total length");
@ -893,7 +957,7 @@ export class BeltPath extends BasicSerializableObject {
DEBUG &&
logger.log(
" Items:",
this.items.map(i => i[_nextDistance])
this.items.map(i => i[0 /* nextDistance */])
);
// Find offset to first item
@ -912,7 +976,7 @@ export class BeltPath extends BasicSerializableObject {
// This item must be dropped
this.items.splice(i, 1);
i -= 1;
itemOffset += item[_nextDistance];
itemOffset += item[0 /* nextDistance */];
continue;
} else {
// This item can be kept, thus its the first we know
@ -990,9 +1054,13 @@ export class BeltPath extends BasicSerializableObject {
// Now, update the distance of our last item
if (this.items.length !== 0) {
const lastItem = this.items[this.items.length - 1];
lastItem[_nextDistance] += otherPath.spacingToFirstItem;
lastItem[0 /* nextDistance */] += otherPath.spacingToFirstItem;
DEBUG &&
logger.log(" Add distance to last item, effectively being", lastItem[_nextDistance], "now");
logger.log(
" Add distance to last item, effectively being",
lastItem[0 /* nextDistance */],
"now"
);
} else {
// Seems we have no items, update our first item distance
this.spacingToFirstItem = oldLength + otherPath.spacingToFirstItem;
@ -1012,7 +1080,7 @@ export class BeltPath extends BasicSerializableObject {
// Aaand push the other paths items
for (let i = 0; i < otherPath.items.length; ++i) {
const item = otherPath.items[i];
this.items.push([item[_nextDistance], item[_item]]);
this.items.push([item[0 /* nextDistance */], item[1 /* item */]]);
}
// Update bounds
@ -1046,6 +1114,11 @@ export class BeltPath extends BasicSerializableObject {
this.debug_checkIntegrity("pre-update");
}
// Skip empty belts
if (this.items.length === 0) {
return;
}
// Divide by item spacing on belts since we use throughput and not speed
let beltSpeed =
this.root.hubGoals.getBeltBaseSpeed() *
@ -1074,30 +1147,40 @@ export class BeltPath extends BasicSerializableObject {
lastItemProcessed === this.items.length - 1 ? 0 : globalConfig.itemSpacingOnBelts;
// Compute how much we can advance
const clampedProgress = Math.max(
0,
Math.min(remainingVelocity, nextDistanceAndItem[_nextDistance] - minimumSpacing)
);
let clampedProgress = nextDistanceAndItem[0 /* nextDistance */] - minimumSpacing;
// Make sure we don't advance more than the remaining velocity has stored
if (remainingVelocity < clampedProgress) {
clampedProgress = remainingVelocity;
}
// Make sure we don't advance back
if (clampedProgress < 0) {
clampedProgress = 0;
}
// Reduce our velocity by the amount we consumed
remainingVelocity -= clampedProgress;
// Reduce the spacing
nextDistanceAndItem[_nextDistance] -= clampedProgress;
nextDistanceAndItem[0 /* nextDistance */] -= clampedProgress;
// Advance all items behind by the progress we made
this.spacingToFirstItem += clampedProgress;
// If the last item can be ejected, eject it and reduce the spacing, because otherwise
// we lose velocity
if (isFirstItemProcessed && nextDistanceAndItem[_nextDistance] < 1e-7) {
if (isFirstItemProcessed && nextDistanceAndItem[0 /* nextDistance */] < 1e-7) {
// Store how much velocity we "lost" because we bumped the item to the end of the
// belt but couldn't move it any farther. We need this to tell the item acceptor
// animation to start a tad later, so everything matches up. Yes I'm a perfectionist.
const excessVelocity = beltSpeed - clampedProgress;
// Try to directly get rid of the item
if (this.tryHandOverItem(nextDistanceAndItem[_item], excessVelocity)) {
if (
this.boundAcceptor &&
this.boundAcceptor(nextDistanceAndItem[1 /* item */], excessVelocity)
) {
this.items.pop();
const itemBehind = this.items[lastItemProcessed - 1];
@ -1108,11 +1191,11 @@ export class BeltPath extends BasicSerializableObject {
// Also see #999
const fixupProgress = Math.max(
0,
Math.min(remainingVelocity, itemBehind[_nextDistance])
Math.min(remainingVelocity, itemBehind[0 /* nextDistance */])
);
// See above
itemBehind[_nextDistance] -= fixupProgress;
itemBehind[0 /* nextDistance */] -= fixupProgress;
remainingVelocity -= fixupProgress;
this.spacingToFirstItem += fixupProgress;
}
@ -1145,8 +1228,8 @@ export class BeltPath extends BasicSerializableObject {
// Check if we have an item which is ready to be emitted
const lastItem = this.items[this.items.length - 1];
if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
if (this.tryHandOverItem(lastItem[_item])) {
if (lastItem && lastItem[0 /* nextDistance */] === 0) {
if (this.boundAcceptor && this.boundAcceptor(lastItem[1 /* item */])) {
this.items.pop();
this.numCompressedItemsAfterFirstItem = Math.max(
0,
@ -1160,50 +1243,6 @@ export class BeltPath extends BasicSerializableObject {
}
}
/**
* Tries to hand over the item to the end entity
* @param {BaseItem} item
*/
tryHandOverItem(item, remainingProgress = 0.0) {
if (!this.acceptorTarget) {
return;
}
const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
// Check if the acceptor has a filter for example
if (targetAcceptorComp && !targetAcceptorComp.canAcceptItem(this.acceptorTarget.slot, item)) {
// Well, this item is not accepted
return false;
}
// Try to pass over
if (
this.root.systemMgr.systems.itemEjector.tryPassOverItem(
item,
this.acceptorTarget.entity,
this.acceptorTarget.slot
)
) {
// Trigger animation on the acceptor comp
const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
if (targetAcceptorComp) {
if (!this.root.app.settings.getAllSettings().simplifiedBelts) {
targetAcceptorComp.onItemAccepted(
this.acceptorTarget.slot,
this.acceptorTarget.direction,
item,
remainingProgress
);
}
}
return true;
}
return false;
}
/**
* Computes a world space position from the given progress
* @param {number} progress
@ -1270,11 +1309,11 @@ export class BeltPath extends BasicSerializableObject {
parameters.context.font = "6px GameFont";
parameters.context.fillStyle = "#111";
parameters.context.fillText(
"" + round4Digits(nextDistanceAndItem[_nextDistance]),
"" + round4Digits(nextDistanceAndItem[0 /* nextDistance */]),
worldPos.x + 5,
worldPos.y + 2
);
progress += nextDistanceAndItem[_nextDistance];
progress += nextDistanceAndItem[0 /* nextDistance */];
if (this.items.length - 1 - this.numCompressedItemsAfterFirstItem === i) {
parameters.context.fillStyle = "red";
@ -1370,7 +1409,7 @@ export class BeltPath extends BasicSerializableObject {
const centerPos = staticComp.localTileToWorld(centerPosLocal).toWorldSpaceCenterOfTile();
parameters.context.globalAlpha = 0.5;
firstItem[_item].drawItemCenteredClipped(centerPos.x, centerPos.y, parameters);
firstItem[1 /* item */].drawItemCenteredClipped(centerPos.x, centerPos.y, parameters);
parameters.context.globalAlpha = 1;
}
@ -1402,7 +1441,7 @@ export class BeltPath extends BasicSerializableObject {
const distanceAndItem = this.items[currentItemIndex];
distanceAndItem[_item].drawItemCenteredClipped(
distanceAndItem[1 /* item */].drawItemCenteredClipped(
worldPos.x,
worldPos.y,
parameters,
@ -1410,7 +1449,7 @@ export class BeltPath extends BasicSerializableObject {
);
// Check for the next item
currentItemPos += distanceAndItem[_nextDistance];
currentItemPos += distanceAndItem[0 /* nextDistance */];
++currentItemIndex;
if (currentItemIndex >= this.items.length) {

View File

@ -71,6 +71,8 @@ export class ItemAcceptorComponent extends Component {
/**
* Returns if this acceptor can accept a new item at slot N
*
* NOTICE: The belt path ignores this for performance reasons and does his own check
* @param {number} slotIndex
* @param {BaseItem=} item
*/

View File

@ -157,9 +157,7 @@ export class GameHUD {
this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root);
}
if (queryParamOptions.sandboxMode || G_IS_DEV) {
this.parts.sandboxController = new HUDSandboxController(this.root);
}
if (!G_IS_RELEASE && !G_IS_DEV) {
this.parts.betaOverlay = new HUDBetaOverlay(this.root);

View File

@ -1,3 +1,4 @@
import { queryParamOptions } from "../../../core/query_parameters";
import { makeDiv } from "../../../core/utils";
import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach";
@ -144,11 +145,25 @@ export class HUDSandboxController extends BaseHUDPart {
}
});
this.visible = !G_IS_DEV;
this.visible = false;
this.domAttach = new DynamicDomAttach(this.root, this.element);
}
isAvailable() {
if (queryParamOptions.sandboxMode || G_IS_DEV) {
return true;
}
// @ts-ignore
if (window.sandboxMode) {
return true;
}
return false;
}
toggle() {
if (!this.visible && !this.isAvailable()) {
return;
}
this.visible = !this.visible;
}

View File

@ -118,7 +118,14 @@ function generateUpgrades(limitedVersion = false) {
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }],
},
{
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }],
required: [
{
shape: G_CHINA_VERSION
? "CyCyCyCy:CyCyCyCy:RyRyRyRy:RuRuRuRu"
: "CbRbRbCb:CwCwCwCw:WbWbWbWb",
amount: 50000,
},
],
},
{
required: [{ shape: preparementShape, amount: 25000 }],
@ -172,7 +179,12 @@ function generateUpgrades(limitedVersion = false) {
required: [{ shape: "WrWrWrWr", amount: 3800 }],
},
{
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }],
required: [
{
shape: G_CHINA_VERSION ? "CuCuCuCu:CwCwCwCw:Sb--Sr--" : "RpRpRpRp:CwCwCwCw",
amount: 6500,
},
],
},
{
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }],
@ -346,7 +358,7 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 13
// Tunnel Tier 2
{
shape: "RpRpRpRp:CwCwCwCw", // painting t3
shape: G_CHINA_VERSION ? "CuCuCuCu:CwCwCwCw:Sb--Sr--" : "RpRpRpRp:CwCwCwCw", // painting t3
required: 3800,
reward: enumHubGoalRewards.reward_underground_belt_tier_2,
},
@ -355,7 +367,7 @@ export function generateLevelDefinitions(limitedVersion = false) {
...(limitedVersion
? [
{
shape: "RpRpRpRp:CwCwCwCw",
shape: G_CHINA_VERSION ? "CuCuCuCu:CwCwCwCw:Sb--Sr--" : "RpRpRpRp:CwCwCwCw",
required: 0,
reward: enumHubGoalRewards.reward_demo_end,
},
@ -389,7 +401,9 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 17
// Double painter
{
shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
shape: G_CHINA_VERSION
? "CyCyCyCy:CyCyCyCy:RyRyRyRy:RuRuRuRu"
: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
required: 20000,
reward: enumHubGoalRewards.reward_painter_double,
},
@ -429,7 +443,9 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 22
// Constant signal
{
shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
shape: G_CHINA_VERSION
? "RrSySrSy:RyCrCwCr:CyCyRyCy"
: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
required: 25000,
reward: enumHubGoalRewards.reward_constant_signal,
},
@ -437,14 +453,18 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 23
// Display
{
shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
shape: G_CHINA_VERSION
? "CrCrCrCr:CwCwCwCw:WwWwWwWw:CrCrCrCr"
: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
required: 25000,
reward: enumHubGoalRewards.reward_display,
},
// 24 Logic gates
{
shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
shape: G_CHINA_VERSION
? "Su----Su:RwRwRwRw:Cu----Cu:CwCwCwCw"
: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
required: 25000,
reward: enumHubGoalRewards.reward_logic_gates,
},

View File

@ -239,6 +239,14 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
return false;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// NOTICE ! THIS CODE IS DUPLICATED IN THE BELT PATH FOR PERFORMANCE REASONS
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
const itemProcessorComp = receiver.components.ItemProcessor;
if (itemProcessorComp) {
// Check for potential filters

View File

@ -224,13 +224,16 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
update() {
this.staleAreaWatcher.update();
const sender = enumUndergroundBeltMode.sender;
const now = this.root.time.now();
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const undergroundComp = entity.components.UndergroundBelt;
if (undergroundComp.mode === enumUndergroundBeltMode.sender) {
if (undergroundComp.mode === sender) {
this.handleSender(entity);
} else {
this.handleReceiver(entity);
this.handleReceiver(entity, now);
}
}
}
@ -327,14 +330,15 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
/**
*
* @param {Entity} entity
* @param {number} now
*/
handleReceiver(entity) {
handleReceiver(entity, now) {
const undergroundComp = entity.components.UndergroundBelt;
// Try to eject items, we only check the first one because it is sorted by remaining time
const nextItemAndDuration = undergroundComp.pendingItems[0];
if (nextItemAndDuration) {
if (this.root.time.now() > nextItemAndDuration[1]) {
if (now > nextItemAndDuration[1]) {
const ejectorComp = entity.components.ItemEjector;
const nextSlotIndex = ejectorComp.getFirstFreeSlot();

1208
translations/base-he.yml Normal file

File diff suppressed because it is too large Load Diff

14957
yarn.lock

File diff suppressed because it is too large Load Diff