From 5ab3afdd308317c6e668e82ee922c410494bff0e Mon Sep 17 00:00:00 2001 From: tobspr Date: Sat, 19 Sep 2020 17:55:36 +0200 Subject: [PATCH] Move item processing into separate methods --- res_raw/atlas.tps | 4 +- .../{rotater-fl.png => rotater-rotate180.png} | Bin src/js/game/buildings/rotater.js | 244 +++---- src/js/game/components/item_processor.js | 4 +- src/js/game/hub_goals.js | 5 +- src/js/game/meta_building_registry.js | 2 +- src/js/game/shape_definition.js | 4 +- src/js/game/shape_definition_manager.js | 518 +++++++------- src/js/game/systems/item_processor.js | 645 ++++++++++-------- src/js/game/tutorial_goals.js | 2 +- src/js/game/tutorial_goals_mappings.js | 104 +-- src/js/jsconfig.json | 3 +- translations/base-en.yaml | 2 +- 13 files changed, 795 insertions(+), 742 deletions(-) rename res_raw/sprites/buildings/{rotater-fl.png => rotater-rotate180.png} (100%) 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.