diff --git a/package.json b/package.json index f3ab2f80..b827a363 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "colors": "^1.3.3", "core-js": "3", "crc": "^3.8.0", - "cssnano-preset-advanced": "^4.0.7", + "cssnano-preset-advanced": "^5.1.2", "debounce-promise": "^3.1.2", "email-validator": "^2.0.4", "eslint": "7.1.0", @@ -46,12 +46,13 @@ "howler": "^2.1.2", "html-loader": "^0.5.5", "ignore-loader": "^0.1.2", + "immutable": "^4.0.0-rc.12", "logrocket": "^1.0.7", "lz-string": "^1.4.4", "markdown-loader": "^4.0.0", "match-all": "^1.2.5", "phonegap-plugin-mobile-accessibility": "^1.0.5", - "postcss": ">=5.0.0", + "postcss": "^8.3.0", "promise-polyfill": "^8.1.0", "query-string": "^6.8.1", "rusha": "^0.8.13", @@ -77,19 +78,19 @@ "@octokit/rest": "^18.0.6", "@typescript-eslint/eslint-plugin": "3.0.1", "@typescript-eslint/parser": "3.0.1", - "autoprefixer": "^9.4.3", + "autoprefixer": "^10.2.6", "babel-plugin-closure-elimination": "^1.3.0", "babel-plugin-console-source": "^2.0.2", "babel-plugin-danger-remove-unused-import": "^1.1.2", "css-mqpacker": "^7.0.0", - "cssnano": "^4.1.10", + "cssnano": "^5.0.5", "eslint-config-prettier": "6.11.0", "eslint-plugin-prettier": "3.1.3", "faster.js": "^1.1.0", "glob": "^7.1.3", "imagemin-mozjpeg": "^8.0.0", "imagemin-pngquant": "^8.0.0", - "jimp": "^0.6.1", + "jimp": "^0.16.1", "js-yaml": "^3.13.1", "postcss-assets": "^5.0.0", "postcss-preset-env": "^6.5.0", diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js index 4c0e1835..0d13fce5 100644 --- a/src/js/game/components/item_processor.js +++ b/src/js/game/components/item_processor.js @@ -1,6 +1,7 @@ import { types } from "../../savegame/serialization"; import { BaseItem } from "../base_item"; import { Component } from "../component"; +const { Set } = require("immutable"); /** @enum {string} */ export const enumItemProcessorTypes = { @@ -27,6 +28,12 @@ export const enumItemProcessorRequirements = { painterQuad: "painterQuad", }; +const specialCaseProcessorTypes = Set.of( + enumItemProcessorTypes.hub, + enumItemProcessorTypes.trash, + enumItemProcessorTypes.goal +); + /** @typedef {{ * item: BaseItem, * requiredSlot?: number, @@ -73,6 +80,12 @@ export class ItemProcessorComponent extends Component { // Type of processing requirement this.processingRequirement = processingRequirement; + /** + * Our current inputs. Mapping of slot number to item. + * @type {Map} + */ + this.inputSlotsMap = new Map(); + this.clear(); } @@ -82,11 +95,7 @@ export class ItemProcessorComponent extends Component { // sure the outputs always match this.nextOutputSlot = 0; - /** - * Our current inputs - * @type {Array<{ item: BaseItem, sourceSlot: number }>} - */ - this.inputSlots = []; + this.inputSlotsMap.clear(); /** * What we are currently processing, empty if we don't produce anything rn @@ -109,25 +118,15 @@ export class ItemProcessorComponent extends Component { * @param {number} sourceSlot */ tryTakeItem(item, sourceSlot) { - if ( - this.type === enumItemProcessorTypes.hub || - this.type === enumItemProcessorTypes.trash || - this.type === enumItemProcessorTypes.goal - ) { + if (specialCaseProcessorTypes.contains(this.type)) { // Hub has special logic .. not really nice but efficient. - this.inputSlots.push({ item, sourceSlot }); + this.inputSlotsMap.set(sourceSlot, item); return true; } - // Check that we only take one item per slot - for (let i = 0; i < this.inputSlots.length; ++i) { - const slot = this.inputSlots[i]; - if (slot.sourceSlot === sourceSlot) { - return false; - } - } + if (this.inputSlotsMap.has(sourceSlot)) return false; - this.inputSlots.push({ item, sourceSlot }); + this.inputSlotsMap.set(sourceSlot, item); return true; } } diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index e06d4a21..5fb57ad8 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -31,8 +31,7 @@ const MAX_QUEUED_CHARGES = 2; * Type of a processor implementation * @typedef {{ * entity: Entity, - * items: Array<{ item: BaseItem, sourceSlot: number }>, - * itemsBySlot: Object, + * itemsBySlotMap: Map, * outItems: Array * }} ProcessorImplementationPayload */ @@ -188,7 +187,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // DEFAULT // By default, we can start processing once all inputs are there case null: { - return processorComp.inputSlots.length >= processorComp.inputsPerCharge; + return processorComp.inputSlotsMap.size >= processorComp.inputsPerCharge; } // QUAD PAINTER @@ -196,18 +195,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter { case enumItemProcessorRequirements.painterQuad: { const pinsComp = entity.components.WiredPins; - /** @type {Object.} */ - const itemsBySlot = {}; - for (let i = 0; i < processorComp.inputSlots.length; ++i) { - itemsBySlot[processorComp.inputSlots[i].sourceSlot] = processorComp.inputSlots[i]; - } - // First slot is the shape, so if it's not there we can't do anything - if (!itemsBySlot[0]) { + if (!processorComp.inputSlotsMap.has(0)) { return false; } - const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); + const shapeItem = /** @type {ShapeItem} */ (processorComp.inputSlotsMap.get(0)); const slotStatus = []; // Check which slots are enabled @@ -232,7 +225,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // Check if all colors of the enabled slots are there for (let i = 0; i < slotStatus.length; ++i) { - if (slotStatus[i] && !itemsBySlot[1 + i]) { + if (slotStatus[i] && !processorComp.inputSlotsMap.has(1 + i)) { // A slot which is enabled wasn't enabled. Make sure if there is anything on the quadrant, // it is not possible to paint, but if there is nothing we can ignore it for (let j = 0; j < 4; ++j) { @@ -260,14 +253,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const processorComp = entity.components.ItemProcessor; // First, take items - const items = processorComp.inputSlots; - processorComp.inputSlots = []; - - /** @type {Object} */ - const itemsBySlot = {}; - for (let i = 0; i < items.length; ++i) { - itemsBySlot[items[i].sourceSlot] = items[i].item; - } + /** @type {Map} */ + const itemsBySlotMap = processorComp.inputSlotsMap; /** @type {Array} */ const outItems = []; @@ -279,11 +266,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // Call implementation handler({ entity, - items, - itemsBySlot, + itemsBySlotMap, outItems, }); + processorComp.inputSlotsMap.clear(); + // Track produced items for (let i = 0; i < outItems.length; ++i) { if (!outItems[i].doNotTrack) { @@ -318,13 +306,14 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const nextSlot = processorComp.nextOutputSlot++ % availableSlots; - for (let i = 0; i < payload.items.length; ++i) { + for (let [key, value] of payload.itemsBySlotMap.entries()) { payload.outItems.push({ - item: payload.items[i].item, - preferredSlot: (nextSlot + i) % availableSlots, + item: value, + preferredSlot: (nextSlot + key) % availableSlots, doNotTrack: true, }); } + return true; } @@ -332,7 +321,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_CUTTER(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); const inputDefinition = inputItem.definition; @@ -353,7 +342,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_CUTTER_QUAD(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); const inputDefinition = inputItem.definition; @@ -374,7 +363,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_ROTATER(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); const inputDefinition = inputItem.definition; @@ -388,7 +377,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_ROTATER_CCW(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); const inputDefinition = inputItem.definition; @@ -402,7 +391,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_ROTATER_180(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); const inputDefinition = inputItem.definition; @@ -416,8 +405,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_STACKER(payload) { - const lowerItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]); - const upperItem = /** @type {ShapeItem} */ (payload.itemsBySlot[1]); + const lowerItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); + const upperItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(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"); @@ -443,8 +432,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { */ 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); + const item1 = /** @type {ColorItem} */ (payload.itemsBySlotMap.get(0)); + const item2 = /** @type {ColorItem} */ (payload.itemsBySlotMap.get(1)); assert(item1 instanceof ColorItem, "Input for color mixer is not a color"); assert(item2 instanceof ColorItem, "Input for color mixer is not a color"); @@ -466,8 +455,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_PAINTER(payload) { - const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]); - const colorItem = /** @type {ColorItem} */ (payload.itemsBySlot[1]); + const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); + const colorItem = /** @type {ColorItem} */ (payload.itemsBySlotMap.get(1)); const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith( shapeItem.definition, @@ -483,9 +472,9 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @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]); + const shapeItem1 = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(0)); + const shapeItem2 = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(1)); + const colorItem = /** @type {ColorItem} */ (payload.itemsBySlotMap.get(2)); assert(shapeItem1 instanceof ShapeItem, "Input for painter is not a shape"); assert(shapeItem2 instanceof ShapeItem, "Input for painter is not a shape"); @@ -513,14 +502,14 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_PAINTER_QUAD(payload) { - const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]); + const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlotMap.get(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; + if (payload.itemsBySlotMap.get(i + 1)) { + colors[i] = /** @type {ColorItem} */ (payload.itemsBySlotMap.get(i + 1)).color; } } @@ -539,7 +528,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { */ process_READER(payload) { // Pass through the item - const item = payload.itemsBySlot[0]; + const item = payload.itemsBySlotMap.get(0); payload.outItems.push({ item, doNotTrack: true, @@ -558,8 +547,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { 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); + for (let value of payload.itemsBySlotMap.values()) { + const item = /** @type {ShapeItem} */ (value); this.root.hubGoals.handleDefinitionDelivered(item.definition); } } @@ -569,12 +558,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter { */ process_GOAL(payload) { const goalComp = payload.entity.components.GoalAcceptor; - const item = payload.items[0].item; + const item = payload.itemsBySlotMap.get(0); const now = this.root.time.now(); if (this.root.gameMode.getIsEditor()) { // while playing in editor, assign the item - goalComp.item = payload.items[0].item; + goalComp.item = payload.itemsBySlotMap.get(0); goalComp.deliveryHistory.push({ item, time: now,