diff --git a/src/js/game/buildings/painter.js b/src/js/game/buildings/painter.js index 61e77a7c..ed64f75c 100644 --- a/src/js/game/buildings/painter.js +++ b/src/js/game/buildings/painter.js @@ -9,6 +9,8 @@ import { defaultBuildingVariant, MetaBuilding } from "../meta_building"; import { GameRoot } from "../root"; import { enumHubGoalRewards } from "../tutorial_goals"; import { enumItemType } from "../base_item"; +import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins"; +import { ProcessingRequirementComponent } from "../components/processing_requirement"; /** @enum {string} */ export const enumPainterVariants = { mirrored: "mirrored", double: "double", quad: "quad" }; @@ -120,6 +122,13 @@ export class MetaPainterBuilding extends MetaBuilding { switch (variant) { case defaultBuildingVariant: case enumPainterVariants.mirrored: { + if (entity.components.WiredPins) { + entity.removeComponent(WiredPinsComponent) + } + if (entity.components.ProcessingRequirement) { + entity.removeComponent(ProcessingRequirementComponent); + } + entity.components.ItemAcceptor.setSlots([ { pos: new Vector(0, 0), @@ -143,6 +152,13 @@ export class MetaPainterBuilding extends MetaBuilding { break; } case enumPainterVariants.double: { + if (entity.components.WiredPins) { + entity.removeComponent(WiredPinsComponent) + } + if (entity.components.ProcessingRequirement) { + entity.removeComponent(ProcessingRequirementComponent); + } + entity.components.ItemAcceptor.setSlots([ { pos: new Vector(0, 0), @@ -170,6 +186,39 @@ export class MetaPainterBuilding extends MetaBuilding { break; } case enumPainterVariants.quad: { + 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 + }, + ] + })); + } + + if (!entity.components.ProcessingRequirement) { + entity.addComponent(new ProcessingRequirementComponent({ + processorType: enumItemProcessorTypes.painterQuad + })); + } + entity.components.ItemAcceptor.setSlots([ { pos: new Vector(0, 0), diff --git a/src/js/game/component_registry.js b/src/js/game/component_registry.js index b1a67bad..23f59834 100644 --- a/src/js/game/component_registry.js +++ b/src/js/game/component_registry.js @@ -15,6 +15,7 @@ import { ConstantSignalComponent } from "./components/constant_signal"; import { LogicGateComponent } from "./components/logic_gate"; import { LeverComponent } from "./components/lever"; import { WireTunnelComponent } from "./components/wire_tunnel"; +import { ProcessingRequirementComponent } from "./components/processing_requirement"; export function initComponentRegistry() { gComponentRegistry.register(StaticMapEntityComponent); @@ -33,6 +34,7 @@ export function initComponentRegistry() { gComponentRegistry.register(LogicGateComponent); gComponentRegistry.register(LeverComponent); gComponentRegistry.register(WireTunnelComponent); + gComponentRegistry.register(ProcessingRequirementComponent); // IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS @@ -41,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" ); diff --git a/src/js/game/components/processing_requirement.js b/src/js/game/components/processing_requirement.js new file mode 100644 index 00000000..4877f609 --- /dev/null +++ b/src/js/game/components/processing_requirement.js @@ -0,0 +1,91 @@ +import { Component } from "../component"; +import { enumItemProcessorTypes } from "./item_processor"; +import { Entity } from "../entity"; +import { BOOL_TRUE_SINGLETON } from "../items/boolean_item"; +import { BaseItem } from "../base_item"; +import { ShapeItem } from "../items/shape_item"; + +export class ProcessingRequirementComponent extends Component { + static getId() { + return "ProcessingRequirement"; + } + + duplicateWithoutContents() { + return new ProcessingRequirementComponent({ + processorType: this.type + }); + } + + /** + * + * @param {object} param0 + * @param {enumItemProcessorTypes=} param0.processorType Which type of processor this is + * + */ + constructor({ processorType = enumItemProcessorTypes.painterQuad }) { + super(); + + // Type of the processor + this.type = processorType; + } + + /** + * Checks whether it's possible to process something + * @param {Entity} entity + */ + canProcess(entity) { + switch (this.type) { + case enumItemProcessorTypes.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 pinsComp = entity.components.WiredPins; + + /** @type {Object.} */ + 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; + const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); + + // Here we check just basic things` + // Stop processing if anything except TRUE is + // set and there is no item. + for (let i = 0; i < 4; ++i) { + const netValue = pinsComp.slots[i].linkedNetwork ? + pinsComp.slots[i].linkedNetwork.currentValue : + null; + + const currentItem = itemsBySlot[i + 1]; + + if ((netValue == null || !netValue.equals(BOOL_TRUE_SINGLETON)) && currentItem == null) { + let quadCount = 0; + + const quadNumber = (i + 3) % 4 + for (let j = 0; j < 4; ++j) { + const layer = shapeItem.definition.layers[j]; + if (layer && layer[quadNumber]) { + quadCount++; + } + } + + if (quadCount > 0) { + return false; + } + } + } + + return true; + } + default: + assertAlways(false, "Unknown requirement for " + this.type); + } + } + + +} diff --git a/src/js/game/entity_components.js b/src/js/game/entity_components.js index 7bc7b8e7..167eab88 100644 --- a/src/js/game/entity_components.js +++ b/src/js/game/entity_components.js @@ -15,6 +15,7 @@ import { ConstantSignalComponent } from "./components/constant_signal"; import { LogicGateComponent } from "./components/logic_gate"; import { LeverComponent } from "./components/lever"; import { WireTunnelComponent } from "./components/wire_tunnel"; +import { ProcessingRequirementComponent } from "./components/processing_requirement"; /* typehints:end */ /** @@ -73,6 +74,9 @@ export class EntityComponentStorage { /** @type {WireTunnelComponent} */ this.WireTunnel; + /** @type {ProcessingRequirementComponent} */ + this.ProcessingRequirement; + /* typehints:end */ } } diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js index a016d23e..a0181fbb 100644 --- a/src/js/game/shape_definition.js +++ b/src/js/game/shape_definition.js @@ -604,7 +604,7 @@ export class ShapeDefinition extends BasicSerializableObject { for (let quadrantIndex = 0; quadrantIndex < 4; ++quadrantIndex) { const item = quadrants[quadrantIndex]; if (item) { - item.color = colors[quadrantIndex]; + item.color = colors[quadrantIndex] || item.color; } } } diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index 9943594c..a20b03cf 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -1,10 +1,10 @@ import { globalConfig } from "../../core/config"; -import { BaseItem } from "../base_item"; +import { BaseItem, enumItemType } from "../base_item"; import { enumColorMixingResults } from "../colors"; import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; -import { BOOL_TRUE_SINGLETON } from "../items/boolean_item"; +import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item"; import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item"; import { ShapeItem } from "../items/shape_item"; @@ -68,9 +68,17 @@ export class ItemProcessorSystem extends GameSystemWithFilter { } } + + // Check if we have an empty queue and can start a new charge if (processorComp.itemsToEject.length === 0) { - if (processorComp.inputSlots.length >= processorComp.inputsPerCharge) { + const procRequirementComp = entity.components.ProcessingRequirement; + + if (procRequirementComp) { + if (procRequirementComp.canProcess(entity)) { + this.startNewCharge(entity); + } + } else if (processorComp.inputSlots.length >= processorComp.inputsPerCharge) { this.startNewCharge(entity); } } @@ -307,22 +315,66 @@ export class ItemProcessorSystem extends GameSystemWithFilter { case enumItemProcessorTypes.painterQuad: { const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); - const colorItem1 = /** @type {ColorItem} */ (itemsBySlot[1].item); - const colorItem2 = /** @type {ColorItem} */ (itemsBySlot[2].item); - const colorItem3 = /** @type {ColorItem} */ (itemsBySlot[3].item); - const colorItem4 = /** @type {ColorItem} */ (itemsBySlot[4].item); - assert(shapeItem instanceof ShapeItem, "Input for painter is not a shape"); - assert(colorItem1 instanceof ColorItem, "Input for painter is not a color"); - assert(colorItem2 instanceof ColorItem, "Input for painter is not a color"); - assert(colorItem3 instanceof ColorItem, "Input for painter is not a color"); - assert(colorItem4 instanceof ColorItem, "Input for painter is not a color"); + + /** @type {Array} */ + const colorItems = [].fill(null, 0, 4); + + for (let i = 0; i < colorItems.length; ++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"); + } + } + + 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} */ + let skipped = []; + for (let i = 0; i < pinValues.length; ++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].getItemType() == enumItemType.color) { + // 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 colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors( shapeItem.definition, - [colorItem2.color, colorItem3.color, colorItem4.color, colorItem1.color] + [ + (!skipped[0] && colorTL) ? colorTL.color : null, + (!skipped[1] && colorTR) ? colorTR.color : null, + (!skipped[2] && colorBR) ? colorBR.color : null, + (!skipped[3] && colorBL) ? colorBL.color : null, + ] ); + // restore items we didn't use + for (let i = 0; i < 4; ++i) { + if (skipped[i]) { + processorComp.inputSlots.push({ + item: colorItems[i], + sourceSlot: i + 1 + }); + } + } + outItems.push({ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition), });