Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 636 KiB After Width: | Height: | Size: 653 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
@ -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,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|