From c23e4ff50b0cb5e903e4089726f86fdec7f1ecd9 Mon Sep 17 00:00:00 2001 From: Jasper Meggitt Date: Sun, 24 May 2020 03:53:47 -0700 Subject: [PATCH] Add unstacker building --- src/js/game/buildings/stacker.js | 72 +++++++++++++++++++++++- src/js/game/components/item_processor.js | 1 + src/js/game/hub_goals.js | 3 +- src/js/game/shape_definition.js | 37 ++++++++++++ src/js/game/shape_definition_manager.js | 15 +++++ src/js/game/systems/item_processor.js | 29 ++++++++-- src/js/game/tutorial_goals.js | 1 + translations/base-en.yaml | 3 + 8 files changed, 151 insertions(+), 10 deletions(-) diff --git a/src/js/game/buildings/stacker.js b/src/js/game/buildings/stacker.js index 9b6f5ef3..5c1d395e 100644 --- a/src/js/game/buildings/stacker.js +++ b/src/js/game/buildings/stacker.js @@ -1,15 +1,17 @@ -import { globalConfig } from "../../core/config"; import { enumDirection, Vector } from "../../core/vector"; import { ItemAcceptorComponent, enumItemAcceptorItemFilter } from "../components/item_acceptor"; import { ItemEjectorComponent } from "../components/item_ejector"; import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor"; import { Entity } from "../entity"; -import { MetaBuilding } from "../meta_building"; +import { defaultBuildingVariant, MetaBuilding } from "../meta_building"; import { GameRoot } from "../root"; import { enumHubGoalRewards } from "../tutorial_goals"; import { formatItemsPerSecond } from "../../core/utils"; import { T } from "../../translations"; +/** @enum {string} */ +export const enumStackerVariants = { unstacker: "unstacker" }; + export class MetaStackerBuilding extends MetaBuilding { constructor() { super("stacker"); @@ -29,10 +31,24 @@ export class MetaStackerBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { - const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.stacker); + const speed = root.hubGoals.getProcessorBaseSpeed( + variant === enumStackerVariants.unstacker + ? enumItemProcessorTypes.unstacker + : enumItemProcessorTypes.stacker + ); return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; } + /** + * @param {GameRoot} root + */ + getAvailableVariants(root) { + if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_unstacker)) { + return [defaultBuildingVariant, enumStackerVariants.unstacker]; + } + return super.getAvailableVariants(root); + } + /** * @param {GameRoot} root */ @@ -74,4 +90,54 @@ export class MetaStackerBuilding extends MetaBuilding { }) ); } + + /** + * + * @param {Entity} entity + * @param {number} rotationVariant + * @param {string} variant + */ + updateVariants(entity, rotationVariant, variant) { + switch (variant) { + case defaultBuildingVariant: { + entity.components.ItemEjector.setSlots([ + { pos: new Vector(0, 0), direction: enumDirection.top }, + ]); + entity.components.ItemAcceptor.setSlots([ + { + pos: new Vector(0, 0), + directions: [enumDirection.bottom], + filter: enumItemAcceptorItemFilter.shape, + }, + { + pos: new Vector(1, 0), + directions: [enumDirection.bottom], + filter: enumItemAcceptorItemFilter.shape, + }, + ]); + entity.components.ItemProcessor.type = enumItemProcessorTypes.stacker; + entity.components.ItemProcessor.inputsPerCharge = 2; + break; + } + case enumStackerVariants.unstacker: { + entity.components.ItemEjector.setSlots([ + { pos: new Vector(0, 0), direction: enumDirection.top }, + { pos: new Vector(1, 0), direction: enumDirection.top }, + ]); + entity.components.ItemAcceptor.setSlots([ + { + pos: new Vector(0, 0), + directions: [enumDirection.bottom], + filter: enumItemAcceptorItemFilter.shape, + }, + ]); + entity.components.ItemProcessor.type = enumItemProcessorTypes.unstacker; + entity.components.ItemProcessor.inputsPerCharge = 1; + break; + } + + default: + assertAlways(false, "Unknown stacker variant: " + variant); + } + } } diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js index 0c4e90c6..8a810142 100644 --- a/src/js/game/components/item_processor.js +++ b/src/js/game/components/item_processor.js @@ -12,6 +12,7 @@ export const enumItemProcessorTypes = { rotater: "rotater", rotaterCCW: "rotaterCCW", stacker: "stacker", + unstacker: "unstacker", trash: "trash", mixer: "mixer", painter: "painter", diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 6c155165..b6e78aed 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -407,7 +407,8 @@ export class HubGoals extends BasicSerializableObject { case enumItemProcessorTypes.cutterQuad: case enumItemProcessorTypes.rotater: case enumItemProcessorTypes.rotaterCCW: - case enumItemProcessorTypes.stacker: { + case enumItemProcessorTypes.stacker: + case enumItemProcessorTypes.unstacker: { assert( globalConfig.buildingSpeeds[processorType], "Processor type has no speed set in globalConfig.buildingSpeeds: " + processorType diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js index 64cc3eab..4473c182 100644 --- a/src/js/game/shape_definition.js +++ b/src/js/game/shape_definition.js @@ -465,6 +465,43 @@ export class ShapeDefinition extends BasicSerializableObject { return new ShapeDefinition({ layers: newLayers }); } + /** + * Stacks the given shape definition on top. + */ + cloneAndUnstack() { + const lowerLayers = this.internalCloneLayers(); + + if (this.isEntirelyEmpty()) { + assert(false, "Can not stack entirely empty definition"); + } + + // Check if there is only one layer + if (this.layers.length === 1) { + return [new ShapeDefinition({ layers: lowerLayers })]; + } + + let upperLayers = []; + + layerCheck: + for (let i = lowerLayers.length - 1; i >= 0; --i) { + const layerToCheck = lowerLayers[i]; + + for (let quadrantIndex = 0; quadrantIndex < 4; ++quadrantIndex) { + // Check for a layer with items on it to remove + if (layerToCheck[quadrantIndex]) { + upperLayers.push(layerToCheck); + lowerLayers[i] = [null, null, null, null]; + break layerCheck; + } + } + } + + return [ + new ShapeDefinition({ layers: lowerLayers }), + new ShapeDefinition({ layers: upperLayers }), + ]; + } + /** * Clones the shape and colors everything in the given color * @param {enumColors} color diff --git a/src/js/game/shape_definition_manager.js b/src/js/game/shape_definition_manager.js index ad682bf0..bade8d39 100644 --- a/src/js/game/shape_definition_manager.js +++ b/src/js/game/shape_definition_manager.js @@ -144,6 +144,21 @@ export class ShapeDefinitionManager extends BasicSerializableObject { )); } + /** + * Generates a definition for stacking the upper definition onto the lower one + * @param {ShapeDefinition} definition + * @returns {[ShapeDefinition, ShapeDefinition]} + */ + shapeActionUnstack(definition) { + const key = "unstack:" + definition.getHash(); + if (this.operationCache[key]) { + return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key]); + } + + const unstacked = definition.cloneAndUnstack(); + return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = unstacked); + } + /** * Generates a definition for painting it with the given color * @param {ShapeDefinition} definition diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index d3f68c04..be229dae 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -23,7 +23,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // First of all, process the current recipe processorComp.secondsUntilEject = Math_max( 0, - processorComp.secondsUntilEject - this.root.dynamicTickrate.deltaSeconds + processorComp.secondsUntilEject - this.root.dynamicTickrate.deltaSeconds, ); // Check if we have any finished items we can eject @@ -196,7 +196,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const stackedDefinition = this.root.shapeDefinitionMgr.shapeActionStack( lowerItem.definition, - upperItem.definition + upperItem.definition, ); outItems.push({ item: new ShapeItem(stackedDefinition), @@ -204,6 +204,23 @@ export class ItemProcessorSystem extends GameSystemWithFilter { break; } + // UNSTACKER + + case enumItemProcessorTypes.unstacker: { + const item = /** @type {ShapeItem} */ (itemsBySlot[0].item); + assert(item instanceof ShapeItem, "Input for unstack is not a shape"); + + const unstackedDefinitions = this.root.shapeDefinitionMgr.shapeActionUnstack(item.definition); + + for (let i = 0; i < unstackedDefinitions.length; ++i) { + outItems.push({ + item: new ShapeItem(unstackedDefinitions[i]), + requiredSlot: i, + }); + } + break; + } + // TRASH case enumItemProcessorTypes.trash: { @@ -244,7 +261,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith( shapeItem.definition, - colorItem.color + colorItem.color, ); outItems.push({ @@ -267,12 +284,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const colorizedDefinition1 = this.root.shapeDefinitionMgr.shapeActionPaintWith( shapeItem1.definition, - colorItem.color + colorItem.color, ); const colorizedDefinition2 = this.root.shapeDefinitionMgr.shapeActionPaintWith( shapeItem2.definition, - colorItem.color + colorItem.color, ); outItems.push({ item: new ShapeItem(colorizedDefinition1), @@ -302,7 +319,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors( shapeItem.definition, - [colorItem2.color, colorItem3.color, colorItem4.color, colorItem1.color] + [colorItem2.color, colorItem3.color, colorItem4.color, colorItem1.color], ); outItems.push({ diff --git a/src/js/game/tutorial_goals.js b/src/js/game/tutorial_goals.js index 83aca89c..286bc915 100644 --- a/src/js/game/tutorial_goals.js +++ b/src/js/game/tutorial_goals.js @@ -11,6 +11,7 @@ export const enumHubGoalRewards = { reward_painter: "reward_painter", reward_mixer: "reward_mixer", reward_stacker: "reward_stacker", + reward_unstacker: "reward_unstacker", reward_splitter: "reward_splitter", reward_tunnel: "reward_tunnel", diff --git a/translations/base-en.yaml b/translations/base-en.yaml index d3fe6aca..2a822991 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -341,6 +341,9 @@ buildings: default: name: &stacker Stacker description: Stacks both items. If they can not be merged, the right item is placed above the left item. + unstacker: + name: Unstacker + description: Takes the top most shape off of a stack and outputs it onto the right. mixer: default: