diff --git a/res_raw/atlas.tps b/res_raw/atlas.tps
index 50c27d8b..5dcc997b 100644
--- a/res_raw/atlas.tps
+++ b/res_raw/atlas.tps
@@ -197,7 +197,7 @@
scaleMode
Smooth
extrude
- 2
+ 4
trimThreshold
2
trimMargin
@@ -281,6 +281,7 @@
sprites/blueprints/underground_belt_exit-tier2.png
sprites/blueprints/underground_belt_exit.png
sprites/blueprints/virtual_processor-analyzer.png
+ sprites/blueprints/virtual_processor-painter.png
sprites/blueprints/virtual_processor-rotater.png
sprites/blueprints/virtual_processor-shapecompare.png
sprites/blueprints/virtual_processor-stacker.png
@@ -309,6 +310,7 @@
sprites/buildings/underground_belt_exit-tier2.png
sprites/buildings/underground_belt_exit.png
sprites/buildings/virtual_processor-analyzer.png
+ sprites/buildings/virtual_processor-painter.png
sprites/buildings/virtual_processor-rotater.png
sprites/buildings/virtual_processor-shapecompare.png
sprites/buildings/virtual_processor-stacker.png
diff --git a/res_raw/sprites/buildings/rotater-fl.png b/res_raw/sprites/buildings/rotater-rotate180.png
similarity index 100%
rename from res_raw/sprites/buildings/rotater-fl.png
rename to res_raw/sprites/buildings/rotater-rotate180.png
diff --git a/src/js/game/buildings/rotater.js b/src/js/game/buildings/rotater.js
index c278ef0d..7e5ef7d7 100644
--- a/src/js/game/buildings/rotater.js
+++ b/src/js/game/buildings/rotater.js
@@ -1,122 +1,122 @@
-import { formatItemsPerSecond } from "../../core/utils";
-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 } from "../components/item_processor";
-import { Entity } from "../entity";
-import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
-import { GameRoot } from "../root";
-import { enumHubGoalRewards } from "../tutorial_goals";
-
-/** @enum {string} */
-export const enumRotaterVariants = { ccw: "ccw", fl: "fl" };
-
-export class MetaRotaterBuilding extends MetaBuilding {
- constructor() {
- super("rotater");
- }
-
- getSilhouetteColor() {
- return "#7dc6cd";
- }
-
- /**
- * @param {GameRoot} root
- * @param {string} variant
- * @returns {Array<[string, string]>}
- */
- getAdditionalStatistics(root, variant) {
- switch (variant) {
- case defaultBuildingVariant: {
- const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotater);
- return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
- }
- case enumRotaterVariants.ccw: {
- const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotaterCCW);
- return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
- }
- case enumRotaterVariants.fl: {
- const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotaterFL);
- return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
- }
- }
- }
-
- /**
- *
- * @param {GameRoot} root
- */
- getAvailableVariants(root) {
- let variants = [defaultBuildingVariant];
- if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_rotater_ccw)) {
- variants.push(enumRotaterVariants.ccw);
- }
- if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_rotater_fl)) {
- variants.push(enumRotaterVariants.fl);
- }
- return variants;
- }
-
- /**
- * @param {GameRoot} root
- */
- getIsUnlocked(root) {
- return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_rotater);
- }
-
- /**
- * Creates the entity at the given location
- * @param {Entity} entity
- */
- setupEntityComponents(entity) {
- entity.addComponent(
- new ItemProcessorComponent({
- inputsPerCharge: 1,
- processorType: enumItemProcessorTypes.rotater,
- })
- );
-
- entity.addComponent(
- new ItemEjectorComponent({
- slots: [{ pos: new Vector(0, 0), direction: enumDirection.top }],
- })
- );
- entity.addComponent(
- new ItemAcceptorComponent({
- slots: [
- {
- pos: new Vector(0, 0),
- directions: [enumDirection.bottom],
- filter: "shape",
- },
- ],
- })
- );
- }
-
- /**
- *
- * @param {Entity} entity
- * @param {number} rotationVariant
- * @param {string} variant
- */
- updateVariants(entity, rotationVariant, variant) {
- switch (variant) {
- case defaultBuildingVariant: {
- entity.components.ItemProcessor.type = enumItemProcessorTypes.rotater;
- break;
- }
- case enumRotaterVariants.ccw: {
- entity.components.ItemProcessor.type = enumItemProcessorTypes.rotaterCCW;
- break;
- }
- case enumRotaterVariants.fl: {
- entity.components.ItemProcessor.type = enumItemProcessorTypes.rotaterFL;
- break;
- }
- default:
- assertAlways(false, "Unknown rotater variant: " + variant);
- }
- }
-}
+import { formatItemsPerSecond } from "../../core/utils";
+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 } from "../components/item_processor";
+import { Entity } from "../entity";
+import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
+import { GameRoot } from "../root";
+import { enumHubGoalRewards } from "../tutorial_goals";
+
+/** @enum {string} */
+export const enumRotaterVariants = { ccw: "ccw", rotate180: "rotate180" };
+
+export class MetaRotaterBuilding extends MetaBuilding {
+ constructor() {
+ super("rotater");
+ }
+
+ getSilhouetteColor() {
+ return "#7dc6cd";
+ }
+
+ /**
+ * @param {GameRoot} root
+ * @param {string} variant
+ * @returns {Array<[string, string]>}
+ */
+ getAdditionalStatistics(root, variant) {
+ switch (variant) {
+ case defaultBuildingVariant: {
+ const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotater);
+ return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
+ }
+ case enumRotaterVariants.ccw: {
+ const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotaterCCW);
+ return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
+ }
+ case enumRotaterVariants.rotate180: {
+ const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotater180);
+ return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
+ }
+ }
+ }
+
+ /**
+ *
+ * @param {GameRoot} root
+ */
+ getAvailableVariants(root) {
+ let variants = [defaultBuildingVariant];
+ if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_rotater_ccw)) {
+ variants.push(enumRotaterVariants.ccw);
+ }
+ if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_rotater_180)) {
+ variants.push(enumRotaterVariants.rotate180);
+ }
+ return variants;
+ }
+
+ /**
+ * @param {GameRoot} root
+ */
+ getIsUnlocked(root) {
+ return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_rotater);
+ }
+
+ /**
+ * Creates the entity at the given location
+ * @param {Entity} entity
+ */
+ setupEntityComponents(entity) {
+ entity.addComponent(
+ new ItemProcessorComponent({
+ inputsPerCharge: 1,
+ processorType: enumItemProcessorTypes.rotater,
+ })
+ );
+
+ entity.addComponent(
+ new ItemEjectorComponent({
+ slots: [{ pos: new Vector(0, 0), direction: enumDirection.top }],
+ })
+ );
+ entity.addComponent(
+ new ItemAcceptorComponent({
+ slots: [
+ {
+ pos: new Vector(0, 0),
+ directions: [enumDirection.bottom],
+ filter: "shape",
+ },
+ ],
+ })
+ );
+ }
+
+ /**
+ *
+ * @param {Entity} entity
+ * @param {number} rotationVariant
+ * @param {string} variant
+ */
+ updateVariants(entity, rotationVariant, variant) {
+ switch (variant) {
+ case defaultBuildingVariant: {
+ entity.components.ItemProcessor.type = enumItemProcessorTypes.rotater;
+ break;
+ }
+ case enumRotaterVariants.ccw: {
+ entity.components.ItemProcessor.type = enumItemProcessorTypes.rotaterCCW;
+ break;
+ }
+ case enumRotaterVariants.rotate180: {
+ entity.components.ItemProcessor.type = enumItemProcessorTypes.rotater180;
+ break;
+ }
+ default:
+ assertAlways(false, "Unknown rotater variant: " + variant);
+ }
+ }
+}
diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js
index 5d51b4a3..4947a521 100644
--- a/src/js/game/components/item_processor.js
+++ b/src/js/game/components/item_processor.js
@@ -1,17 +1,15 @@
import { types } from "../../savegame/serialization";
import { BaseItem } from "../base_item";
import { Component } from "../component";
-import { typeItemSingleton } from "../item_resolver";
/** @enum {string} */
export const enumItemProcessorTypes = {
splitter: "splitter",
- splitterWires: "splitterWires",
cutter: "cutter",
cutterQuad: "cutterQuad",
rotater: "rotater",
rotaterCCW: "rotaterCCW",
- rotaterFL: "rotaterFL",
+ rotater180: "rotater180",
stacker: "stacker",
trash: "trash",
mixer: "mixer",
diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js
index 71817ebd..f0cd0002 100644
--- a/src/js/game/hub_goals.js
+++ b/src/js/game/hub_goals.js
@@ -396,9 +396,6 @@ export class HubGoals extends BasicSerializableObject {
*/
getProcessorBaseSpeed(processorType) {
switch (processorType) {
- case enumItemProcessorTypes.splitterWires:
- return globalConfig.wiresSpeedItemsPerSecond * 2;
-
case enumItemProcessorTypes.trash:
case enumItemProcessorTypes.hub:
return 1e30;
@@ -427,7 +424,7 @@ export class HubGoals extends BasicSerializableObject {
case enumItemProcessorTypes.cutterQuad:
case enumItemProcessorTypes.rotater:
case enumItemProcessorTypes.rotaterCCW:
- case enumItemProcessorTypes.rotaterFL:
+ case enumItemProcessorTypes.rotater180:
case enumItemProcessorTypes.stacker: {
assert(
globalConfig.buildingSpeeds[processorType],
diff --git a/src/js/game/meta_building_registry.js b/src/js/game/meta_building_registry.js
index e1168ef8..9fe57089 100644
--- a/src/js/game/meta_building_registry.js
+++ b/src/js/game/meta_building_registry.js
@@ -70,7 +70,7 @@ export function initMetaBuildingRegistry() {
// Rotater
registerBuildingVariant(11, MetaRotaterBuilding);
registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw);
- registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.fl);
+ registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.rotate180);
// Stacker
registerBuildingVariant(14, MetaStackerBuilding);
diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js
index 65b72a1a..9060a1b5 100644
--- a/src/js/game/shape_definition.js
+++ b/src/js/game/shape_definition.js
@@ -487,10 +487,10 @@ export class ShapeDefinition extends BasicSerializableObject {
}
/**
- * Returns a definition which was rotated 180 degrees (flipped)
+ * Returns a definition which was rotated 180 degrees
* @returns {ShapeDefinition}
*/
- cloneRotateFL() {
+ cloneRotate180() {
const newLayers = this.internalCloneLayers();
for (let layerIndex = 0; layerIndex < newLayers.length; ++layerIndex) {
const quadrants = newLayers[layerIndex];
diff --git a/src/js/game/shape_definition_manager.js b/src/js/game/shape_definition_manager.js
index ef0d592f..86723fcd 100644
--- a/src/js/game/shape_definition_manager.js
+++ b/src/js/game/shape_definition_manager.js
@@ -1,259 +1,259 @@
-import { createLogger } from "../core/logging";
-import { BasicSerializableObject } from "../savegame/serialization";
-import { enumColors } from "./colors";
-import { ShapeItem } from "./items/shape_item";
-import { GameRoot } from "./root";
-import { enumSubShape, ShapeDefinition } from "./shape_definition";
-
-const logger = createLogger("shape_definition_manager");
-
-export class ShapeDefinitionManager extends BasicSerializableObject {
- static getId() {
- return "ShapeDefinitionManager";
- }
-
- /**
- *
- * @param {GameRoot} root
- */
- constructor(root) {
- super();
- this.root = root;
-
- /**
- * Store a cache from key -> definition
- * @type {Object}
- */
- this.shapeKeyToDefinition = {};
-
- /**
- * Store a cache from key -> item
- */
- this.shapeKeyToItem = {};
-
- // Caches operations in the form of 'operation:def1[:def2]'
- /** @type {Object.|ShapeDefinition>} */
- this.operationCache = {};
- }
-
- /**
- * Returns a shape instance from a given short key
- * @param {string} hash
- * @returns {ShapeDefinition}
- */
- getShapeFromShortKey(hash) {
- const cached = this.shapeKeyToDefinition[hash];
- if (cached) {
- return cached;
- }
- return (this.shapeKeyToDefinition[hash] = ShapeDefinition.fromShortKey(hash));
- }
-
- /**
- * Returns a item instance from a given short key
- * @param {string} hash
- * @returns {ShapeItem}
- */
- getShapeItemFromShortKey(hash) {
- const cached = this.shapeKeyToItem[hash];
- if (cached) {
- return cached;
- }
- const definition = this.getShapeFromShortKey(hash);
- return (this.shapeKeyToItem[hash] = new ShapeItem(definition));
- }
-
- /**
- * Returns a shape item for a given definition
- * @param {ShapeDefinition} definition
- * @returns {ShapeItem}
- */
- getShapeItemFromDefinition(definition) {
- return this.getShapeItemFromShortKey(definition.getHash());
- }
-
- /**
- * Registers a new shape definition
- * @param {ShapeDefinition} definition
- */
- registerShapeDefinition(definition) {
- const id = definition.getHash();
- assert(!this.shapeKeyToDefinition[id], "Shape Definition " + id + " already exists");
- this.shapeKeyToDefinition[id] = definition;
- // logger.log("Registered shape with key", id);
- }
-
- /**
- * Generates a definition for splitting a shape definition in two halfs
- * @param {ShapeDefinition} definition
- * @returns {[ShapeDefinition, ShapeDefinition]}
- */
- shapeActionCutHalf(definition) {
- const key = "cut:" + definition.getHash();
- if (this.operationCache[key]) {
- return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key]);
- }
- const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
- const leftSide = definition.cloneFilteredByQuadrants([0, 1]);
-
- return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [
- this.registerOrReturnHandle(rightSide),
- this.registerOrReturnHandle(leftSide),
- ]);
- }
-
- /**
- * Generates a definition for splitting a shape definition in four quads
- * @param {ShapeDefinition} definition
- * @returns {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]}
- */
- shapeActionCutQuad(definition) {
- const key = "cut-quad:" + definition.getHash();
- if (this.operationCache[key]) {
- return /** @type {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]} */ (this
- .operationCache[key]);
- }
-
- return /** @type {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]} */ (this.operationCache[
- key
- ] = [
- this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([0])),
- this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([1])),
- this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([2])),
- this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([3])),
- ]);
- }
-
- /**
- * Generates a definition for rotating a shape clockwise
- * @param {ShapeDefinition} definition
- * @returns {ShapeDefinition}
- */
- shapeActionRotateCW(definition) {
- const key = "rotate-cw:" + definition.getHash();
- if (this.operationCache[key]) {
- return /** @type {ShapeDefinition} */ (this.operationCache[key]);
- }
-
- const rotated = definition.cloneRotateCW();
-
- return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
- rotated
- ));
- }
-
- /**
- * Generates a definition for rotating a shape counter clockwise
- * @param {ShapeDefinition} definition
- * @returns {ShapeDefinition}
- */
- shapeActionRotateCCW(definition) {
- const key = "rotate-ccw:" + definition.getHash();
- if (this.operationCache[key]) {
- return /** @type {ShapeDefinition} */ (this.operationCache[key]);
- }
-
- const rotated = definition.cloneRotateCCW();
-
- return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
- rotated
- ));
- }
-
- /**
- * Generates a definition for rotating a shape counter clockwise
- * @param {ShapeDefinition} definition
- * @returns {ShapeDefinition}
- */
- shapeActionRotateFL(definition) {
- const key = "rotate-fl:" + definition.getHash();
- if (this.operationCache[key]) {
- return /** @type {ShapeDefinition} */ (this.operationCache[key]);
- }
-
- const rotated = definition.cloneRotateFL();
-
- return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
- rotated
- ));
- }
-
- /**
- * Generates a definition for stacking the upper definition onto the lower one
- * @param {ShapeDefinition} lowerDefinition
- * @param {ShapeDefinition} upperDefinition
- * @returns {ShapeDefinition}
- */
- shapeActionStack(lowerDefinition, upperDefinition) {
- const key = "stack:" + lowerDefinition.getHash() + ":" + upperDefinition.getHash();
- if (this.operationCache[key]) {
- return /** @type {ShapeDefinition} */ (this.operationCache[key]);
- }
- const stacked = lowerDefinition.cloneAndStackWith(upperDefinition);
- return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
- stacked
- ));
- }
-
- /**
- * Generates a definition for painting it with the given color
- * @param {ShapeDefinition} definition
- * @param {enumColors} color
- * @returns {ShapeDefinition}
- */
- shapeActionPaintWith(definition, color) {
- const key = "paint:" + definition.getHash() + ":" + color;
- if (this.operationCache[key]) {
- return /** @type {ShapeDefinition} */ (this.operationCache[key]);
- }
- const colorized = definition.cloneAndPaintWith(color);
- return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
- colorized
- ));
- }
-
- /**
- * Generates a definition for painting it with the 4 colors
- * @param {ShapeDefinition} definition
- * @param {[enumColors, enumColors, enumColors, enumColors]} colors
- * @returns {ShapeDefinition}
- */
- shapeActionPaintWith4Colors(definition, colors) {
- const key = "paint4:" + definition.getHash() + ":" + colors.join(",");
- if (this.operationCache[key]) {
- return /** @type {ShapeDefinition} */ (this.operationCache[key]);
- }
- const colorized = definition.cloneAndPaintWith4Colors(colors);
- return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
- colorized
- ));
- }
-
- /**
- * Checks if we already have cached this definition, and if so throws it away and returns the already
- * cached variant
- * @param {ShapeDefinition} definition
- */
- registerOrReturnHandle(definition) {
- const id = definition.getHash();
- if (this.shapeKeyToDefinition[id]) {
- return this.shapeKeyToDefinition[id];
- }
- this.shapeKeyToDefinition[id] = definition;
- // logger.log("Registered shape with key (2)", id);
- return definition;
- }
-
- /**
- *
- * @param {[enumSubShape, enumSubShape, enumSubShape, enumSubShape]} subShapes
- * @returns {ShapeDefinition}
- */
- getDefinitionFromSimpleShapes(subShapes, color = enumColors.uncolored) {
- const shapeLayer = /** @type {import("./shape_definition").ShapeLayer} */ (subShapes.map(
- subShape => ({ subShape, color })
- ));
-
- return this.registerOrReturnHandle(new ShapeDefinition({ layers: [shapeLayer] }));
- }
-}
+import { createLogger } from "../core/logging";
+import { BasicSerializableObject } from "../savegame/serialization";
+import { enumColors } from "./colors";
+import { ShapeItem } from "./items/shape_item";
+import { GameRoot } from "./root";
+import { enumSubShape, ShapeDefinition } from "./shape_definition";
+
+const logger = createLogger("shape_definition_manager");
+
+export class ShapeDefinitionManager extends BasicSerializableObject {
+ static getId() {
+ return "ShapeDefinitionManager";
+ }
+
+ /**
+ *
+ * @param {GameRoot} root
+ */
+ constructor(root) {
+ super();
+ this.root = root;
+
+ /**
+ * Store a cache from key -> definition
+ * @type {Object}
+ */
+ this.shapeKeyToDefinition = {};
+
+ /**
+ * Store a cache from key -> item
+ */
+ this.shapeKeyToItem = {};
+
+ // Caches operations in the form of 'operation:def1[:def2]'
+ /** @type {Object.|ShapeDefinition>} */
+ this.operationCache = {};
+ }
+
+ /**
+ * Returns a shape instance from a given short key
+ * @param {string} hash
+ * @returns {ShapeDefinition}
+ */
+ getShapeFromShortKey(hash) {
+ const cached = this.shapeKeyToDefinition[hash];
+ if (cached) {
+ return cached;
+ }
+ return (this.shapeKeyToDefinition[hash] = ShapeDefinition.fromShortKey(hash));
+ }
+
+ /**
+ * Returns a item instance from a given short key
+ * @param {string} hash
+ * @returns {ShapeItem}
+ */
+ getShapeItemFromShortKey(hash) {
+ const cached = this.shapeKeyToItem[hash];
+ if (cached) {
+ return cached;
+ }
+ const definition = this.getShapeFromShortKey(hash);
+ return (this.shapeKeyToItem[hash] = new ShapeItem(definition));
+ }
+
+ /**
+ * Returns a shape item for a given definition
+ * @param {ShapeDefinition} definition
+ * @returns {ShapeItem}
+ */
+ getShapeItemFromDefinition(definition) {
+ return this.getShapeItemFromShortKey(definition.getHash());
+ }
+
+ /**
+ * Registers a new shape definition
+ * @param {ShapeDefinition} definition
+ */
+ registerShapeDefinition(definition) {
+ const id = definition.getHash();
+ assert(!this.shapeKeyToDefinition[id], "Shape Definition " + id + " already exists");
+ this.shapeKeyToDefinition[id] = definition;
+ // logger.log("Registered shape with key", id);
+ }
+
+ /**
+ * Generates a definition for splitting a shape definition in two halfs
+ * @param {ShapeDefinition} definition
+ * @returns {[ShapeDefinition, ShapeDefinition]}
+ */
+ shapeActionCutHalf(definition) {
+ const key = "cut:" + definition.getHash();
+ if (this.operationCache[key]) {
+ return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key]);
+ }
+ const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
+ const leftSide = definition.cloneFilteredByQuadrants([0, 1]);
+
+ return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [
+ this.registerOrReturnHandle(rightSide),
+ this.registerOrReturnHandle(leftSide),
+ ]);
+ }
+
+ /**
+ * Generates a definition for splitting a shape definition in four quads
+ * @param {ShapeDefinition} definition
+ * @returns {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]}
+ */
+ shapeActionCutQuad(definition) {
+ const key = "cut-quad:" + definition.getHash();
+ if (this.operationCache[key]) {
+ return /** @type {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]} */ (this
+ .operationCache[key]);
+ }
+
+ return /** @type {[ShapeDefinition, ShapeDefinition, ShapeDefinition, ShapeDefinition]} */ (this.operationCache[
+ key
+ ] = [
+ this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([0])),
+ this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([1])),
+ this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([2])),
+ this.registerOrReturnHandle(definition.cloneFilteredByQuadrants([3])),
+ ]);
+ }
+
+ /**
+ * Generates a definition for rotating a shape clockwise
+ * @param {ShapeDefinition} definition
+ * @returns {ShapeDefinition}
+ */
+ shapeActionRotateCW(definition) {
+ const key = "rotate-cw:" + definition.getHash();
+ if (this.operationCache[key]) {
+ return /** @type {ShapeDefinition} */ (this.operationCache[key]);
+ }
+
+ const rotated = definition.cloneRotateCW();
+
+ return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
+ rotated
+ ));
+ }
+
+ /**
+ * Generates a definition for rotating a shape counter clockwise
+ * @param {ShapeDefinition} definition
+ * @returns {ShapeDefinition}
+ */
+ shapeActionRotateCCW(definition) {
+ const key = "rotate-ccw:" + definition.getHash();
+ if (this.operationCache[key]) {
+ return /** @type {ShapeDefinition} */ (this.operationCache[key]);
+ }
+
+ const rotated = definition.cloneRotateCCW();
+
+ return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
+ rotated
+ ));
+ }
+
+ /**
+ * Generates a definition for rotating a shape FL
+ * @param {ShapeDefinition} definition
+ * @returns {ShapeDefinition}
+ */
+ shapeActionRotate180(definition) {
+ const key = "rotate-fl:" + definition.getHash();
+ if (this.operationCache[key]) {
+ return /** @type {ShapeDefinition} */ (this.operationCache[key]);
+ }
+
+ const rotated = definition.cloneRotate180();
+
+ return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
+ rotated
+ ));
+ }
+
+ /**
+ * Generates a definition for stacking the upper definition onto the lower one
+ * @param {ShapeDefinition} lowerDefinition
+ * @param {ShapeDefinition} upperDefinition
+ * @returns {ShapeDefinition}
+ */
+ shapeActionStack(lowerDefinition, upperDefinition) {
+ const key = "stack:" + lowerDefinition.getHash() + ":" + upperDefinition.getHash();
+ if (this.operationCache[key]) {
+ return /** @type {ShapeDefinition} */ (this.operationCache[key]);
+ }
+ const stacked = lowerDefinition.cloneAndStackWith(upperDefinition);
+ return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
+ stacked
+ ));
+ }
+
+ /**
+ * Generates a definition for painting it with the given color
+ * @param {ShapeDefinition} definition
+ * @param {enumColors} color
+ * @returns {ShapeDefinition}
+ */
+ shapeActionPaintWith(definition, color) {
+ const key = "paint:" + definition.getHash() + ":" + color;
+ if (this.operationCache[key]) {
+ return /** @type {ShapeDefinition} */ (this.operationCache[key]);
+ }
+ const colorized = definition.cloneAndPaintWith(color);
+ return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
+ colorized
+ ));
+ }
+
+ /**
+ * Generates a definition for painting it with the 4 colors
+ * @param {ShapeDefinition} definition
+ * @param {[enumColors, enumColors, enumColors, enumColors]} colors
+ * @returns {ShapeDefinition}
+ */
+ shapeActionPaintWith4Colors(definition, colors) {
+ const key = "paint4:" + definition.getHash() + ":" + colors.join(",");
+ if (this.operationCache[key]) {
+ return /** @type {ShapeDefinition} */ (this.operationCache[key]);
+ }
+ const colorized = definition.cloneAndPaintWith4Colors(colors);
+ return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
+ colorized
+ ));
+ }
+
+ /**
+ * Checks if we already have cached this definition, and if so throws it away and returns the already
+ * cached variant
+ * @param {ShapeDefinition} definition
+ */
+ registerOrReturnHandle(definition) {
+ const id = definition.getHash();
+ if (this.shapeKeyToDefinition[id]) {
+ return this.shapeKeyToDefinition[id];
+ }
+ this.shapeKeyToDefinition[id] = definition;
+ // logger.log("Registered shape with key (2)", id);
+ return definition;
+ }
+
+ /**
+ *
+ * @param {[enumSubShape, enumSubShape, enumSubShape, enumSubShape]} subShapes
+ * @returns {ShapeDefinition}
+ */
+ getDefinitionFromSimpleShapes(subShapes, color = enumColors.uncolored) {
+ const shapeLayer = /** @type {import("./shape_definition").ShapeLayer} */ (subShapes.map(
+ subShape => ({ subShape, color })
+ ));
+
+ return this.registerOrReturnHandle(new ShapeDefinition({ layers: [shapeLayer] }));
+ }
+}
diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js
index 3789eecc..8da414f2 100644
--- a/src/js/game/systems/item_processor.js
+++ b/src/js/game/systems/item_processor.js
@@ -16,9 +16,56 @@ import { ShapeItem } from "../items/shape_item";
*/
const MAX_QUEUED_CHARGES = 2;
+/**
+ * Whole data for a produced item
+ *
+ * @typedef {{
+ * item: BaseItem,
+ * preferredSlot?: number,
+ * requiredSlot?: number,
+ * doNotTrack?: boolean
+ * }} ProducedItem
+ */
+
+/**
+ * Type of a processor implementation
+ * @typedef {{
+ * entity: Entity,
+ * items: Array<{ item: BaseItem, sourceSlot: number }>,
+ * itemsBySlot: Object,
+ * outItems: Array
+ * }} ProcessorImplementationPayload
+ */
+
export class ItemProcessorSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [ItemProcessorComponent]);
+
+ /**
+ * @type {Object}
+ */
+ this.handlers = {
+ [enumItemProcessorTypes.splitter]: this.process_SPLITTER,
+ [enumItemProcessorTypes.cutter]: this.process_CUTTER,
+ [enumItemProcessorTypes.cutterQuad]: this.process_CUTTER_QUAD,
+ [enumItemProcessorTypes.rotater]: this.process_ROTATER,
+ [enumItemProcessorTypes.rotaterCCW]: this.process_ROTATER_CCW,
+ [enumItemProcessorTypes.rotater180]: this.process_ROTATER_180,
+ [enumItemProcessorTypes.stacker]: this.process_STACKER,
+ [enumItemProcessorTypes.trash]: this.process_TRASH,
+ [enumItemProcessorTypes.mixer]: this.process_MIXER,
+ [enumItemProcessorTypes.painter]: this.process_PAINTER,
+ [enumItemProcessorTypes.painterDouble]: this.process_PAINTER_DOUBLE,
+ [enumItemProcessorTypes.painterQuad]: this.process_PAINTER_QUAD,
+ [enumItemProcessorTypes.hub]: this.process_HUB,
+ [enumItemProcessorTypes.filter]: this.process_FILTER,
+ [enumItemProcessorTypes.reader]: this.process_READER,
+ };
+
+ // Bind all handlers
+ for (const key in this.handlers) {
+ this.handlers[key] = this.handlers[key].bind(this);
+ }
}
update() {
@@ -238,310 +285,30 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const items = processorComp.inputSlots;
processorComp.inputSlots = [];
- /** @type {Object.} */
+ /** @type {Object} */
const itemsBySlot = {};
for (let i = 0; i < items.length; ++i) {
- itemsBySlot[items[i].sourceSlot] = items[i];
+ itemsBySlot[items[i].sourceSlot] = items[i].item;
}
- /** @type {Array<{item: BaseItem, requiredSlot?: number, preferredSlot?: number}>} */
+ /** @type {Array} */
const outItems = [];
- // Whether to track the production towards the analytics
- let trackProduction = true;
+ /** @type {function(ProcessorImplementationPayload) : void} */
+ const handler = this.handlers[processorComp.type];
+ assert(handler, "No handler for processor type defined: " + processorComp.type);
- // DO SOME MAGIC
-
- switch (processorComp.type) {
- // SPLITTER
- case enumItemProcessorTypes.splitterWires:
- case enumItemProcessorTypes.splitter: {
- trackProduction = false;
- const availableSlots = entity.components.ItemEjector.slots.length;
-
- let nextSlot = processorComp.nextOutputSlot++ % availableSlots;
- for (let i = 0; i < items.length; ++i) {
- outItems.push({
- item: items[i].item,
- preferredSlot: (nextSlot + i) % availableSlots,
- });
- }
- break;
- }
-
- // CUTTER
- case enumItemProcessorTypes.cutter: {
- const inputItem = /** @type {ShapeItem} */ (items[0].item);
- assert(inputItem instanceof ShapeItem, "Input for cut is not a shape");
- const inputDefinition = inputItem.definition;
-
- const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutHalf(inputDefinition);
-
- for (let i = 0; i < cutDefinitions.length; ++i) {
- const definition = cutDefinitions[i];
- if (!definition.isEntirelyEmpty()) {
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
- requiredSlot: i,
- });
- }
- }
-
- break;
- }
-
- // CUTTER (Quad)
- case enumItemProcessorTypes.cutterQuad: {
- const inputItem = /** @type {ShapeItem} */ (items[0].item);
- assert(inputItem instanceof ShapeItem, "Input for cut is not a shape");
- const inputDefinition = inputItem.definition;
-
- const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutQuad(inputDefinition);
-
- for (let i = 0; i < cutDefinitions.length; ++i) {
- const definition = cutDefinitions[i];
- if (!definition.isEntirelyEmpty()) {
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
- requiredSlot: i,
- });
- }
- }
-
- break;
- }
-
- // ROTATER
- case enumItemProcessorTypes.rotater: {
- const inputItem = /** @type {ShapeItem} */ (items[0].item);
- assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape");
- const inputDefinition = inputItem.definition;
-
- const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(inputDefinition);
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
- });
- break;
- }
-
- // ROTATER (CCW)
- case enumItemProcessorTypes.rotaterCCW: {
- const inputItem = /** @type {ShapeItem} */ (items[0].item);
- assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape");
- const inputDefinition = inputItem.definition;
-
- const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCCW(inputDefinition);
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
- });
- break;
- }
-
- // ROTATER (FL)
- case enumItemProcessorTypes.rotaterFL: {
- const inputItem = /** @type {ShapeItem} */ (items[0].item);
- assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape");
- const inputDefinition = inputItem.definition;
-
- const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateFL(inputDefinition);
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
- });
- break;
- }
-
- // STACKER
-
- case enumItemProcessorTypes.stacker: {
- const lowerItem = /** @type {ShapeItem} */ (itemsBySlot[0].item);
- const upperItem = /** @type {ShapeItem} */ (itemsBySlot[1].item);
-
- assert(lowerItem instanceof ShapeItem, "Input for lower stack is not a shape");
- assert(upperItem instanceof ShapeItem, "Input for upper stack is not a shape");
-
- const stackedDefinition = this.root.shapeDefinitionMgr.shapeActionStack(
- lowerItem.definition,
- upperItem.definition
- );
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(stackedDefinition),
- });
- break;
- }
-
- // TRASH
-
- case enumItemProcessorTypes.trash: {
- // Well this one is easy .. simply do nothing with the item
- break;
- }
-
- // MIXER
-
- case enumItemProcessorTypes.mixer: {
- // Find both colors and combine them
- const item1 = /** @type {ColorItem} */ (items[0].item);
- const item2 = /** @type {ColorItem} */ (items[1].item);
- assert(item1 instanceof ColorItem, "Input for color mixer is not a color");
- assert(item2 instanceof ColorItem, "Input for color mixer is not a color");
-
- const color1 = item1.color;
- const color2 = item2.color;
-
- // Try finding mixer color, and if we can't mix it we simply return the same color
- const mixedColor = enumColorMixingResults[color1][color2];
- let resultColor = color1;
- if (mixedColor) {
- resultColor = mixedColor;
- }
- outItems.push({
- item: COLOR_ITEM_SINGLETONS[resultColor],
- });
-
- break;
- }
-
- // PAINTER
-
- case enumItemProcessorTypes.painter: {
- const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item);
- const colorItem = /** @type {ColorItem} */ (itemsBySlot[1].item);
-
- const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith(
- shapeItem.definition,
- colorItem.color
- );
-
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition),
- });
-
- break;
- }
-
- // PAINTER (DOUBLE)
-
- case enumItemProcessorTypes.painterDouble: {
- const shapeItem1 = /** @type {ShapeItem} */ (itemsBySlot[0].item);
- const shapeItem2 = /** @type {ShapeItem} */ (itemsBySlot[1].item);
- const colorItem = /** @type {ColorItem} */ (itemsBySlot[2].item);
-
- assert(shapeItem1 instanceof ShapeItem, "Input for painter is not a shape");
- assert(shapeItem2 instanceof ShapeItem, "Input for painter is not a shape");
- assert(colorItem instanceof ColorItem, "Input for painter is not a color");
-
- const colorizedDefinition1 = this.root.shapeDefinitionMgr.shapeActionPaintWith(
- shapeItem1.definition,
- colorItem.color
- );
-
- const colorizedDefinition2 = this.root.shapeDefinitionMgr.shapeActionPaintWith(
- shapeItem2.definition,
- colorItem.color
- );
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition1),
- });
-
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition2),
- });
-
- break;
- }
-
- // PAINTER (QUAD)
-
- case enumItemProcessorTypes.painterQuad: {
- const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item);
- assert(shapeItem instanceof ShapeItem, "Input for painter is not a shape");
-
- /** @type {Array} */
- const colors = [null, null, null, null];
- for (let i = 0; i < 4; ++i) {
- if (itemsBySlot[i + 1]) {
- colors[i] = /** @type {ColorItem} */ (itemsBySlot[i + 1].item).color;
- }
- }
-
- const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors(
- shapeItem.definition,
- /** @type {[string, string, string, string]} */ (colors)
- );
-
- outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition),
- });
- break;
- }
-
- // FILTER
- case enumItemProcessorTypes.filter: {
- // TODO
- trackProduction = false;
-
- const item = itemsBySlot[0].item;
-
- const network = entity.components.WiredPins.slots[0].linkedNetwork;
- if (!network || !network.currentValue) {
- outItems.push({
- item,
- requiredSlot: 1,
- });
- break;
- }
-
- const value = network.currentValue;
- if (value.equals(BOOL_TRUE_SINGLETON) || value.equals(item)) {
- outItems.push({
- item,
- requiredSlot: 0,
- });
- } else {
- outItems.push({
- item,
- requiredSlot: 1,
- });
- }
-
- break;
- }
-
- // READER
- case enumItemProcessorTypes.reader: {
- // Pass through the item
- const item = itemsBySlot[0].item;
- outItems.push({ item });
-
- // Track the item
- const readerComp = entity.components.BeltReader;
- readerComp.lastItemTimes.push(this.root.time.now());
- readerComp.lastItem = item;
- break;
- }
-
- // HUB
- case enumItemProcessorTypes.hub: {
- trackProduction = false;
-
- const hubComponent = entity.components.Hub;
- assert(hubComponent, "Hub item processor has no hub component");
-
- for (let i = 0; i < items.length; ++i) {
- const item = /** @type {ShapeItem} */ (items[i].item);
- this.root.hubGoals.handleDefinitionDelivered(item.definition);
- }
-
- break;
- }
-
- default:
- assertAlways(false, "Unkown item processor type: " + processorComp.type);
- }
+ // Call implementation
+ handler({
+ entity,
+ items,
+ itemsBySlot,
+ outItems,
+ });
// Track produced items
- if (trackProduction) {
- for (let i = 0; i < outItems.length; ++i) {
+ for (let i = 0; i < outItems.length; ++i) {
+ if (!outItems[i].doNotTrack) {
this.root.signals.itemProduced.dispatch(outItems[i].item);
}
}
@@ -559,4 +326,292 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
remainingTime: timeToProcess,
});
}
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_SPLITTER(payload) {
+ // trackProduction = false;
+ const availableSlots = payload.entity.components.ItemEjector.slots.length;
+ const processorComp = payload.entity.components.ItemProcessor;
+
+ const nextSlot = processorComp.nextOutputSlot++ % availableSlots;
+
+ for (let i = 0; i < payload.items.length; ++i) {
+ payload.outItems.push({
+ item: payload.items[i].item,
+ preferredSlot: (nextSlot + i) % availableSlots,
+ doNotTrack: true,
+ });
+ }
+ return true;
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_CUTTER(payload) {
+ const inputItem = /** @type {ShapeItem} */ (payload.items[0].item);
+ assert(inputItem instanceof ShapeItem, "Input for cut is not a shape");
+ const inputDefinition = inputItem.definition;
+
+ const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutHalf(inputDefinition);
+
+ for (let i = 0; i < cutDefinitions.length; ++i) {
+ const definition = cutDefinitions[i];
+ if (!definition.isEntirelyEmpty()) {
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
+ requiredSlot: i,
+ });
+ }
+ }
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_CUTTER_QUAD(payload) {
+ const inputItem = /** @type {ShapeItem} */ (payload.items[0].item);
+ assert(inputItem instanceof ShapeItem, "Input for cut is not a shape");
+ const inputDefinition = inputItem.definition;
+
+ const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutQuad(inputDefinition);
+
+ for (let i = 0; i < cutDefinitions.length; ++i) {
+ const definition = cutDefinitions[i];
+ if (!definition.isEntirelyEmpty()) {
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
+ requiredSlot: i,
+ });
+ }
+ }
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_ROTATER(payload) {
+ const inputItem = /** @type {ShapeItem} */ (payload.items[0].item);
+ assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape");
+ const inputDefinition = inputItem.definition;
+
+ const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(inputDefinition);
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_ROTATER_CCW(payload) {
+ const inputItem = /** @type {ShapeItem} */ (payload.items[0].item);
+ assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape");
+ const inputDefinition = inputItem.definition;
+
+ const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCCW(inputDefinition);
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_ROTATER_180(payload) {
+ const inputItem = /** @type {ShapeItem} */ (payload.items[0].item);
+ assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape");
+ const inputDefinition = inputItem.definition;
+
+ const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotate180(inputDefinition);
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition),
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_STACKER(payload) {
+ const lowerItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]);
+ const upperItem = /** @type {ShapeItem} */ (payload.itemsBySlot[1]);
+
+ assert(lowerItem instanceof ShapeItem, "Input for lower stack is not a shape");
+ assert(upperItem instanceof ShapeItem, "Input for upper stack is not a shape");
+
+ const stackedDefinition = this.root.shapeDefinitionMgr.shapeActionStack(
+ lowerItem.definition,
+ upperItem.definition
+ );
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(stackedDefinition),
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_TRASH(payload) {
+ // Do nothing ..
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_MIXER(payload) {
+ // Find both colors and combine them
+ const item1 = /** @type {ColorItem} */ (payload.items[0].item);
+ const item2 = /** @type {ColorItem} */ (payload.items[1].item);
+ assert(item1 instanceof ColorItem, "Input for color mixer is not a color");
+ assert(item2 instanceof ColorItem, "Input for color mixer is not a color");
+
+ const color1 = item1.color;
+ const color2 = item2.color;
+
+ // Try finding mixer color, and if we can't mix it we simply return the same color
+ const mixedColor = enumColorMixingResults[color1][color2];
+ let resultColor = color1;
+ if (mixedColor) {
+ resultColor = mixedColor;
+ }
+ payload.outItems.push({
+ item: COLOR_ITEM_SINGLETONS[resultColor],
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_PAINTER(payload) {
+ const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]);
+ const colorItem = /** @type {ColorItem} */ (payload.itemsBySlot[1]);
+
+ const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith(
+ shapeItem.definition,
+ colorItem.color
+ );
+
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition),
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_PAINTER_DOUBLE(payload) {
+ const shapeItem1 = /** @type {ShapeItem} */ (payload.itemsBySlot[0]);
+ const shapeItem2 = /** @type {ShapeItem} */ (payload.itemsBySlot[1]);
+ const colorItem = /** @type {ColorItem} */ (payload.itemsBySlot[2]);
+
+ assert(shapeItem1 instanceof ShapeItem, "Input for painter is not a shape");
+ assert(shapeItem2 instanceof ShapeItem, "Input for painter is not a shape");
+ assert(colorItem instanceof ColorItem, "Input for painter is not a color");
+
+ const colorizedDefinition1 = this.root.shapeDefinitionMgr.shapeActionPaintWith(
+ shapeItem1.definition,
+ colorItem.color
+ );
+
+ const colorizedDefinition2 = this.root.shapeDefinitionMgr.shapeActionPaintWith(
+ shapeItem2.definition,
+ colorItem.color
+ );
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition1),
+ });
+
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition2),
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_PAINTER_QUAD(payload) {
+ const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]);
+ assert(shapeItem instanceof ShapeItem, "Input for painter is not a shape");
+
+ /** @type {Array} */
+ const colors = [null, null, null, null];
+ for (let i = 0; i < 4; ++i) {
+ if (payload.itemsBySlot[i + 1]) {
+ colors[i] = /** @type {ColorItem} */ (payload.itemsBySlot[i + 1]).color;
+ }
+ }
+
+ const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors(
+ shapeItem.definition,
+ /** @type {[string, string, string, string]} */ (colors)
+ );
+
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition),
+ });
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_FILTER(payload) {
+ const item = payload.itemsBySlot[0];
+
+ const network = payload.entity.components.WiredPins.slots[0].linkedNetwork;
+ if (!network || !network.currentValue) {
+ payload.outItems.push({
+ item,
+ requiredSlot: 1,
+ doNotTrack: true,
+ });
+ return;
+ }
+
+ const value = network.currentValue;
+ if (value.equals(BOOL_TRUE_SINGLETON) || value.equals(item)) {
+ payload.outItems.push({
+ item,
+ requiredSlot: 0,
+ doNotTrack: true,
+ });
+ } else {
+ payload.outItems.push({
+ item,
+ requiredSlot: 1,
+ doNotTrack: true,
+ });
+ }
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_READER(payload) {
+ // Pass through the item
+ const item = payload.itemsBySlot[0];
+ payload.outItems.push({
+ item,
+ doNotTrack: true,
+ });
+
+ // Track the item
+ const readerComp = payload.entity.components.BeltReader;
+ readerComp.lastItemTimes.push(this.root.time.now());
+ readerComp.lastItem = item;
+ }
+
+ /**
+ * @param {ProcessorImplementationPayload} payload
+ */
+ process_HUB(payload) {
+ const hubComponent = payload.entity.components.Hub;
+ assert(hubComponent, "Hub item processor has no hub component");
+
+ for (let i = 0; i < payload.items.length; ++i) {
+ const item = /** @type {ShapeItem} */ (payload.items[i].item);
+ this.root.hubGoals.handleDefinitionDelivered(item.definition);
+ }
+ }
}
diff --git a/src/js/game/tutorial_goals.js b/src/js/game/tutorial_goals.js
index 9084f508..6dce9617 100644
--- a/src/js/game/tutorial_goals.js
+++ b/src/js/game/tutorial_goals.js
@@ -15,7 +15,7 @@ export const enumHubGoalRewards = {
reward_tunnel: "reward_tunnel",
reward_rotater_ccw: "reward_rotater_ccw",
- reward_rotater_fl: "reward_rotater_fl",
+ reward_rotater_180: "reward_rotater_fl",
reward_miner_chainable: "reward_miner_chainable",
reward_underground_belt_tier_2: "reward_underground_belt_tier_2",
reward_splitter_compact: "reward_splitter_compact",
diff --git a/src/js/game/tutorial_goals_mappings.js b/src/js/game/tutorial_goals_mappings.js
index 923c2814..93fa64c4 100644
--- a/src/js/game/tutorial_goals_mappings.js
+++ b/src/js/game/tutorial_goals_mappings.js
@@ -1,52 +1,52 @@
-import { MetaBuilding, defaultBuildingVariant } from "./meta_building";
-import { MetaCutterBuilding, enumCutterVariants } from "./buildings/cutter";
-import { MetaRotaterBuilding, enumRotaterVariants } from "./buildings/rotater";
-import { MetaPainterBuilding, enumPainterVariants } from "./buildings/painter";
-import { MetaMixerBuilding } from "./buildings/mixer";
-import { MetaStackerBuilding } from "./buildings/stacker";
-import { MetaSplitterBuilding, enumSplitterVariants } from "./buildings/splitter";
-import { MetaUndergroundBeltBuilding, enumUndergroundBeltVariants } from "./buildings/underground_belt";
-import { MetaMinerBuilding, enumMinerVariants } from "./buildings/miner";
-import { MetaTrashBuilding, enumTrashVariants } from "./buildings/trash";
-
-/** @typedef {Array<[typeof MetaBuilding, string]>} TutorialGoalReward */
-
-import { enumHubGoalRewards } from "./tutorial_goals";
-
-/**
- * Helper method for proper types
- * @returns {TutorialGoalReward}
- */
-const typed = x => x;
-
-/**
- * Stores which reward unlocks what
- * @enum {TutorialGoalReward?}
- */
-export const enumHubGoalRewardsToContentUnlocked = {
- [enumHubGoalRewards.reward_cutter_and_trash]: typed([[MetaCutterBuilding, defaultBuildingVariant]]),
- [enumHubGoalRewards.reward_rotater]: typed([[MetaRotaterBuilding, defaultBuildingVariant]]),
- [enumHubGoalRewards.reward_painter]: typed([[MetaPainterBuilding, defaultBuildingVariant]]),
- [enumHubGoalRewards.reward_mixer]: typed([[MetaMixerBuilding, defaultBuildingVariant]]),
- [enumHubGoalRewards.reward_stacker]: typed([[MetaStackerBuilding, defaultBuildingVariant]]),
- [enumHubGoalRewards.reward_splitter]: typed([[MetaSplitterBuilding, defaultBuildingVariant]]),
- [enumHubGoalRewards.reward_tunnel]: typed([[MetaUndergroundBeltBuilding, defaultBuildingVariant]]),
-
- [enumHubGoalRewards.reward_rotater_ccw]: typed([[MetaRotaterBuilding, enumRotaterVariants.ccw]]),
- [enumHubGoalRewards.reward_rotater_fl]: typed([[MetaRotaterBuilding, enumRotaterVariants.fl]]),
- [enumHubGoalRewards.reward_miner_chainable]: typed([[MetaMinerBuilding, enumMinerVariants.chainable]]),
- [enumHubGoalRewards.reward_underground_belt_tier_2]: typed([
- [MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2],
- ]),
- [enumHubGoalRewards.reward_splitter_compact]: typed([
- [MetaSplitterBuilding, enumSplitterVariants.compact],
- ]),
- [enumHubGoalRewards.reward_cutter_quad]: typed([[MetaCutterBuilding, enumCutterVariants.quad]]),
- [enumHubGoalRewards.reward_painter_double]: typed([[MetaPainterBuilding, enumPainterVariants.double]]),
- [enumHubGoalRewards.reward_painter_quad]: typed([[MetaPainterBuilding, enumPainterVariants.quad]]),
- [enumHubGoalRewards.reward_storage]: typed([[MetaTrashBuilding, enumTrashVariants.storage]]),
-
- [enumHubGoalRewards.reward_freeplay]: null,
- [enumHubGoalRewards.no_reward]: null,
- [enumHubGoalRewards.no_reward_freeplay]: null,
-};
+import { MetaBuilding, defaultBuildingVariant } from "./meta_building";
+import { MetaCutterBuilding, enumCutterVariants } from "./buildings/cutter";
+import { MetaRotaterBuilding, enumRotaterVariants } from "./buildings/rotater";
+import { MetaPainterBuilding, enumPainterVariants } from "./buildings/painter";
+import { MetaMixerBuilding } from "./buildings/mixer";
+import { MetaStackerBuilding } from "./buildings/stacker";
+import { MetaSplitterBuilding, enumSplitterVariants } from "./buildings/splitter";
+import { MetaUndergroundBeltBuilding, enumUndergroundBeltVariants } from "./buildings/underground_belt";
+import { MetaMinerBuilding, enumMinerVariants } from "./buildings/miner";
+import { MetaTrashBuilding, enumTrashVariants } from "./buildings/trash";
+
+/** @typedef {Array<[typeof MetaBuilding, string]>} TutorialGoalReward */
+
+import { enumHubGoalRewards } from "./tutorial_goals";
+
+/**
+ * Helper method for proper types
+ * @returns {TutorialGoalReward}
+ */
+const typed = x => x;
+
+/**
+ * Stores which reward unlocks what
+ * @enum {TutorialGoalReward?}
+ */
+export const enumHubGoalRewardsToContentUnlocked = {
+ [enumHubGoalRewards.reward_cutter_and_trash]: typed([[MetaCutterBuilding, defaultBuildingVariant]]),
+ [enumHubGoalRewards.reward_rotater]: typed([[MetaRotaterBuilding, defaultBuildingVariant]]),
+ [enumHubGoalRewards.reward_painter]: typed([[MetaPainterBuilding, defaultBuildingVariant]]),
+ [enumHubGoalRewards.reward_mixer]: typed([[MetaMixerBuilding, defaultBuildingVariant]]),
+ [enumHubGoalRewards.reward_stacker]: typed([[MetaStackerBuilding, defaultBuildingVariant]]),
+ [enumHubGoalRewards.reward_splitter]: typed([[MetaSplitterBuilding, defaultBuildingVariant]]),
+ [enumHubGoalRewards.reward_tunnel]: typed([[MetaUndergroundBeltBuilding, defaultBuildingVariant]]),
+
+ [enumHubGoalRewards.reward_rotater_ccw]: typed([[MetaRotaterBuilding, enumRotaterVariants.ccw]]),
+ [enumHubGoalRewards.reward_rotater_180]: typed([[MetaRotaterBuilding, enumRotaterVariants.rotate180]]),
+ [enumHubGoalRewards.reward_miner_chainable]: typed([[MetaMinerBuilding, enumMinerVariants.chainable]]),
+ [enumHubGoalRewards.reward_underground_belt_tier_2]: typed([
+ [MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2],
+ ]),
+ [enumHubGoalRewards.reward_splitter_compact]: typed([
+ [MetaSplitterBuilding, enumSplitterVariants.compact],
+ ]),
+ [enumHubGoalRewards.reward_cutter_quad]: typed([[MetaCutterBuilding, enumCutterVariants.quad]]),
+ [enumHubGoalRewards.reward_painter_double]: typed([[MetaPainterBuilding, enumPainterVariants.double]]),
+ [enumHubGoalRewards.reward_painter_quad]: typed([[MetaPainterBuilding, enumPainterVariants.quad]]),
+ [enumHubGoalRewards.reward_storage]: typed([[MetaTrashBuilding, enumTrashVariants.storage]]),
+
+ [enumHubGoalRewards.reward_freeplay]: null,
+ [enumHubGoalRewards.no_reward]: null,
+ [enumHubGoalRewards.no_reward_freeplay]: null,
+};
diff --git a/src/js/jsconfig.json b/src/js/jsconfig.json
index e28a1c04..99d65145 100644
--- a/src/js/jsconfig.json
+++ b/src/js/jsconfig.json
@@ -2,5 +2,6 @@
"compilerOptions": {
"target": "es6",
"checkJs": true
- }
+ },
+ "include": ["./**/*.js"]
}
diff --git a/translations/base-en.yaml b/translations/base-en.yaml
index 45fd7175..4e228026 100644
--- a/translations/base-en.yaml
+++ b/translations/base-en.yaml
@@ -518,7 +518,7 @@ buildings:
ccw:
name: Rotate (CCW)
description: Rotates shapes counter-clockwise by 90 degrees.
- fl:
+ rotate180:
name: Rotate (180)
description: Rotates shapes by 180 degrees.