mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Refactor item acceptor and processor to be cleaner and work more smoothly
This commit is contained in:
parent
f13a48e812
commit
242b99b19b
@ -87,17 +87,14 @@ export const globalConfig = {
|
|||||||
puzzleMaxBoundsSize: 20,
|
puzzleMaxBoundsSize: 20,
|
||||||
puzzleValidationDurationSeconds: 30,
|
puzzleValidationDurationSeconds: 30,
|
||||||
|
|
||||||
buildingSpeeds: {
|
buildingRatios: {
|
||||||
cutter: 1 / 4,
|
cutter: 4,
|
||||||
cutterQuad: 1 / 4,
|
cutterQuad: 4,
|
||||||
rotater: 1 / 1,
|
painter: 6,
|
||||||
rotaterCCW: 1 / 1,
|
painterDouble: 8,
|
||||||
rotater180: 1 / 1,
|
painterQuad: 2,
|
||||||
painter: 1 / 6,
|
mixer: 5,
|
||||||
painterDouble: 1 / 8,
|
stacker: 8,
|
||||||
painterQuad: 1 / 2,
|
|
||||||
mixer: 1 / 5,
|
|
||||||
stacker: 1 / 8,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Zooming
|
// Zooming
|
||||||
|
@ -130,10 +130,12 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
/**
|
/**
|
||||||
* Tries to accept the item
|
* Tries to accept the item
|
||||||
* @param {BaseItem} item
|
* @param {BaseItem} item
|
||||||
|
* @param {number} extraProgress
|
||||||
*/
|
*/
|
||||||
tryAcceptItem(item) {
|
tryAcceptItem(item, extraProgress = 0) {
|
||||||
if (this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts) {
|
if (this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts) {
|
||||||
// So, since we already need one tick to accept this item we will add this directly.
|
// So, since we already need one tick to accept this item we will add this directly.
|
||||||
|
// this means we are moving it forwards twice in one tick, but otherwise belts won't be full :(
|
||||||
const beltProgressPerTick =
|
const beltProgressPerTick =
|
||||||
this.root.hubGoals.getBeltBaseSpeed() *
|
this.root.hubGoals.getBeltBaseSpeed() *
|
||||||
this.root.dynamicTickrate.deltaSeconds *
|
this.root.dynamicTickrate.deltaSeconds *
|
||||||
@ -141,7 +143,7 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
|
|
||||||
// First, compute how much progress we can make *at max*
|
// First, compute how much progress we can make *at max*
|
||||||
const maxProgress = Math.max(0, this.spacingToFirstItem - globalConfig.itemSpacingOnBelts);
|
const maxProgress = Math.max(0, this.spacingToFirstItem - globalConfig.itemSpacingOnBelts);
|
||||||
const initialProgress = Math.min(maxProgress, beltProgressPerTick);
|
const initialProgress = Math.min(maxProgress, beltProgressPerTick + extraProgress);
|
||||||
|
|
||||||
this.items.unshift([this.spacingToFirstItem - initialProgress, item]);
|
this.items.unshift([this.spacingToFirstItem - initialProgress, item]);
|
||||||
this.spacingToFirstItem = initialProgress;
|
this.spacingToFirstItem = initialProgress;
|
||||||
@ -227,8 +229,6 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noSimplifiedBelts = !this.root.app.settings.getAllSettings().simplifiedBelts;
|
|
||||||
|
|
||||||
DEBUG && !debug_Silent && logger.log(" Found target entity", targetEntity.uid);
|
DEBUG && !debug_Silent && logger.log(" Found target entity", targetEntity.uid);
|
||||||
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
||||||
const targetBeltComp = targetEntity.components.Belt;
|
const targetBeltComp = targetEntity.components.Belt;
|
||||||
@ -274,95 +274,24 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const matchingSlotIndex = matchingSlot.index;
|
const matchingSlotIndex = matchingSlot.index;
|
||||||
const passOver = this.computePassOverFunctionWithoutBelts(targetEntity, matchingSlotIndex);
|
|
||||||
if (!passOver) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchingDirection = enumInvertedDirections[ejectingDirection];
|
return function (item, startProgress = 0.0) {
|
||||||
const filter = matchingSlot.slot.filter;
|
const storageComp = targetEntity.components.Storage;
|
||||||
|
if (
|
||||||
return function (item, remainingProgress = 0.0) {
|
storageComp &&
|
||||||
// Check if the acceptor has a filter
|
storageComp.tryAcceptItem(item) &&
|
||||||
if (filter && item._type !== filter) {
|
targetAcceptorComp.tryAcceptItem(matchingSlotIndex, item, startProgress)
|
||||||
return false;
|
) {
|
||||||
}
|
// unique duplicated code for storage
|
||||||
|
return true;
|
||||||
// Try to pass over
|
|
||||||
if (passOver(item, matchingSlotIndex)) {
|
|
||||||
// Trigger animation on the acceptor comp
|
|
||||||
if (noSimplifiedBelts) {
|
|
||||||
targetAcceptorComp.onItemAccepted(
|
|
||||||
matchingSlotIndex,
|
|
||||||
matchingDirection,
|
|
||||||
item,
|
|
||||||
remainingProgress
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
if (targetAcceptorComp.tryAcceptItem(matchingSlotIndex, item, startProgress)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following code will be compiled out outside of dev versions
|
// Following code will be compiled out outside of dev versions
|
||||||
/* dev:start */
|
/* dev:start */
|
||||||
|
|
||||||
|
@ -104,8 +104,7 @@ export class MetaBalancerBuilding extends MetaBuilding {
|
|||||||
speedMultiplier = 1;
|
speedMultiplier = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const speed =
|
const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.balancer) * speedMultiplier;
|
||||||
(root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.balancer) / 2) * speedMultiplier;
|
|
||||||
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
|
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,25 +47,6 @@ export class MetaTrashBuilding extends MetaBuilding {
|
|||||||
return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
|
return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
|
||||||
}
|
}
|
||||||
|
|
||||||
addAchievementReceiver(entity) {
|
|
||||||
if (!entity.root) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemProcessor = entity.components.ItemProcessor;
|
|
||||||
const tryTakeItem = itemProcessor.tryTakeItem.bind(itemProcessor);
|
|
||||||
|
|
||||||
itemProcessor.tryTakeItem = () => {
|
|
||||||
const taken = tryTakeItem(...arguments);
|
|
||||||
|
|
||||||
if (taken) {
|
|
||||||
entity.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.trash1000, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return taken;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the entity at the given location
|
* Creates the entity at the given location
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
@ -100,7 +81,5 @@ export class MetaTrashBuilding extends MetaBuilding {
|
|||||||
processorType: enumItemProcessorTypes.trash,
|
processorType: enumItemProcessorTypes.trash,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addAchievementReceiver(entity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,6 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
|||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
*/
|
*/
|
||||||
setupEntityComponents(entity) {
|
setupEntityComponents(entity) {
|
||||||
// Required, since the item processor needs this.
|
|
||||||
entity.addComponent(
|
entity.addComponent(
|
||||||
new ItemEjectorComponent({
|
new ItemEjectorComponent({
|
||||||
slots: [],
|
slots: [],
|
||||||
|
@ -6,7 +6,7 @@ import { typeItemSingleton } from "../item_resolver";
|
|||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* item: BaseItem,
|
* item: BaseItem,
|
||||||
* progress: number
|
* extraProgress: number
|
||||||
* }} PendingFilterItem
|
* }} PendingFilterItem
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -24,14 +24,14 @@ export class FilterComponent extends Component {
|
|||||||
pendingItemsToLeaveThrough: types.array(
|
pendingItemsToLeaveThrough: types.array(
|
||||||
types.structured({
|
types.structured({
|
||||||
item: typeItemSingleton,
|
item: typeItemSingleton,
|
||||||
progress: types.ufloat,
|
extraProgress: types.ufloat,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
pendingItemsToReject: types.array(
|
pendingItemsToReject: types.array(
|
||||||
types.structured({
|
types.structured({
|
||||||
item: typeItemSingleton,
|
item: typeItemSingleton,
|
||||||
progress: types.ufloat,
|
extraProgress: types.ufloat, //@SENSETODO will need save migration
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,9 @@ import { enumDirection, enumInvertedDirections, Vector } from "../../core/vector
|
|||||||
import { types } from "../../savegame/serialization";
|
import { types } from "../../savegame/serialization";
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { Component } from "../component";
|
import { Component } from "../component";
|
||||||
|
import { Entity } from "../entity";
|
||||||
|
import { typeItemSingleton } from "../item_resolver";
|
||||||
|
import { GameRoot } from "../root";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -24,34 +27,69 @@ import { Component } from "../component";
|
|||||||
* filter?: ItemType
|
* filter?: ItemType
|
||||||
* }} ItemAcceptorSlotConfig */
|
* }} ItemAcceptorSlotConfig */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Array<{
|
||||||
|
* slotIndex: number,
|
||||||
|
* item: BaseItem,
|
||||||
|
* animProgress: number,
|
||||||
|
* }>} ItemAcceptorInputs
|
||||||
|
*
|
||||||
|
* @typedef {Array<{
|
||||||
|
* slotIndex: number,
|
||||||
|
* item: BaseItem,
|
||||||
|
* extraProgress: number
|
||||||
|
* }>} ItemAcceptorCompletedInputs
|
||||||
|
*
|
||||||
|
* @typedef {{
|
||||||
|
* root: GameRoot,
|
||||||
|
* entity: Entity,
|
||||||
|
* item: BaseItem,
|
||||||
|
* slotIndex: number,
|
||||||
|
* extraProgress: number
|
||||||
|
* }} InputCompletedArgs
|
||||||
|
*/
|
||||||
|
|
||||||
export class ItemAcceptorComponent extends Component {
|
export class ItemAcceptorComponent extends Component {
|
||||||
static getId() {
|
static getId() {
|
||||||
return "ItemAcceptor";
|
return "ItemAcceptor";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getSchema() {
|
||||||
|
return {
|
||||||
|
inputs: types.array(
|
||||||
|
types.structured({
|
||||||
|
slotIndex: types.uint,
|
||||||
|
item: typeItemSingleton,
|
||||||
|
animProgress: types.ufloat,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
completedInputs: types.array(
|
||||||
|
types.structured({
|
||||||
|
slotIndex: types.uint,
|
||||||
|
item: typeItemSingleton,
|
||||||
|
extraProgress: types.ufloat,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} param0
|
* @param {object} param0
|
||||||
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items
|
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items
|
||||||
|
* @param {number=} param0.maxSlotInputs The maximum amount of items one slot can accept before it is full
|
||||||
*/
|
*/
|
||||||
constructor({ slots = [] }) {
|
constructor({ slots = [], maxSlotInputs = 2 }) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
/** @type {ItemAcceptorInputs} */
|
||||||
|
this.inputs = [];
|
||||||
|
/** @type {ItemAcceptorCompletedInputs} */
|
||||||
|
this.completedInputs = [];
|
||||||
this.setSlots(slots);
|
this.setSlots(slots);
|
||||||
this.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
// setting this to 1 will cause throughput issues at very high speeds
|
||||||
/**
|
this.maxSlotInputs = maxSlotInputs;
|
||||||
* Fixes belt animations
|
|
||||||
* @type {Array<{
|
|
||||||
* item: BaseItem,
|
|
||||||
* slotIndex: number,
|
|
||||||
* animProgress: number,
|
|
||||||
* direction: enumDirection
|
|
||||||
* }>}
|
|
||||||
*/
|
|
||||||
this.itemConsumptionAnimations = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,31 +112,42 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if this acceptor can accept a new item at slot N
|
* Called when trying to input a new item
|
||||||
*
|
|
||||||
* 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
|
||||||
|
* @param {number} startProgress World space remaining progress, can be set to set the start position of the item
|
||||||
|
* @returns {boolean} if the input was succesful
|
||||||
*/
|
*/
|
||||||
canAcceptItem(slotIndex, item) {
|
tryAcceptItem(slotIndex, item, startProgress = 0.0) {
|
||||||
const slot = this.slots[slotIndex];
|
const slot = this.slots[slotIndex];
|
||||||
return !slot.filter || slot.filter === item.getItemType();
|
|
||||||
|
let existingInputs = 0;
|
||||||
|
for (let i = 0; i < this.inputs.length; i++) {
|
||||||
|
if (this.inputs[i].slotIndex == slotIndex) {
|
||||||
|
existingInputs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.completedInputs.length; i++) {
|
||||||
|
if (this.completedInputs[i].slotIndex == slotIndex) {
|
||||||
|
existingInputs++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (existingInputs >= this.maxSlotInputs) {
|
||||||
* Called when an item has been accepted so that
|
return false;
|
||||||
* @param {number} slotIndex
|
}
|
||||||
* @param {enumDirection} direction
|
|
||||||
* @param {BaseItem} item
|
if (slot.filter && slot.filter != item.getItemType()) {
|
||||||
* @param {number} remainingProgress World space remaining progress, can be set to set the start position of the item
|
return false;
|
||||||
*/
|
}
|
||||||
onItemAccepted(slotIndex, direction, item, remainingProgress = 0.0) {
|
|
||||||
this.itemConsumptionAnimations.push({
|
// if the start progress is bigger than 0.5, the remainder should get passed on to the ejector
|
||||||
item,
|
this.inputs.push({
|
||||||
slotIndex,
|
slotIndex,
|
||||||
direction,
|
item,
|
||||||
animProgress: Math.min(1, remainingProgress * 2),
|
animProgress: startProgress,
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,28 +127,16 @@ export class ItemEjectorComponent extends Component {
|
|||||||
* Tries to eject a given item
|
* Tries to eject a given item
|
||||||
* @param {number} slotIndex
|
* @param {number} slotIndex
|
||||||
* @param {BaseItem} item
|
* @param {BaseItem} item
|
||||||
|
* @param {number} startingProgress
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
tryEject(slotIndex, item) {
|
tryEject(slotIndex, item, startingProgress = 0.0) {
|
||||||
if (!this.canEjectOnSlot(slotIndex)) {
|
if (!this.canEjectOnSlot(slotIndex)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.slots[slotIndex].item = item;
|
this.slots[slotIndex].item = item;
|
||||||
this.slots[slotIndex].lastItem = item;
|
this.slots[slotIndex].lastItem = item;
|
||||||
this.slots[slotIndex].progress = 0;
|
this.slots[slotIndex].progress = startingProgress;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the given slot and returns the item it had
|
|
||||||
* @param {number} slotIndex
|
|
||||||
* @returns {BaseItem|null}
|
|
||||||
*/
|
|
||||||
takeSlotItem(slotIndex) {
|
|
||||||
const slot = this.slots[slotIndex];
|
|
||||||
const item = slot.item;
|
|
||||||
slot.item = null;
|
|
||||||
slot.progress = 0.0;
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ export const enumItemProcessorRequirements = {
|
|||||||
|
|
||||||
/** @typedef {{
|
/** @typedef {{
|
||||||
* item: BaseItem,
|
* item: BaseItem,
|
||||||
|
* extraProgress?: number
|
||||||
* requiredSlot?: number,
|
* requiredSlot?: number,
|
||||||
* preferredSlot?: number
|
* preferredSlot?: number
|
||||||
* }} EjectorItemToEject */
|
* }} EjectorItemToEject */
|
||||||
@ -38,6 +39,13 @@ export const enumItemProcessorRequirements = {
|
|||||||
* items: Array<EjectorItemToEject>,
|
* items: Array<EjectorItemToEject>,
|
||||||
* }} EjectorCharge */
|
* }} EjectorCharge */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* item: BaseItem
|
||||||
|
* extraProgress: number
|
||||||
|
* }} ItemProcessorInput
|
||||||
|
*/
|
||||||
|
|
||||||
export class ItemProcessorComponent extends Component {
|
export class ItemProcessorComponent extends Component {
|
||||||
static getId() {
|
static getId() {
|
||||||
return "ItemProcessor";
|
return "ItemProcessor";
|
||||||
@ -73,12 +81,6 @@ export class ItemProcessorComponent extends Component {
|
|||||||
// Type of processing requirement
|
// Type of processing requirement
|
||||||
this.processingRequirement = processingRequirement;
|
this.processingRequirement = processingRequirement;
|
||||||
|
|
||||||
/**
|
|
||||||
* Our current inputs
|
|
||||||
* @type {Map<number, BaseItem>}
|
|
||||||
*/
|
|
||||||
this.inputSlots = new Map();
|
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,21 +90,13 @@ export class ItemProcessorComponent extends Component {
|
|||||||
// sure the outputs always match
|
// sure the outputs always match
|
||||||
this.nextOutputSlot = 0;
|
this.nextOutputSlot = 0;
|
||||||
|
|
||||||
this.inputSlots.clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current input count
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
this.inputCount = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What we are currently processing, empty if we don't produce anything rn
|
* What we are currently processing, empty if we don't produce anything rn
|
||||||
* requiredSlot: Item *must* be ejected on this slot
|
* requiredSlot: Item *must* be ejected on this slot
|
||||||
* preferredSlot: Item *can* be ejected on this slot, but others are fine too if the one is not usable
|
* preferredSlot: Item *can* be ejected on this slot, but others are fine too if the one is not usable
|
||||||
* @type {Array<EjectorCharge>}
|
* @type {EjectorCharge|null}
|
||||||
*/
|
*/
|
||||||
this.ongoingCharges = [];
|
this.currentCharge = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much processing time we have left from the last tick
|
* How much processing time we have left from the last tick
|
||||||
@ -115,30 +109,4 @@ export class ItemProcessorComponent extends Component {
|
|||||||
*/
|
*/
|
||||||
this.queuedEjects = [];
|
this.queuedEjects = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to take the item
|
|
||||||
* @param {BaseItem} item
|
|
||||||
* @param {number} sourceSlot
|
|
||||||
*/
|
|
||||||
tryTakeItem(item, sourceSlot) {
|
|
||||||
if (
|
|
||||||
this.type === enumItemProcessorTypes.hub ||
|
|
||||||
this.type === enumItemProcessorTypes.trash ||
|
|
||||||
this.type === enumItemProcessorTypes.goal
|
|
||||||
) {
|
|
||||||
// Hub has special logic .. not really nice but efficient.
|
|
||||||
this.inputSlots.set(this.inputCount, item);
|
|
||||||
this.inputCount++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we only take one item per slot
|
|
||||||
if (this.inputSlots.has(sourceSlot)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.inputSlots.set(sourceSlot, item);
|
|
||||||
this.inputCount++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -46,32 +46,19 @@ export class StorageComponent extends Component {
|
|||||||
* Returns whether this storage can accept the item
|
* Returns whether this storage can accept the item
|
||||||
* @param {BaseItem} item
|
* @param {BaseItem} item
|
||||||
*/
|
*/
|
||||||
canAcceptItem(item) {
|
tryAcceptItem(item) {
|
||||||
if (this.storedCount >= this.maximumStorage) {
|
if (this.storedCount >= this.maximumStorage) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this.storedItem || this.storedCount === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemType = item.getItemType();
|
const itemType = item.getItemType();
|
||||||
|
if (this.storedCount > 0 && this.storedItem && itemType !== this.storedItem.getItemType()) {
|
||||||
// Check type matches
|
|
||||||
if (itemType !== this.storedItem.getItemType()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemType === "color") {
|
this.storedItem = item;
|
||||||
return /** @type {ColorItem} */ (this.storedItem).color === /** @type {ColorItem} */ (item).color;
|
this.storedCount++;
|
||||||
}
|
|
||||||
|
|
||||||
if (itemType === "shape") {
|
return true;
|
||||||
return (
|
|
||||||
/** @type {ShapeItem} */ (this.storedItem).definition.getHash() ===
|
|
||||||
/** @type {ShapeItem} */ (item).definition.getHash()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,61 +56,31 @@ export class UndergroundBeltComponent extends Component {
|
|||||||
this.consumptionAnimations = [];
|
this.consumptionAnimations = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used on both receiver and sender.
|
* Used only on reciever to store which items are currently "travelling"
|
||||||
* Reciever: Used to store the next item to transfer, and to block input while doing this
|
* @type {Array<[BaseItem, number]>} Format is [Item, Tile progress]
|
||||||
* Sender: Used to store which items are currently "travelling"
|
|
||||||
* @type {Array<[BaseItem, number]>} Format is [Item, ingame time to eject the item]
|
|
||||||
*/
|
*/
|
||||||
this.pendingItems = [];
|
this.pendingItems = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to accept an item from an external source like a regular belt or building
|
|
||||||
* @param {BaseItem} item
|
|
||||||
* @param {number} beltSpeed How fast this item travels
|
|
||||||
*/
|
|
||||||
tryAcceptExternalItem(item, beltSpeed) {
|
|
||||||
if (this.mode !== enumUndergroundBeltMode.sender) {
|
|
||||||
// Only senders accept external items
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pendingItems.length > 0) {
|
|
||||||
// We currently have a pending item
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pendingItems.push([item, 0]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to accept a tunneled item
|
* Tries to accept a tunneled item
|
||||||
* @param {BaseItem} item
|
* @param {BaseItem} item
|
||||||
* @param {number} travelDistance How many tiles this item has to travel
|
* @param {number} travelDistance
|
||||||
* @param {number} beltSpeed How fast this item travels
|
* @param {number} startProgress The starting tile progress
|
||||||
* @param {number} now Current ingame time
|
|
||||||
*/
|
*/
|
||||||
tryAcceptTunneledItem(item, travelDistance, beltSpeed, now) {
|
tryAcceptTunneledItem(item, travelDistance, startProgress = 0) {
|
||||||
if (this.mode !== enumUndergroundBeltMode.receiver) {
|
if (this.mode !== enumUndergroundBeltMode.receiver) {
|
||||||
// Only receivers can accept tunneled items
|
// Only receivers can accept tunneled items
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notice: We assume that for all items the travel distance is the same
|
// Notice: We assume that for all items the travel distance is the same
|
||||||
const maxItemsInTunnel = (2 + travelDistance) / globalConfig.itemSpacingOnBelts;
|
const maxItemsInTunnel = travelDistance / globalConfig.itemSpacingOnBelts;
|
||||||
if (this.pendingItems.length >= maxItemsInTunnel) {
|
if (this.pendingItems.length >= maxItemsInTunnel) {
|
||||||
// Simulate a real belt which gets full at some point
|
// Simulate a real belt which gets full at some point
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTICE:
|
this.pendingItems.push([item, startProgress]);
|
||||||
// This corresponds to the item ejector - it needs 0.5 additional tiles to eject the item.
|
|
||||||
// So instead of adding 1 we add 0.5 only.
|
|
||||||
// Additionally it takes 1 tile for the acceptor which we just add on top.
|
|
||||||
const travelDuration = (travelDistance + 1.5) / beltSpeed / globalConfig.itemSpacingOnBelts;
|
|
||||||
|
|
||||||
this.pendingItems.push([item, now + travelDuration]);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,20 +150,20 @@ export class GameSystemManager {
|
|||||||
|
|
||||||
add("belt", BeltSystem);
|
add("belt", BeltSystem);
|
||||||
|
|
||||||
add("undergroundBelt", UndergroundBeltSystem);
|
|
||||||
|
|
||||||
add("miner", MinerSystem);
|
add("miner", MinerSystem);
|
||||||
|
|
||||||
add("storage", StorageSystem);
|
add("storage", StorageSystem);
|
||||||
|
|
||||||
|
add("itemEjector", ItemEjectorSystem);
|
||||||
|
|
||||||
|
add("undergroundBelt", UndergroundBeltSystem);
|
||||||
|
|
||||||
add("itemProcessor", ItemProcessorSystem);
|
add("itemProcessor", ItemProcessorSystem);
|
||||||
|
|
||||||
add("filter", FilterSystem);
|
add("filter", FilterSystem);
|
||||||
|
|
||||||
add("itemProducer", ItemProducerSystem);
|
add("itemProducer", ItemProducerSystem);
|
||||||
|
|
||||||
add("itemEjector", ItemEjectorSystem);
|
|
||||||
|
|
||||||
if (this.root.gameMode.hasResources()) {
|
if (this.root.gameMode.hasResources()) {
|
||||||
add("mapResources", MapResourcesSystem);
|
add("mapResources", MapResourcesSystem);
|
||||||
}
|
}
|
||||||
|
@ -507,55 +507,36 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processor speed
|
* Processor time to process
|
||||||
* @param {enumItemProcessorTypes} processorType
|
* @param {enumItemProcessorTypes} processorType
|
||||||
* @returns {number} items / sec
|
* @returns {number} process time in seconds
|
||||||
*/
|
*/
|
||||||
getProcessorBaseSpeed(processorType) {
|
getProcessingTime(processorType) {
|
||||||
if (this.root.gameMode.throughputDoesNotMatter()) {
|
if (this.root.gameMode.throughputDoesNotMatter()) {
|
||||||
return globalConfig.beltSpeedItemsPerSecond * globalConfig.puzzleModeSpeed * 10;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (processorType) {
|
switch (processorType) {
|
||||||
case enumItemProcessorTypes.trash:
|
case enumItemProcessorTypes.trash:
|
||||||
case enumItemProcessorTypes.hub:
|
case enumItemProcessorTypes.hub:
|
||||||
case enumItemProcessorTypes.goal:
|
case enumItemProcessorTypes.goal:
|
||||||
return 1e30;
|
|
||||||
case enumItemProcessorTypes.balancer:
|
case enumItemProcessorTypes.balancer:
|
||||||
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt * 2;
|
|
||||||
case enumItemProcessorTypes.reader:
|
case enumItemProcessorTypes.reader:
|
||||||
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt;
|
case enumItemProcessorTypes.rotater:
|
||||||
|
case enumItemProcessorTypes.rotaterCCW:
|
||||||
|
case enumItemProcessorTypes.rotater180:
|
||||||
|
return 0;
|
||||||
|
|
||||||
case enumItemProcessorTypes.mixer:
|
case enumItemProcessorTypes.mixer:
|
||||||
case enumItemProcessorTypes.painter:
|
case enumItemProcessorTypes.painter:
|
||||||
case enumItemProcessorTypes.painterDouble:
|
case enumItemProcessorTypes.painterDouble:
|
||||||
case enumItemProcessorTypes.painterQuad: {
|
case enumItemProcessorTypes.painterQuad: {
|
||||||
assert(
|
return this.getProcessorTimeWithUpgrades(this.upgradeImprovements.painting, processorType);
|
||||||
globalConfig.buildingSpeeds[processorType],
|
|
||||||
"Processor type has no speed set in globalConfig.buildingSpeeds: " + processorType
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
globalConfig.beltSpeedItemsPerSecond *
|
|
||||||
this.upgradeImprovements.painting *
|
|
||||||
globalConfig.buildingSpeeds[processorType]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case enumItemProcessorTypes.cutter:
|
case enumItemProcessorTypes.cutter:
|
||||||
case enumItemProcessorTypes.cutterQuad:
|
case enumItemProcessorTypes.cutterQuad:
|
||||||
case enumItemProcessorTypes.rotater:
|
|
||||||
case enumItemProcessorTypes.rotaterCCW:
|
|
||||||
case enumItemProcessorTypes.rotater180:
|
|
||||||
case enumItemProcessorTypes.stacker: {
|
case enumItemProcessorTypes.stacker: {
|
||||||
assert(
|
return this.getProcessorTimeWithUpgrades(this.upgradeImprovements.processors, processorType);
|
||||||
globalConfig.buildingSpeeds[processorType],
|
|
||||||
"Processor type has no speed set in globalConfig.buildingSpeeds: " + processorType
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
globalConfig.beltSpeedItemsPerSecond *
|
|
||||||
this.upgradeImprovements.processors *
|
|
||||||
globalConfig.buildingSpeeds[processorType]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (MOD_ITEM_PROCESSOR_SPEEDS[processorType]) {
|
if (MOD_ITEM_PROCESSOR_SPEEDS[processorType]) {
|
||||||
@ -564,6 +545,34 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
assertAlways(false, "invalid processor type: " + processorType);
|
assertAlways(false, "invalid processor type: " + processorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1 / globalConfig.beltSpeedItemsPerSecond;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} upgrade
|
||||||
|
* @param {enumItemProcessorTypes} processorType
|
||||||
|
*/
|
||||||
|
getProcessorTimeWithUpgrades(upgrade, processorType) {
|
||||||
|
assert(
|
||||||
|
globalConfig.buildingRatios[processorType],
|
||||||
|
"Processor type has no speed set in globalConfig.buildingSpeeds: " + processorType
|
||||||
|
);
|
||||||
|
|
||||||
|
const processorTime =
|
||||||
|
globalConfig.buildingRatios[processorType] / globalConfig.beltSpeedItemsPerSecond;
|
||||||
|
return processorTime / upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor speed
|
||||||
|
* @param {enumItemProcessorTypes} processorType
|
||||||
|
* @returns {number} items/sec
|
||||||
|
*/
|
||||||
|
getProcessorBaseSpeed(processorType) {
|
||||||
|
const time = this.getProcessingTime(processorType);
|
||||||
|
if (!time) {
|
||||||
|
return this.getBeltBaseSpeed();
|
||||||
|
}
|
||||||
|
return 1 / time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { FilterComponent } from "../components/filter";
|
import { FilterComponent } from "../components/filter";
|
||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
@ -13,29 +12,25 @@ export class FilterSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
const progress =
|
|
||||||
this.root.dynamicTickrate.deltaSeconds *
|
|
||||||
this.root.hubGoals.getBeltBaseSpeed() *
|
|
||||||
globalConfig.itemSpacingOnBelts;
|
|
||||||
|
|
||||||
const requiredProgress = 1 - progress;
|
|
||||||
|
|
||||||
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 filterComp = entity.components.Filter;
|
const filterComp = entity.components.Filter;
|
||||||
|
const acceptorComp = entity.components.ItemAcceptor;
|
||||||
const ejectorComp = entity.components.ItemEjector;
|
const ejectorComp = entity.components.ItemEjector;
|
||||||
|
|
||||||
// Process payloads
|
// Take items from acceptor
|
||||||
|
const input = acceptorComp.completedInputs[0];
|
||||||
|
if (input && this.tryAcceptItem(entity, input.item, input.extraProgress)) {
|
||||||
|
acceptorComp.completedInputs.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output to ejector
|
||||||
const slotsAndLists = [filterComp.pendingItemsToLeaveThrough, filterComp.pendingItemsToReject];
|
const slotsAndLists = [filterComp.pendingItemsToLeaveThrough, filterComp.pendingItemsToReject];
|
||||||
for (let slotIndex = 0; slotIndex < slotsAndLists.length; ++slotIndex) {
|
for (let slotIndex = 0; slotIndex < slotsAndLists.length; ++slotIndex) {
|
||||||
const pendingItems = slotsAndLists[slotIndex];
|
const pendingItems = slotsAndLists[slotIndex];
|
||||||
|
|
||||||
for (let j = 0; j < pendingItems.length; ++j) {
|
for (let j = 0; j < pendingItems.length; ++j) {
|
||||||
const nextItem = pendingItems[j];
|
const nextItem = pendingItems[j];
|
||||||
// Advance next item
|
|
||||||
nextItem.progress = Math.min(requiredProgress, nextItem.progress + progress);
|
|
||||||
// Check if it's ready to eject
|
|
||||||
if (nextItem.progress >= requiredProgress - 1e-5) {
|
|
||||||
if (ejectorComp.tryEject(slotIndex, nextItem.item)) {
|
if (ejectorComp.tryEject(slotIndex, nextItem.item)) {
|
||||||
pendingItems.shift();
|
pendingItems.shift();
|
||||||
}
|
}
|
||||||
@ -43,15 +38,14 @@ export class FilterSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
* @param {number} slot
|
|
||||||
* @param {BaseItem} item
|
* @param {BaseItem} item
|
||||||
|
* @param {number} startProgress
|
||||||
*/
|
*/
|
||||||
tryAcceptItem(entity, slot, item) {
|
tryAcceptItem(entity, item, startProgress) {
|
||||||
const network = entity.components.WiredPins.slots[0].linkedNetwork;
|
const network = entity.components.WiredPins.slots[0].linkedNetwork;
|
||||||
if (!network || !network.hasValue()) {
|
if (!network || !network.hasValue()) {
|
||||||
// Filter is not connected
|
// Filter is not connected
|
||||||
@ -78,7 +72,7 @@ export class FilterSystem extends GameSystemWithFilter {
|
|||||||
// Actually accept item
|
// Actually accept item
|
||||||
listToCheck.push({
|
listToCheck.push({
|
||||||
item,
|
item,
|
||||||
progress: 0.0,
|
extraProgress: startProgress,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
import { globalConfig } from "../../core/config";
|
||||||
import { DrawParameters } from "../../core/draw_parameters";
|
import { DrawParameters } from "../../core/draw_parameters";
|
||||||
import { fastArrayDelete } from "../../core/utils";
|
|
||||||
import { enumDirectionToVector } from "../../core/vector";
|
import { enumDirectionToVector } from "../../core/vector";
|
||||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||||
@ -9,49 +8,36 @@ import { MapChunkView } from "../map_chunk_view";
|
|||||||
export class ItemAcceptorSystem extends GameSystemWithFilter {
|
export class ItemAcceptorSystem extends GameSystemWithFilter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root, [ItemAcceptorComponent]);
|
super(root, [ItemAcceptorComponent]);
|
||||||
|
|
||||||
// Well ... it's better to be verbose I guess?
|
|
||||||
this.accumulatedTicksWhileInMapOverview = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
if (this.root.app.settings.getAllSettings().simplifiedBelts) {
|
// same code for belts, acceptors and ejectors - add helper method???
|
||||||
// Disabled in potato mode
|
const progressGrowth =
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This system doesn't render anything while in map overview,
|
|
||||||
// so simply accumulate ticks
|
|
||||||
if (this.root.camera.getIsMapOverlayActive()) {
|
|
||||||
++this.accumulatedTicksWhileInMapOverview;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute how much ticks we missed
|
|
||||||
const numTicks = 1 + this.accumulatedTicksWhileInMapOverview;
|
|
||||||
const progress =
|
|
||||||
this.root.dynamicTickrate.deltaSeconds *
|
this.root.dynamicTickrate.deltaSeconds *
|
||||||
2 *
|
|
||||||
this.root.hubGoals.getBeltBaseSpeed() *
|
this.root.hubGoals.getBeltBaseSpeed() *
|
||||||
globalConfig.itemSpacingOnBelts * // * 2 because its only a half tile
|
globalConfig.itemSpacingOnBelts;
|
||||||
numTicks;
|
|
||||||
|
|
||||||
// Reset accumulated ticks
|
|
||||||
this.accumulatedTicksWhileInMapOverview = 0;
|
|
||||||
|
|
||||||
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 aceptorComp = entity.components.ItemAcceptor;
|
const acceptorComp = entity.components.ItemAcceptor;
|
||||||
const animations = aceptorComp.itemConsumptionAnimations;
|
const inputs = acceptorComp.inputs;
|
||||||
|
const maxProgress = 0.5;
|
||||||
|
|
||||||
// Process item consumption animations to avoid items popping from the belts
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
for (let animIndex = 0; animIndex < animations.length; ++animIndex) {
|
const input = inputs[i];
|
||||||
const anim = animations[animIndex];
|
input.animProgress += progressGrowth;
|
||||||
anim.animProgress += progress;
|
|
||||||
if (anim.animProgress > 1) {
|
if (input.animProgress < maxProgress) {
|
||||||
fastArrayDelete(animations, animIndex);
|
continue;
|
||||||
animIndex -= 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputs.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
acceptorComp.completedInputs.push({
|
||||||
|
slotIndex: input.slotIndex,
|
||||||
|
item: input.item,
|
||||||
|
extraProgress: input.animProgress - maxProgress,
|
||||||
|
}); // will be handled on the SAME frame due to processor system being afterwards
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,10 +61,9 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
|
for (let i = 0; i < acceptorComp.inputs.length; i++) {
|
||||||
const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
|
const input = acceptorComp.inputs[i];
|
||||||
animIndex
|
const { item, animProgress, slotIndex } = input;
|
||||||
];
|
|
||||||
|
|
||||||
const slotData = acceptorComp.slots[slotIndex];
|
const slotData = acceptorComp.slots[slotIndex];
|
||||||
const realSlotPos = staticComp.localTileToWorld(slotData.pos);
|
const realSlotPos = staticComp.localTileToWorld(slotData.pos);
|
||||||
@ -88,10 +73,11 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
|
const fadeOutDirection =
|
||||||
|
enumDirectionToVector[staticComp.localDirectionToWorld(slotData.direction)];
|
||||||
const finalTile = realSlotPos.subScalars(
|
const finalTile = realSlotPos.subScalars(
|
||||||
fadeOutDirection.x * (animProgress / 2 - 0.5),
|
fadeOutDirection.x * (animProgress - 0.5),
|
||||||
fadeOutDirection.y * (animProgress / 2 - 0.5)
|
fadeOutDirection.y * (animProgress - 0.5)
|
||||||
);
|
);
|
||||||
|
|
||||||
item.drawItemCenteredClipped(
|
item.drawItemCenteredClipped(
|
||||||
|
@ -4,7 +4,6 @@ import { createLogger } from "../../core/logging";
|
|||||||
import { Rectangle } from "../../core/rectangle";
|
import { Rectangle } from "../../core/rectangle";
|
||||||
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
||||||
import { enumDirection, enumDirectionToVector } from "../../core/vector";
|
import { enumDirection, enumDirectionToVector } from "../../core/vector";
|
||||||
import { BaseItem } from "../base_item";
|
|
||||||
import { BeltComponent } from "../components/belt";
|
import { BeltComponent } from "../components/belt";
|
||||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||||
@ -139,10 +138,15 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
this.staleAreaDetector.update();
|
this.staleAreaDetector.update();
|
||||||
|
|
||||||
// Precompute effective belt speed
|
// Precompute effective belt speed
|
||||||
let progressGrowth = 2 * this.root.dynamicTickrate.deltaSeconds;
|
let progressGrowth =
|
||||||
|
this.root.dynamicTickrate.deltaSeconds *
|
||||||
|
this.root.hubGoals.getBeltBaseSpeed() *
|
||||||
|
globalConfig.itemSpacingOnBelts;
|
||||||
|
// it's only half a belt
|
||||||
|
const maxProgress = 0.5;
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.instantBelts) {
|
if (G_IS_DEV && globalConfig.debug.instantBelts) {
|
||||||
progressGrowth = 1;
|
progressGrowth = maxProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go over all cache entries
|
// Go over all cache entries
|
||||||
@ -159,29 +163,27 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sourceSlot.progress < maxProgress) {
|
||||||
// Advance items on the slot
|
// Advance items on the slot
|
||||||
sourceSlot.progress = Math.min(
|
sourceSlot.progress += progressGrowth;
|
||||||
1,
|
}
|
||||||
sourceSlot.progress +
|
|
||||||
progressGrowth *
|
|
||||||
this.root.hubGoals.getBeltBaseSpeed() *
|
|
||||||
globalConfig.itemSpacingOnBelts
|
|
||||||
);
|
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.disableEjectorProcessing) {
|
if (G_IS_DEV && globalConfig.debug.disableEjectorProcessing) {
|
||||||
sourceSlot.progress = 1.0;
|
sourceSlot.progress = maxProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we are still in the process of ejecting, can't proceed then
|
// Check if we are still in the process of ejecting, can't proceed then
|
||||||
if (sourceSlot.progress < 1.0) {
|
if (sourceSlot.progress < maxProgress) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extraProgress = sourceSlot.progress - maxProgress;
|
||||||
|
|
||||||
// Check if we are ejecting to a belt path
|
// Check if we are ejecting to a belt path
|
||||||
const destPath = sourceSlot.cachedBeltPath;
|
const destPath = sourceSlot.cachedBeltPath;
|
||||||
if (destPath) {
|
if (destPath) {
|
||||||
// Try passing the item over
|
// Try passing the item over
|
||||||
if (destPath.tryAcceptItem(item)) {
|
if (destPath.tryAcceptItem(item, extraProgress)) {
|
||||||
sourceSlot.item = null;
|
sourceSlot.item = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,108 +195,25 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
// Check if the target acceptor can actually accept this item
|
// Check if the target acceptor can actually accept this item
|
||||||
const destEntity = sourceSlot.cachedTargetEntity;
|
const destEntity = sourceSlot.cachedTargetEntity;
|
||||||
const destSlot = sourceSlot.cachedDestSlot;
|
const destSlot = sourceSlot.cachedDestSlot;
|
||||||
if (destSlot) {
|
if (destEntity && destSlot) {
|
||||||
const targetAcceptorComp = destEntity.components.ItemAcceptor;
|
const targetAcceptorComp = destEntity.components.ItemAcceptor;
|
||||||
if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) {
|
const storageComp = destEntity.components.Storage;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to hand over the item
|
|
||||||
if (this.tryPassOverItem(item, destEntity, destSlot.index)) {
|
|
||||||
// Handover successful, clear slot
|
|
||||||
if (!this.root.app.settings.getAllSettings().simplifiedBelts) {
|
|
||||||
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.slot.direction, item);
|
|
||||||
}
|
|
||||||
sourceSlot.item = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {BaseItem} item
|
|
||||||
* @param {Entity} receiver
|
|
||||||
* @param {number} slotIndex
|
|
||||||
*/
|
|
||||||
tryPassOverItem(item, receiver, slotIndex) {
|
|
||||||
// Try figuring out how what to do with the item
|
|
||||||
// @TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell.
|
|
||||||
|
|
||||||
const beltComp = receiver.components.Belt;
|
|
||||||
if (beltComp) {
|
|
||||||
const path = beltComp.assignedPath;
|
|
||||||
assert(path, "belt has no path");
|
|
||||||
if (path.tryAcceptItem(item)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Belt can have nothing else
|
|
||||||
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
|
|
||||||
if (!this.root.systemMgr.systems.itemProcessor.checkRequirements(receiver, item, slotIndex)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Its an item processor ..
|
|
||||||
if (itemProcessorComp.tryTakeItem(item, slotIndex)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Item processor can have nothing else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const undergroundBeltComp = receiver.components.UndergroundBelt;
|
|
||||||
if (undergroundBeltComp) {
|
|
||||||
// Its an underground belt. yay.
|
|
||||||
if (
|
if (
|
||||||
undergroundBeltComp.tryAcceptExternalItem(
|
storageComp &&
|
||||||
item,
|
storageComp.tryAcceptItem(item) &&
|
||||||
this.root.hubGoals.getUndergroundBeltBaseSpeed()
|
targetAcceptorComp.tryAcceptItem(destSlot.index, item, extraProgress)
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return true;
|
// unique duplicated code for storage - hacky :(
|
||||||
|
sourceSlot.item = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetAcceptorComp.tryAcceptItem(destSlot.index, item, extraProgress)) {
|
||||||
|
// Handover successful, clear slot
|
||||||
|
sourceSlot.item = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underground belt can have nothing else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageComp = receiver.components.Storage;
|
|
||||||
if (storageComp) {
|
|
||||||
// It's a storage
|
|
||||||
if (storageComp.canAcceptItem(item)) {
|
|
||||||
storageComp.takeItem(item);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage can't have anything else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterComp = receiver.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.
|
|
||||||
if (this.root.systemMgr.systems.filter.tryAcceptItem(receiver, slotIndex, item)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -333,7 +252,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Limit the progress to the maximum available space on the next belt (also see #1000)
|
// Limit the progress to the maximum available space on the next belt (also see #1000)
|
||||||
let progress = slot.progress;
|
let progress = Math.min(0.5, slot.progress);
|
||||||
const nextBeltPath = slot.cachedBeltPath;
|
const nextBeltPath = slot.cachedBeltPath;
|
||||||
if (nextBeltPath) {
|
if (nextBeltPath) {
|
||||||
/*
|
/*
|
||||||
@ -368,20 +287,11 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
^ ^ item @ 0.9
|
^ ^ item @ 0.9
|
||||||
^ max progress = 0.3
|
^ max progress = 0.3
|
||||||
|
|
||||||
Because now our range actually only goes to the end of the building, and not towards the center of the building, we need to multiply
|
|
||||||
all values by 2:
|
|
||||||
|
|
||||||
Building Belt
|
|
||||||
| X | X |
|
|
||||||
| 0.........1.........2 |
|
|
||||||
^ ^ item @ 1.8
|
|
||||||
^ max progress = 0.6
|
|
||||||
|
|
||||||
And that's it! If you summarize the calculations from above into a formula, you get the one below.
|
And that's it! If you summarize the calculations from above into a formula, you get the one below.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const maxProgress =
|
const maxProgress =
|
||||||
(0.5 + nextBeltPath.spacingToFirstItem - globalConfig.itemSpacingOnBelts) * 2;
|
0.5 + nextBeltPath.spacingToFirstItem - globalConfig.itemSpacingOnBelts;
|
||||||
progress = Math.min(maxProgress, progress);
|
progress = Math.min(maxProgress, progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,8 +309,8 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
const realDirection = staticComp.localDirectionToWorld(slot.direction);
|
const realDirection = staticComp.localDirectionToWorld(slot.direction);
|
||||||
const realDirectionVector = enumDirectionToVector[realDirection];
|
const realDirectionVector = enumDirectionToVector[realDirection];
|
||||||
|
|
||||||
const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * progress;
|
const tileX = realPosition.x + 0.5 + realDirectionVector.x * progress;
|
||||||
const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * progress;
|
const tileY = realPosition.y + 0.5 + realDirectionVector.y * progress;
|
||||||
|
|
||||||
const worldX = tileX * globalConfig.tileSize;
|
const worldX = tileX * globalConfig.tileSize;
|
||||||
const worldY = tileY * globalConfig.tileSize;
|
const worldY = tileY * globalConfig.tileSize;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
import { globalConfig } from "../../core/config";
|
||||||
|
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { enumColorMixingResults, enumColors } from "../colors";
|
import { enumColorMixingResults, enumColors } from "../colors";
|
||||||
import {
|
import {
|
||||||
@ -12,16 +13,12 @@ import { isTruthyItem } from "../items/boolean_item";
|
|||||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||||
import { ShapeItem } from "../items/shape_item";
|
import { ShapeItem } from "../items/shape_item";
|
||||||
|
|
||||||
/**
|
|
||||||
* We need to allow queuing charges, otherwise the throughput will stall
|
|
||||||
*/
|
|
||||||
const MAX_QUEUED_CHARGES = 2;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whole data for a produced item
|
* Whole data for a produced item
|
||||||
*
|
*
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* item: BaseItem,
|
* item: BaseItem,
|
||||||
|
* extraProgress?: number,
|
||||||
* preferredSlot?: number,
|
* preferredSlot?: number,
|
||||||
* requiredSlot?: number,
|
* requiredSlot?: number,
|
||||||
* doNotTrack?: boolean
|
* doNotTrack?: boolean
|
||||||
@ -33,7 +30,6 @@ const MAX_QUEUED_CHARGES = 2;
|
|||||||
* @typedef {{
|
* @typedef {{
|
||||||
* entity: Entity,
|
* entity: Entity,
|
||||||
* items: Map<number, BaseItem>,
|
* items: Map<number, BaseItem>,
|
||||||
* inputCount: number,
|
|
||||||
* outItems: Array<ProducedItem>
|
* outItems: Array<ProducedItem>
|
||||||
* }} ProcessorImplementationPayload
|
* }} ProcessorImplementationPayload
|
||||||
*/
|
*/
|
||||||
@ -82,8 +78,14 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
const processorComp = entity.components.ItemProcessor;
|
const processorComp = entity.components.ItemProcessor;
|
||||||
const ejectorComp = entity.components.ItemEjector;
|
const ejectorComp = entity.components.ItemEjector;
|
||||||
|
|
||||||
const currentCharge = processorComp.ongoingCharges[0];
|
// Check if we have an empty queue and can start a new charge - do this first so we don't waste a tick
|
||||||
|
if (!processorComp.currentCharge) {
|
||||||
|
if (this.canProcess(entity)) {
|
||||||
|
this.startNewCharge(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentCharge = processorComp.currentCharge;
|
||||||
if (currentCharge) {
|
if (currentCharge) {
|
||||||
// Process next charge
|
// Process next charge
|
||||||
if (currentCharge.remainingTime > 0.0) {
|
if (currentCharge.remainingTime > 0.0) {
|
||||||
@ -103,19 +105,13 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
processorComp.queuedEjects.push(itemsToEject[j]);
|
processorComp.queuedEjects.push(itemsToEject[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
processorComp.ongoingCharges.shift();
|
processorComp.currentCharge = null;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have an empty queue and can start a new charge
|
|
||||||
if (processorComp.ongoingCharges.length < MAX_QUEUED_CHARGES) {
|
|
||||||
if (this.canProcess(entity)) {
|
|
||||||
this.startNewCharge(entity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Go over all items and try to eject them
|
||||||
for (let j = 0; j < processorComp.queuedEjects.length; ++j) {
|
for (let j = 0; j < processorComp.queuedEjects.length; ++j) {
|
||||||
const { item, requiredSlot, preferredSlot } = processorComp.queuedEjects[j];
|
const { item, requiredSlot, preferredSlot, extraProgress } = processorComp.queuedEjects[j];
|
||||||
|
|
||||||
assert(ejectorComp, "To eject items, the building needs to have an ejector");
|
assert(ejectorComp, "To eject items, the building needs to have an ejector");
|
||||||
|
|
||||||
@ -139,7 +135,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
|
|
||||||
if (slot !== null) {
|
if (slot !== null) {
|
||||||
// Alright, we can actually eject
|
// Alright, we can actually eject
|
||||||
if (!ejectorComp.tryEject(slot, item)) {
|
if (!ejectorComp.tryEject(slot, item, extraProgress)) {
|
||||||
assert(false, "Failed to eject");
|
assert(false, "Failed to eject");
|
||||||
} else {
|
} else {
|
||||||
processorComp.queuedEjects.splice(j, 1);
|
processorComp.queuedEjects.splice(j, 1);
|
||||||
@ -150,53 +146,21 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// requirements are no longer needed as items will always be accepted, only the next method is.
|
||||||
* Returns true if the entity should accept the given item on the given slot.
|
|
||||||
* This should only be called with matching items! I.e. if a color item is expected
|
|
||||||
* on the given slot, then only a color item must be passed.
|
|
||||||
* @param {Entity} entity
|
|
||||||
* @param {BaseItem} item The item to accept
|
|
||||||
* @param {number} slotIndex The slot index
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
checkRequirements(entity, item, slotIndex) {
|
|
||||||
const itemProcessorComp = entity.components.ItemProcessor;
|
|
||||||
const pinsComp = entity.components.WiredPins;
|
|
||||||
|
|
||||||
switch (itemProcessorComp.processingRequirement) {
|
|
||||||
case enumItemProcessorRequirements.painterQuad: {
|
|
||||||
if (slotIndex === 0) {
|
|
||||||
// Always accept the shape
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the network value at the given slot
|
|
||||||
const network = pinsComp.slots[slotIndex - 1].linkedNetwork;
|
|
||||||
const slotIsEnabled = network && network.hasValue() && isTruthyItem(network.currentValue);
|
|
||||||
if (!slotIsEnabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, everything is accepted
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether it's possible to process something
|
* Checks whether it's possible to process something
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
*/
|
*/
|
||||||
canProcess(entity) {
|
canProcess(entity) {
|
||||||
|
const acceptorComp = entity.components.ItemAcceptor;
|
||||||
const processorComp = entity.components.ItemProcessor;
|
const processorComp = entity.components.ItemProcessor;
|
||||||
|
|
||||||
switch (processorComp.processingRequirement) {
|
switch (processorComp.processingRequirement) {
|
||||||
// DEFAULT
|
// DEFAULT
|
||||||
// By default, we can start processing once all inputs are there
|
// By default, we can start processing once all inputs are there
|
||||||
case null: {
|
case null: {
|
||||||
return processorComp.inputCount >= processorComp.inputsPerCharge;
|
return acceptorComp.completedInputs.length >= processorComp.inputsPerCharge;
|
||||||
}
|
}
|
||||||
|
|
||||||
// QUAD PAINTER
|
// QUAD PAINTER
|
||||||
@ -204,8 +168,13 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
case enumItemProcessorRequirements.painterQuad: {
|
case enumItemProcessorRequirements.painterQuad: {
|
||||||
const pinsComp = entity.components.WiredPins;
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
|
||||||
|
const input = acceptorComp.completedInputs[0];
|
||||||
|
if (!input) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// First slot is the shape, so if it's not there we can't do anything
|
// First slot is the shape, so if it's not there we can't do anything
|
||||||
const shapeItem = /** @type {ShapeItem} */ (processorComp.inputSlots.get(0));
|
const shapeItem = /** @type {ShapeItem} */ (input.item);
|
||||||
if (!shapeItem) {
|
if (!shapeItem) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -234,7 +203,10 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
|
|
||||||
// Check if all colors of the enabled slots are there
|
// Check if all colors of the enabled slots are there
|
||||||
for (let i = 0; i < slotStatus.length; ++i) {
|
for (let i = 0; i < slotStatus.length; ++i) {
|
||||||
if (slotStatus[i] && !processorComp.inputSlots.get(1 + i)) {
|
if (
|
||||||
|
slotStatus[i] &&
|
||||||
|
!acceptorComp.completedInputs.find(input => input.slotIndex == i + 1) // @TODO this is slow
|
||||||
|
) {
|
||||||
// A slot which is enabled wasn't enabled. Make sure if there is anything on the quadrant,
|
// A slot which is enabled wasn't enabled. Make sure if there is anything on the quadrant,
|
||||||
// it is not possible to paint, but if there is nothing we can ignore it
|
// it is not possible to paint, but if there is nothing we can ignore it
|
||||||
for (let j = 0; j < 4; ++j) {
|
for (let j = 0; j < 4; ++j) {
|
||||||
@ -259,10 +231,21 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
*/
|
*/
|
||||||
startNewCharge(entity) {
|
startNewCharge(entity) {
|
||||||
|
const acceptorComp = entity.components.ItemAcceptor;
|
||||||
const processorComp = entity.components.ItemProcessor;
|
const processorComp = entity.components.ItemProcessor;
|
||||||
|
|
||||||
// First, take items
|
// First, take inputs - but only one from each
|
||||||
const items = processorComp.inputSlots;
|
const inputs = acceptorComp.completedInputs;
|
||||||
|
|
||||||
|
// split inputs efficiently
|
||||||
|
let items = new Map();
|
||||||
|
let extraProgress = 0;
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
const input = inputs[i];
|
||||||
|
|
||||||
|
items.set(input.slotIndex, input.item);
|
||||||
|
extraProgress = Math.max(extraProgress, input.extraProgress);
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {Array<ProducedItem>} */
|
/** @type {Array<ProducedItem>} */
|
||||||
const outItems = [];
|
const outItems = [];
|
||||||
@ -276,7 +259,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
entity,
|
entity,
|
||||||
items,
|
items,
|
||||||
outItems,
|
outItems,
|
||||||
inputCount: processorComp.inputCount,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Track produced items
|
// Track produced items
|
||||||
@ -284,23 +266,35 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
if (!outItems[i].doNotTrack) {
|
if (!outItems[i].doNotTrack) {
|
||||||
this.root.signals.itemProduced.dispatch(outItems[i].item);
|
this.root.signals.itemProduced.dispatch(outItems[i].item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// also set extra progress
|
||||||
|
outItems[i].extraProgress = extraProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue Charge
|
// Queue Charge
|
||||||
const baseSpeed = this.root.hubGoals.getProcessorBaseSpeed(processorComp.type);
|
const originalTime = this.root.hubGoals.getProcessingTime(processorComp.type);
|
||||||
const originalTime = 1 / baseSpeed;
|
|
||||||
|
|
||||||
const bonusTimeToApply = Math.min(originalTime, processorComp.bonusTime);
|
const bonusTimeToApply = Math.min(originalTime, processorComp.bonusTime);
|
||||||
const timeToProcess = originalTime - bonusTimeToApply;
|
const timeToProcess = originalTime - bonusTimeToApply;
|
||||||
|
|
||||||
processorComp.bonusTime -= bonusTimeToApply;
|
processorComp.bonusTime -= bonusTimeToApply;
|
||||||
processorComp.ongoingCharges.push({
|
|
||||||
|
processorComp.currentCharge = {
|
||||||
items: outItems,
|
items: outItems,
|
||||||
remainingTime: timeToProcess,
|
remainingTime: timeToProcess,
|
||||||
});
|
};
|
||||||
|
|
||||||
processorComp.inputSlots.clear();
|
// only remove one item from each slot - we don't want to delete extra items!
|
||||||
processorComp.inputCount = 0;
|
let usedSlots = [];
|
||||||
|
for (let i = 0; i < acceptorComp.completedInputs.length; i++) {
|
||||||
|
const index = acceptorComp.completedInputs[i].slotIndex;
|
||||||
|
|
||||||
|
if (!usedSlots.includes(index)) {
|
||||||
|
usedSlots.push(index);
|
||||||
|
acceptorComp.completedInputs.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -445,7 +439,14 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
* @param {ProcessorImplementationPayload} payload
|
* @param {ProcessorImplementationPayload} payload
|
||||||
*/
|
*/
|
||||||
process_TRASH(payload) {
|
process_TRASH(payload) {
|
||||||
// Do nothing ..
|
// Hardcoded - 4 inputs
|
||||||
|
for (let i = 0; i < 4; ++i) {
|
||||||
|
const item = /** @type {ShapeItem} */ (payload.items.get(i));
|
||||||
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
payload.entity.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.trash1000, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -569,8 +570,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
const hubComponent = payload.entity.components.Hub;
|
const hubComponent = payload.entity.components.Hub;
|
||||||
assert(hubComponent, "Hub item processor has no hub component");
|
assert(hubComponent, "Hub item processor has no hub component");
|
||||||
|
|
||||||
// Hardcoded
|
// Hardcoded - 16 inputs
|
||||||
for (let i = 0; i < payload.inputCount; ++i) {
|
for (let i = 0; i < 16; ++i) {
|
||||||
const item = /** @type {ShapeItem} */ (payload.items.get(i));
|
const item = /** @type {ShapeItem} */ (payload.items.get(i));
|
||||||
if (!item) {
|
if (!item) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -31,6 +31,9 @@ export class StorageSystem extends GameSystemWithFilter {
|
|||||||
const storageComp = entity.components.Storage;
|
const storageComp = entity.components.Storage;
|
||||||
const pinsComp = entity.components.WiredPins;
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
|
||||||
|
// storage needs to delete completed inputs, since the items are already added
|
||||||
|
entity.components.ItemAcceptor.completedInputs = [];
|
||||||
|
|
||||||
// Eject from storage
|
// Eject from storage
|
||||||
if (storageComp.storedItem && storageComp.storedCount > 0) {
|
if (storageComp.storedItem && storageComp.storedCount > 0) {
|
||||||
const ejectorComp = entity.components.ItemEjector;
|
const ejectorComp = entity.components.ItemEjector;
|
||||||
|
@ -3,7 +3,6 @@ import { Loader } from "../../core/loader";
|
|||||||
import { createLogger } from "../../core/logging";
|
import { createLogger } from "../../core/logging";
|
||||||
import { Rectangle } from "../../core/rectangle";
|
import { Rectangle } from "../../core/rectangle";
|
||||||
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
||||||
import { fastArrayDelete } from "../../core/utils";
|
|
||||||
import {
|
import {
|
||||||
enumAngleToDirection,
|
enumAngleToDirection,
|
||||||
enumDirection,
|
enumDirection,
|
||||||
@ -225,7 +224,11 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
|
|||||||
this.staleAreaWatcher.update();
|
this.staleAreaWatcher.update();
|
||||||
|
|
||||||
const sender = enumUndergroundBeltMode.sender;
|
const sender = enumUndergroundBeltMode.sender;
|
||||||
const now = this.root.time.now();
|
|
||||||
|
const progressGrowth =
|
||||||
|
this.root.dynamicTickrate.deltaSeconds *
|
||||||
|
this.root.hubGoals.getBeltBaseSpeed() *
|
||||||
|
globalConfig.itemSpacingOnBelts;
|
||||||
|
|
||||||
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];
|
||||||
@ -233,7 +236,7 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
|
|||||||
if (undergroundComp.mode === sender) {
|
if (undergroundComp.mode === sender) {
|
||||||
this.handleSender(entity);
|
this.handleSender(entity);
|
||||||
} else {
|
} else {
|
||||||
this.handleReceiver(entity, now);
|
this.handleReceiver(entity, progressGrowth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,8 +256,8 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
|
|||||||
|
|
||||||
// Search in the direction of the tunnel
|
// Search in the direction of the tunnel
|
||||||
for (
|
for (
|
||||||
let searchOffset = 0;
|
let searchOffset = 1;
|
||||||
searchOffset < globalConfig.undergroundBeltMaxTilesByTier[undergroundComp.tier];
|
searchOffset < globalConfig.undergroundBeltMaxTilesByTier[undergroundComp.tier] + 1;
|
||||||
++searchOffset
|
++searchOffset
|
||||||
) {
|
) {
|
||||||
currentTile = currentTile.add(searchVector);
|
currentTile = currentTile.add(searchVector);
|
||||||
@ -281,6 +284,8 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure to link the other way as well
|
||||||
|
receiverUndergroundComp.cachedLinkedEntity = { entity: null, distance: searchOffset };
|
||||||
return { entity: potentialReceiver, distance: searchOffset };
|
return { entity: potentialReceiver, distance: searchOffset };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +299,7 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
|
|||||||
*/
|
*/
|
||||||
handleSender(entity) {
|
handleSender(entity) {
|
||||||
const undergroundComp = entity.components.UndergroundBelt;
|
const undergroundComp = entity.components.UndergroundBelt;
|
||||||
|
const acceptorComp = entity.components.ItemAcceptor;
|
||||||
|
|
||||||
// Find the current receiver
|
// Find the current receiver
|
||||||
let cacheEntry = undergroundComp.cachedLinkedEntity;
|
let cacheEntry = undergroundComp.cachedLinkedEntity;
|
||||||
@ -307,22 +313,17 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have any items to eject
|
const input = acceptorComp.completedInputs[0];
|
||||||
const nextItemAndDuration = undergroundComp.pendingItems[0];
|
if (input) {
|
||||||
if (nextItemAndDuration) {
|
|
||||||
assert(undergroundComp.pendingItems.length === 1, "more than 1 pending");
|
|
||||||
|
|
||||||
// Check if the receiver can accept it
|
// Check if the receiver can accept it
|
||||||
if (
|
if (
|
||||||
cacheEntry.entity.components.UndergroundBelt.tryAcceptTunneledItem(
|
cacheEntry.entity.components.UndergroundBelt.tryAcceptTunneledItem(
|
||||||
nextItemAndDuration[0],
|
input.item,
|
||||||
cacheEntry.distance,
|
cacheEntry.distance,
|
||||||
this.root.hubGoals.getUndergroundBeltBaseSpeed(),
|
input.extraProgress
|
||||||
this.root.time.now()
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// Drop this item
|
acceptorComp.completedInputs.shift();
|
||||||
fastArrayDelete(undergroundComp.pendingItems, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,20 +331,28 @@ export class UndergroundBeltSystem extends GameSystemWithFilter {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
* @param {number} now
|
* @param {number} progressGrowth
|
||||||
*/
|
*/
|
||||||
handleReceiver(entity, now) {
|
handleReceiver(entity, progressGrowth) {
|
||||||
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
|
if (!undergroundComp.cachedLinkedEntity) return;
|
||||||
const nextItemAndDuration = undergroundComp.pendingItems[0];
|
const distance = undergroundComp.cachedLinkedEntity.distance;
|
||||||
if (nextItemAndDuration) {
|
|
||||||
if (now > nextItemAndDuration[1]) {
|
// Move items along
|
||||||
|
for (let i = 0; i < undergroundComp.pendingItems.length; i++) {
|
||||||
|
const itemAndProgress = undergroundComp.pendingItems[i];
|
||||||
|
if (itemAndProgress[1] < distance) {
|
||||||
|
itemAndProgress[1] += progressGrowth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemAndProgress[1] >= distance) {
|
||||||
const ejectorComp = entity.components.ItemEjector;
|
const ejectorComp = entity.components.ItemEjector;
|
||||||
|
|
||||||
const nextSlotIndex = ejectorComp.getFirstFreeSlot();
|
const nextSlotIndex = ejectorComp.getFirstFreeSlot();
|
||||||
if (nextSlotIndex !== null) {
|
if (nextSlotIndex !== null) {
|
||||||
if (ejectorComp.tryEject(nextSlotIndex, nextItemAndDuration[0])) {
|
const extraProgress = itemAndProgress[1] - distance;
|
||||||
|
if (ejectorComp.tryEject(nextSlotIndex, itemAndProgress[0], extraProgress)) {
|
||||||
undergroundComp.pendingItems.shift();
|
undergroundComp.pendingItems.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user