mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Rework quad painter and filter, rework logic gates
This commit is contained in:
1457
src/js/core/utils.js
1457
src/js/core/utils.js
File diff suppressed because it is too large
Load Diff
@@ -1,86 +1,91 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { LeverComponent } from "../components/lever";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { ItemProcessorComponent, enumItemProcessorTypes } from "../components/item_processor";
|
||||
|
||||
export class MetaFilterBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("filter");
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#c45c2e";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
getIsUnlocked(root) {
|
||||
// @todo
|
||||
return true;
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
return new Vector(2, 1);
|
||||
}
|
||||
|
||||
getShowWiresLayerPreview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(
|
||||
new WiredPinsComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.left,
|
||||
type: enumPinSlotType.logicalAcceptor,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemAcceptorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemEjectorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.top,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
direction: enumDirection.right,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemProcessorComponent({
|
||||
processorType: enumItemProcessorTypes.filter,
|
||||
inputsPerCharge: 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { LeverComponent } from "../components/lever";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import {
|
||||
ItemProcessorComponent,
|
||||
enumItemProcessorTypes,
|
||||
enumItemProcessorRequirements,
|
||||
} from "../components/item_processor";
|
||||
|
||||
export class MetaFilterBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("filter");
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#c45c2e";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
getIsUnlocked(root) {
|
||||
// @todo
|
||||
return true;
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
return new Vector(2, 1);
|
||||
}
|
||||
|
||||
getShowWiresLayerPreview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(
|
||||
new WiredPinsComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.left,
|
||||
type: enumPinSlotType.logicalAcceptor,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemAcceptorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemEjectorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.top,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
direction: enumDirection.right,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemProcessorComponent({
|
||||
processorType: enumItemProcessorTypes.filter,
|
||||
inputsPerCharge: 1,
|
||||
processingRequirement: enumItemProcessorRequirements.filter,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ import { enumDirection, Vector } from "../../core/vector";
|
||||
import { T } from "../../translations";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent, enumItemProcessorRequirements } from "../components/item_processor";
|
||||
import {
|
||||
enumItemProcessorTypes,
|
||||
ItemProcessorComponent,
|
||||
enumItemProcessorRequirements,
|
||||
} from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
@@ -120,8 +124,10 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
switch (variant) {
|
||||
case defaultBuildingVariant:
|
||||
case enumPainterVariants.mirrored: {
|
||||
// REGULAR PAINTER
|
||||
|
||||
if (entity.components.WiredPins) {
|
||||
entity.removeComponent(WiredPinsComponent)
|
||||
entity.removeComponent(WiredPinsComponent);
|
||||
}
|
||||
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
@@ -139,18 +145,22 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
},
|
||||
]);
|
||||
|
||||
entity.components.ItemEjector.setSlots([
|
||||
{ pos: new Vector(1, 0), direction: enumDirection.right },
|
||||
]);
|
||||
|
||||
entity.components.ItemProcessor.type = enumItemProcessorTypes.painter;
|
||||
entity.components.ItemProcessor.processingRequirement = null;
|
||||
entity.components.ItemProcessor.inputsPerCharge = 2;
|
||||
|
||||
entity.components.ItemEjector.setSlots([
|
||||
{ pos: new Vector(1, 0), direction: enumDirection.right },
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
case enumPainterVariants.double: {
|
||||
// DOUBLE PAINTER
|
||||
|
||||
if (entity.components.WiredPins) {
|
||||
entity.removeComponent(WiredPinsComponent)
|
||||
entity.removeComponent(WiredPinsComponent);
|
||||
}
|
||||
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
@@ -171,43 +181,46 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
},
|
||||
]);
|
||||
|
||||
entity.components.ItemProcessor.type = enumItemProcessorTypes.painterDouble;
|
||||
entity.components.ItemProcessor.processingRequirement = null;
|
||||
entity.components.ItemProcessor.inputsPerCharge = 3;
|
||||
|
||||
entity.components.ItemEjector.setSlots([
|
||||
{ pos: new Vector(1, 0), direction: enumDirection.right },
|
||||
]);
|
||||
|
||||
entity.components.ItemProcessor.type = enumItemProcessorTypes.painterDouble;
|
||||
entity.components.ItemProcessor.processingRequirement = null;
|
||||
entity.components.ItemProcessor.inputsPerCharge = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
case enumPainterVariants.quad: {
|
||||
// QUAD PAINTER
|
||||
|
||||
if (!entity.components.WiredPins) {
|
||||
entity.addComponent(new WiredPinsComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor
|
||||
},
|
||||
]
|
||||
}));
|
||||
entity.addComponent(new WiredPinsComponent({ slots: [] }));
|
||||
}
|
||||
|
||||
entity.components.WiredPins.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor,
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor,
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor,
|
||||
},
|
||||
]);
|
||||
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
@@ -236,15 +249,18 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
},
|
||||
]);
|
||||
|
||||
entity.components.ItemProcessor.type = enumItemProcessorTypes.painterQuad;
|
||||
entity.components.ItemProcessor.processingRequirement = enumItemProcessorRequirements.painterQuad;
|
||||
entity.components.ItemProcessor.inputsPerCharge = 5;
|
||||
|
||||
entity.components.ItemEjector.setSlots([
|
||||
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
||||
]);
|
||||
|
||||
entity.components.ItemProcessor.type = enumItemProcessorTypes.painterQuad;
|
||||
entity.components.ItemProcessor.processingRequirement =
|
||||
enumItemProcessorRequirements.painterQuad;
|
||||
entity.components.ItemProcessor.inputsPerCharge = 5;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assertAlways(false, "Unknown painter variant: " + variant);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function initComponentRegistry() {
|
||||
assert(
|
||||
// @ts-ignore
|
||||
require.context("./components", false, /.*\.js/i).keys().length ===
|
||||
gComponentRegistry.getNumEntries(),
|
||||
gComponentRegistry.getNumEntries(),
|
||||
"Not all components are registered"
|
||||
);
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ export const enumItemProcessorTypes = {
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumItemProcessorRequirements = {
|
||||
painterQuad: "painterQuad"
|
||||
painterQuad: "painterQuad",
|
||||
filter: "filter",
|
||||
};
|
||||
|
||||
export class ItemProcessorComponent extends Component {
|
||||
@@ -71,7 +72,7 @@ export class ItemProcessorComponent extends Component {
|
||||
constructor({
|
||||
processorType = enumItemProcessorTypes.splitter,
|
||||
processingRequirement = null,
|
||||
inputsPerCharge = 1
|
||||
inputsPerCharge = 1,
|
||||
}) {
|
||||
super();
|
||||
|
||||
|
||||
@@ -1,165 +1,171 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "./root";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../core/logging";
|
||||
import { BeltSystem } from "./systems/belt";
|
||||
import { ItemEjectorSystem } from "./systems/item_ejector";
|
||||
import { MapResourcesSystem } from "./systems/map_resources";
|
||||
import { MinerSystem } from "./systems/miner";
|
||||
import { ItemProcessorSystem } from "./systems/item_processor";
|
||||
import { UndergroundBeltSystem } from "./systems/underground_belt";
|
||||
import { HubSystem } from "./systems/hub";
|
||||
import { StaticMapEntitySystem } from "./systems/static_map_entity";
|
||||
import { ItemAcceptorSystem } from "./systems/item_acceptor";
|
||||
import { StorageSystem } from "./systems/storage";
|
||||
import { WiredPinsSystem } from "./systems/wired_pins";
|
||||
import { BeltUnderlaysSystem } from "./systems/belt_underlays";
|
||||
import { WireSystem } from "./systems/wire";
|
||||
import { ConstantSignalSystem } from "./systems/constant_signal";
|
||||
import { LogicGateSystem } from "./systems/logic_gate";
|
||||
import { LeverSystem } from "./systems/lever";
|
||||
import { DisplaySystem } from "./systems/display";
|
||||
|
||||
const logger = createLogger("game_system_manager");
|
||||
|
||||
export class GameSystemManager {
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
|
||||
this.systems = {
|
||||
/* typehints:start */
|
||||
/** @type {BeltSystem} */
|
||||
belt: null,
|
||||
|
||||
/** @type {ItemEjectorSystem} */
|
||||
itemEjector: null,
|
||||
|
||||
/** @type {MapResourcesSystem} */
|
||||
mapResources: null,
|
||||
|
||||
/** @type {MinerSystem} */
|
||||
miner: null,
|
||||
|
||||
/** @type {ItemProcessorSystem} */
|
||||
itemProcessor: null,
|
||||
|
||||
/** @type {UndergroundBeltSystem} */
|
||||
undergroundBelt: null,
|
||||
|
||||
/** @type {HubSystem} */
|
||||
hub: null,
|
||||
|
||||
/** @type {StaticMapEntitySystem} */
|
||||
staticMapEntities: null,
|
||||
|
||||
/** @type {ItemAcceptorSystem} */
|
||||
itemAcceptor: null,
|
||||
|
||||
/** @type {StorageSystem} */
|
||||
storage: null,
|
||||
|
||||
/** @type {WiredPinsSystem} */
|
||||
wiredPins: null,
|
||||
|
||||
/** @type {BeltUnderlaysSystem} */
|
||||
beltUnderlays: null,
|
||||
|
||||
/** @type {WireSystem} */
|
||||
wire: null,
|
||||
|
||||
/** @type {ConstantSignalSystem} */
|
||||
constantSignal: null,
|
||||
|
||||
/** @type {LogicGateSystem} */
|
||||
logicGate: null,
|
||||
|
||||
/** @type {LeverSystem} */
|
||||
lever: null,
|
||||
|
||||
/** @type {DisplaySystem} */
|
||||
display: null,
|
||||
|
||||
/* typehints:end */
|
||||
};
|
||||
this.systemUpdateOrder = [];
|
||||
|
||||
this.internalInitSystems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all systems
|
||||
*/
|
||||
internalInitSystems() {
|
||||
const add = (id, systemClass) => {
|
||||
this.systems[id] = new systemClass(this.root);
|
||||
this.systemUpdateOrder.push(id);
|
||||
};
|
||||
|
||||
// Order is important!
|
||||
|
||||
add("belt", BeltSystem);
|
||||
|
||||
add("undergroundBelt", UndergroundBeltSystem);
|
||||
|
||||
add("miner", MinerSystem);
|
||||
|
||||
add("storage", StorageSystem);
|
||||
|
||||
add("itemProcessor", ItemProcessorSystem);
|
||||
|
||||
add("itemEjector", ItemEjectorSystem);
|
||||
|
||||
add("mapResources", MapResourcesSystem);
|
||||
|
||||
add("hub", HubSystem);
|
||||
|
||||
add("staticMapEntities", StaticMapEntitySystem);
|
||||
|
||||
add("wiredPins", WiredPinsSystem);
|
||||
|
||||
add("beltUnderlays", BeltUnderlaysSystem);
|
||||
|
||||
add("constantSignal", ConstantSignalSystem);
|
||||
|
||||
// IMPORTANT: Must be after belt system since belt system can change the
|
||||
// orientation of an entity after it is placed -> the item acceptor cache
|
||||
// then would be invalid
|
||||
add("itemAcceptor", ItemAcceptorSystem);
|
||||
|
||||
// WIRES section
|
||||
add("lever", LeverSystem);
|
||||
|
||||
// IMPORTANT: We have 2 phases: In phase 1 we compute the output values of all gates,
|
||||
// processors etc. In phase 2 we propagate it through the wires network
|
||||
add("logicGate", LogicGateSystem);
|
||||
|
||||
// Wires must be after all gate, signal etc logic!
|
||||
add("wire", WireSystem);
|
||||
|
||||
add("display", DisplaySystem);
|
||||
|
||||
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all systems
|
||||
*/
|
||||
update() {
|
||||
for (let i = 0; i < this.systemUpdateOrder.length; ++i) {
|
||||
const system = this.systems[this.systemUpdateOrder[i]];
|
||||
system.update();
|
||||
}
|
||||
}
|
||||
|
||||
refreshCaches() {
|
||||
for (let i = 0; i < this.systemUpdateOrder.length; ++i) {
|
||||
const system = this.systems[this.systemUpdateOrder[i]];
|
||||
system.refreshCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "./root";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../core/logging";
|
||||
import { BeltSystem } from "./systems/belt";
|
||||
import { ItemEjectorSystem } from "./systems/item_ejector";
|
||||
import { MapResourcesSystem } from "./systems/map_resources";
|
||||
import { MinerSystem } from "./systems/miner";
|
||||
import { ItemProcessorSystem } from "./systems/item_processor";
|
||||
import { UndergroundBeltSystem } from "./systems/underground_belt";
|
||||
import { HubSystem } from "./systems/hub";
|
||||
import { StaticMapEntitySystem } from "./systems/static_map_entity";
|
||||
import { ItemAcceptorSystem } from "./systems/item_acceptor";
|
||||
import { StorageSystem } from "./systems/storage";
|
||||
import { WiredPinsSystem } from "./systems/wired_pins";
|
||||
import { BeltUnderlaysSystem } from "./systems/belt_underlays";
|
||||
import { WireSystem } from "./systems/wire";
|
||||
import { ConstantSignalSystem } from "./systems/constant_signal";
|
||||
import { LogicGateSystem } from "./systems/logic_gate";
|
||||
import { LeverSystem } from "./systems/lever";
|
||||
import { DisplaySystem } from "./systems/display";
|
||||
import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays";
|
||||
|
||||
const logger = createLogger("game_system_manager");
|
||||
|
||||
export class GameSystemManager {
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
|
||||
this.systems = {
|
||||
/* typehints:start */
|
||||
/** @type {BeltSystem} */
|
||||
belt: null,
|
||||
|
||||
/** @type {ItemEjectorSystem} */
|
||||
itemEjector: null,
|
||||
|
||||
/** @type {MapResourcesSystem} */
|
||||
mapResources: null,
|
||||
|
||||
/** @type {MinerSystem} */
|
||||
miner: null,
|
||||
|
||||
/** @type {ItemProcessorSystem} */
|
||||
itemProcessor: null,
|
||||
|
||||
/** @type {UndergroundBeltSystem} */
|
||||
undergroundBelt: null,
|
||||
|
||||
/** @type {HubSystem} */
|
||||
hub: null,
|
||||
|
||||
/** @type {StaticMapEntitySystem} */
|
||||
staticMapEntities: null,
|
||||
|
||||
/** @type {ItemAcceptorSystem} */
|
||||
itemAcceptor: null,
|
||||
|
||||
/** @type {StorageSystem} */
|
||||
storage: null,
|
||||
|
||||
/** @type {WiredPinsSystem} */
|
||||
wiredPins: null,
|
||||
|
||||
/** @type {BeltUnderlaysSystem} */
|
||||
beltUnderlays: null,
|
||||
|
||||
/** @type {WireSystem} */
|
||||
wire: null,
|
||||
|
||||
/** @type {ConstantSignalSystem} */
|
||||
constantSignal: null,
|
||||
|
||||
/** @type {LogicGateSystem} */
|
||||
logicGate: null,
|
||||
|
||||
/** @type {LeverSystem} */
|
||||
lever: null,
|
||||
|
||||
/** @type {DisplaySystem} */
|
||||
display: null,
|
||||
|
||||
/** @type {ItemProcessorOverlaysSystem} */
|
||||
itemProcessorOverlays: null,
|
||||
|
||||
/* typehints:end */
|
||||
};
|
||||
this.systemUpdateOrder = [];
|
||||
|
||||
this.internalInitSystems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all systems
|
||||
*/
|
||||
internalInitSystems() {
|
||||
const add = (id, systemClass) => {
|
||||
this.systems[id] = new systemClass(this.root);
|
||||
this.systemUpdateOrder.push(id);
|
||||
};
|
||||
|
||||
// Order is important!
|
||||
|
||||
add("belt", BeltSystem);
|
||||
|
||||
add("undergroundBelt", UndergroundBeltSystem);
|
||||
|
||||
add("miner", MinerSystem);
|
||||
|
||||
add("storage", StorageSystem);
|
||||
|
||||
add("itemProcessor", ItemProcessorSystem);
|
||||
|
||||
add("itemEjector", ItemEjectorSystem);
|
||||
|
||||
add("mapResources", MapResourcesSystem);
|
||||
|
||||
add("hub", HubSystem);
|
||||
|
||||
add("staticMapEntities", StaticMapEntitySystem);
|
||||
|
||||
add("wiredPins", WiredPinsSystem);
|
||||
|
||||
add("beltUnderlays", BeltUnderlaysSystem);
|
||||
|
||||
add("constantSignal", ConstantSignalSystem);
|
||||
|
||||
// IMPORTANT: Must be after belt system since belt system can change the
|
||||
// orientation of an entity after it is placed -> the item acceptor cache
|
||||
// then would be invalid
|
||||
add("itemAcceptor", ItemAcceptorSystem);
|
||||
|
||||
// WIRES section
|
||||
add("lever", LeverSystem);
|
||||
|
||||
// IMPORTANT: We have 2 phases: In phase 1 we compute the output values of all gates,
|
||||
// processors etc. In phase 2 we propagate it through the wires network
|
||||
add("logicGate", LogicGateSystem);
|
||||
|
||||
// Wires must be after all gate, signal etc logic!
|
||||
add("wire", WireSystem);
|
||||
|
||||
add("display", DisplaySystem);
|
||||
|
||||
add("itemProcessorOverlays", ItemProcessorOverlaysSystem);
|
||||
|
||||
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all systems
|
||||
*/
|
||||
update() {
|
||||
for (let i = 0; i < this.systemUpdateOrder.length; ++i) {
|
||||
const system = this.systems[this.systemUpdateOrder[i]];
|
||||
system.update();
|
||||
}
|
||||
}
|
||||
|
||||
refreshCaches() {
|
||||
for (let i = 0; i < this.systemUpdateOrder.length; ++i) {
|
||||
const system = this.systems[this.systemUpdateOrder[i]];
|
||||
system.refreshCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,70 @@
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { globalConfig } from "../../core/config";
|
||||
|
||||
export class BooleanItem extends BaseItem {
|
||||
static getId() {
|
||||
return "boolean_item";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return types.uint;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
deserialize(data) {
|
||||
this.value = data;
|
||||
}
|
||||
|
||||
/** @returns {"boolean"} **/
|
||||
getItemType() {
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
constructor(value) {
|
||||
super();
|
||||
this.value = value ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BaseItem} other
|
||||
*/
|
||||
equalsImpl(other) {
|
||||
return this.value === /** @type {BooleanItem} */ (other).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} diameter
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
let sprite;
|
||||
if (this.value) {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_true.png");
|
||||
} else {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_false.png");
|
||||
}
|
||||
sprite.drawCachedCentered(parameters, x, y, diameter);
|
||||
}
|
||||
}
|
||||
|
||||
export const BOOL_FALSE_SINGLETON = new BooleanItem(0);
|
||||
export const BOOL_TRUE_SINGLETON = new BooleanItem(1);
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { globalConfig } from "../../core/config";
|
||||
|
||||
export class BooleanItem extends BaseItem {
|
||||
static getId() {
|
||||
return "boolean_item";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return types.uint;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
deserialize(data) {
|
||||
this.value = data;
|
||||
}
|
||||
|
||||
/** @returns {"boolean"} **/
|
||||
getItemType() {
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
constructor(value) {
|
||||
super();
|
||||
this.value = value ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BaseItem} other
|
||||
*/
|
||||
equalsImpl(other) {
|
||||
return this.value === /** @type {BooleanItem} */ (other).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} diameter
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
let sprite;
|
||||
if (this.value) {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_true.png");
|
||||
} else {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_false.png");
|
||||
}
|
||||
sprite.drawCachedCentered(parameters, x, y, diameter);
|
||||
}
|
||||
}
|
||||
|
||||
export const BOOL_FALSE_SINGLETON = new BooleanItem(0);
|
||||
export const BOOL_TRUE_SINGLETON = new BooleanItem(1);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseItem} item
|
||||
*/
|
||||
export function isTrueItem(item) {
|
||||
return item && item.getItemType() === "boolean" && /** @type {BooleanItem} */ (item).value;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export class MapChunkView extends MapChunk {
|
||||
systems.lever.drawChunk(parameters, this);
|
||||
systems.display.drawChunk(parameters, this);
|
||||
systems.storage.drawChunk(parameters, this);
|
||||
systems.itemProcessorOverlays.drawChunk(parameters, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,99 +1,97 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { DisplayComponent } from "../components/display";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { BooleanItem } from "../items/boolean_item";
|
||||
|
||||
export class DisplaySystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [DisplayComponent]);
|
||||
|
||||
/** @type {Object<string, import("../../core/draw_utils").AtlasSprite>} */
|
||||
this.displaySprites = {};
|
||||
|
||||
for (const colorId in enumColors) {
|
||||
if (colorId === enumColors.uncolored) {
|
||||
continue;
|
||||
}
|
||||
this.displaySprites[colorId] = Loader.getSprite("sprites/wires/display/" + colorId + ".png");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color / value a display should show
|
||||
* @param {BaseItem} value
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
getDisplayItem(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (value.getItemType()) {
|
||||
case "boolean": {
|
||||
return /** @type {BooleanItem} */ (value).value
|
||||
? COLOR_ITEM_SINGLETONS[enumColors.white]
|
||||
: null;
|
||||
}
|
||||
|
||||
case "color": {
|
||||
const item = /**@type {ColorItem} */ (value);
|
||||
return item.color === enumColors.uncolored ? null : item;
|
||||
}
|
||||
|
||||
case "shape": {
|
||||
return value;
|
||||
}
|
||||
|
||||
default:
|
||||
assertAlways(false, "Unknown item type: " + value.getItemType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a given chunk
|
||||
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
if (entity && entity.components.Display) {
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
const network = pinsComp.slots[0].linkedNetwork;
|
||||
|
||||
if (!network || !network.currentValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = this.getDisplayItem(network.currentValue);
|
||||
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const origin = entity.components.StaticMapEntity.origin;
|
||||
if (value.getItemType() === "color") {
|
||||
this.displaySprites[/** @type {ColorItem} */ (value).color].drawCachedCentered(
|
||||
parameters,
|
||||
(origin.x + 0.5) * globalConfig.tileSize,
|
||||
(origin.y + 0.5) * globalConfig.tileSize,
|
||||
globalConfig.tileSize
|
||||
);
|
||||
} else if (value.getItemType() === "shape") {
|
||||
value.drawItemCenteredClipped(
|
||||
(origin.x + 0.5) * globalConfig.tileSize,
|
||||
(origin.y + 0.5) * globalConfig.tileSize,
|
||||
parameters,
|
||||
30
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { DisplayComponent } from "../components/display";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { isTrueItem } from "../items/boolean_item";
|
||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class DisplaySystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [DisplayComponent]);
|
||||
|
||||
/** @type {Object<string, import("../../core/draw_utils").AtlasSprite>} */
|
||||
this.displaySprites = {};
|
||||
|
||||
for (const colorId in enumColors) {
|
||||
if (colorId === enumColors.uncolored) {
|
||||
continue;
|
||||
}
|
||||
this.displaySprites[colorId] = Loader.getSprite("sprites/wires/display/" + colorId + ".png");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color / value a display should show
|
||||
* @param {BaseItem} value
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
getDisplayItem(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (value.getItemType()) {
|
||||
case "boolean": {
|
||||
return isTrueItem(value) ? COLOR_ITEM_SINGLETONS[enumColors.white] : null;
|
||||
}
|
||||
|
||||
case "color": {
|
||||
const item = /**@type {ColorItem} */ (value);
|
||||
return item.color === enumColors.uncolored ? null : item;
|
||||
}
|
||||
|
||||
case "shape": {
|
||||
return value;
|
||||
}
|
||||
|
||||
default:
|
||||
assertAlways(false, "Unknown item type: " + value.getItemType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a given chunk
|
||||
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
if (entity && entity.components.Display) {
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
const network = pinsComp.slots[0].linkedNetwork;
|
||||
|
||||
if (!network || !network.currentValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = this.getDisplayItem(network.currentValue);
|
||||
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const origin = entity.components.StaticMapEntity.origin;
|
||||
if (value.getItemType() === "color") {
|
||||
this.displaySprites[/** @type {ColorItem} */ (value).color].drawCachedCentered(
|
||||
parameters,
|
||||
(origin.x + 0.5) * globalConfig.tileSize,
|
||||
(origin.y + 0.5) * globalConfig.tileSize,
|
||||
globalConfig.tileSize
|
||||
);
|
||||
} else if (value.getItemType() === "shape") {
|
||||
value.drawItemCenteredClipped(
|
||||
(origin.x + 0.5) * globalConfig.tileSize,
|
||||
(origin.y + 0.5) * globalConfig.tileSize,
|
||||
parameters,
|
||||
30
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,388 +1,381 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { createLogger } from "../../core/logging";
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { enumDirection, 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 { enumItemProcessorTypes } from "../components/item_processor";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
const logger = createLogger("systems/ejector");
|
||||
|
||||
export class ItemEjectorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [ItemEjectorComponent]);
|
||||
|
||||
this.root.signals.entityAdded.add(this.checkForCacheInvalidation, this);
|
||||
this.root.signals.entityDestroyed.add(this.checkForCacheInvalidation, this);
|
||||
this.root.signals.postLoadHook.add(this.recomputeCache, this);
|
||||
|
||||
/**
|
||||
* @type {Rectangle}
|
||||
*/
|
||||
this.areaToRecompute = null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
checkForCacheInvalidation(entity) {
|
||||
if (!this.root.gameInitialized) {
|
||||
return;
|
||||
}
|
||||
if (!entity.components.StaticMapEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimize for the common case: adding or removing one building at a time. Clicking
|
||||
// and dragging can cause up to 4 add/remove signals.
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const bounds = staticComp.getTileSpaceBounds();
|
||||
const expandedBounds = bounds.expandedInAllDirections(2);
|
||||
|
||||
if (this.areaToRecompute) {
|
||||
this.areaToRecompute = this.areaToRecompute.getUnion(expandedBounds);
|
||||
} else {
|
||||
this.areaToRecompute = expandedBounds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Precomputes the cache, which makes up for a huge performance improvement
|
||||
*/
|
||||
recomputeCache() {
|
||||
if (this.areaToRecompute) {
|
||||
logger.log("Recomputing cache using rectangle");
|
||||
if (G_IS_DEV && globalConfig.debug.renderChanges) {
|
||||
this.root.hud.parts.changesDebugger.renderChange(
|
||||
"ejector-area",
|
||||
this.areaToRecompute,
|
||||
"#fe50a6"
|
||||
);
|
||||
}
|
||||
this.recomputeAreaCache();
|
||||
this.areaToRecompute = null;
|
||||
} else {
|
||||
logger.log("Full cache recompute");
|
||||
if (G_IS_DEV && globalConfig.debug.renderChanges) {
|
||||
this.root.hud.parts.changesDebugger.renderChange(
|
||||
"ejector-full",
|
||||
new Rectangle(-1000, -1000, 2000, 2000),
|
||||
"#fe50a6"
|
||||
);
|
||||
}
|
||||
|
||||
// Try to find acceptors for every ejector
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
this.recomputeSingleEntityCache(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recomputes the cache in the given area
|
||||
*/
|
||||
recomputeAreaCache() {
|
||||
const area = this.areaToRecompute;
|
||||
let entryCount = 0;
|
||||
|
||||
logger.log("Recomputing area:", area.x, area.y, "/", area.w, area.h);
|
||||
|
||||
// Store the entities we already recomputed, so we don't do work twice
|
||||
const recomputedEntities = new Set();
|
||||
|
||||
for (let x = area.x; x < area.right(); ++x) {
|
||||
for (let y = area.y; y < area.bottom(); ++y) {
|
||||
const entities = this.root.map.getLayersContentsMultipleXY(x, y);
|
||||
for (let i = 0; i < entities.length; ++i) {
|
||||
const entity = entities[i];
|
||||
|
||||
// Recompute the entity in case its relevant for this system and it
|
||||
// hasn't already been computed
|
||||
if (!recomputedEntities.has(entity.uid) && entity.components.ItemEjector) {
|
||||
recomputedEntities.add(entity.uid);
|
||||
this.recomputeSingleEntityCache(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
recomputeSingleEntityCache(entity) {
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
for (let slotIndex = 0; slotIndex < ejectorComp.slots.length; ++slotIndex) {
|
||||
const ejectorSlot = ejectorComp.slots[slotIndex];
|
||||
|
||||
// Clear the old cache.
|
||||
ejectorSlot.cachedDestSlot = null;
|
||||
ejectorSlot.cachedTargetEntity = null;
|
||||
ejectorSlot.cachedBeltPath = null;
|
||||
|
||||
// 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
|
||||
// Since there can be cross layer dependencies, check on all layers
|
||||
const targetEntities = this.root.map.getLayersContentsMultipleXY(
|
||||
ejectSlotTargetWsTile.x,
|
||||
ejectSlotTargetWsTile.y
|
||||
);
|
||||
|
||||
for (let i = 0; i < targetEntities.length; ++i) {
|
||||
const targetEntity = targetEntities[i];
|
||||
|
||||
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
||||
const targetBeltComp = targetEntity.components.Belt;
|
||||
|
||||
// Check for belts (special case)
|
||||
if (targetBeltComp) {
|
||||
const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
|
||||
if (ejectSlotWsDirection === beltAcceptingDirection) {
|
||||
ejectorSlot.cachedTargetEntity = targetEntity;
|
||||
ejectorSlot.cachedBeltPath = targetBeltComp.assignedPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for item acceptors
|
||||
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
|
||||
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;
|
||||
}
|
||||
|
||||
// A slot can always be connected to one other slot only
|
||||
ejectorSlot.cachedTargetEntity = targetEntity;
|
||||
ejectorSlot.cachedDestSlot = matchingSlot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.areaToRecompute) {
|
||||
this.recomputeCache();
|
||||
}
|
||||
|
||||
// Precompute effective belt speed
|
||||
let progressGrowth = 2 * this.root.dynamicTickrate.deltaSeconds;
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.instantBelts) {
|
||||
progressGrowth = 1;
|
||||
}
|
||||
|
||||
// Go over all cache entries
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const sourceEntity = this.allEntities[i];
|
||||
const sourceEjectorComp = sourceEntity.components.ItemEjector;
|
||||
if (!sourceEjectorComp.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const slots = sourceEjectorComp.slots;
|
||||
for (let j = 0; j < slots.length; ++j) {
|
||||
const sourceSlot = slots[j];
|
||||
const item = sourceSlot.item;
|
||||
if (!item) {
|
||||
// No item available to be ejected
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetEntity = sourceSlot.cachedTargetEntity;
|
||||
|
||||
// Advance items on the slot
|
||||
sourceSlot.progress = Math.min(
|
||||
1,
|
||||
sourceSlot.progress +
|
||||
progressGrowth *
|
||||
this.root.hubGoals.getBeltBaseSpeed() *
|
||||
globalConfig.itemSpacingOnBelts
|
||||
);
|
||||
|
||||
// Check if we are still in the process of ejecting, can't proceed then
|
||||
if (sourceSlot.progress < 1.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we are ejecting to a belt path
|
||||
const destPath = sourceSlot.cachedBeltPath;
|
||||
if (destPath) {
|
||||
// Try passing the item over
|
||||
if (destPath.tryAcceptItem(item)) {
|
||||
sourceSlot.item = null;
|
||||
}
|
||||
|
||||
// Always stop here, since there can *either* be a belt path *or*
|
||||
// a slot
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the target acceptor can actually accept this item
|
||||
const destSlot = sourceSlot.cachedDestSlot;
|
||||
if (destSlot) {
|
||||
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
|
||||
if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to hand over the item
|
||||
if (this.tryPassOverItem(item, targetEntity, destSlot.index)) {
|
||||
// Handover successful, clear slot
|
||||
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item);
|
||||
sourceSlot.item = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseItem} item
|
||||
* @param {Entity} receiver
|
||||
* @param {number} slotIndex
|
||||
*/
|
||||
tryPassOverItem(item, receiver, slotIndex) {
|
||||
// Try figuring out how what to do with the item
|
||||
// TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell.
|
||||
// Also its just a few cases (hope it stays like this .. :x).
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const itemProcessorComp = receiver.components.ItemProcessor;
|
||||
if (itemProcessorComp) {
|
||||
// @todo HACK
|
||||
// Check if there are pins, and if so if they are connected
|
||||
if (itemProcessorComp.type === enumItemProcessorTypes.filter) {
|
||||
const pinsComp = receiver.components.WiredPins;
|
||||
if (pinsComp && pinsComp.slots.length === 1) {
|
||||
const network = pinsComp.slots[0].linkedNetwork;
|
||||
if (!network || !network.currentValue) {
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
if (!ejectorComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 = staticComp.localTileToWorld(slot.pos);
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) {
|
||||
// Not within this chunk
|
||||
continue;
|
||||
}
|
||||
|
||||
const realDirection = staticComp.localDirectionToWorld(slot.direction);
|
||||
const realDirectionVector = enumDirectionToVector[realDirection];
|
||||
|
||||
const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
|
||||
const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
|
||||
|
||||
const worldX = tileX * globalConfig.tileSize;
|
||||
const worldY = tileY * globalConfig.tileSize;
|
||||
|
||||
ejectedItem.drawItemCenteredClipped(
|
||||
worldX,
|
||||
worldY,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { createLogger } from "../../core/logging";
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { enumDirection, 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 { enumItemProcessorTypes } from "../components/item_processor";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
const logger = createLogger("systems/ejector");
|
||||
|
||||
export class ItemEjectorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [ItemEjectorComponent]);
|
||||
|
||||
this.root.signals.entityAdded.add(this.checkForCacheInvalidation, this);
|
||||
this.root.signals.entityDestroyed.add(this.checkForCacheInvalidation, this);
|
||||
this.root.signals.postLoadHook.add(this.recomputeCache, this);
|
||||
|
||||
/**
|
||||
* @type {Rectangle}
|
||||
*/
|
||||
this.areaToRecompute = null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
checkForCacheInvalidation(entity) {
|
||||
if (!this.root.gameInitialized) {
|
||||
return;
|
||||
}
|
||||
if (!entity.components.StaticMapEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimize for the common case: adding or removing one building at a time. Clicking
|
||||
// and dragging can cause up to 4 add/remove signals.
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const bounds = staticComp.getTileSpaceBounds();
|
||||
const expandedBounds = bounds.expandedInAllDirections(2);
|
||||
|
||||
if (this.areaToRecompute) {
|
||||
this.areaToRecompute = this.areaToRecompute.getUnion(expandedBounds);
|
||||
} else {
|
||||
this.areaToRecompute = expandedBounds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Precomputes the cache, which makes up for a huge performance improvement
|
||||
*/
|
||||
recomputeCache() {
|
||||
if (this.areaToRecompute) {
|
||||
logger.log("Recomputing cache using rectangle");
|
||||
if (G_IS_DEV && globalConfig.debug.renderChanges) {
|
||||
this.root.hud.parts.changesDebugger.renderChange(
|
||||
"ejector-area",
|
||||
this.areaToRecompute,
|
||||
"#fe50a6"
|
||||
);
|
||||
}
|
||||
this.recomputeAreaCache();
|
||||
this.areaToRecompute = null;
|
||||
} else {
|
||||
logger.log("Full cache recompute");
|
||||
if (G_IS_DEV && globalConfig.debug.renderChanges) {
|
||||
this.root.hud.parts.changesDebugger.renderChange(
|
||||
"ejector-full",
|
||||
new Rectangle(-1000, -1000, 2000, 2000),
|
||||
"#fe50a6"
|
||||
);
|
||||
}
|
||||
|
||||
// Try to find acceptors for every ejector
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
this.recomputeSingleEntityCache(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recomputes the cache in the given area
|
||||
*/
|
||||
recomputeAreaCache() {
|
||||
const area = this.areaToRecompute;
|
||||
let entryCount = 0;
|
||||
|
||||
logger.log("Recomputing area:", area.x, area.y, "/", area.w, area.h);
|
||||
|
||||
// Store the entities we already recomputed, so we don't do work twice
|
||||
const recomputedEntities = new Set();
|
||||
|
||||
for (let x = area.x; x < area.right(); ++x) {
|
||||
for (let y = area.y; y < area.bottom(); ++y) {
|
||||
const entities = this.root.map.getLayersContentsMultipleXY(x, y);
|
||||
for (let i = 0; i < entities.length; ++i) {
|
||||
const entity = entities[i];
|
||||
|
||||
// Recompute the entity in case its relevant for this system and it
|
||||
// hasn't already been computed
|
||||
if (!recomputedEntities.has(entity.uid) && entity.components.ItemEjector) {
|
||||
recomputedEntities.add(entity.uid);
|
||||
this.recomputeSingleEntityCache(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
recomputeSingleEntityCache(entity) {
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
for (let slotIndex = 0; slotIndex < ejectorComp.slots.length; ++slotIndex) {
|
||||
const ejectorSlot = ejectorComp.slots[slotIndex];
|
||||
|
||||
// Clear the old cache.
|
||||
ejectorSlot.cachedDestSlot = null;
|
||||
ejectorSlot.cachedTargetEntity = null;
|
||||
ejectorSlot.cachedBeltPath = null;
|
||||
|
||||
// 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
|
||||
// Since there can be cross layer dependencies, check on all layers
|
||||
const targetEntities = this.root.map.getLayersContentsMultipleXY(
|
||||
ejectSlotTargetWsTile.x,
|
||||
ejectSlotTargetWsTile.y
|
||||
);
|
||||
|
||||
for (let i = 0; i < targetEntities.length; ++i) {
|
||||
const targetEntity = targetEntities[i];
|
||||
|
||||
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
||||
const targetBeltComp = targetEntity.components.Belt;
|
||||
|
||||
// Check for belts (special case)
|
||||
if (targetBeltComp) {
|
||||
const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
|
||||
if (ejectSlotWsDirection === beltAcceptingDirection) {
|
||||
ejectorSlot.cachedTargetEntity = targetEntity;
|
||||
ejectorSlot.cachedBeltPath = targetBeltComp.assignedPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for item acceptors
|
||||
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
|
||||
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;
|
||||
}
|
||||
|
||||
// A slot can always be connected to one other slot only
|
||||
ejectorSlot.cachedTargetEntity = targetEntity;
|
||||
ejectorSlot.cachedDestSlot = matchingSlot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.areaToRecompute) {
|
||||
this.recomputeCache();
|
||||
}
|
||||
|
||||
// Precompute effective belt speed
|
||||
let progressGrowth = 2 * this.root.dynamicTickrate.deltaSeconds;
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.instantBelts) {
|
||||
progressGrowth = 1;
|
||||
}
|
||||
|
||||
// Go over all cache entries
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const sourceEntity = this.allEntities[i];
|
||||
const sourceEjectorComp = sourceEntity.components.ItemEjector;
|
||||
if (!sourceEjectorComp.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const slots = sourceEjectorComp.slots;
|
||||
for (let j = 0; j < slots.length; ++j) {
|
||||
const sourceSlot = slots[j];
|
||||
const item = sourceSlot.item;
|
||||
if (!item) {
|
||||
// No item available to be ejected
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetEntity = sourceSlot.cachedTargetEntity;
|
||||
|
||||
// Advance items on the slot
|
||||
sourceSlot.progress = Math.min(
|
||||
1,
|
||||
sourceSlot.progress +
|
||||
progressGrowth *
|
||||
this.root.hubGoals.getBeltBaseSpeed() *
|
||||
globalConfig.itemSpacingOnBelts
|
||||
);
|
||||
|
||||
// Check if we are still in the process of ejecting, can't proceed then
|
||||
if (sourceSlot.progress < 1.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we are ejecting to a belt path
|
||||
const destPath = sourceSlot.cachedBeltPath;
|
||||
if (destPath) {
|
||||
// Try passing the item over
|
||||
if (destPath.tryAcceptItem(item)) {
|
||||
sourceSlot.item = null;
|
||||
}
|
||||
|
||||
// Always stop here, since there can *either* be a belt path *or*
|
||||
// a slot
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the target acceptor can actually accept this item
|
||||
const destSlot = sourceSlot.cachedDestSlot;
|
||||
if (destSlot) {
|
||||
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
|
||||
if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to hand over the item
|
||||
if (this.tryPassOverItem(item, targetEntity, destSlot.index)) {
|
||||
// Handover successful, clear slot
|
||||
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item);
|
||||
sourceSlot.item = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseItem} item
|
||||
* @param {Entity} receiver
|
||||
* @param {number} slotIndex
|
||||
*/
|
||||
tryPassOverItem(item, receiver, slotIndex) {
|
||||
// Try figuring out how what to do with the item
|
||||
// TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell.
|
||||
// Also its just a few cases (hope it stays like this .. :x).
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
if (!ejectorComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 = staticComp.localTileToWorld(slot.pos);
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) {
|
||||
// Not within this chunk
|
||||
continue;
|
||||
}
|
||||
|
||||
const realDirection = staticComp.localDirectionToWorld(slot.direction);
|
||||
const realDirectionVector = enumDirectionToVector[realDirection];
|
||||
|
||||
const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
|
||||
const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
|
||||
|
||||
const worldX = tileX * globalConfig.tileSize;
|
||||
const worldY = tileY * globalConfig.tileSize;
|
||||
|
||||
ejectedItem.drawItemCenteredClipped(
|
||||
worldX,
|
||||
worldY,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors, enumColorMixingResults } from "../colors";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent, enumItemProcessorRequirements } from "../components/item_processor";
|
||||
import { enumColorMixingResults, enumColors } from "../colors";
|
||||
import {
|
||||
enumItemProcessorRequirements,
|
||||
enumItemProcessorTypes,
|
||||
ItemProcessorComponent,
|
||||
} from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
||||
import { BOOL_TRUE_SINGLETON, isTrueItem } from "../items/boolean_item";
|
||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { ShapeItem } from "../items/shape_item";
|
||||
|
||||
@@ -68,78 +72,154 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Check if we have an empty queue and can start a new charge
|
||||
if (processorComp.itemsToEject.length === 0) {
|
||||
if (entity.components.ItemProcessor.processingRequirement) {
|
||||
if (this.canProcess(entity)) {
|
||||
this.startNewCharge(entity);
|
||||
}
|
||||
} else if (processorComp.inputSlots.length >= processorComp.inputsPerCharge) {
|
||||
if (this.canProcess(entity)) {
|
||||
this.startNewCharge(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the entity should accept the given item on the given slot.
|
||||
* This should only be called with matching items! I.e. if a color item is expected
|
||||
* on the given slot, then only a color item must be passed.
|
||||
* @param {Entity} entity
|
||||
* @param {BaseItem} item The item to accept
|
||||
* @param {number} slotIndex The slot index
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkRequirements(entity, item, slotIndex) {
|
||||
const itemProcessorComp = entity.components.ItemProcessor;
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
|
||||
switch (itemProcessorComp.processingRequirement) {
|
||||
case enumItemProcessorRequirements.painterQuad: {
|
||||
if (slotIndex === 0) {
|
||||
// Always accept the shape
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the network value at the given slot
|
||||
const network = pinsComp.slots[slotIndex - 1].linkedNetwork;
|
||||
const slotIsEnabled = network && isTrueItem(network.currentValue);
|
||||
if (!slotIsEnabled) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case enumItemProcessorRequirements.filter: {
|
||||
const network = pinsComp.slots[0].linkedNetwork;
|
||||
if (!network || !network.currentValue) {
|
||||
// Item filter is not connected
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if "false" was passed in
|
||||
const item = network.currentValue;
|
||||
if (item.getItemType() === "boolean") {
|
||||
if (!(/** @type {BooleanItem} */ (item).value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, all good
|
||||
return true;
|
||||
}
|
||||
|
||||
// By default, everything is accepted
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether it's possible to process something
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
canProcess(entity) {
|
||||
switch (entity.components.ItemProcessor.processingRequirement) {
|
||||
case enumItemProcessorRequirements.painterQuad: {
|
||||
// For quad-painter, pins match slots
|
||||
// boolean true means "disable input"
|
||||
// a color means "disable if not matched"
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
switch (processorComp.processingRequirement) {
|
||||
// DEFAULT
|
||||
// By default, we can start processing once all inputs are there
|
||||
case null: {
|
||||
return processorComp.inputSlots.length >= processorComp.inputsPerCharge;
|
||||
}
|
||||
|
||||
// QUAD PAINTER
|
||||
// For the quad painter, it might be possible to start processing earlier
|
||||
case enumItemProcessorRequirements.painterQuad: {
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
|
||||
/** @type {Object.<string, { item: BaseItem, sourceSlot: number }>} */
|
||||
/** @type {Object.<number, { item: BaseItem, sourceSlot: number }>} */
|
||||
const itemsBySlot = {};
|
||||
for (let i = 0; i < processorComp.inputSlots.length; ++i) {
|
||||
itemsBySlot[processorComp.inputSlots[i].sourceSlot] = processorComp.inputSlots[i];
|
||||
}
|
||||
|
||||
// first slot is the shape
|
||||
if (!itemsBySlot[0]) return false;
|
||||
// First slot is the shape, so if it's not there we can't do anything
|
||||
if (!itemsBySlot[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item);
|
||||
const slotStatus = [];
|
||||
|
||||
// Here we check just basic things`
|
||||
// Stop processing if anything except TRUE is
|
||||
// set and there is no item.
|
||||
// Check which slots are enabled
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
const netValue = pinsComp.slots[i].linkedNetwork ?
|
||||
pinsComp.slots[i].linkedNetwork.currentValue :
|
||||
null;
|
||||
// Extract the network value on the Nth pin
|
||||
const networkValue = pinsComp.slots[i].linkedNetwork
|
||||
? pinsComp.slots[i].linkedNetwork.currentValue
|
||||
: null;
|
||||
|
||||
const currentItem = itemsBySlot[i + 1];
|
||||
// If there is no "1" on that slot, don't paint there
|
||||
if (!isTrueItem(networkValue)) {
|
||||
slotStatus.push(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((netValue == null || !netValue.equals(BOOL_TRUE_SINGLETON)) && currentItem == null) {
|
||||
let quadCount = 0;
|
||||
slotStatus.push(true);
|
||||
}
|
||||
|
||||
// All slots are disabled
|
||||
if (!slotStatus.includes(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if all colors of the enabled slots are there
|
||||
for (let i = 0; i < slotStatus.length; ++i) {
|
||||
if (slotStatus[i] && !itemsBySlot[1 + i]) {
|
||||
// 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
|
||||
for (let j = 0; j < 4; ++j) {
|
||||
const layer = shapeItem.definition.layers[j];
|
||||
if (layer && layer[i]) {
|
||||
quadCount++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (quadCount > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// FILTER
|
||||
// Double check with linked network
|
||||
case enumItemProcessorRequirements.filter: {
|
||||
const network = entity.components.WiredPins.slots[0].linkedNetwork;
|
||||
if (!network || !network.currentValue) {
|
||||
// Item filter is not connected
|
||||
return false;
|
||||
}
|
||||
|
||||
return processorComp.inputSlots.length >= processorComp.inputsPerCharge;
|
||||
}
|
||||
|
||||
default:
|
||||
assertAlways(
|
||||
false,
|
||||
"Unknown requirement for " + entity.components.ItemProcessor.processingRequirement
|
||||
);
|
||||
assertAlways(false, "Unknown requirement for " + processorComp.processingRequirement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,60 +455,22 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item);
|
||||
assert(shapeItem instanceof ShapeItem, "Input for painter is not a shape");
|
||||
|
||||
/** @type {Array<ColorItem>} */
|
||||
const colorItems = [].fill(null, 0, 4);
|
||||
|
||||
/** @type {Array<enumColors>} */
|
||||
const colors = [null, null, null, null];
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
if (itemsBySlot[i + 1]) {
|
||||
colorItems[i] = /** @type {ColorItem} */ (itemsBySlot[i + 1].item);
|
||||
assert(colorItems[i] instanceof ColorItem, "Input for painter is not a color");
|
||||
colors[i] = /** @type {ColorItem} */ (itemsBySlot[i + 1].item).color;
|
||||
}
|
||||
}
|
||||
|
||||
const pinValues = entity.components.WiredPins.slots
|
||||
.map(slot => slot.linkedNetwork ? slot.linkedNetwork.currentValue : BOOL_FALSE_SINGLETON);
|
||||
|
||||
// @todo cleanup
|
||||
const colorTL = colorItems[0];
|
||||
const colorTR = colorItems[1];
|
||||
const colorBR = colorItems[2];
|
||||
const colorBL = colorItems[3];
|
||||
|
||||
/** @type {Array<boolean>} */
|
||||
let skipped = [];
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
skipped[i] = pinValues[i] ? pinValues[i].equals(BOOL_TRUE_SINGLETON) : false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
if (colorItems[i] == null) {
|
||||
skipped[i] = false; // make sure we never insert null item back
|
||||
} else if (pinValues[i] instanceof ColorItem) {
|
||||
// if pin value is a color, skip anything except that color
|
||||
// but still require any color, because it would not work on
|
||||
// slow factories.
|
||||
if (!colorItems[i].equals(pinValues[i])) {
|
||||
skipped[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toColor = [
|
||||
(!skipped[0] && colorTL) ? colorTL.color : null,
|
||||
(!skipped[1] && colorTR) ? colorTR.color : null,
|
||||
(!skipped[2] && colorBR) ? colorBR.color : null,
|
||||
(!skipped[3] && colorBL) ? colorBL.color : null,
|
||||
];
|
||||
|
||||
const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors(
|
||||
shapeItem.definition,
|
||||
/** @type {[enumColors, enumColors, enumColors, enumColors]} */(toColor)
|
||||
/** @type {[string, string, string, string]} */ (colors)
|
||||
);
|
||||
|
||||
outItems.push({
|
||||
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition),
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
101
src/js/game/systems/item_processor_overlays.js
Normal file
101
src/js/game/systems/item_processor_overlays.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import { GameSystem } from "../game_system";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { enumItemProcessorRequirements } from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { isTrueItem } from "../items/boolean_item";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { smoothPulse } from "../../core/utils";
|
||||
|
||||
export class ItemProcessorOverlaysSystem extends GameSystem {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
this.spriteDisabled = Loader.getSprite("sprites/misc/processor_disabled.png");
|
||||
this.spriteDisconnected = Loader.getSprite("sprites/misc/processor_disconnected.png");
|
||||
|
||||
this.drawnUids = new Set();
|
||||
|
||||
this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this);
|
||||
}
|
||||
|
||||
clearDrawnUids() {
|
||||
this.drawnUids.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
if (!processorComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const requirement = processorComp.processingRequirement;
|
||||
if (!requirement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.drawnUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.drawnUids.add(entity.uid);
|
||||
|
||||
switch (requirement) {
|
||||
case enumItemProcessorRequirements.painterQuad: {
|
||||
this.drawConnectedSlotRequirement(parameters, entity);
|
||||
break;
|
||||
}
|
||||
case enumItemProcessorRequirements.filter: {
|
||||
this.drawConnectedSlotRequirement(parameters, entity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawConnectedSlotRequirement(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
|
||||
let anySlotConnected = false;
|
||||
|
||||
// Check if any slot has a value
|
||||
for (let i = 0; i < pinsComp.slots.length; ++i) {
|
||||
const slot = pinsComp.slots[i];
|
||||
const network = slot.linkedNetwork;
|
||||
if (network && network.currentValue) {
|
||||
anySlotConnected = true;
|
||||
|
||||
if (isTrueItem(network.currentValue)) {
|
||||
// No need to draw anything
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pulse = smoothPulse(this.root.time.now());
|
||||
parameters.context.globalAlpha = 0.6 + 0.4 * pulse;
|
||||
const sprite = anySlotConnected ? this.spriteDisabled : this.spriteDisconnected;
|
||||
sprite.drawCachedCentered(
|
||||
parameters,
|
||||
(staticComp.origin.x + 0.5) * globalConfig.tileSize,
|
||||
(staticComp.origin.y + 0.5) * globalConfig.tileSize,
|
||||
globalConfig.tileSize * (0.7 + 0.2 * pulse)
|
||||
);
|
||||
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { enumColors } from "../colors";
|
||||
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
|
||||
import { enumPinSlotType } from "../components/wired_pins";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, BooleanItem } from "../items/boolean_item";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, isTrueItem } from "../items/boolean_item";
|
||||
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
import { ShapeItem } from "../items/shape_item";
|
||||
@@ -76,28 +76,9 @@ export class LogicGateSystem extends GameSystemWithFilter {
|
||||
*/
|
||||
compute_AND(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for AND");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 || !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const itemType = param1.getItemType();
|
||||
|
||||
if (itemType !== param2.getItemType()) {
|
||||
// Differing type
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
if (itemType === "boolean") {
|
||||
return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
return isTrueItem(parameters[0]) && isTrueItem(parameters[1])
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,18 +86,7 @@ export class LogicGateSystem extends GameSystemWithFilter {
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_NOT(parameters) {
|
||||
const item = parameters[0];
|
||||
if (!item) {
|
||||
return BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
if (item.getItemType() !== "boolean") {
|
||||
// Not a boolean actually
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const value = /** @type {BooleanItem} */ (item).value;
|
||||
return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON;
|
||||
return isTrueItem(parameters[0]) ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,27 +95,9 @@ export class LogicGateSystem extends GameSystemWithFilter {
|
||||
*/
|
||||
compute_XOR(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for XOR");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 && !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
// Check for the right types
|
||||
if (param1 && param1.getItemType() !== "boolean") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
if (param2 && param2.getItemType() !== "boolean") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0;
|
||||
const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0;
|
||||
|
||||
return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||
return isTrueItem(parameters[0]) ^ isTrueItem(parameters[1])
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,20 +106,9 @@ export class LogicGateSystem extends GameSystemWithFilter {
|
||||
*/
|
||||
compute_OR(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for OR");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 && !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const valueParam1 =
|
||||
param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0;
|
||||
const valueParam2 =
|
||||
param2 && param2.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param2).value : 0;
|
||||
|
||||
return valueParam1 || valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||
return isTrueItem(parameters[0]) || isTrueItem(parameters[1])
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,21 +117,11 @@ export class LogicGateSystem extends GameSystemWithFilter {
|
||||
*/
|
||||
compute_IF(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for IF");
|
||||
|
||||
const flag = parameters[0];
|
||||
const value = parameters[1];
|
||||
if (!flag || !value) {
|
||||
// Not enough params
|
||||
return null;
|
||||
}
|
||||
|
||||
if (flag.getItemType() !== "boolean") {
|
||||
// Flag is not a boolean
|
||||
return null;
|
||||
}
|
||||
|
||||
// pass through item
|
||||
if (/** @type {BooleanItem} */ (flag).value) {
|
||||
if (isTrueItem(flag)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user