mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Properly process belt dependencies and fix items not travelling linear on belts
This commit is contained in:
@@ -12,9 +12,14 @@ import { gMetaBuildingRegistry } from "../../core/global_registries";
|
||||
import { MetaBeltBaseBuilding } from "../buildings/belt_base";
|
||||
import { defaultBuildingVariant } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { createLogger } from "../../core/logging";
|
||||
|
||||
const BELT_ANIM_COUNT = 6;
|
||||
|
||||
const logger = createLogger("belt");
|
||||
|
||||
/** @typedef {Array<{ entity: Entity, followUp: Entity }>} BeltCache */
|
||||
|
||||
export class BeltSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [BeltComponent]);
|
||||
@@ -26,7 +31,7 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
[enumDirection.left]: Loader.getSprite("sprites/belt/left_0.png"),
|
||||
[enumDirection.right]: Loader.getSprite("sprites/belt/right_0.png"),
|
||||
};
|
||||
/**
|
||||
/**b
|
||||
* @type {Object.<enumDirection, Array<AtlasSprite>>}
|
||||
*/
|
||||
this.beltAnimations = {
|
||||
@@ -58,6 +63,11 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
|
||||
this.root.signals.entityAdded.add(this.updateSurroundingBeltPlacement, this);
|
||||
this.root.signals.entityDestroyed.add(this.updateSurroundingBeltPlacement, this);
|
||||
|
||||
this.cacheNeedsUpdate = true;
|
||||
|
||||
/** @type {BeltCache} */
|
||||
this.beltCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,6 +84,10 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.components.Belt) {
|
||||
this.cacheNeedsUpdate = true;
|
||||
}
|
||||
|
||||
const metaBelt = gMetaBuildingRegistry.findByClass(MetaBeltBaseBuilding);
|
||||
|
||||
// Compute affected area
|
||||
@@ -98,6 +112,7 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
);
|
||||
targetStaticComp.rotation = rotation;
|
||||
metaBelt.updateVariants(targetEntity, rotationVariant, defaultBuildingVariant);
|
||||
this.cacheNeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,97 +125,155 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a given entity
|
||||
* Finds the follow up entity for a given belt. Used for building the dependencies
|
||||
* @param {Entity} entity
|
||||
* @param {Set} processedEntities
|
||||
*/
|
||||
updateBelt(entity, processedEntities) {
|
||||
if (processedEntities.has(entity.uid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
processedEntities.add(entity.uid);
|
||||
|
||||
// Divide by item spacing on belts since we use throughput and not speed
|
||||
const beltSpeed =
|
||||
this.root.hubGoals.getBeltBaseSpeed() *
|
||||
this.root.dynamicTickrate.deltaSeconds *
|
||||
globalConfig.itemSpacingOnBelts;
|
||||
const beltComp = entity.components.Belt;
|
||||
findFollowUpEntity(entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const items = beltComp.sortedItems;
|
||||
const beltComp = entity.components.Belt;
|
||||
|
||||
if (items.length === 0) {
|
||||
// Fast out for performance
|
||||
const followUpDirection = staticComp.localDirectionToWorld(beltComp.direction);
|
||||
const followUpVector = enumDirectionToVector[followUpDirection];
|
||||
|
||||
const followUpTile = staticComp.origin.add(followUpVector);
|
||||
const followUpEntity = this.root.map.getTileContent(followUpTile);
|
||||
|
||||
if (followUpEntity) {
|
||||
const followUpBeltComp = followUpEntity.components.Belt;
|
||||
if (followUpBeltComp) {
|
||||
return followUpEntity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single entity to the cache
|
||||
* @param {Entity} entity
|
||||
* @param {BeltCache} cache
|
||||
* @param {Set} visited
|
||||
*/
|
||||
computeSingleBeltCache(entity, cache, visited) {
|
||||
// Check for double visit
|
||||
if (visited.has(entity.uid)) {
|
||||
return;
|
||||
}
|
||||
visited.add(entity.uid);
|
||||
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
let maxProgress = 1;
|
||||
|
||||
// When ejecting, we can not go further than the item spacing since it
|
||||
// will be on the corner
|
||||
if (ejectorComp.isAnySlotEjecting()) {
|
||||
maxProgress = 1 - globalConfig.itemSpacingOnBelts;
|
||||
} else {
|
||||
// Find follow up belt to make sure we don't clash items
|
||||
const followUpDirection = staticComp.localDirectionToWorld(beltComp.direction);
|
||||
const followUpVector = enumDirectionToVector[followUpDirection];
|
||||
|
||||
const followUpTile = staticComp.origin.add(followUpVector);
|
||||
const followUpEntity = this.root.map.getTileContent(followUpTile);
|
||||
|
||||
if (followUpEntity) {
|
||||
const followUpBeltComp = followUpEntity.components.Belt;
|
||||
if (followUpBeltComp) {
|
||||
// Update follow up belt first
|
||||
this.updateBelt(followUpEntity, processedEntities);
|
||||
|
||||
const spacingOnBelt = followUpBeltComp.getDistanceToFirstItemCenter();
|
||||
maxProgress = Math_min(1, 1 - globalConfig.itemSpacingOnBelts + spacingOnBelt);
|
||||
}
|
||||
}
|
||||
const followUp = this.findFollowUpEntity(entity);
|
||||
if (followUp) {
|
||||
// Process followup first
|
||||
this.computeSingleBeltCache(followUp, cache, visited);
|
||||
}
|
||||
|
||||
let speedMultiplier = 1;
|
||||
if (beltComp.direction !== enumDirection.top) {
|
||||
// Shaped belts are longer, thus being quicker
|
||||
speedMultiplier = 1.41;
|
||||
cache.push({ entity, followUp });
|
||||
}
|
||||
|
||||
computeBeltCache() {
|
||||
logger.log("Updating belt cache");
|
||||
|
||||
let cache = [];
|
||||
let visited = new Set();
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
this.computeSingleBeltCache(this.allEntities[i], cache, visited);
|
||||
}
|
||||
assert(
|
||||
cache.length === this.allEntities.length,
|
||||
"Belt cache mismatch: Has " + cache.length + " entries but should have " + this.allEntities.length
|
||||
);
|
||||
|
||||
for (let itemIndex = items.length - 1; itemIndex >= 0; --itemIndex) {
|
||||
const itemAndProgress = items[itemIndex];
|
||||
|
||||
const newProgress = itemAndProgress[0] + speedMultiplier * beltSpeed;
|
||||
if (newProgress >= 1.0) {
|
||||
// Try to give this item to a new belt
|
||||
const freeSlot = ejectorComp.getFirstFreeSlot();
|
||||
|
||||
if (freeSlot === null) {
|
||||
// So, we don't have a free slot - damned!
|
||||
itemAndProgress[0] = 1.0;
|
||||
maxProgress = 1 - globalConfig.itemSpacingOnBelts;
|
||||
} else {
|
||||
// We got a free slot, remove this item and keep it on the ejector slot
|
||||
if (!ejectorComp.tryEject(freeSlot, itemAndProgress[1])) {
|
||||
assert(false, "Ejection failed");
|
||||
}
|
||||
items.splice(itemIndex, 1);
|
||||
maxProgress = 1;
|
||||
}
|
||||
} else {
|
||||
itemAndProgress[0] = Math_min(newProgress, maxProgress);
|
||||
maxProgress = itemAndProgress[0] - globalConfig.itemSpacingOnBelts;
|
||||
}
|
||||
}
|
||||
this.beltCache = cache;
|
||||
}
|
||||
|
||||
update() {
|
||||
const processedEntities = new Set();
|
||||
if (this.cacheNeedsUpdate) {
|
||||
this.cacheNeedsUpdate = false;
|
||||
this.computeBeltCache();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
this.updateBelt(entity, processedEntities);
|
||||
for (let i = 0; i < this.beltCache.length; ++i) {
|
||||
const { entity, followUp } = this.beltCache[i];
|
||||
|
||||
// Divide by item spacing on belts since we use throughput and not speed
|
||||
const beltSpeed =
|
||||
this.root.hubGoals.getBeltBaseSpeed() *
|
||||
this.root.dynamicTickrate.deltaSeconds *
|
||||
globalConfig.itemSpacingOnBelts;
|
||||
const beltComp = entity.components.Belt;
|
||||
const items = beltComp.sortedItems;
|
||||
|
||||
if (items.length === 0) {
|
||||
// Fast out for performance
|
||||
continue;
|
||||
}
|
||||
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
let maxProgress = 1;
|
||||
|
||||
// When ejecting, we can not go further than the item spacing since it
|
||||
// will be on the corner
|
||||
if (ejectorComp.isAnySlotEjecting()) {
|
||||
maxProgress = 1 - globalConfig.itemSpacingOnBelts;
|
||||
} else {
|
||||
// Otherwise our progress depends on the follow up
|
||||
if (followUp) {
|
||||
const spacingOnBelt = followUp.components.Belt.getDistanceToFirstItemCenter();
|
||||
maxProgress = Math_min(2, 1 - globalConfig.itemSpacingOnBelts + spacingOnBelt);
|
||||
}
|
||||
}
|
||||
|
||||
let speedMultiplier = 1;
|
||||
if (beltComp.direction !== enumDirection.top) {
|
||||
// Shaped belts are longer, thus being quicker
|
||||
speedMultiplier = 1.41;
|
||||
}
|
||||
|
||||
// Not really nice. haven't found the reason for this yet.
|
||||
if (items.length > 2 / globalConfig.itemSpacingOnBelts) {
|
||||
logger.error("Fixing broken belt:", entity, items);
|
||||
beltComp.sortedItems = [];
|
||||
}
|
||||
|
||||
for (let itemIndex = items.length - 1; itemIndex >= 0; --itemIndex) {
|
||||
const progressAndItem = items[itemIndex];
|
||||
|
||||
progressAndItem[0] = Math_min(maxProgress, progressAndItem[0] + speedMultiplier * beltSpeed);
|
||||
|
||||
if (progressAndItem[0] >= 1.0) {
|
||||
if (followUp) {
|
||||
const followUpBelt = followUp.components.Belt;
|
||||
if (followUpBelt.canAcceptNewItem()) {
|
||||
followUpBelt.takeNewItem(progressAndItem[1], progressAndItem[0] - 1.0);
|
||||
items.splice(itemIndex, 1);
|
||||
} else {
|
||||
// Well, we couldn't really take it to a follow up belt, keep it at
|
||||
// max progress
|
||||
progressAndItem[0] = 1.0;
|
||||
maxProgress = 1 - globalConfig.itemSpacingOnBelts;
|
||||
}
|
||||
} else {
|
||||
// Try to give this item to a new belt
|
||||
const freeSlot = ejectorComp.getFirstFreeSlot();
|
||||
if (freeSlot === null) {
|
||||
// So, we don't have a free slot - damned!
|
||||
progressAndItem[0] = 1.0;
|
||||
maxProgress = 1 - globalConfig.itemSpacingOnBelts;
|
||||
} else {
|
||||
// We got a free slot, remove this item and keep it on the ejector slot
|
||||
if (!ejectorComp.tryEject(freeSlot, progressAndItem[1])) {
|
||||
assert(false, "Ejection failed");
|
||||
}
|
||||
items.splice(itemIndex, 1);
|
||||
// Do not override max progress at all
|
||||
// maxProgress = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We just moved this item forward, so determine the maximum progress of other items
|
||||
maxProgress = progressAndItem[0] - globalConfig.itemSpacingOnBelts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +285,6 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
drawChunk(parameters, chunk) {
|
||||
if (parameters.zoomLevel < globalConfig.mapChunkOverviewMinZoom) {
|
||||
return;
|
||||
1;
|
||||
}
|
||||
|
||||
const speedMultiplier = this.root.hubGoals.getBeltBaseSpeed();
|
||||
|
||||
Reference in New Issue
Block a user