mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Initial commit
This commit is contained in:
201
src/js/game/systems/belt.js
Normal file
201
src/js/game/systems/belt.js
Normal file
@@ -0,0 +1,201 @@
|
||||
import { Math_radians, Math_min } from "../../core/builtins";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { AtlasSprite } from "../../core/sprites";
|
||||
import { BeltComponent } from "../components/belt";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
const BELT_ANIM_COUNT = 6;
|
||||
|
||||
export class BeltSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [BeltComponent]);
|
||||
/**
|
||||
* @type {Object.<enumDirection, Array<AtlasSprite>>}
|
||||
*/
|
||||
this.beltSprites = {
|
||||
[enumDirection.top]: Loader.getSprite("sprites/belt/forward_0.png"),
|
||||
[enumDirection.left]: Loader.getSprite("sprites/belt/left_0.png"),
|
||||
[enumDirection.right]: Loader.getSprite("sprites/belt/right_0.png"),
|
||||
};
|
||||
/**
|
||||
* @type {Object.<enumDirection, Array<AtlasSprite>>}
|
||||
*/
|
||||
this.beltAnimations = {
|
||||
[enumDirection.top]: [
|
||||
Loader.getSprite("sprites/belt/forward_0.png"),
|
||||
Loader.getSprite("sprites/belt/forward_1.png"),
|
||||
Loader.getSprite("sprites/belt/forward_2.png"),
|
||||
Loader.getSprite("sprites/belt/forward_3.png"),
|
||||
Loader.getSprite("sprites/belt/forward_4.png"),
|
||||
Loader.getSprite("sprites/belt/forward_5.png"),
|
||||
],
|
||||
[enumDirection.left]: [
|
||||
Loader.getSprite("sprites/belt/left_0.png"),
|
||||
Loader.getSprite("sprites/belt/left_1.png"),
|
||||
Loader.getSprite("sprites/belt/left_2.png"),
|
||||
Loader.getSprite("sprites/belt/left_3.png"),
|
||||
Loader.getSprite("sprites/belt/left_4.png"),
|
||||
Loader.getSprite("sprites/belt/left_5.png"),
|
||||
],
|
||||
[enumDirection.right]: [
|
||||
Loader.getSprite("sprites/belt/right_0.png"),
|
||||
Loader.getSprite("sprites/belt/right_1.png"),
|
||||
Loader.getSprite("sprites/belt/right_2.png"),
|
||||
Loader.getSprite("sprites/belt/right_3.png"),
|
||||
Loader.getSprite("sprites/belt/right_4.png"),
|
||||
Loader.getSprite("sprites/belt/right_5.png"),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityItems.bind(this));
|
||||
}
|
||||
|
||||
update() {
|
||||
const beltSpeed = this.root.hubGoals.getBeltBaseSpeed() * globalConfig.physicsDeltaSeconds;
|
||||
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const beltComp = entity.components.Belt;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const items = beltComp.sortedItems;
|
||||
if (items.length === 0) {
|
||||
// Fast out for performance
|
||||
continue;
|
||||
}
|
||||
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
let maxProgress = 1;
|
||||
|
||||
// When ejecting, we can not go further than the item spacing since it
|
||||
// will be on the corner
|
||||
if (ejectorComp.isAnySlotEjecting()) {
|
||||
maxProgress = 1 - globalConfig.itemSpacingOnBelts;
|
||||
} else {
|
||||
// Find follow up belt to make sure we don't clash items
|
||||
const followUpDirection = staticComp.localDirectionToWorld(beltComp.direction);
|
||||
const followUpVector = enumDirectionToVector[followUpDirection];
|
||||
|
||||
const followUpTile = staticComp.origin.add(followUpVector);
|
||||
const followUpEntity = this.root.map.getTileContent(followUpTile);
|
||||
|
||||
if (followUpEntity) {
|
||||
const followUpBeltComp = followUpEntity.components.Belt;
|
||||
if (followUpBeltComp) {
|
||||
const spacingOnBelt = followUpBeltComp.getDistanceToFirstItemCenter();
|
||||
maxProgress = Math_min(1, 1 - globalConfig.itemSpacingOnBelts + spacingOnBelt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let speedMultiplier = 1;
|
||||
if (beltComp.direction !== enumDirection.top) {
|
||||
// Shaped belts are longer, thus being quicker
|
||||
speedMultiplier = 1.41;
|
||||
}
|
||||
|
||||
for (let itemIndex = items.length - 1; itemIndex >= 0; --itemIndex) {
|
||||
const itemAndProgress = items[itemIndex];
|
||||
|
||||
const newProgress = itemAndProgress[0] + speedMultiplier * beltSpeed;
|
||||
if (newProgress >= 1.0) {
|
||||
// Try to give this item to a new belt
|
||||
const freeSlot = ejectorComp.getFirstFreeSlot();
|
||||
|
||||
if (freeSlot === null) {
|
||||
// So, we don't have a free slot - damned!
|
||||
itemAndProgress[0] = 1.0;
|
||||
maxProgress = 1 - globalConfig.itemSpacingOnBelts;
|
||||
} else {
|
||||
// We got a free slot, remove this item and keep it on the ejector slot
|
||||
if (!ejectorComp.tryEject(freeSlot, itemAndProgress[1])) {
|
||||
assert(false, "Ejection failed");
|
||||
}
|
||||
items.splice(itemIndex, 1);
|
||||
maxProgress = 1;
|
||||
}
|
||||
} else {
|
||||
itemAndProgress[0] = Math_min(newProgress, maxProgress);
|
||||
maxProgress = itemAndProgress[0] - globalConfig.itemSpacingOnBelts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
if (parameters.zoomLevel < globalConfig.mapChunkOverviewMinZoom) {
|
||||
return;
|
||||
1;
|
||||
}
|
||||
|
||||
const speedMultiplier = this.root.hubGoals.getBeltBaseSpeed();
|
||||
|
||||
// SYNC with systems/item_processor.js:drawEntityUnderlays!
|
||||
// 126 / 42 is the exact animation speed of the png animation
|
||||
const animationIndex = Math.floor(
|
||||
(this.root.time.now() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42
|
||||
);
|
||||
const contents = chunk.contents;
|
||||
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
|
||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||
const entity = contents[x][y];
|
||||
|
||||
if (entity && entity.components.Belt) {
|
||||
const direction = entity.components.Belt.direction;
|
||||
const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT];
|
||||
|
||||
entity.components.StaticMapEntity.drawSpriteOnFullEntityBounds(
|
||||
parameters,
|
||||
sprite,
|
||||
0,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntityItems(parameters, entity) {
|
||||
const beltComp = entity.components.Belt;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
const items = beltComp.sortedItems;
|
||||
|
||||
if (items.length === 0) {
|
||||
// Fast out for performance
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
const itemAndProgress = items[i];
|
||||
|
||||
// Nice would be const [pos, item] = itemAndPos; but that gets polyfilled and is super slow then
|
||||
const progress = itemAndProgress[0];
|
||||
const item = itemAndProgress[1];
|
||||
|
||||
const position = staticComp.applyRotationToVector(beltComp.transformBeltToLocalSpace(progress));
|
||||
|
||||
item.draw(
|
||||
(staticComp.origin.x + position.x + 0.5) * globalConfig.tileSize,
|
||||
(staticComp.origin.y + position.y + 0.5) * globalConfig.tileSize,
|
||||
parameters
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/js/game/systems/hub.js
Normal file
83
src/js/game/systems/hub.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { HubComponent } from "../components/hub";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Entity } from "../entity";
|
||||
import { formatBigNumber } from "../../core/utils";
|
||||
|
||||
export class HubSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [HubComponent]);
|
||||
}
|
||||
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this));
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
|
||||
const hubComponent = entity.components.Hub;
|
||||
|
||||
const queue = hubComponent.definitionsToAnalyze;
|
||||
for (let k = 0; k < queue.length; ++k) {
|
||||
const definition = queue[k];
|
||||
this.root.hubGoals.handleDefinitionDelivered(definition);
|
||||
}
|
||||
|
||||
hubComponent.definitionsToAnalyze = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntity(parameters, entity) {
|
||||
const context = parameters.context;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
const pos = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||
|
||||
const definition = this.root.hubGoals.currentGoal.definition;
|
||||
|
||||
definition.draw(pos.x - 25, pos.y - 10, parameters, 40);
|
||||
|
||||
const goals = this.root.hubGoals.currentGoal;
|
||||
|
||||
const textOffsetX = 2;
|
||||
const textOffsetY = -6;
|
||||
|
||||
// Deliver count
|
||||
context.font = "bold 25px GameFont";
|
||||
context.fillStyle = "#64666e";
|
||||
context.textAlign = "left";
|
||||
context.fillText(
|
||||
"" + formatBigNumber(this.root.hubGoals.getCurrentGoalDelivered()),
|
||||
pos.x + textOffsetX,
|
||||
pos.y + textOffsetY
|
||||
);
|
||||
|
||||
// Required
|
||||
context.font = "13px GameFont";
|
||||
context.fillStyle = "#a4a6b0";
|
||||
context.fillText(
|
||||
"/ " + formatBigNumber(goals.required),
|
||||
pos.x + textOffsetX,
|
||||
pos.y + textOffsetY + 13
|
||||
);
|
||||
|
||||
// Reward
|
||||
context.font = "bold 11px GameFont";
|
||||
context.fillStyle = "#fd0752";
|
||||
context.textAlign = "center";
|
||||
context.fillText(goals.reward.toUpperCase(), pos.x, pos.y + 46);
|
||||
|
||||
// Level
|
||||
context.font = "bold 11px GameFont";
|
||||
context.fillStyle = "#fff";
|
||||
context.fillText("" + this.root.hubGoals.level, pos.x - 42, pos.y - 36);
|
||||
|
||||
context.textAlign = "left";
|
||||
}
|
||||
}
|
||||
173
src/js/game/systems/item_ejector.js
Normal file
173
src/js/game/systems/item_ejector.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { enumDirectionToVector, Vector } from "../../core/vector";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { Math_min } from "../../core/builtins";
|
||||
|
||||
export class ItemEjectorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [ItemEjectorComponent]);
|
||||
}
|
||||
|
||||
update() {
|
||||
const effectiveBeltSpeed = this.root.hubGoals.getBeltBaseSpeed();
|
||||
const progressGrowth = (effectiveBeltSpeed / 0.5) * globalConfig.physicsDeltaSeconds;
|
||||
|
||||
// Try to find acceptors for every ejector
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
// For every ejector slot, try to find an acceptor
|
||||
for (let ejectorSlotIndex = 0; ejectorSlotIndex < ejectorComp.slots.length; ++ejectorSlotIndex) {
|
||||
const ejectorSlot = ejectorComp.slots[ejectorSlotIndex];
|
||||
const ejectingItem = ejectorSlot.item;
|
||||
if (!ejectingItem) {
|
||||
// No item ejected
|
||||
continue;
|
||||
}
|
||||
|
||||
ejectorSlot.progress = Math_min(1, ejectorSlot.progress + progressGrowth);
|
||||
if (ejectorSlot.progress < 1.0) {
|
||||
// Still ejecting
|
||||
continue;
|
||||
}
|
||||
|
||||
// Figure out where and into which direction we eject items
|
||||
const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos);
|
||||
const ejectSlotWsDirection = staticComp.localDirectionToWorld(ejectorSlot.direction);
|
||||
const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
|
||||
const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
|
||||
|
||||
// Try to find the given acceptor component to take the item
|
||||
const targetEntity = this.root.map.getTileContent(ejectSlotTargetWsTile);
|
||||
if (!targetEntity) {
|
||||
// No consumer for item
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
|
||||
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
||||
if (!targetAcceptorComp) {
|
||||
// Entity doesn't accept items
|
||||
continue;
|
||||
}
|
||||
|
||||
const matchingSlot = targetAcceptorComp.findMatchingSlot(
|
||||
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
|
||||
targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection)
|
||||
);
|
||||
|
||||
if (!matchingSlot) {
|
||||
// No matching slot found
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!targetAcceptorComp.canAcceptItem(matchingSlot.index, ejectingItem)) {
|
||||
// Can not accept item
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
this.tryPassOverItem(
|
||||
ejectingItem,
|
||||
targetEntity,
|
||||
matchingSlot.index,
|
||||
matchingSlot.acceptedDirection
|
||||
)
|
||||
) {
|
||||
ejectorSlot.item = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseItem} item
|
||||
* @param {Entity} receiver
|
||||
* @param {number} slotIndex
|
||||
* @param {string} localDirection
|
||||
*/
|
||||
tryPassOverItem(item, receiver, slotIndex, localDirection) {
|
||||
// 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.
|
||||
// Also its just a few cases (hope it stays like this .. :x).
|
||||
|
||||
const beltComp = receiver.components.Belt;
|
||||
if (beltComp) {
|
||||
// Ayy, its a belt!
|
||||
if (beltComp.canAcceptNewItem(localDirection)) {
|
||||
beltComp.takeNewItem(item, localDirection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const itemProcessorComp = receiver.components.ItemProcessor;
|
||||
if (itemProcessorComp) {
|
||||
// Its an item processor ..
|
||||
if (itemProcessorComp.tryTakeItem(item, slotIndex, localDirection)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const undergroundBeltCmop = receiver.components.UndergroundBelt;
|
||||
if (undergroundBeltCmop) {
|
||||
// Its an underground belt. yay.
|
||||
if (
|
||||
undergroundBeltCmop.tryAcceptExternalItem(
|
||||
item,
|
||||
this.root.hubGoals.getUndergroundBeltBaseSpeed()
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawSingleEntity.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawSingleEntity(parameters, entity) {
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
for (let i = 0; i < ejectorComp.slots.length; ++i) {
|
||||
const slot = ejectorComp.slots[i];
|
||||
const ejectedItem = slot.item;
|
||||
if (!ejectedItem) {
|
||||
// No item
|
||||
continue;
|
||||
}
|
||||
|
||||
const realPosition = slot.pos.rotateFastMultipleOf90(staticComp.rotationDegrees);
|
||||
const realDirection = Vector.transformDirectionFromMultipleOf90(
|
||||
slot.direction,
|
||||
staticComp.rotationDegrees
|
||||
);
|
||||
const realDirectionVector = enumDirectionToVector[realDirection];
|
||||
|
||||
const tileX =
|
||||
staticComp.origin.x + realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
|
||||
const tileY =
|
||||
staticComp.origin.y + realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
|
||||
|
||||
const worldX = tileX * globalConfig.tileSize;
|
||||
const worldY = tileY * globalConfig.tileSize;
|
||||
|
||||
ejectedItem.draw(worldX, worldY, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
341
src/js/game/systems/item_processor.js
Normal file
341
src/js/game/systems/item_processor.js
Normal file
@@ -0,0 +1,341 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { ItemProcessorComponent, enumItemProcessorTypes } from "../components/item_processor";
|
||||
import { Math_max, Math_radians } from "../../core/builtins";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { ShapeItem } from "../items/shape_item";
|
||||
import { enumDirectionToVector, enumDirection, enumDirectionToAngle } from "../../core/vector";
|
||||
import { ColorItem } from "../items/color_item";
|
||||
import { enumColorMixingResults } from "../colors";
|
||||
import { drawRotatedSprite } from "../../core/draw_utils";
|
||||
|
||||
export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [ItemProcessorComponent]);
|
||||
|
||||
this.sprites = {};
|
||||
for (const key in enumItemProcessorTypes) {
|
||||
this.sprites[key] = Loader.getSprite("sprites/buildings/" + key + ".png");
|
||||
}
|
||||
|
||||
this.underlayBeltSprites = [
|
||||
Loader.getSprite("sprites/belt/forward_0.png"),
|
||||
Loader.getSprite("sprites/belt/forward_1.png"),
|
||||
Loader.getSprite("sprites/belt/forward_2.png"),
|
||||
Loader.getSprite("sprites/belt/forward_3.png"),
|
||||
Loader.getSprite("sprites/belt/forward_4.png"),
|
||||
Loader.getSprite("sprites/belt/forward_5.png"),
|
||||
];
|
||||
}
|
||||
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this));
|
||||
}
|
||||
|
||||
drawUnderlays(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityUnderlays.bind(this));
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
|
||||
// First of all, process the current recipe
|
||||
processorComp.secondsUntilEject = Math_max(
|
||||
0,
|
||||
processorComp.secondsUntilEject - globalConfig.physicsDeltaSeconds
|
||||
);
|
||||
|
||||
// Also, process item consumption animations to avoid items popping from the belts
|
||||
for (let animIndex = 0; animIndex < processorComp.itemConsumptionAnimations.length; ++animIndex) {
|
||||
const anim = processorComp.itemConsumptionAnimations[animIndex];
|
||||
anim.animProgress +=
|
||||
globalConfig.physicsDeltaSeconds * this.root.hubGoals.getBeltBaseSpeed() * 2;
|
||||
if (anim.animProgress > 1) {
|
||||
processorComp.itemConsumptionAnimations.splice(animIndex, 1);
|
||||
animIndex -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have any finished items we can eject
|
||||
if (
|
||||
processorComp.secondsUntilEject === 0 && // it was processed in time
|
||||
processorComp.itemsToEject.length > 0 // we have some items left to eject
|
||||
) {
|
||||
for (let itemIndex = 0; itemIndex < processorComp.itemsToEject.length; ++itemIndex) {
|
||||
const { item, requiredSlot, preferredSlot } = processorComp.itemsToEject[itemIndex];
|
||||
|
||||
let slot = null;
|
||||
if (requiredSlot !== null && requiredSlot !== undefined) {
|
||||
// We have a slot override, check if that is free
|
||||
if (ejectorComp.canEjectOnSlot(requiredSlot)) {
|
||||
slot = requiredSlot;
|
||||
}
|
||||
} else if (preferredSlot !== null && preferredSlot !== undefined) {
|
||||
// We have a slot preference, try using it but otherwise use a free slot
|
||||
if (ejectorComp.canEjectOnSlot(preferredSlot)) {
|
||||
slot = preferredSlot;
|
||||
} else {
|
||||
slot = ejectorComp.getFirstFreeSlot();
|
||||
}
|
||||
} else {
|
||||
// We can eject on any slot
|
||||
slot = ejectorComp.getFirstFreeSlot();
|
||||
}
|
||||
|
||||
if (slot !== null) {
|
||||
// Alright, we can actually eject
|
||||
if (!ejectorComp.tryEject(slot, item)) {
|
||||
assert(false, "Failed to eject");
|
||||
} else {
|
||||
processorComp.itemsToEject.splice(itemIndex, 1);
|
||||
itemIndex -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have an empty queue and can start a new charge
|
||||
if (processorComp.itemsToEject.length === 0) {
|
||||
if (processorComp.inputSlots.length === processorComp.inputsPerCharge) {
|
||||
this.startNewCharge(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new charge for the entity
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
startNewCharge(entity) {
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
|
||||
// First, take items
|
||||
const items = processorComp.inputSlots;
|
||||
processorComp.inputSlots = [];
|
||||
|
||||
const baseSpeed = this.root.hubGoals.getProcessorBaseSpeed(processorComp.type);
|
||||
processorComp.secondsUntilEject = 1 / baseSpeed;
|
||||
|
||||
/** @type {Array<{item: BaseItem, requiredSlot?: number, preferredSlot?: number}>} */
|
||||
const outItems = [];
|
||||
|
||||
// DO SOME MAGIC
|
||||
|
||||
switch (processorComp.type) {
|
||||
// SPLITTER
|
||||
case enumItemProcessorTypes.splitter: {
|
||||
let nextSlot = processorComp.nextOutputSlot++ % 2;
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
outItems.push({
|
||||
item: items[i].item,
|
||||
preferredSlot: (nextSlot + i) % 2,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// CUTTER
|
||||
case enumItemProcessorTypes.cutter: {
|
||||
const inputItem = /** @type {ShapeItem} */ (items[0].item);
|
||||
assert(inputItem instanceof ShapeItem, "Input for cut is not a shape");
|
||||
const inputDefinition = inputItem.definition;
|
||||
|
||||
const [cutDefinition1, cutDefinition2] = this.root.shapeDefinitionMgr.shapeActionCutHalf(
|
||||
inputDefinition
|
||||
);
|
||||
|
||||
if (!cutDefinition1.isEntirelyEmpty()) {
|
||||
outItems.push({
|
||||
item: new ShapeItem(cutDefinition1),
|
||||
requiredSlot: 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (!cutDefinition2.isEntirelyEmpty()) {
|
||||
outItems.push({
|
||||
item: new ShapeItem(cutDefinition2),
|
||||
requiredSlot: 1,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// ROTATER
|
||||
case enumItemProcessorTypes.rotater: {
|
||||
const inputItem = /** @type {ShapeItem} */ (items[0].item);
|
||||
assert(inputItem instanceof ShapeItem, "Input for cut is not a shape");
|
||||
const inputDefinition = inputItem.definition;
|
||||
|
||||
const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(inputDefinition);
|
||||
outItems.push({
|
||||
item: new ShapeItem(rotatedDefinition),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// STACKER
|
||||
|
||||
case enumItemProcessorTypes.stacker: {
|
||||
const item1 = items[0];
|
||||
const item2 = items[1];
|
||||
|
||||
const lowerItem = /** @type {ShapeItem} */ (item1.sourceSlot === 0 ? item1.item : item2.item);
|
||||
const upperItem = /** @type {ShapeItem} */ (item1.sourceSlot === 1 ? item1.item : item2.item);
|
||||
assert(lowerItem instanceof ShapeItem, "Input for lower stack is not a shape");
|
||||
assert(upperItem instanceof ShapeItem, "Input for upper stack is not a shape");
|
||||
|
||||
const stackedDefinition = this.root.shapeDefinitionMgr.shapeActionStack(
|
||||
lowerItem.definition,
|
||||
upperItem.definition
|
||||
);
|
||||
outItems.push({
|
||||
item: new ShapeItem(stackedDefinition),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// TRASH
|
||||
|
||||
case enumItemProcessorTypes.trash: {
|
||||
// Well this one is easy .. simply do nothing with the item
|
||||
break;
|
||||
}
|
||||
|
||||
// MIXER
|
||||
|
||||
case enumItemProcessorTypes.mixer: {
|
||||
// Find both colors and combine them
|
||||
const item1 = /** @type {ColorItem} */ (items[0].item);
|
||||
const item2 = /** @type {ColorItem} */ (items[1].item);
|
||||
assert(item1 instanceof ColorItem, "Input for color mixer is not a color");
|
||||
assert(item2 instanceof ColorItem, "Input for color mixer is not a color");
|
||||
|
||||
const color1 = item1.color;
|
||||
const color2 = item2.color;
|
||||
|
||||
// Try finding mixer color, and if we can't mix it we simply return the same color
|
||||
const mixedColor = enumColorMixingResults[color1][color2];
|
||||
let resultColor = color1;
|
||||
if (mixedColor) {
|
||||
resultColor = mixedColor;
|
||||
}
|
||||
outItems.push({
|
||||
item: new ColorItem(resultColor),
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// PAINTER
|
||||
|
||||
case enumItemProcessorTypes.painter: {
|
||||
const item1 = items[0];
|
||||
const item2 = items[1];
|
||||
|
||||
const shapeItem = /** @type {ShapeItem} */ (item1.sourceSlot === 0 ? item1.item : item2.item);
|
||||
const colorItem = /** @type {ColorItem} */ (item1.sourceSlot === 1 ? item1.item : item2.item);
|
||||
|
||||
const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith(
|
||||
shapeItem.definition,
|
||||
colorItem.color
|
||||
);
|
||||
|
||||
outItems.push({
|
||||
item: new ShapeItem(colorizedDefinition),
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// HUB
|
||||
|
||||
case enumItemProcessorTypes.hub: {
|
||||
const shapeItem = /** @type {ShapeItem} */ (items[0].item);
|
||||
|
||||
const hubComponent = entity.components.Hub;
|
||||
assert(hubComponent, "Hub item processor has no hub component");
|
||||
|
||||
hubComponent.queueShapeDefinition(shapeItem.definition);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assertAlways(false, "Unkown item processor type: " + processorComp.type);
|
||||
}
|
||||
|
||||
processorComp.itemsToEject = outItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntity(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
const acceptorComp = entity.components.ItemAcceptor;
|
||||
|
||||
for (let animIndex = 0; animIndex < processorComp.itemConsumptionAnimations.length; ++animIndex) {
|
||||
const { item, slotIndex, animProgress, direction } = processorComp.itemConsumptionAnimations[
|
||||
animIndex
|
||||
];
|
||||
|
||||
const slotData = acceptorComp.slots[slotIndex];
|
||||
const slotWorldPos = staticComp.applyRotationToVector(slotData.pos).add(staticComp.origin);
|
||||
|
||||
const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
|
||||
const finalTile = slotWorldPos.subScalars(
|
||||
fadeOutDirection.x * (animProgress / 2 - 0.5),
|
||||
fadeOutDirection.y * (animProgress / 2 - 0.5)
|
||||
);
|
||||
item.draw(
|
||||
(finalTile.x + 0.5) * globalConfig.tileSize,
|
||||
(finalTile.y + 0.5) * globalConfig.tileSize,
|
||||
parameters
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntityUnderlays(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
|
||||
const underlays = processorComp.beltUnderlays;
|
||||
for (let i = 0; i < underlays.length; ++i) {
|
||||
const { pos, direction } = underlays[i];
|
||||
|
||||
const transformedPos = staticComp.localTileToWorld(pos);
|
||||
const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)];
|
||||
|
||||
// SYNC with systems/belt.js:drawSingleEntity!
|
||||
const animationIndex = Math.floor(
|
||||
(this.root.time.now() *
|
||||
this.root.hubGoals.getBeltBaseSpeed() *
|
||||
this.underlayBeltSprites.length *
|
||||
126) /
|
||||
42
|
||||
);
|
||||
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length],
|
||||
x: (transformedPos.x + 0.5) * globalConfig.tileSize,
|
||||
y: (transformedPos.y + 0.5) * globalConfig.tileSize,
|
||||
angle: Math_radians(angle),
|
||||
size: globalConfig.tileSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/js/game/systems/map_resources.js
Normal file
56
src/js/game/systems/map_resources.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { GameSystem } from "../game_system";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class MapResourcesSystem extends GameSystem {
|
||||
/**
|
||||
* Draws the map resources
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const renderItems = parameters.zoomLevel >= globalConfig.mapChunkOverviewMinZoom;
|
||||
|
||||
parameters.context.globalAlpha = 0.5;
|
||||
const layer = chunk.lowerLayer;
|
||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||
const row = layer[x];
|
||||
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
|
||||
const lowerItem = row[y];
|
||||
if (lowerItem) {
|
||||
parameters.context.fillStyle = lowerItem.getBackgroundColorAsResource();
|
||||
parameters.context.fillRect(
|
||||
(chunk.tileX + x) * globalConfig.tileSize,
|
||||
(chunk.tileY + y) * globalConfig.tileSize,
|
||||
globalConfig.tileSize,
|
||||
globalConfig.tileSize
|
||||
);
|
||||
if (renderItems) {
|
||||
lowerItem.draw(
|
||||
(chunk.tileX + x + 0.5) * globalConfig.tileSize,
|
||||
(chunk.tileY + y + 0.5) * globalConfig.tileSize,
|
||||
parameters
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameters.context.globalAlpha = 1;
|
||||
|
||||
if (!renderItems) {
|
||||
// Render patches instead
|
||||
const patches = chunk.patches;
|
||||
for (let i = 0; i < patches.length; ++i) {
|
||||
const { pos, item, size } = patches[i];
|
||||
|
||||
item.draw(
|
||||
(chunk.tileX + pos.x + 0.5) * globalConfig.tileSize,
|
||||
(chunk.tileY + pos.y + 0.5) * globalConfig.tileSize,
|
||||
parameters,
|
||||
80
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/js/game/systems/miner.js
Normal file
87
src/js/game/systems/miner.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { MinerComponent } from "../components/miner";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class MinerSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [MinerComponent]);
|
||||
}
|
||||
|
||||
update() {
|
||||
const miningSpeed = this.root.hubGoals.getMinerBaseSpeed();
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
|
||||
const minerComp = entity.components.Miner;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const ejectComp = entity.components.ItemEjector;
|
||||
|
||||
if (this.root.time.isIngameTimerExpired(minerComp.lastMiningTime, 1 / miningSpeed)) {
|
||||
if (!ejectComp.canEjectOnSlot(0)) {
|
||||
// We can't eject further
|
||||
continue;
|
||||
}
|
||||
|
||||
// Actually mine
|
||||
minerComp.lastMiningTime = this.root.time.now();
|
||||
|
||||
const lowerLayerItem = this.root.map.getLowerLayerContentXY(
|
||||
staticComp.origin.x,
|
||||
staticComp.origin.y
|
||||
);
|
||||
if (!lowerLayerItem) {
|
||||
// Nothing below;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try actually ejecting
|
||||
if (!ejectComp.tryEject(0, lowerLayerItem)) {
|
||||
assert(false, "Failed to eject");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.contents;
|
||||
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
|
||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||
const entity = contents[x][y];
|
||||
|
||||
if (entity && entity.components.Miner) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const lowerLayerItem = this.root.map.getLowerLayerContentXY(
|
||||
staticComp.origin.x,
|
||||
staticComp.origin.y
|
||||
);
|
||||
|
||||
if (lowerLayerItem) {
|
||||
const padding = 3;
|
||||
parameters.context.fillStyle = lowerLayerItem.getBackgroundColorAsResource();
|
||||
parameters.context.fillRect(
|
||||
staticComp.origin.x * globalConfig.tileSize + padding,
|
||||
staticComp.origin.y * globalConfig.tileSize + padding,
|
||||
globalConfig.tileSize - 2 * padding,
|
||||
globalConfig.tileSize - 2 * padding
|
||||
);
|
||||
}
|
||||
|
||||
if (lowerLayerItem) {
|
||||
lowerLayerItem.draw(
|
||||
(0.5 + staticComp.origin.x) * globalConfig.tileSize,
|
||||
(0.5 + staticComp.origin.y) * globalConfig.tileSize,
|
||||
parameters
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/js/game/systems/static_map_entity.js
Normal file
72
src/js/game/systems/static_map_entity.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import { GameSystem } from "../game_system";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { enumDirection } from "../../core/vector";
|
||||
|
||||
export class StaticMapEntitySystem extends GameSystem {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
this.beltOverviewSprites = {
|
||||
[enumDirection.top]: Loader.getSprite("sprites/map_overview/belt_forward.png"),
|
||||
[enumDirection.right]: Loader.getSprite("sprites/map_overview/belt_right.png"),
|
||||
[enumDirection.left]: Loader.getSprite("sprites/map_overview/belt_left.png"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the static entities
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
if (G_IS_DEV && globalConfig.debug.doNotRenderStatics) {
|
||||
return;
|
||||
}
|
||||
|
||||
const drawOutlinesOnly = parameters.zoomLevel < globalConfig.mapChunkOverviewMinZoom;
|
||||
|
||||
const contents = chunk.contents;
|
||||
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
|
||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||
const entity = contents[x][y];
|
||||
|
||||
if (entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
if (drawOutlinesOnly) {
|
||||
const rect = staticComp.getTileSpaceBounds();
|
||||
parameters.context.fillStyle = staticComp.silhouetteColor || "#aaa";
|
||||
const beltComp = entity.components.Belt;
|
||||
if (beltComp) {
|
||||
const sprite = this.beltOverviewSprites[beltComp.direction];
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 0, false);
|
||||
} else {
|
||||
parameters.context.fillRect(
|
||||
rect.x * globalConfig.tileSize,
|
||||
rect.y * globalConfig.tileSize,
|
||||
rect.w * globalConfig.tileSize,
|
||||
rect.h * globalConfig.tileSize
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const spriteKey = staticComp.spriteKey;
|
||||
if (spriteKey) {
|
||||
// Check if origin is contained to avoid drawing entities multiple times
|
||||
if (
|
||||
staticComp.origin.x >= chunk.tileX &&
|
||||
staticComp.origin.x < chunk.tileX + globalConfig.mapChunkSize &&
|
||||
staticComp.origin.y >= chunk.tileY &&
|
||||
staticComp.origin.y < chunk.tileY + globalConfig.mapChunkSize
|
||||
) {
|
||||
const sprite = Loader.getSprite(spriteKey);
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/js/game/systems/underground_belt.js
Normal file
129
src/js/game/systems/underground_belt.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { UndergroundBeltComponent, enumUndergroundBeltMode } from "../components/underground_belt";
|
||||
import { Entity } from "../entity";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { Math_max } from "../../core/builtins";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { enumDirection, enumDirectionToVector, enumDirectionToAngle } from "../../core/vector";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
|
||||
export class UndergroundBeltSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [UndergroundBeltComponent]);
|
||||
|
||||
this.beltSprites = {
|
||||
[enumUndergroundBeltMode.sender]: Loader.getSprite(
|
||||
"sprites/buildings/underground_belt_entry.png"
|
||||
),
|
||||
[enumUndergroundBeltMode.receiver]: Loader.getSprite(
|
||||
"sprites/buildings/underground_belt_exit.png"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
|
||||
const undergroundComp = entity.components.UndergroundBelt;
|
||||
|
||||
// Decrease remaining time of all items in belt
|
||||
for (let k = 0; k < undergroundComp.pendingItems.length; ++k) {
|
||||
const item = undergroundComp.pendingItems[k];
|
||||
item[1] = Math_max(0, item[1] - globalConfig.physicsDeltaSeconds);
|
||||
}
|
||||
|
||||
if (undergroundComp.mode === enumUndergroundBeltMode.sender) {
|
||||
this.handleSender(entity);
|
||||
} else {
|
||||
this.handleReceiver(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
handleSender(entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const undergroundComp = entity.components.UndergroundBelt;
|
||||
|
||||
// Check if we have any item
|
||||
|
||||
if (undergroundComp.pendingItems.length > 0) {
|
||||
const nextItemAndDuration = undergroundComp.pendingItems[0];
|
||||
const remainingTime = nextItemAndDuration[1];
|
||||
const nextItem = nextItemAndDuration[0];
|
||||
|
||||
if (remainingTime === 0) {
|
||||
// Try to find a receiver
|
||||
const searchDirection = staticComp.localDirectionToWorld(enumDirection.top);
|
||||
const searchVector = enumDirectionToVector[searchDirection];
|
||||
const targetRotation = enumDirectionToAngle[searchDirection];
|
||||
|
||||
let currentTile = staticComp.origin;
|
||||
|
||||
for (
|
||||
let searchOffset = 0;
|
||||
searchOffset < globalConfig.undergroundBeltMaxTiles;
|
||||
++searchOffset
|
||||
) {
|
||||
currentTile = currentTile.add(searchVector);
|
||||
|
||||
const contents = this.root.map.getTileContent(currentTile);
|
||||
if (contents) {
|
||||
const receiverUndergroundComp = contents.components.UndergroundBelt;
|
||||
if (receiverUndergroundComp) {
|
||||
const receiverStaticComp = contents.components.StaticMapEntity;
|
||||
if (receiverStaticComp.rotationDegrees === targetRotation) {
|
||||
if (receiverUndergroundComp.mode === enumUndergroundBeltMode.receiver) {
|
||||
// Try to pass over the item to the receiver
|
||||
if (
|
||||
receiverUndergroundComp.tryAcceptTunneledItem(
|
||||
nextItem,
|
||||
searchOffset,
|
||||
this.root.hubGoals.getUndergroundBeltBaseSpeed()
|
||||
)
|
||||
) {
|
||||
undergroundComp.pendingItems = [];
|
||||
}
|
||||
}
|
||||
|
||||
// When we hit some underground belt, always stop, no matter what
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
handleReceiver(entity) {
|
||||
const undergroundComp = entity.components.UndergroundBelt;
|
||||
|
||||
// Try to eject items, we only check the first one cuz its sorted by remaining time
|
||||
const items = undergroundComp.pendingItems;
|
||||
if (items.length > 0) {
|
||||
const nextItemAndDuration = undergroundComp.pendingItems[0];
|
||||
const remainingTime = nextItemAndDuration[1];
|
||||
const nextItem = nextItemAndDuration[0];
|
||||
|
||||
if (remainingTime <= 0) {
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
const nextSlotIndex = ejectorComp.getFirstFreeSlot();
|
||||
if (nextSlotIndex !== null) {
|
||||
if (ejectorComp.tryEject(nextSlotIndex, nextItem)) {
|
||||
items.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user