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", () => { win.on("closed", () => {
console.log("Window closed"); console.log("Window closed");
win = null; 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) { if (isDev) {
menu = new Menu(); menu = new Menu();

View File

@ -10,10 +10,10 @@
"start": "electron --disable-direct-composition --in-process-gpu ." "start": "electron --disable-direct-composition --in-process-gpu ."
}, },
"devDependencies": { "devDependencies": {
"electron": "10.4.0" "electron": "10.4.3"
}, },
"optionalDependencies": { "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": { "dependencies": {
"async-lock": "^1.2.8" "async-lock": "^1.2.8"

View File

@ -1,5 +1,5 @@
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
const { ipcMain } = require("electron"); const { ipcMain } = require("electron");
let greenworks = null; let greenworks = null;
@ -11,10 +11,10 @@ try {
appId = parseInt(fs.readFileSync(path.join(__dirname, "steam_appid.txt"), "utf8")); appId = parseInt(fs.readFileSync(path.join(__dirname, "steam_appid.txt"), "utf8"));
} catch (err) { } catch (err) {
// greenworks is not installed // greenworks is not installed
// throw err; console.warn("Failed to load steam api:", err);
} }
function init (isDev) { function init(isDev) {
if (!greenworks) { if (!greenworks) {
return; return;
} }
@ -34,11 +34,16 @@ function init (isDev) {
initialized = true; initialized = true;
} }
function listen () { function listen() {
ipcMain.handle("steam:is-initialized", isInitialized); ipcMain.handle("steam:is-initialized", isInitialized);
if (!greenworks || !initialized) { if (!initialized) {
console.log("Ignoring Steam IPC events"); 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; return;
} }
@ -53,7 +58,7 @@ function isInitialized(event) {
function getAchievementNames(event) { function getAchievementNames(event) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const achievements = greenworks.getAchievementNames() const achievements = greenworks.getAchievementNames();
resolve(achievements); resolve(achievements);
} catch (err) { } catch (err) {
reject(err); reject(err);
@ -63,11 +68,15 @@ function getAchievementNames(event) {
function activateAchievement(event, id) { function activateAchievement(event, id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
greenworks.activateAchievement(id, () => resolve(), err => reject(err)) greenworks.activateAchievement(
id,
() => resolve(),
err => reject(err)
);
}); });
} }
module.exports = { module.exports = {
init, 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" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
electron@10.4.0: electron@10.4.3:
version "10.4.0" version "10.4.3"
resolved "https://registry.yarnpkg.com/electron/-/electron-10.4.0.tgz#018385914474b56110a5a43087a53c114b67c08d" resolved "https://registry.yarnpkg.com/electron/-/electron-10.4.3.tgz#8d1c0f5e562d1b78dcec8074c0d59e58137fd508"
integrity sha512-qK8OOCWuNvEFWThmjkukkqDwIpBqULlDuMXVC9MC/2P4UaWJEjIYvBmBuTyxtFcKoE3kWvcWyeRDUuvzVxxXjA== integrity sha512-qL8XZBII9KQHr1+YmVMj1AqyTR2I8/lxozvKEWoKKSkF8Hl6GzzxrLXRfISP7aDAvsJEyyhc6b2/42ME8hG5JA==
dependencies: dependencies:
"@electron/get" "^1.0.1" "@electron/get" "^1.0.1"
"@types/node" "^12.0.12" "@types/node" "^12.0.12"
@ -503,9 +503,9 @@ serialize-error@^7.0.1:
dependencies: dependencies:
type-fest "^0.13.1" 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" 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: sprintf-js@^1.1.2:
version "1.1.2" version "1.1.2"

View File

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

View File

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

View File

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

View File

@ -13,8 +13,6 @@ import { GameRoot } from "./root";
const logger = createLogger("belt_path"); const logger = createLogger("belt_path");
// Helpers for more semantic access into interleaved arrays // Helpers for more semantic access into interleaved arrays
const _nextDistance = 0;
const _item = 1;
const DEBUG = G_IS_DEV && false; const DEBUG = G_IS_DEV && false;
@ -174,7 +172,7 @@ export class BeltPath extends BasicSerializableObject {
* Recomputes cache variables once the path was changed * Recomputes cache variables once the path was changed
*/ */
onPathChanged() { onPathChanged() {
this.acceptorTarget = this.computeAcceptingEntityAndSlot(); this.boundAcceptor = this.computeAcceptingEntityAndSlot();
/** /**
* How many items past the first item are compressed * 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 * Finds the entity which accepts our items
* @param {boolean=} debug_Silent Whether debug output should be silent * @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) { computeAcceptingEntityAndSlot(debug_Silent = false) {
DEBUG && !debug_Silent && logger.log("Recomputing acceptor target"); DEBUG && !debug_Silent && logger.log("Recomputing acceptor target");
@ -214,55 +212,142 @@ export class BeltPath extends BasicSerializableObject {
"regular" "regular"
); );
if (targetEntity) { if (!targetEntity) {
DEBUG && !debug_Silent && logger.log(" Found target entity", targetEntity.uid); return;
const targetStaticComp = targetEntity.components.StaticMapEntity; }
const targetBeltComp = targetEntity.components.Belt;
// Check for belts (special case) const noSimplifiedBelts = !this.root.app.settings.getAllSettings().simplifiedBelts;
if (targetBeltComp) {
const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top); DEBUG && !debug_Silent && logger.log(" Found target entity", targetEntity.uid);
DEBUG && const targetStaticComp = targetEntity.components.StaticMapEntity;
!debug_Silent && const targetBeltComp = targetEntity.components.Belt;
logger.log(
" Entity is accepting items from", // Check for belts (special case)
ejectSlotWsDirection, if (targetBeltComp) {
"vs", const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
beltAcceptingDirection, DEBUG &&
"Rotation:", !debug_Silent &&
targetStaticComp.rotation logger.log(
" Entity is accepting items from",
ejectSlotWsDirection,
"vs",
beltAcceptingDirection,
"Rotation:",
targetStaticComp.rotation
);
if (ejectSlotWsDirection === beltAcceptingDirection) {
return item => {
const path = targetBeltComp.assignedPath;
assert(path, "belt has no path");
return path.tryAcceptItem(item);
};
}
}
// Check for item acceptors
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
if (!targetAcceptorComp) {
// Entity doesn't accept items
return;
}
const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection);
const matchingSlot = targetAcceptorComp.findMatchingSlot(
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
ejectingDirection
);
if (!matchingSlot) {
// No matching slot found
return;
}
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
); );
if (ejectSlotWsDirection === beltAcceptingDirection) {
return {
entity: targetEntity,
direction: null,
slot: 0,
};
} }
return true;
} }
return false;
};
}
// Check for item acceptors /**
const targetAcceptorComp = targetEntity.components.ItemAcceptor; * Computes a method to pass over the item to the entity
if (!targetAcceptorComp) { * @param {Entity} entity
// Entity doesn't accept items * @param {number} matchingSlotIndex
return; * @returns {(item: BaseItem, slotIndex: number) => boolean | void}
} */
computePassOverFunctionWithoutBelts(entity, matchingSlotIndex) {
const systems = this.root.systemMgr.systems;
const hubGoals = this.root.hubGoals;
const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection); // NOTICE: THIS IS COPIED FROM THE ITEM EJECTOR SYSTEM FOR PEROFMANCE REASONS
const matchingSlot = targetAcceptorComp.findMatchingSlot(
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
ejectingDirection
);
if (!matchingSlot) { const itemProcessorComp = entity.components.ItemProcessor;
// No matching slot found if (itemProcessorComp) {
return; // Its an item processor ..
} return function (item) {
// Check for potential filters
if (!systems.itemProcessor.checkRequirements(entity, item, matchingSlotIndex)) {
return;
}
return itemProcessorComp.tryTakeItem(item, matchingSlotIndex);
};
}
return { const undergroundBeltComp = entity.components.UndergroundBelt;
entity: targetEntity, if (undergroundBeltComp) {
slot: matchingSlot.index, // Its an underground belt. yay.
direction: enumInvertedDirections[ejectingDirection], 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) { for (let i = 0; i < this.items.length; ++i) {
const item = this.items[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( return fail(
"Item has invalid offset to next item: ", "Item has invalid offset to next item: ",
item[_nextDistance], item[0 /* nextDistance */],
"(total length:", "(total length:",
this.totalLength, this.totalLength,
")" ")"
); );
} }
currentPos += item[_nextDistance]; currentPos += item[0 /* nextDistance */];
} }
// Check the total sum matches // Check the total sum matches
@ -387,7 +472,7 @@ export class BeltPath extends BasicSerializableObject {
this.spacingToFirstItem, this.spacingToFirstItem,
") and items does not match total length (", ") and items does not match total length (",
this.totalLength, 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 // Check acceptor
const acceptor = this.computeAcceptingEntityAndSlot(true); const acceptor = this.computeAcceptingEntityAndSlot(true);
if (!!acceptor !== !!this.acceptorTarget) { if (!!acceptor !== !!this.boundAcceptor) {
return fail("Acceptor target mismatch, acceptor", !!acceptor, "vs stored", !!this.acceptorTarget); return fail("Acceptor target mismatch, acceptor", !!acceptor, "vs stored", !!this.boundAcceptor);
}
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
);
}
} }
// Check first nonzero offset // Check first nonzero offset
let firstNonzero = 0; let firstNonzero = 0;
for (let i = this.items.length - 2; i >= 0; --i) { 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; ++firstNonzero;
} else { } else {
break; break;
@ -483,11 +539,11 @@ export class BeltPath extends BasicSerializableObject {
DEBUG && DEBUG &&
logger.log( logger.log(
" Extended spacing of last item from", " Extended spacing of last item from",
lastItem[_nextDistance], lastItem[0 /* nextDistance */],
"to", "to",
lastItem[_nextDistance] + additionalLength lastItem[0 /* nextDistance */] + additionalLength
); );
lastItem[_nextDistance] += additionalLength; lastItem[0 /* nextDistance */] += additionalLength;
} }
// Assign reference // Assign reference
@ -618,7 +674,7 @@ export class BeltPath extends BasicSerializableObject {
DEBUG && DEBUG &&
logger.log( logger.log(
"Old items are", "Old items are",
this.items.map(i => i[_nextDistance]) this.items.map(i => i[0 /* nextDistance */])
); );
// Create second path // Create second path
@ -628,7 +684,7 @@ export class BeltPath extends BasicSerializableObject {
let itemPos = this.spacingToFirstItem; let itemPos = this.spacingToFirstItem;
for (let i = 0; i < this.items.length; ++i) { for (let i = 0; i < this.items.length; ++i) {
const item = this.items[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"); 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) // Check if its on the second path (otherwise its on the removed belt and simply lost)
if (itemPos >= secondPathStart) { if (itemPos >= secondPathStart) {
// Put item on second path // Put item on second path
secondPath.items.push([distanceToNext, item[_item]]); secondPath.items.push([distanceToNext, item[1 /* item */]]);
DEBUG && DEBUG &&
logger.log( logger.log(
" Put item to second path @", " Put item to second path @",
@ -672,7 +728,7 @@ export class BeltPath extends BasicSerializableObject {
"to", "to",
clampedDistanceToNext clampedDistanceToNext
); );
item[_nextDistance] = clampedDistanceToNext; item[0 /* nextDistance */] = clampedDistanceToNext;
} }
} }
@ -683,13 +739,13 @@ export class BeltPath extends BasicSerializableObject {
DEBUG && DEBUG &&
logger.log( logger.log(
"New items are", "New items are",
this.items.map(i => i[_nextDistance]) this.items.map(i => i[0 /* nextDistance */])
); );
DEBUG && DEBUG &&
logger.log( logger.log(
"And second path items are", "And second path items are",
secondPath.items.map(i => i[_nextDistance]) secondPath.items.map(i => i[0 /* nextDistance */])
); );
// Adjust our total length // Adjust our total length
@ -776,9 +832,17 @@ export class BeltPath extends BasicSerializableObject {
continue; 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; lastItemOffset = itemOffset;
itemOffset += item[_nextDistance]; itemOffset += item[0 /* nextDistance */];
} }
// If we still have an item, make sure the last item matches // If we still have an item, make sure the last item matches
@ -805,7 +869,7 @@ export class BeltPath extends BasicSerializableObject {
this.totalLength, this.totalLength,
")" ")"
); );
this.items[this.items.length - 1][_nextDistance] = lastDistance; this.items[this.items.length - 1][0 /* nextDistance */] = lastDistance;
} else { } else {
DEBUG && logger.log(" Removed all items so we'll update spacing to total length"); DEBUG && logger.log(" Removed all items so we'll update spacing to total length");
@ -893,7 +957,7 @@ export class BeltPath extends BasicSerializableObject {
DEBUG && DEBUG &&
logger.log( logger.log(
" Items:", " Items:",
this.items.map(i => i[_nextDistance]) this.items.map(i => i[0 /* nextDistance */])
); );
// Find offset to first item // Find offset to first item
@ -912,7 +976,7 @@ export class BeltPath extends BasicSerializableObject {
// This item must be dropped // This item must be dropped
this.items.splice(i, 1); this.items.splice(i, 1);
i -= 1; i -= 1;
itemOffset += item[_nextDistance]; itemOffset += item[0 /* nextDistance */];
continue; continue;
} else { } else {
// This item can be kept, thus its the first we know // 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 // Now, update the distance of our last item
if (this.items.length !== 0) { if (this.items.length !== 0) {
const lastItem = this.items[this.items.length - 1]; const lastItem = this.items[this.items.length - 1];
lastItem[_nextDistance] += otherPath.spacingToFirstItem; lastItem[0 /* nextDistance */] += otherPath.spacingToFirstItem;
DEBUG && 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 { } else {
// Seems we have no items, update our first item distance // Seems we have no items, update our first item distance
this.spacingToFirstItem = oldLength + otherPath.spacingToFirstItem; this.spacingToFirstItem = oldLength + otherPath.spacingToFirstItem;
@ -1012,7 +1080,7 @@ export class BeltPath extends BasicSerializableObject {
// Aaand push the other paths items // Aaand push the other paths items
for (let i = 0; i < otherPath.items.length; ++i) { for (let i = 0; i < otherPath.items.length; ++i) {
const item = otherPath.items[i]; const item = otherPath.items[i];
this.items.push([item[_nextDistance], item[_item]]); this.items.push([item[0 /* nextDistance */], item[1 /* item */]]);
} }
// Update bounds // Update bounds
@ -1046,6 +1114,11 @@ export class BeltPath extends BasicSerializableObject {
this.debug_checkIntegrity("pre-update"); 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 // Divide by item spacing on belts since we use throughput and not speed
let beltSpeed = let beltSpeed =
this.root.hubGoals.getBeltBaseSpeed() * this.root.hubGoals.getBeltBaseSpeed() *
@ -1074,30 +1147,40 @@ export class BeltPath extends BasicSerializableObject {
lastItemProcessed === this.items.length - 1 ? 0 : globalConfig.itemSpacingOnBelts; lastItemProcessed === this.items.length - 1 ? 0 : globalConfig.itemSpacingOnBelts;
// Compute how much we can advance // Compute how much we can advance
const clampedProgress = Math.max( let clampedProgress = nextDistanceAndItem[0 /* nextDistance */] - minimumSpacing;
0,
Math.min(remainingVelocity, nextDistanceAndItem[_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 // Reduce our velocity by the amount we consumed
remainingVelocity -= clampedProgress; remainingVelocity -= clampedProgress;
// Reduce the spacing // Reduce the spacing
nextDistanceAndItem[_nextDistance] -= clampedProgress; nextDistanceAndItem[0 /* nextDistance */] -= clampedProgress;
// Advance all items behind by the progress we made // Advance all items behind by the progress we made
this.spacingToFirstItem += clampedProgress; this.spacingToFirstItem += clampedProgress;
// If the last item can be ejected, eject it and reduce the spacing, because otherwise // If the last item can be ejected, eject it and reduce the spacing, because otherwise
// we lose velocity // we lose velocity
if (isFirstItemProcessed && nextDistanceAndItem[_nextDistance] < 1e-7) { if (isFirstItemProcessed && nextDistanceAndItem[0 /* nextDistance */] < 1e-7) {
// Store how much velocity we "lost" because we bumped the item to the end of the // 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 // 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. // animation to start a tad later, so everything matches up. Yes I'm a perfectionist.
const excessVelocity = beltSpeed - clampedProgress; const excessVelocity = beltSpeed - clampedProgress;
// Try to directly get rid of the item // 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(); this.items.pop();
const itemBehind = this.items[lastItemProcessed - 1]; const itemBehind = this.items[lastItemProcessed - 1];
@ -1108,11 +1191,11 @@ export class BeltPath extends BasicSerializableObject {
// Also see #999 // Also see #999
const fixupProgress = Math.max( const fixupProgress = Math.max(
0, 0,
Math.min(remainingVelocity, itemBehind[_nextDistance]) Math.min(remainingVelocity, itemBehind[0 /* nextDistance */])
); );
// See above // See above
itemBehind[_nextDistance] -= fixupProgress; itemBehind[0 /* nextDistance */] -= fixupProgress;
remainingVelocity -= fixupProgress; remainingVelocity -= fixupProgress;
this.spacingToFirstItem += 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 // Check if we have an item which is ready to be emitted
const lastItem = this.items[this.items.length - 1]; const lastItem = this.items[this.items.length - 1];
if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) { if (lastItem && lastItem[0 /* nextDistance */] === 0) {
if (this.tryHandOverItem(lastItem[_item])) { if (this.boundAcceptor && this.boundAcceptor(lastItem[1 /* item */])) {
this.items.pop(); this.items.pop();
this.numCompressedItemsAfterFirstItem = Math.max( this.numCompressedItemsAfterFirstItem = Math.max(
0, 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 * Computes a world space position from the given progress
* @param {number} progress * @param {number} progress
@ -1270,11 +1309,11 @@ export class BeltPath extends BasicSerializableObject {
parameters.context.font = "6px GameFont"; parameters.context.font = "6px GameFont";
parameters.context.fillStyle = "#111"; parameters.context.fillStyle = "#111";
parameters.context.fillText( parameters.context.fillText(
"" + round4Digits(nextDistanceAndItem[_nextDistance]), "" + round4Digits(nextDistanceAndItem[0 /* nextDistance */]),
worldPos.x + 5, worldPos.x + 5,
worldPos.y + 2 worldPos.y + 2
); );
progress += nextDistanceAndItem[_nextDistance]; progress += nextDistanceAndItem[0 /* nextDistance */];
if (this.items.length - 1 - this.numCompressedItemsAfterFirstItem === i) { if (this.items.length - 1 - this.numCompressedItemsAfterFirstItem === i) {
parameters.context.fillStyle = "red"; parameters.context.fillStyle = "red";
@ -1370,7 +1409,7 @@ export class BeltPath extends BasicSerializableObject {
const centerPos = staticComp.localTileToWorld(centerPosLocal).toWorldSpaceCenterOfTile(); const centerPos = staticComp.localTileToWorld(centerPosLocal).toWorldSpaceCenterOfTile();
parameters.context.globalAlpha = 0.5; 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; parameters.context.globalAlpha = 1;
} }
@ -1402,7 +1441,7 @@ export class BeltPath extends BasicSerializableObject {
const distanceAndItem = this.items[currentItemIndex]; const distanceAndItem = this.items[currentItemIndex];
distanceAndItem[_item].drawItemCenteredClipped( distanceAndItem[1 /* item */].drawItemCenteredClipped(
worldPos.x, worldPos.x,
worldPos.y, worldPos.y,
parameters, parameters,
@ -1410,7 +1449,7 @@ export class BeltPath extends BasicSerializableObject {
); );
// Check for the next item // Check for the next item
currentItemPos += distanceAndItem[_nextDistance]; currentItemPos += distanceAndItem[0 /* nextDistance */];
++currentItemIndex; ++currentItemIndex;
if (currentItemIndex >= this.items.length) { 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 * 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 {number} slotIndex
* @param {BaseItem=} item * @param {BaseItem=} item
*/ */

View File

@ -157,9 +157,7 @@ export class GameHUD {
this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root); this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root);
} }
if (queryParamOptions.sandboxMode || G_IS_DEV) { this.parts.sandboxController = new HUDSandboxController(this.root);
this.parts.sandboxController = new HUDSandboxController(this.root);
}
if (!G_IS_RELEASE && !G_IS_DEV) { if (!G_IS_RELEASE && !G_IS_DEV) {
this.parts.betaOverlay = new HUDBetaOverlay(this.root); 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 { makeDiv } from "../../../core/utils";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach"; 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); 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() { toggle() {
if (!this.visible && !this.isAvailable()) {
return;
}
this.visible = !this.visible; this.visible = !this.visible;
} }

View File

@ -118,7 +118,14 @@ function generateUpgrades(limitedVersion = false) {
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }], 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 }], required: [{ shape: preparementShape, amount: 25000 }],
@ -172,7 +179,12 @@ function generateUpgrades(limitedVersion = false) {
required: [{ shape: "WrWrWrWr", amount: 3800 }], 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 }], required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }],
@ -346,7 +358,7 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 13 // 13
// Tunnel Tier 2 // Tunnel Tier 2
{ {
shape: "RpRpRpRp:CwCwCwCw", // painting t3 shape: G_CHINA_VERSION ? "CuCuCuCu:CwCwCwCw:Sb--Sr--" : "RpRpRpRp:CwCwCwCw", // painting t3
required: 3800, required: 3800,
reward: enumHubGoalRewards.reward_underground_belt_tier_2, reward: enumHubGoalRewards.reward_underground_belt_tier_2,
}, },
@ -355,7 +367,7 @@ export function generateLevelDefinitions(limitedVersion = false) {
...(limitedVersion ...(limitedVersion
? [ ? [
{ {
shape: "RpRpRpRp:CwCwCwCw", shape: G_CHINA_VERSION ? "CuCuCuCu:CwCwCwCw:Sb--Sr--" : "RpRpRpRp:CwCwCwCw",
required: 0, required: 0,
reward: enumHubGoalRewards.reward_demo_end, reward: enumHubGoalRewards.reward_demo_end,
}, },
@ -389,7 +401,9 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 17 // 17
// Double painter // 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, required: 20000,
reward: enumHubGoalRewards.reward_painter_double, reward: enumHubGoalRewards.reward_painter_double,
}, },
@ -429,7 +443,9 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 22 // 22
// Constant signal // 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, required: 25000,
reward: enumHubGoalRewards.reward_constant_signal, reward: enumHubGoalRewards.reward_constant_signal,
}, },
@ -437,14 +453,18 @@ export function generateLevelDefinitions(limitedVersion = false) {
// 23 // 23
// Display // Display
{ {
shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy", shape: G_CHINA_VERSION
? "CrCrCrCr:CwCwCwCw:WwWwWwWw:CrCrCrCr"
: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
required: 25000, required: 25000,
reward: enumHubGoalRewards.reward_display, reward: enumHubGoalRewards.reward_display,
}, },
// 24 Logic gates // 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, required: 25000,
reward: enumHubGoalRewards.reward_logic_gates, reward: enumHubGoalRewards.reward_logic_gates,
}, },

View File

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

View File

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