mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Merge bf17ccf202
into 4e5e5c8ef7
This commit is contained in:
commit
0f022f54cf
@ -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
|
||||||
|
@ -119,29 +119,18 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
this.numCompressedItemsAfterFirstItem = 0;
|
this.numCompressedItemsAfterFirstItem = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this path can accept a new item
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
canAcceptItem() {
|
|
||||||
return this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
const beltProgressPerTick =
|
|
||||||
this.root.hubGoals.getBeltBaseSpeed() *
|
|
||||||
this.root.dynamicTickrate.deltaSeconds *
|
|
||||||
globalConfig.itemSpacingOnBelts;
|
|
||||||
|
|
||||||
// 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, extraProgress);
|
||||||
|
|
||||||
this.items.unshift([this.spacingToFirstItem - initialProgress, item]);
|
this.items.unshift([this.spacingToFirstItem - initialProgress, item]);
|
||||||
this.spacingToFirstItem = initialProgress;
|
this.spacingToFirstItem = initialProgress;
|
||||||
@ -227,8 +216,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 +261,15 @@ 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;
|
if (targetAcceptorComp.tryAcceptItem(targetEntity, matchingSlotIndex, item, startProgress)) {
|
||||||
|
|
||||||
return function (item, remainingProgress = 0.0) {
|
|
||||||
// Check if the acceptor has a filter
|
|
||||||
if (filter && item._type !== filter) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to pass over
|
|
||||||
if (passOver(item, matchingSlotIndex)) {
|
|
||||||
// Trigger animation on the acceptor comp
|
|
||||||
if (noSimplifiedBelts) {
|
|
||||||
targetAcceptorComp.onItemAccepted(
|
|
||||||
matchingSlotIndex,
|
|
||||||
matchingDirection,
|
|
||||||
item,
|
|
||||||
remainingProgress
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
return 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)]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { formatItemsPerSecond } from "../../core/utils";
|
import { formatItemsPerSecond } from "../../core/utils";
|
||||||
import { enumDirection, Vector } from "../../core/vector";
|
import { enumDirection, Vector } from "../../core/vector";
|
||||||
import { T } from "../../translations";
|
import { T } from "../../translations";
|
||||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
import { enumInputRequirements, ItemAcceptorComponent } from "../components/item_acceptor";
|
||||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||||
import {
|
import {
|
||||||
enumItemProcessorTypes,
|
enumItemProcessorTypes,
|
||||||
@ -274,6 +274,7 @@ export class MetaPainterBuilding extends MetaBuilding {
|
|||||||
filter: "color",
|
filter: "color",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
entity.components.ItemAcceptor.inputRequirement = enumInputRequirements.quadPainter;
|
||||||
|
|
||||||
entity.components.ItemEjector.setSlots([
|
entity.components.ItemEjector.setSlots([
|
||||||
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { formatBigNumber } from "../../core/utils";
|
import { formatBigNumber } from "../../core/utils";
|
||||||
import { enumDirection, Vector } from "../../core/vector";
|
import { enumDirection, Vector } from "../../core/vector";
|
||||||
import { T } from "../../translations";
|
import { T } from "../../translations";
|
||||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
import { enumInputRequirements, ItemAcceptorComponent } from "../components/item_acceptor";
|
||||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||||
import { StorageComponent } from "../components/storage";
|
import { StorageComponent } from "../components/storage";
|
||||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||||
@ -81,6 +81,7 @@ export class MetaStorageBuilding extends MetaBuilding {
|
|||||||
direction: enumDirection.bottom,
|
direction: enumDirection.bottom,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
inputRequirement: enumInputRequirements.storage,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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,10 @@ 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 { isTruthyItem } from "../items/boolean_item";
|
||||||
|
import { typeItemSingleton } from "../item_resolver";
|
||||||
|
import { GameRoot } from "../root";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -24,34 +28,85 @@ 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @enum {string} */
|
||||||
|
export const enumInputRequirements = {
|
||||||
|
quadPainter: "quadPainter",
|
||||||
|
storage: "storage",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MOD_INPUT_REQUIREMENTS = [];
|
||||||
|
|
||||||
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
|
||||||
|
* @param {string|null=} param0.inputRequirement The requirement to accept items
|
||||||
*/
|
*/
|
||||||
constructor({ slots = [] }) {
|
constructor({ slots = [], maxSlotInputs = 2, inputRequirement = null }) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.setSlots(slots);
|
this.setSlots(slots);
|
||||||
|
|
||||||
|
this.inputRequirement = inputRequirement;
|
||||||
|
|
||||||
|
// setting this to 1 will cause throughput issues at very high speeds
|
||||||
|
this.maxSlotInputs = maxSlotInputs;
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
/**
|
/** @type {ItemAcceptorInputs} */
|
||||||
* Fixes belt animations
|
this.inputs = [];
|
||||||
* @type {Array<{
|
/** @type {ItemAcceptorCompletedInputs} */
|
||||||
* item: BaseItem,
|
this.completedInputs = [];
|
||||||
* slotIndex: number,
|
|
||||||
* animProgress: number,
|
|
||||||
* direction: enumDirection
|
|
||||||
* }>}
|
|
||||||
*/
|
|
||||||
this.itemConsumptionAnimations = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,31 +129,107 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if this acceptor can accept a new item at slot N
|
|
||||||
*
|
*
|
||||||
* NOTICE: The belt path ignores this for performance reasons and does his own check
|
* @param {Entity} entity
|
||||||
|
* @param {BaseItem} item
|
||||||
* @param {number} slotIndex
|
* @param {number} slotIndex
|
||||||
* @param {BaseItem=} item
|
* @returns
|
||||||
*/
|
*/
|
||||||
canAcceptItem(slotIndex, item) {
|
canAcceptItem(entity, item, slotIndex) {
|
||||||
const slot = this.slots[slotIndex];
|
const slot = this.slots[slotIndex];
|
||||||
return !slot.filter || slot.filter === item.getItemType();
|
|
||||||
|
// make sure there is a slot and we match the filter
|
||||||
|
if (slot && !(slot.filter && slot.filter != item.getItemType())) {
|
||||||
|
if (MOD_INPUT_REQUIREMENTS[this.inputRequirement]) {
|
||||||
|
return MOD_INPUT_REQUIREMENTS[this.inputRequirement].call(this, {
|
||||||
|
entity,
|
||||||
|
item,
|
||||||
|
slotIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.inputRequirement) {
|
||||||
|
case null: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case enumInputRequirements.quadPainter: {
|
||||||
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
case enumInputRequirements.storage: {
|
||||||
|
const storageComp = entity.components.Storage;
|
||||||
|
|
||||||
|
if (storageComp.storedCount >= storageComp.maximumStorage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const itemType = item.getItemType();
|
||||||
|
if (storageComp.storedItem && itemType !== storageComp.storedItem.getItemType()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the item straight away - this way different kinds of items can't be inq the acceptor
|
||||||
|
storageComp.storedItem = item;
|
||||||
|
storageComp.storedCount++;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
assertAlways(false, "Input requirement is not recognised: " + this.inputRequirement);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an item has been accepted so that
|
* Called when trying to input a new item
|
||||||
|
* @param {Entity} entity
|
||||||
* @param {number} slotIndex
|
* @param {number} slotIndex
|
||||||
* @param {enumDirection} direction
|
|
||||||
* @param {BaseItem} item
|
* @param {BaseItem} item
|
||||||
* @param {number} remainingProgress World space remaining progress, can be set to set the start position of the 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
|
||||||
*/
|
*/
|
||||||
onItemAccepted(slotIndex, direction, item, remainingProgress = 0.0) {
|
tryAcceptItem(entity, slotIndex, item, startProgress = 0.0) {
|
||||||
this.itemConsumptionAnimations.push({
|
// make sure we have space to actually accept
|
||||||
item,
|
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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.canAcceptItem(entity, item, slotIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the start progress is bigger than 0.5, the remainder should get passed on to the ejector
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { types } from "../../savegame/serialization";
|
import { types } from "../../savegame/serialization";
|
||||||
|
import { TypeString } from "../../savegame/serialization_data_types";
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { Component } from "../component";
|
import { Component } from "../component";
|
||||||
|
import { typeItemSingleton } from "../item_resolver";
|
||||||
|
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
export const enumItemProcessorTypes = {
|
export const enumItemProcessorTypes = {
|
||||||
@ -29,6 +31,7 @@ export const enumItemProcessorRequirements = {
|
|||||||
|
|
||||||
/** @typedef {{
|
/** @typedef {{
|
||||||
* item: BaseItem,
|
* item: BaseItem,
|
||||||
|
* extraProgress?: number
|
||||||
* requiredSlot?: number,
|
* requiredSlot?: number,
|
||||||
* preferredSlot?: number
|
* preferredSlot?: number
|
||||||
* }} EjectorItemToEject */
|
* }} EjectorItemToEject */
|
||||||
@ -38,6 +41,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";
|
||||||
@ -46,6 +56,19 @@ export class ItemProcessorComponent extends Component {
|
|||||||
static getSchema() {
|
static getSchema() {
|
||||||
return {
|
return {
|
||||||
nextOutputSlot: types.uint,
|
nextOutputSlot: types.uint,
|
||||||
|
currentCharge: types.nullable(
|
||||||
|
types.structured({
|
||||||
|
remainingTime: types.ufloat,
|
||||||
|
items: types.array(
|
||||||
|
types.structured({
|
||||||
|
item: typeItemSingleton,
|
||||||
|
extraProgress: types.nullable(types.float),
|
||||||
|
requiredSlot: types.nullable(types.uint),
|
||||||
|
preferredSlot: types.nullable(types.uint),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,12 +96,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 +105,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 +124,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,13 @@ 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 { Entity } from "../entity";
|
||||||
import { typeItemSingleton } from "../item_resolver";
|
|
||||||
|
|
||||||
const chainBufferSize = 6;
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* item: BaseItem,
|
||||||
|
* extraProgress?: number,
|
||||||
|
* }} MinerItem
|
||||||
|
*/
|
||||||
|
|
||||||
export class MinerComponent extends Component {
|
export class MinerComponent extends Component {
|
||||||
static getId() {
|
static getId() {
|
||||||
@ -14,17 +18,17 @@ export class MinerComponent extends Component {
|
|||||||
static getSchema() {
|
static getSchema() {
|
||||||
// cachedMinedItem is not serialized.
|
// cachedMinedItem is not serialized.
|
||||||
return {
|
return {
|
||||||
lastMiningTime: types.ufloat,
|
progress: types.ufloat,
|
||||||
itemChainBuffer: types.array(typeItemSingleton),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor({ chainable = false }) {
|
constructor({ chainable = false }) {
|
||||||
super();
|
super();
|
||||||
this.lastMiningTime = 0;
|
this.progress = 0;
|
||||||
this.chainable = chainable;
|
this.chainable = chainable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The item we are mining beneath us
|
||||||
* @type {BaseItem}
|
* @type {BaseItem}
|
||||||
*/
|
*/
|
||||||
this.cachedMinedItem = null;
|
this.cachedMinedItem = null;
|
||||||
@ -35,30 +39,11 @@ export class MinerComponent extends Component {
|
|||||||
* @type {Entity|null|false}
|
* @type {Entity|null|false}
|
||||||
*/
|
*/
|
||||||
this.cachedChainedMiner = null;
|
this.cachedChainedMiner = null;
|
||||||
|
|
||||||
this.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
/**
|
/**
|
||||||
* Stores items from other miners which were chained to this
|
* The miner at the end of the chain, which actually ejects the items
|
||||||
* miner.
|
* If the value is false, it means there is no entity, and we don't have to re-check
|
||||||
* @type {Array<BaseItem>}
|
* @type {Entity|null|false}
|
||||||
*/
|
*/
|
||||||
this.itemChainBuffer = [];
|
this.cachedExitMiner = null;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {BaseItem} item
|
|
||||||
*/
|
|
||||||
tryAcceptChainedItem(item) {
|
|
||||||
if (this.itemChainBuffer.length > chainBufferSize) {
|
|
||||||
// Well, this one is full
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.itemChainBuffer.push(item);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,7 @@ import { types } from "../../savegame/serialization";
|
|||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { Component } from "../component";
|
import { Component } from "../component";
|
||||||
import { typeItemSingleton } from "../item_resolver";
|
import { typeItemSingleton } from "../item_resolver";
|
||||||
import { ColorItem } from "../items/color_item";
|
|
||||||
import { ShapeItem } from "../items/shape_item";
|
|
||||||
|
|
||||||
/** @type {{
|
|
||||||
* [x: string]: (item: BaseItem) => Boolean
|
|
||||||
* }} */
|
|
||||||
export const MODS_ADDITIONAL_STORAGE_ITEM_RESOLVER = {};
|
|
||||||
export class StorageComponent extends Component {
|
export class StorageComponent extends Component {
|
||||||
static getId() {
|
static getId() {
|
||||||
return "Storage";
|
return "Storage";
|
||||||
@ -46,42 +40,6 @@ export class StorageComponent extends Component {
|
|||||||
this.overlayOpacity = 0;
|
this.overlayOpacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this storage can accept the item
|
|
||||||
* @param {BaseItem} item
|
|
||||||
*/
|
|
||||||
canAcceptItem(item) {
|
|
||||||
if (this.storedCount >= this.maximumStorage) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!this.storedItem || this.storedCount === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemType = item.getItemType();
|
|
||||||
|
|
||||||
if (itemType !== this.storedItem.getItemType()) {
|
|
||||||
// Check type matches
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MODS_ADDITIONAL_STORAGE_ITEM_RESOLVER[itemType]) {
|
|
||||||
return MODS_ADDITIONAL_STORAGE_ITEM_RESOLVER[itemType].apply(this, [item]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemType === "color") {
|
|
||||||
return /** @type {ColorItem} */ (this.storedItem).color === /** @type {ColorItem} */ (item).color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemType === "shape") {
|
|
||||||
return (
|
|
||||||
/** @type {ShapeItem} */ (this.storedItem).definition.getHash() ===
|
|
||||||
/** @type {ShapeItem} */ (item).definition.getHash()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the storage is full
|
* Returns whether the storage is full
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
|
@ -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,11 +150,13 @@ export class GameSystemManager {
|
|||||||
|
|
||||||
add("belt", BeltSystem);
|
add("belt", BeltSystem);
|
||||||
|
|
||||||
add("undergroundBelt", UndergroundBeltSystem);
|
add("storage", StorageSystem);
|
||||||
|
|
||||||
|
add("itemEjector", ItemEjectorSystem);
|
||||||
|
|
||||||
add("miner", MinerSystem);
|
add("miner", MinerSystem);
|
||||||
|
|
||||||
add("storage", StorageSystem);
|
add("undergroundBelt", UndergroundBeltSystem);
|
||||||
|
|
||||||
add("itemProcessor", ItemProcessorSystem);
|
add("itemProcessor", ItemProcessorSystem);
|
||||||
|
|
||||||
@ -162,8 +164,6 @@ export class GameSystemManager {
|
|||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,17 +303,14 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||||||
const mouseTile = worldPos.toTileSpace();
|
const mouseTile = worldPos.toTileSpace();
|
||||||
|
|
||||||
// Compute best rotation variant
|
// Compute best rotation variant
|
||||||
const {
|
const { rotation, rotationVariant, connectedEntities } =
|
||||||
rotation,
|
metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({
|
||||||
rotationVariant,
|
root: this.root,
|
||||||
connectedEntities,
|
tile: mouseTile,
|
||||||
} = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({
|
rotation: this.currentBaseRotation,
|
||||||
root: this.root,
|
variant: this.currentVariant.get(),
|
||||||
tile: mouseTile,
|
layer: metaBuilding.getLayer(),
|
||||||
rotation: this.currentBaseRotation,
|
});
|
||||||
variant: this.currentVariant.get(),
|
|
||||||
layer: metaBuilding.getLayer(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if there are connected entities
|
// Check if there are connected entities
|
||||||
if (connectedEntities) {
|
if (connectedEntities) {
|
||||||
@ -657,8 +654,16 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||||||
// Connected to a belt
|
// Connected to a belt
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
} else if (minerComp && minerComp.chainable && destMiner && destMiner.chainable) {
|
} else if (minerComp && minerComp.chainable && destMiner && destMiner.chainable) {
|
||||||
// Chainable miners connected to eachother
|
const worldTile = staticComp.localTileToWorld(slot.pos);
|
||||||
isConnected = true;
|
if (
|
||||||
|
this.root.map.getLowerLayerContentXY(worldTile.x, worldTile.y) ==
|
||||||
|
destMiner.cachedMinedItem
|
||||||
|
) {
|
||||||
|
// Chainable miners connected to eachother
|
||||||
|
isConnected = true;
|
||||||
|
} else {
|
||||||
|
isBlocked = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// This one is blocked
|
// This one is blocked
|
||||||
isBlocked = true;
|
isBlocked = true;
|
||||||
|
@ -134,6 +134,7 @@ export class HUDMinerHighlight extends BaseHUDPart {
|
|||||||
findConnectedMiners(entity, seenUids = new Set()) {
|
findConnectedMiners(entity, seenUids = new Set()) {
|
||||||
let results = [];
|
let results = [];
|
||||||
const origin = entity.components.StaticMapEntity.origin;
|
const origin = entity.components.StaticMapEntity.origin;
|
||||||
|
const originMinerComp = entity.components.Miner;
|
||||||
|
|
||||||
if (!seenUids.has(entity.uid)) {
|
if (!seenUids.has(entity.uid)) {
|
||||||
seenUids.add(entity.uid);
|
seenUids.add(entity.uid);
|
||||||
@ -157,7 +158,11 @@ export class HUDMinerHighlight extends BaseHUDPart {
|
|||||||
);
|
);
|
||||||
if (contents) {
|
if (contents) {
|
||||||
const minerComp = contents.components.Miner;
|
const minerComp = contents.components.Miner;
|
||||||
if (minerComp && minerComp.chainable) {
|
if (
|
||||||
|
minerComp &&
|
||||||
|
minerComp.chainable &&
|
||||||
|
originMinerComp.cachedMinedItem == minerComp.cachedMinedItem
|
||||||
|
) {
|
||||||
// Found a miner connected to this entity
|
// Found a miner connected to this entity
|
||||||
if (!seenUids.has(contents.uid)) {
|
if (!seenUids.has(contents.uid)) {
|
||||||
if (this.root.systemMgr.systems.miner.findChainedMiner(contents) === entity) {
|
if (this.root.systemMgr.systems.miner.findChainedMiner(contents) === entity) {
|
||||||
|
@ -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,32 +12,27 @@ 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
|
if (ejectorComp.tryEject(slotIndex, nextItem.item)) {
|
||||||
nextItem.progress = Math.min(requiredProgress, nextItem.progress + progress);
|
pendingItems.shift();
|
||||||
// Check if it's ready to eject
|
|
||||||
if (nextItem.progress >= requiredProgress - 1e-5) {
|
|
||||||
if (ejectorComp.tryEject(slotIndex, nextItem.item)) {
|
|
||||||
pendingItems.shift();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,10 +42,10 @@ 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,32 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance items on the slot
|
// Limit progress here as well
|
||||||
sourceSlot.progress = Math.min(
|
let progressLimit = maxProgress;
|
||||||
1,
|
const destPath = sourceSlot.cachedBeltPath;
|
||||||
sourceSlot.progress +
|
if (destPath) {
|
||||||
progressGrowth *
|
progressLimit += destPath.spacingToFirstItem - globalConfig.itemSpacingOnBelts;
|
||||||
this.root.hubGoals.getBeltBaseSpeed() *
|
}
|
||||||
globalConfig.itemSpacingOnBelts
|
|
||||||
);
|
if (sourceSlot.progress < progressLimit) {
|
||||||
|
// Advance items on the slot
|
||||||
|
sourceSlot.progress += progressGrowth;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we are ejecting to a belt path
|
const extraProgress = sourceSlot.progress - maxProgress;
|
||||||
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,110 +200,17 @@ 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)) {
|
if (targetAcceptorComp.tryAcceptItem(destEntity, destSlot.index, item, extraProgress)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to hand over the item
|
|
||||||
if (this.tryPassOverItem(item, destEntity, destSlot.index)) {
|
|
||||||
// Handover successful, clear slot
|
// Handover successful, clear slot
|
||||||
if (!this.root.app.settings.getAllSettings().simplifiedBelts) {
|
|
||||||
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.slot.direction, item);
|
|
||||||
}
|
|
||||||
sourceSlot.item = null;
|
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 (
|
|
||||||
undergroundBeltComp.tryAcceptExternalItem(
|
|
||||||
item,
|
|
||||||
this.root.hubGoals.getUndergroundBeltBaseSpeed()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DrawParameters} parameters
|
* @param {DrawParameters} parameters
|
||||||
* @param {MapChunkView} chunk
|
* @param {MapChunkView} chunk
|
||||||
@ -333,7 +247,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 +282,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 +304,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,29 +30,19 @@ 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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of a processor implementation
|
|
||||||
* @typedef {{
|
|
||||||
* entity: Entity,
|
|
||||||
* item: BaseItem,
|
|
||||||
* slotIndex: number
|
|
||||||
* }} ProccessingRequirementsImplementationPayload
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Object<string, (ProcessorImplementationPayload) => void>}
|
* @type {Object<string, (ProcessorImplementationPayload) => void>}
|
||||||
*/
|
*/
|
||||||
export const MOD_ITEM_PROCESSOR_HANDLERS = {};
|
export const MOD_ITEM_PROCESSOR_HANDLERS = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Object<string, (ProccessingRequirementsImplementationPayload) => boolean>}
|
* @type {Object<string, ({entity: Entity}) => boolean>}
|
||||||
*/
|
*/
|
||||||
export const MODS_PROCESSING_REQUIREMENTS = {};
|
export const MODS_CAN_PROCESS = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Object<string, ({entity: Entity}) => boolean>}
|
* @type {Object<string, ({entity: Entity}) => boolean>}
|
||||||
@ -101,8 +88,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) {
|
||||||
@ -122,19 +115,25 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
processorComp.queuedEjects.push(itemsToEject[j]);
|
processorComp.queuedEjects.push(itemsToEject[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
processorComp.ongoingCharges.shift();
|
processorComp.currentCharge = null;
|
||||||
}
|
|
||||||
}
|
// now that the charge is complete, empty the inputs now
|
||||||
|
let usedSlots = [];
|
||||||
// Check if we have an empty queue and can start a new charge
|
const acceptorComp = entity.components.ItemAcceptor;
|
||||||
if (processorComp.ongoingCharges.length < MAX_QUEUED_CHARGES) {
|
for (let i = 0; i < acceptorComp.completedInputs.length; i++) {
|
||||||
if (this.canProcess(entity)) {
|
const index = acceptorComp.completedInputs[i].slotIndex;
|
||||||
this.startNewCharge(entity);
|
if (!usedSlots.includes(index)) {
|
||||||
|
usedSlots.push(index);
|
||||||
|
acceptorComp.completedInputs.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
@ -158,7 +157,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);
|
||||||
@ -169,54 +168,14 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// input requirements are now handled in the item acceptor, which also fits better with what the acceptor is supposed to do
|
||||||
* 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;
|
|
||||||
|
|
||||||
if (MODS_PROCESSING_REQUIREMENTS[itemProcessorComp.processingRequirement]) {
|
|
||||||
return MODS_PROCESSING_REQUIREMENTS[itemProcessorComp.processingRequirement].bind(this)({
|
|
||||||
entity,
|
|
||||||
item,
|
|
||||||
slotIndex,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
if (MODS_CAN_PROCESS[processorComp.processingRequirement]) {
|
if (MODS_CAN_PROCESS[processorComp.processingRequirement]) {
|
||||||
@ -229,16 +188,35 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
// 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;
|
// Since each slot might have more than one input, don't check each slot more than once
|
||||||
|
let usedSlots = [];
|
||||||
|
for (let i = 0; i < acceptorComp.completedInputs.length; i++) {
|
||||||
|
const index = acceptorComp.completedInputs[i].slotIndex;
|
||||||
|
if (!usedSlots.includes(index)) {
|
||||||
|
usedSlots.push(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usedSlots.length >= processorComp.inputsPerCharge;
|
||||||
}
|
}
|
||||||
|
|
||||||
// QUAD PAINTER
|
// QUAD PAINTER
|
||||||
// For the quad painter, it might be possible to start processing earlier
|
// For the quad painter, it might be possible to start processing earlier
|
||||||
case enumItemProcessorRequirements.painterQuad: {
|
case enumItemProcessorRequirements.painterQuad: {
|
||||||
const pinsComp = entity.components.WiredPins;
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
const inputs = acceptorComp.completedInputs;
|
||||||
|
|
||||||
|
// split inputs efficiently
|
||||||
|
let items = new Map();
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
const input = inputs[i];
|
||||||
|
|
||||||
|
if (!items.get(input.slotIndex)) {
|
||||||
|
items.set(input.slotIndex, input.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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} */ (items.get(0));
|
||||||
if (!shapeItem) {
|
if (!shapeItem) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -267,7 +245,7 @@ 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] && !items.get(1 + i)) {
|
||||||
// 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) {
|
||||||
@ -278,7 +256,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,10 +269,25 @@ 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];
|
||||||
|
|
||||||
|
if (!items.get(input.slotIndex)) {
|
||||||
|
items.set(input.slotIndex, input.item);
|
||||||
|
extraProgress = Math.max(extraProgress, input.extraProgress);
|
||||||
|
//inputs.splice(i, 1);
|
||||||
|
//i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {Array<ProducedItem>} */
|
/** @type {Array<ProducedItem>} */
|
||||||
const outItems = [];
|
const outItems = [];
|
||||||
@ -309,7 +301,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
entity,
|
entity,
|
||||||
items,
|
items,
|
||||||
outItems,
|
outItems,
|
||||||
inputCount: processorComp.inputCount,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Track produced items
|
// Track produced items
|
||||||
@ -317,23 +308,23 @@ 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();
|
|
||||||
processorComp.inputCount = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -478,7 +469,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -602,8 +600,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;
|
||||||
|
@ -24,7 +24,9 @@ export class ItemProducerSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.item = network.currentValue;
|
this.item = network.currentValue;
|
||||||
ejectorComp.tryEject(0, this.item);
|
|
||||||
|
// Basically start ejecting at the exit of the ejector. Hacky, but who cares. It works, and its not in the base game :)
|
||||||
|
ejectorComp.tryEject(0, this.item, 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
import { globalConfig } from "../../core/config";
|
||||||
import { DrawParameters } from "../../core/draw_parameters";
|
import { DrawParameters } from "../../core/draw_parameters";
|
||||||
import { enumDirectionToVector } from "../../core/vector";
|
import { enumDirectionToVector } from "../../core/vector";
|
||||||
import { BaseItem } from "../base_item";
|
|
||||||
import { MinerComponent } from "../components/miner";
|
import { MinerComponent } from "../components/miner";
|
||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||||
@ -31,20 +30,18 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
let miningSpeed = this.root.hubGoals.getMinerBaseSpeed();
|
let progressGrowth = this.root.dynamicTickrate.deltaSeconds * this.root.hubGoals.getMinerBaseSpeed();
|
||||||
|
|
||||||
|
const targetProgress = 1;
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.instantMiners) {
|
if (G_IS_DEV && globalConfig.debug.instantMiners) {
|
||||||
miningSpeed *= 100;
|
progressGrowth = targetProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 minerComp = entity.components.Miner;
|
const minerComp = entity.components.Miner;
|
||||||
|
|
||||||
// Reset everything on recompute
|
|
||||||
if (this.needsRecompute) {
|
|
||||||
minerComp.cachedChainedMiner = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if miner is above an actual tile
|
// Check if miner is above an actual tile
|
||||||
if (!minerComp.cachedMinedItem) {
|
if (!minerComp.cachedMinedItem) {
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
@ -58,25 +55,64 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
minerComp.cachedMinedItem = tileBelow;
|
minerComp.cachedMinedItem = tileBelow;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, try to get rid of chained items
|
// Reset everything on recompute
|
||||||
if (minerComp.itemChainBuffer.length > 0) {
|
if (this.needsRecompute) {
|
||||||
if (this.tryPerformMinerEject(entity, minerComp.itemChainBuffer[0])) {
|
minerComp.cachedChainedMiner = null;
|
||||||
minerComp.itemChainBuffer.shift();
|
minerComp.cachedExitMiner = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are a chained miner
|
||||||
|
if (minerComp.chainable) {
|
||||||
|
if (!minerComp.cachedChainedMiner) {
|
||||||
|
minerComp.cachedChainedMiner = this.findChainedMiner(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't calculate on the same tick as recompute, or so miners wont have caches yet
|
||||||
|
if (minerComp.cachedChainedMiner && !minerComp.cachedExitMiner && !this.needsRecompute) {
|
||||||
|
minerComp.cachedExitMiner = this.findExitMiner(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we now have a target at the end of the chain - if so, that's what we will progress
|
||||||
|
const exitEntity = minerComp.cachedExitMiner;
|
||||||
|
if (exitEntity) {
|
||||||
|
const exitMinerComp = exitEntity.components.Miner;
|
||||||
|
if (exitMinerComp.progress < targetProgress + 0.5) {
|
||||||
|
// we can add on some extra progress
|
||||||
|
exitMinerComp.progress += progressGrowth;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mineDuration = 1 / miningSpeed;
|
//make sure progress never gets out of control
|
||||||
const timeSinceMine = this.root.time.now() - minerComp.lastMiningTime;
|
minerComp.progress = Math.min(minerComp.progress, targetProgress + 0.5);
|
||||||
if (timeSinceMine > mineDuration) {
|
if (minerComp.progress >= targetProgress) {
|
||||||
// Store how much we overflowed
|
// We can try to eject
|
||||||
const buffer = Math.min(timeSinceMine - mineDuration, this.root.dynamicTickrate.deltaSeconds);
|
const extraProgress = minerComp.progress - targetProgress;
|
||||||
|
|
||||||
if (this.tryPerformMinerEject(entity, minerComp.cachedMinedItem)) {
|
const ejectorComp = entity.components.ItemEjector;
|
||||||
|
if (ejectorComp.tryEject(0, minerComp.cachedMinedItem, extraProgress)) {
|
||||||
// Analytics hook
|
// Analytics hook
|
||||||
this.root.signals.itemProduced.dispatch(minerComp.cachedMinedItem);
|
this.root.signals.itemProduced.dispatch(minerComp.cachedMinedItem);
|
||||||
// Store mining time
|
|
||||||
minerComp.lastMiningTime = this.root.time.now() - buffer;
|
minerComp.progress -= targetProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minerComp.progress < targetProgress) {
|
||||||
|
minerComp.progress += progressGrowth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minerComp.progress >= targetProgress) {
|
||||||
|
// We can try to eject
|
||||||
|
const extraProgress = minerComp.progress - targetProgress;
|
||||||
|
|
||||||
|
const ejectorComp = entity.components.ItemEjector;
|
||||||
|
if (ejectorComp.tryEject(0, minerComp.cachedMinedItem, extraProgress)) {
|
||||||
|
// Analytics hook
|
||||||
|
this.root.signals.itemProduced.dispatch(minerComp.cachedMinedItem);
|
||||||
|
|
||||||
|
minerComp.progress -= targetProgress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,6 +129,7 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
findChainedMiner(entity) {
|
findChainedMiner(entity) {
|
||||||
const ejectComp = entity.components.ItemEjector;
|
const ejectComp = entity.components.ItemEjector;
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
|
const minedItem = entity.components.Miner.cachedMinedItem;
|
||||||
const contentsBelow = this.root.map.getLowerLayerContentXY(staticComp.origin.x, staticComp.origin.y);
|
const contentsBelow = this.root.map.getLowerLayerContentXY(staticComp.origin.x, staticComp.origin.y);
|
||||||
if (!contentsBelow) {
|
if (!contentsBelow) {
|
||||||
// This miner has no contents
|
// This miner has no contents
|
||||||
@ -109,7 +146,11 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
// Check if we are connected to another miner and thus do not eject directly
|
// Check if we are connected to another miner and thus do not eject directly
|
||||||
if (targetContents) {
|
if (targetContents) {
|
||||||
const targetMinerComp = targetContents.components.Miner;
|
const targetMinerComp = targetContents.components.Miner;
|
||||||
if (targetMinerComp && targetMinerComp.chainable) {
|
if (
|
||||||
|
targetMinerComp &&
|
||||||
|
targetMinerComp.chainable &&
|
||||||
|
targetMinerComp.cachedMinedItem == minedItem
|
||||||
|
) {
|
||||||
const targetLowerLayer = this.root.map.getLowerLayerContentXY(targetTile.x, targetTile.y);
|
const targetLowerLayer = this.root.map.getLowerLayerContentXY(targetTile.x, targetTile.y);
|
||||||
if (targetLowerLayer) {
|
if (targetLowerLayer) {
|
||||||
return targetContents;
|
return targetContents;
|
||||||
@ -121,39 +162,37 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Finds the target exit miner for a given entity
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
* @param {BaseItem} item
|
* @returns {Entity|false} The exit miner entity or null if not found
|
||||||
*/
|
*/
|
||||||
tryPerformMinerEject(entity, item) {
|
findExitMiner(entity) {
|
||||||
const minerComp = entity.components.Miner;
|
const minerComp = entity.components.Miner;
|
||||||
const ejectComp = entity.components.ItemEjector;
|
// Recompute exit miner if we are not at the front
|
||||||
|
let targetEntity = minerComp.cachedChainedMiner;
|
||||||
|
|
||||||
// Check if we are a chained miner
|
const ourPosition = entity.components.StaticMapEntity.origin;
|
||||||
if (minerComp.chainable) {
|
|
||||||
const targetEntity = minerComp.cachedChainedMiner;
|
|
||||||
|
|
||||||
// Check if the cache has to get recomputed
|
/** @type {Entity|null|false} */
|
||||||
if (targetEntity === null) {
|
let nextTarget = targetEntity;
|
||||||
minerComp.cachedChainedMiner = this.findChainedMiner(entity);
|
while (nextTarget) {
|
||||||
}
|
targetEntity = nextTarget;
|
||||||
|
if (targetEntity.components.StaticMapEntity.origin == ourPosition) {
|
||||||
// Check if we now have a target
|
// we are in a loop, do nothing
|
||||||
if (targetEntity) {
|
targetEntity = null;
|
||||||
const targetMinerComp = targetEntity.components.Miner;
|
break;
|
||||||
if (targetMinerComp.tryAcceptChainedItem(item)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const targetMinerComp = targetEntity.components.Miner;
|
||||||
|
nextTarget = targetMinerComp.cachedChainedMiner;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seems we are a regular miner or at the end of a row, try actually ejecting
|
if (targetEntity) {
|
||||||
if (ejectComp.tryEject(0, item)) {
|
const targetMinerComp = targetEntity.components.Miner;
|
||||||
return true;
|
if (targetMinerComp.cachedMinedItem == minerComp.cachedMinedItem) {
|
||||||
|
// only chain the same items
|
||||||
|
return targetEntity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import { SavegameInterface_V1008 } from "./schemas/1008";
|
|||||||
import { SavegameInterface_V1009 } from "./schemas/1009";
|
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||||
import { MODS } from "../mods/modloader";
|
import { MODS } from "../mods/modloader";
|
||||||
import { SavegameInterface_V1010 } from "./schemas/1010";
|
import { SavegameInterface_V1010 } from "./schemas/1010";
|
||||||
|
import { SavegameInterface_V1011 } from "./schemas/1011";
|
||||||
|
|
||||||
const logger = createLogger("savegame");
|
const logger = createLogger("savegame");
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static getCurrentVersion() {
|
static getCurrentVersion() {
|
||||||
return 1010;
|
return 1011;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,6 +169,11 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
data.version = 1010;
|
data.version = 1010;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.version === 1010) {
|
||||||
|
SavegameInterface_V1011.migrate1010to1011(data);
|
||||||
|
data.version = 1011;
|
||||||
|
}
|
||||||
|
|
||||||
return ExplainedResult.good();
|
return ExplainedResult.good();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import { SavegameInterface_V1007 } from "./schemas/1007";
|
|||||||
import { SavegameInterface_V1008 } from "./schemas/1008";
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
import { SavegameInterface_V1009 } from "./schemas/1009";
|
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||||
import { SavegameInterface_V1010 } from "./schemas/1010";
|
import { SavegameInterface_V1010 } from "./schemas/1010";
|
||||||
|
import { SavegameInterface_V1011 } from "./schemas/1011";
|
||||||
|
|
||||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||||
export const savegameInterfaces = {
|
export const savegameInterfaces = {
|
||||||
@ -25,6 +26,7 @@ export const savegameInterfaces = {
|
|||||||
1008: SavegameInterface_V1008,
|
1008: SavegameInterface_V1008,
|
||||||
1009: SavegameInterface_V1009,
|
1009: SavegameInterface_V1009,
|
||||||
1010: SavegameInterface_V1010,
|
1010: SavegameInterface_V1010,
|
||||||
|
1011: SavegameInterface_V1011,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger("savegame_interface_registry");
|
const logger = createLogger("savegame_interface_registry");
|
||||||
|
44
src/js/savegame/schemas/1011.js
Normal file
44
src/js/savegame/schemas/1011.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { createLogger } from "../../core/logging.js";
|
||||||
|
import { ItemProcessorComponent } from "../../game/components/item_processor.js";
|
||||||
|
import { MinerComponent } from "../../game/components/miner.js";
|
||||||
|
import { Entity } from "../../game/entity.js";
|
||||||
|
import { SavegameInterface_V1010 } from "./1010.js";
|
||||||
|
|
||||||
|
const schema = require("./1011.json");
|
||||||
|
const logger = createLogger("savegame_interface/1011");
|
||||||
|
|
||||||
|
export class SavegameInterface_V1011 extends SavegameInterface_V1010 {
|
||||||
|
getVersion() {
|
||||||
|
return 1011;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchemaUncached() {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../savegame_typedefs.js").SavegameData} data
|
||||||
|
*/
|
||||||
|
static migrate1010to1011(data) {
|
||||||
|
logger.log("Migrating 1010 to 1011");
|
||||||
|
const dump = data.dump;
|
||||||
|
if (!dump) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Array<Entity} */
|
||||||
|
const entities = dump.entities;
|
||||||
|
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
const entity = entities[i];
|
||||||
|
const minerComp = entity.components.Miner;
|
||||||
|
if (minerComp) {
|
||||||
|
minerComp.progress = 0;
|
||||||
|
}
|
||||||
|
const processorComp = entity.components.ItemProcessor;
|
||||||
|
if (processorComp) {
|
||||||
|
processorComp.currentCharge = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
src/js/savegame/schemas/1011.json
Normal file
5
src/js/savegame/schemas/1011.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [],
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user