diff --git a/res/ui/building_icons/goal_acceptor.png b/res/ui/building_icons/goal_acceptor.png new file mode 100644 index 00000000..ca267ea6 Binary files /dev/null and b/res/ui/building_icons/goal_acceptor.png differ diff --git a/res/ui/building_tutorials/goal_acceptor.png b/res/ui/building_tutorials/goal_acceptor.png new file mode 100644 index 00000000..b0d15387 Binary files /dev/null and b/res/ui/building_tutorials/goal_acceptor.png differ diff --git a/res_raw/sprites/blueprints/goal_acceptor.png b/res_raw/sprites/blueprints/goal_acceptor.png new file mode 100644 index 00000000..2288c07d Binary files /dev/null and b/res_raw/sprites/blueprints/goal_acceptor.png differ diff --git a/res_raw/sprites/buildings/goal_acceptor.png b/res_raw/sprites/buildings/goal_acceptor.png new file mode 100644 index 00000000..573ef372 Binary files /dev/null and b/res_raw/sprites/buildings/goal_acceptor.png differ diff --git a/src/css/resources.scss b/src/css/resources.scss index 041c9765..769829f6 100644 --- a/src/css/resources.scss +++ b/src/css/resources.scss @@ -1,6 +1,6 @@ $buildings: belt, cutter, miner, mixer, painter, rotater, balancer, stacker, trash, underground_belt, wire, constant_signal, logic_gate, lever, filter, wire_tunnel, display, virtual_processor, reader, storage, - transistor, analyzer, comparator, item_producer, constant_producer; + transistor, analyzer, comparator, item_producer, constant_producer, goal_acceptor; @each $building in $buildings { [data-icon="building_icons/#{$building}.png"] { @@ -14,7 +14,7 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2, reader, rotater-rotate180, display, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not, logic_gate-xor, analyzer, virtual_processor-rotater, virtual_processor-unstacker, item_producer, constant_producer, virtual_processor-stacker, virtual_processor-painter, wire-second, painter, - painter-mirrored, comparator; + painter-mirrored, comparator, goal_acceptor; @each $building in $buildingsAndVariants { [data-icon="building_tutorials/#{$building}.png"] { /* @load-async */ diff --git a/src/js/game/buildings/constant_producer.js b/src/js/game/buildings/constant_producer.js index 68889674..2725402a 100644 --- a/src/js/game/buildings/constant_producer.js +++ b/src/js/game/buildings/constant_producer.js @@ -3,6 +3,7 @@ import { Entity } from "../entity"; /* typehints:end */ import { enumDirection, Vector } from "../../core/vector"; +import { enumConstantSignalType, ConstantSignalComponent } from "../components/constant_signal"; import { ItemEjectorComponent } from "../components/item_ejector"; import { enumItemProducerType, ItemProducerComponent } from "../components/item_producer"; import { MetaBuilding } from "../meta_building"; @@ -31,5 +32,10 @@ export class MetaConstantProducerBuilding extends MetaBuilding { type: enumItemProducerType.wireless, }) ); + entity.addComponent( + new ConstantSignalComponent({ + type: enumConstantSignalType.wireless, + }) + ); } } diff --git a/src/js/game/buildings/goal_acceptor.js b/src/js/game/buildings/goal_acceptor.js new file mode 100644 index 00000000..bb50cd47 --- /dev/null +++ b/src/js/game/buildings/goal_acceptor.js @@ -0,0 +1,52 @@ +/* typehints:start */ +import { Entity } from "../entity"; +/* typehints:end */ + +import { enumDirection, Vector } from "../../core/vector"; +import { enumBeltReaderType, BeltReaderComponent } from "../components/belt_reader"; +import { GoalAcceptorComponent } from "../components/goal_acceptor"; +import { ItemEjectorComponent } from "../components/item_ejector"; +import { ItemAcceptorComponent } from "../components/item_acceptor"; +import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor"; +import { MetaBuilding } from "../meta_building"; + +export class MetaGoalAcceptorBuilding extends MetaBuilding { + constructor() { + super("goal_acceptor"); + } + + getSilhouetteColor() { + return "#ce418a"; + } + + /** + * Creates the entity at the given location + * @param {Entity} entity + */ + setupEntityComponents(entity) { + entity.addComponent( + new ItemAcceptorComponent({ + slots: [ + { + pos: new Vector(0, 0), + directions: [enumDirection.top], + }, + ], + }) + ); + + entity.addComponent( + new ItemProcessorComponent({ + processorType: enumItemProcessorTypes.goal, + }) + ); + + entity.addComponent( + new BeltReaderComponent({ + type: enumBeltReaderType.wireless, + }) + ); + + entity.addComponent(new GoalAcceptorComponent({})); + } +} diff --git a/src/js/game/buildings/reader.js b/src/js/game/buildings/reader.js index 006d6582..62207564 100644 --- a/src/js/game/buildings/reader.js +++ b/src/js/game/buildings/reader.js @@ -110,6 +110,6 @@ export class MetaReaderBuilding extends MetaBuilding { }) ); - entity.addComponent(new BeltReaderComponent()); + entity.addComponent(new BeltReaderComponent({})); } } diff --git a/src/js/game/component_registry.js b/src/js/game/component_registry.js index f094e60d..9c9247e6 100644 --- a/src/js/game/component_registry.js +++ b/src/js/game/component_registry.js @@ -19,6 +19,7 @@ import { DisplayComponent } from "./components/display"; import { BeltReaderComponent } from "./components/belt_reader"; import { FilterComponent } from "./components/filter"; import { ItemProducerComponent } from "./components/item_producer"; +import { GoalAcceptorComponent } from "./components/goal_acceptor"; export function initComponentRegistry() { gComponentRegistry.register(StaticMapEntityComponent); @@ -41,6 +42,7 @@ export function initComponentRegistry() { gComponentRegistry.register(BeltReaderComponent); gComponentRegistry.register(FilterComponent); gComponentRegistry.register(ItemProducerComponent); + gComponentRegistry.register(GoalAcceptorComponent); // IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS diff --git a/src/js/game/components/belt_reader.js b/src/js/game/components/belt_reader.js index d451bab5..c7f05511 100644 --- a/src/js/game/components/belt_reader.js +++ b/src/js/game/components/belt_reader.js @@ -3,6 +3,12 @@ import { BaseItem } from "../base_item"; import { typeItemSingleton } from "../item_resolver"; import { types } from "../../savegame/serialization"; +/** @enum {string} */ +export const enumBeltReaderType = { + wired: "wired", + wireless: "wireless", +}; + export class BeltReaderComponent extends Component { static getId() { return "BeltReader"; @@ -10,13 +16,20 @@ export class BeltReaderComponent extends Component { static getSchema() { return { + type: types.string, lastItem: types.nullable(typeItemSingleton), }; } - constructor() { + /** + * @param {object} param0 + * @param {string=} param0.type + */ + constructor({ type = enumBeltReaderType.wired }) { super(); + this.type = type; + /** * Which items went through the reader, we only store the time * @type {Array} @@ -41,4 +54,8 @@ export class BeltReaderComponent extends Component { */ this.lastThroughputComputation = 0; } + + isWireless() { + return this.type === enumBeltReaderType.wireless; + } } diff --git a/src/js/game/components/constant_signal.js b/src/js/game/components/constant_signal.js index 286108be..d2186bda 100644 --- a/src/js/game/components/constant_signal.js +++ b/src/js/game/components/constant_signal.js @@ -4,6 +4,12 @@ import { Component } from "../component"; import { BaseItem } from "../base_item"; import { typeItemSingleton } from "../item_resolver"; +/** @enum {string} */ +export const enumConstantSignalType = { + wired: "wired", + wireless: "wireless", +}; + export class ConstantSignalComponent extends Component { static getId() { return "ConstantSignal"; @@ -11,6 +17,7 @@ export class ConstantSignalComponent extends Component { static getSchema() { return { + type: types.string, signal: types.nullable(typeItemSingleton), }; } @@ -21,15 +28,22 @@ export class ConstantSignalComponent extends Component { */ copyAdditionalStateTo(otherComponent) { otherComponent.signal = this.signal; + otherComponent.type = this.type; } /** * * @param {object} param0 + * @param {string=} param0.type * @param {BaseItem=} param0.signal The signal to store */ - constructor({ signal = null }) { + constructor({ signal = null, type = enumConstantSignalType.wired }) { super(); this.signal = signal; + this.type = type; + } + + isWireless() { + return this.type === enumConstantSignalType.wireless; } } diff --git a/src/js/game/components/goal_acceptor.js b/src/js/game/components/goal_acceptor.js new file mode 100644 index 00000000..72c157d7 --- /dev/null +++ b/src/js/game/components/goal_acceptor.js @@ -0,0 +1,22 @@ +import { BaseItem } from "../base_item"; +import { Component } from "../component"; + +export class GoalAcceptorComponent extends Component { + static getId() { + return "GoalAcceptor"; + } + + /** + * @param {object} param0 + * @param {BaseItem=} param0.item + * @param {number=} param0.rate + */ + constructor({ item = null, rate = null }) { + super(); + this.item = item; + this.rate = rate; + + this.achieved = false; + this.achievedOnce = false; + } +} diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js index fd466662..166dd49c 100644 --- a/src/js/game/components/item_processor.js +++ b/src/js/game/components/item_processor.js @@ -19,6 +19,7 @@ export const enumItemProcessorTypes = { hub: "hub", filter: "filter", reader: "reader", + goal: "goal", }; /** @enum {string} */ @@ -104,7 +105,11 @@ export class ItemProcessorComponent extends Component { * @param {number} sourceSlot */ tryTakeItem(item, sourceSlot) { - if (this.type === enumItemProcessorTypes.hub || this.type === enumItemProcessorTypes.trash) { + if ( + this.type === enumItemProcessorTypes.hub || + this.type === enumItemProcessorTypes.trash || + this.type === enumItemProcessorTypes.goal + ) { // Hub has special logic .. not really nice but efficient. this.inputSlots.push({ item, sourceSlot }); return true; diff --git a/src/js/game/components/item_producer.js b/src/js/game/components/item_producer.js index 576b6813..4cb36132 100644 --- a/src/js/game/components/item_producer.js +++ b/src/js/game/components/item_producer.js @@ -1,3 +1,4 @@ +import { types } from "../../savegame/serialization"; import { Component } from "../component"; /** @enum {string} */ @@ -11,12 +12,22 @@ export class ItemProducerComponent extends Component { return "ItemProducer"; } + static getSchema() { + return { + type: types.string, + }; + } + /** - * @param {object} options - * @prop {type=} options.type + * @param {object} param0 + * @param {string=} param0.type */ constructor({ type = enumItemProducerType.wired }) { super(); this.type = type; } + + isWireless() { + return this.type === enumItemProducerType.wireless; + } } diff --git a/src/js/game/entity_components.js b/src/js/game/entity_components.js index 7dee590a..163be9f9 100644 --- a/src/js/game/entity_components.js +++ b/src/js/game/entity_components.js @@ -19,6 +19,7 @@ import { DisplayComponent } from "./components/display"; import { BeltReaderComponent } from "./components/belt_reader"; import { FilterComponent } from "./components/filter"; import { ItemProducerComponent } from "./components/item_producer"; +import { GoalAcceptorComponent } from "./components/goal_acceptor"; /* typehints:end */ /** @@ -89,6 +90,9 @@ export class EntityComponentStorage { /** @type {ItemProducerComponent} */ this.ItemProducer; + /** @type {GoalAcceptorComponent} */ + this.GoalAcceptor; + /* typehints:end */ } } diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index 121a0bcc..9220acaa 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -24,6 +24,8 @@ import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays"; import { BeltReaderSystem } from "./systems/belt_reader"; import { FilterSystem } from "./systems/filter"; import { ItemProducerSystem } from "./systems/item_producer"; +import { ConstantProducerSystem } from "./systems/constant_producer"; +import { GoalAcceptorSystem } from "./systems/goal_acceptor"; import { ZoneSystem } from "./systems/zone"; const logger = createLogger("game_system_manager"); @@ -101,6 +103,12 @@ export class GameSystemManager { /** @type {ItemProducerSystem} */ itemProducer: null, + /** @type {ConstantProducerSystem} */ + ConstantProducer: null, + + /** @type {GoalAcceptorSystem} */ + GoalAcceptor: null, + /** @type {ZoneSystem} */ zone: null, @@ -171,6 +179,10 @@ export class GameSystemManager { add("itemProcessorOverlays", ItemProcessorOverlaysSystem); + add("constantProducer", ConstantProducerSystem); + + add("goalAcceptor", GoalAcceptorSystem); + if (this.root.gameMode.hasZone()) { add("zone", ZoneSystem); } diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 327b6da7..b3536a3c 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -500,6 +500,7 @@ export class HubGoals extends BasicSerializableObject { switch (processorType) { case enumItemProcessorTypes.trash: case enumItemProcessorTypes.hub: + case enumItemProcessorTypes.goal: return 1e30; case enumItemProcessorTypes.balancer: return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt * 2; diff --git a/src/js/game/hud/parts/buildings_toolbar.js b/src/js/game/hud/parts/buildings_toolbar.js index 67da616e..2881eece 100644 --- a/src/js/game/hud/parts/buildings_toolbar.js +++ b/src/js/game/hud/parts/buildings_toolbar.js @@ -16,12 +16,14 @@ import { HUDBaseToolbar } from "./base_toolbar"; import { MetaStorageBuilding } from "../../buildings/storage"; import { MetaItemProducerBuilding } from "../../buildings/item_producer"; import { MetaConstantProducerBuilding } from "../../buildings/constant_producer"; +import { MetaGoalAcceptorBuilding } from "../../buildings/goal_acceptor"; export class HUDBuildingsToolbar extends HUDBaseToolbar { constructor(root) { super(root, { primaryBuildings: [ MetaConstantProducerBuilding, + MetaGoalAcceptorBuilding, MetaBeltBuilding, MetaBalancerBuilding, MetaUndergroundBeltBuilding, diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 8ed35bfe..ad9f6e8e 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -49,8 +49,9 @@ export const KEYMAPPINGS = { }, buildings: { - // Puzzle - constant_producer: { keyCode: 192 }, // "`" + // Puzzle buildings + constant_producer: { keyCode: key("H") }, + goal_acceptor: { keyCode: key("N") }, // Primary Toolbar belt: { keyCode: key("1") }, diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js index bc9dcfbb..06ff7337 100644 --- a/src/js/game/map_chunk_view.js +++ b/src/js/game/map_chunk_view.js @@ -76,6 +76,7 @@ export class MapChunkView extends MapChunk { systems.lever.drawChunk(parameters, this); systems.display.drawChunk(parameters, this); systems.storage.drawChunk(parameters, this); + systems.constantProducer.drawChunk(parameters, this); systems.itemProcessorOverlays.drawChunk(parameters, this); } diff --git a/src/js/game/meta_building_registry.js b/src/js/game/meta_building_registry.js index b1c16c2a..90a253f0 100644 --- a/src/js/game/meta_building_registry.js +++ b/src/js/game/meta_building_registry.js @@ -10,6 +10,7 @@ import { MetaConstantSignalBuilding } from "./buildings/constant_signal"; import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter"; import { MetaDisplayBuilding } from "./buildings/display"; import { MetaFilterBuilding } from "./buildings/filter"; +import { MetaGoalAcceptorBuilding } from "./buildings/goal_acceptor"; import { MetaHubBuilding } from "./buildings/hub"; import { MetaItemProducerBuilding } from "./buildings/item_producer"; import { MetaLeverBuilding } from "./buildings/lever"; @@ -46,6 +47,7 @@ export function initMetaBuildingRegistry() { gMetaBuildingRegistry.register(MetaStorageBuilding); gMetaBuildingRegistry.register(MetaBeltBuilding); gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding); + gMetaBuildingRegistry.register(MetaGoalAcceptorBuilding); gMetaBuildingRegistry.register(MetaHubBuilding); gMetaBuildingRegistry.register(MetaWireBuilding); gMetaBuildingRegistry.register(MetaConstantSignalBuilding); @@ -170,6 +172,9 @@ export function initMetaBuildingRegistry() { // Constant producer registerBuildingVariant(62, MetaConstantProducerBuilding); + // Goal acceptor + registerBuildingVariant(63, MetaGoalAcceptorBuilding); + // Propagate instances for (const key in gBuildingVariants) { gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass( diff --git a/src/js/game/modes/puzzle.js b/src/js/game/modes/puzzle.js index e9ad44ea..4f07abe7 100644 --- a/src/js/game/modes/puzzle.js +++ b/src/js/game/modes/puzzle.js @@ -117,4 +117,9 @@ export class PuzzleGameMode extends GameMode { getMinimumZoom() { return 1; } + + /** @returns {boolean} */ + getIsFreeplayAvailable() { + return true; + } } diff --git a/src/js/game/modes/puzzle_edit.js b/src/js/game/modes/puzzle_edit.js index f2645a75..cf3ad301 100644 --- a/src/js/game/modes/puzzle_edit.js +++ b/src/js/game/modes/puzzle_edit.js @@ -2,7 +2,10 @@ import { GameRoot } from "../root"; /* typehints:end */ +// import { MetaBeltBuilding } from "../buildings/belt"; import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; +import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor"; +// import { MetaItemProducerBuilding } from "../buildings/item_producer"; import { enumGameModeIds } from "../game_mode"; import { PuzzleGameMode } from "./puzzle"; @@ -17,6 +20,9 @@ export class PuzzleEditGameMode extends PuzzleGameMode { this.setBuildings({ [MetaConstantProducerBuilding.name]: true, + // [MetaBeltBuilding.name]: true, + [MetaGoalAcceptorBuilding.name]: true, + // [MetaItemProducerBuilding.name]: true, }); } } diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 5a9e5f63..c2923d13 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -5,6 +5,7 @@ import { GameRoot } from "../root"; import { queryParamOptions } from "../../core/query_parameters"; import { findNiceIntegerValue } from "../../core/utils"; import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; +import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor"; import { MetaItemProducerBuilding } from "../buildings/item_producer"; import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode"; import { ShapeDefinition } from "../shape_definition"; @@ -494,6 +495,7 @@ export class RegularGameMode extends GameMode { this.setBuildings({ [MetaConstantProducerBuilding.name]: false, + [MetaGoalAcceptorBuilding.name]: false, [MetaItemProducerBuilding.name]: queryParamOptions.sandboxMode || G_IS_DEV, }); } diff --git a/src/js/game/systems/belt_reader.js b/src/js/game/systems/belt_reader.js index fbd00b6c..f6080aa9 100644 --- a/src/js/game/systems/belt_reader.js +++ b/src/js/game/systems/belt_reader.js @@ -14,7 +14,6 @@ export class BeltReaderSystem extends GameSystemWithFilter { const minimumTimeForThroughput = now - 1; for (let i = 0; i < this.allEntities.length; ++i) { const entity = this.allEntities[i]; - const readerComp = entity.components.BeltReader; const pinsComp = entity.components.WiredPins; @@ -23,12 +22,14 @@ export class BeltReaderSystem extends GameSystemWithFilter { readerComp.lastItemTimes.shift(); } - pinsComp.slots[1].value = readerComp.lastItem; - pinsComp.slots[0].value = - (readerComp.lastItemTimes[readerComp.lastItemTimes.length - 1] || 0) > - minimumTimeForThroughput - ? BOOL_TRUE_SINGLETON - : BOOL_FALSE_SINGLETON; + if (!entity.components.BeltReader.isWireless()) { + pinsComp.slots[1].value = readerComp.lastItem; + pinsComp.slots[0].value = + (readerComp.lastItemTimes[readerComp.lastItemTimes.length - 1] || 0) > + minimumTimeForThroughput + ? BOOL_TRUE_SINGLETON + : BOOL_FALSE_SINGLETON; + } if (now - readerComp.lastThroughputComputation > 0.5) { // Compute throughput diff --git a/src/js/game/systems/constant_producer.js b/src/js/game/systems/constant_producer.js new file mode 100644 index 00000000..9b1ec96f --- /dev/null +++ b/src/js/game/systems/constant_producer.js @@ -0,0 +1,54 @@ +/* typehints:start */ +import { GameRoot } from "../root"; +/* typehints:end */ + +import { globalConfig } from "../../core/config"; +import { ConstantSignalComponent } from "../components/constant_signal"; +import { ItemProducerComponent } from "../components/item_producer"; +import { GameSystemWithFilter } from "../game_system_with_filter"; + +export class ConstantProducerSystem extends GameSystemWithFilter { + /** @param {GameRoot} root */ + constructor(root) { + super(root, [ConstantSignalComponent, ItemProducerComponent]); + } + + update() { + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; + const producerComp = entity.components.ItemProducer; + const signalComp = entity.components.ConstantSignal; + + if (!producerComp.isWireless() || !signalComp.isWireless()) { + continue; + } + + const ejectorComp = entity.components.ItemEjector; + + ejectorComp.tryEject(0, signalComp.signal); + } + } + + drawChunk(parameters, chunk) { + const contents = chunk.containedEntitiesByLayer.regular; + for (let i = 0; i < contents.length; ++i) { + const producerComp = contents[i].components.ItemProducer; + const signalComp = contents[i].components.ConstantSignal; + + if (!producerComp || !producerComp.isWireless() || !signalComp || !signalComp.isWireless()) { + return; + } + + const staticComp = contents[i].components.StaticMapEntity; + const item = signalComp.signal; + + if (!item) { + return; + } + + // TODO: Better looking overlay + const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); + item.drawItemCenteredClipped(center.x, center.y, parameters, globalConfig.tileSize); + } + } +} diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js index d698c1d5..6a9c2a02 100644 --- a/src/js/game/systems/constant_signal.js +++ b/src/js/game/systems/constant_signal.js @@ -6,9 +6,10 @@ import { fillInLinkIntoTranslation } from "../../core/utils"; import { T } from "../../translations"; import { BaseItem } from "../base_item"; import { enumColors } from "../colors"; -import { ConstantSignalComponent } from "../components/constant_signal"; +import { enumConstantSignalType, ConstantSignalComponent } from "../components/constant_signal"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; +import { HUDPinnedShapes } from "../hud/parts/pinned_shapes"; import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item"; import { COLOR_ITEM_SINGLETONS } from "../items/color_item"; import { ShapeDefinition } from "../shape_definition"; @@ -26,8 +27,13 @@ export class ConstantSignalSystem extends GameSystemWithFilter { // Set signals for (let i = 0; i < this.allEntities.length; ++i) { const entity = this.allEntities[i]; - const pinsComp = entity.components.WiredPins; const signalComp = entity.components.ConstantSignal; + + if (signalComp.isWireless()) { + continue; + } + + const pinsComp = entity.components.WiredPins; pinsComp.slots[0].value = signalComp.signal; } } @@ -54,23 +60,33 @@ export class ConstantSignalSystem extends GameSystemWithFilter { validator: val => this.parseSignalCode(val), }); + const items = [ + BOOL_FALSE_SINGLETON, + BOOL_TRUE_SINGLETON, + ...Object.values(COLOR_ITEM_SINGLETONS), + this.root.shapeDefinitionMgr.getShapeItemFromShortKey(this.root.gameMode.getBlueprintShapeKey()), + ]; + + if (this.root.gameMode.hasHub()) { + items.push( + this.root.shapeDefinitionMgr.getShapeItemFromDefinition( + this.root.hubGoals.currentGoal.definition + ) + ); + } + + if (!this.root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)) { + items.push( + ...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key => + this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key) + ) + ); + } + const itemInput = new FormElementItemChooser({ id: "signalItem", label: null, - items: [ - BOOL_FALSE_SINGLETON, - BOOL_TRUE_SINGLETON, - ...Object.values(COLOR_ITEM_SINGLETONS), - this.root.shapeDefinitionMgr.getShapeItemFromDefinition( - this.root.hubGoals.currentGoal.definition - ), - this.root.shapeDefinitionMgr.getShapeItemFromShortKey( - this.root.gameMode.getBlueprintShapeKey() - ), - ...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key => - this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key) - ), - ], + items, }); const dialog = new DialogWithForm({ @@ -103,7 +119,6 @@ export class ConstantSignalSystem extends GameSystemWithFilter { } if (itemInput.chosenItem) { - console.log(itemInput.chosenItem); constantComp.signal = itemInput.chosenItem; } else { constantComp.signal = this.parseSignalCode(signalValueInput.getValue()); diff --git a/src/js/game/systems/goal_acceptor.js b/src/js/game/systems/goal_acceptor.js new file mode 100644 index 00000000..e24eb80b --- /dev/null +++ b/src/js/game/systems/goal_acceptor.js @@ -0,0 +1,120 @@ +/* typehints:start */ +import { GameRoot } from "../root"; +/* typehints:end */ + +import { THIRDPARTY_URLS, globalConfig } from "../../core/config"; +import { DialogWithForm } from "../../core/modal_dialog_elements"; +import { FormElementInput, FormElementItemChooser } from "../../core/modal_dialog_forms"; +import { fillInLinkIntoTranslation } from "../../core/utils"; +import { T } from "../../translations"; +import { GoalAcceptorComponent } from "../components/goal_acceptor"; +import { GameSystemWithFilter } from "../game_system_with_filter"; +// import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item"; +// import { COLOR_ITEM_SINGLETONS } from "../items/color_item"; + +export class GoalAcceptorSystem extends GameSystemWithFilter { + /** @param {GameRoot} root */ + constructor(root) { + super(root, [GoalAcceptorComponent]); + + this.root.signals.entityManuallyPlaced.add(this.editGoal, this); + } + + update() { + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; + const goalComp = entity.components.GoalAcceptor; + const readerComp = entity.components.BeltReader; + + // Check against goals (set on placement) + } + + // Check if goal criteria has been met for all goals + } + + drawChunk(parameters, chunk) { + /* + *const contents = chunk.containedEntitiesByLayer.regular; + *for (let i = 0; i < contents.length; ++i) {} + */ + } + + editGoal(entity) { + if (!entity.components.GoalAcceptor) { + return; + } + + const uid = entity.uid; + const goalComp = entity.components.GoalAcceptor; + + const itemInput = new FormElementInput({ + id: "goalItemInput", + label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer), + placeholder: "CuCuCuCu", + defaultValue: "CuCuCuCu", + validator: val => this.parseItem(val), + }); + + const rateInput = new FormElementInput({ + id: "goalRateInput", + label: "Rate:", + placeholder: "0", + defaultValue: "0", + validator: val => !isNaN(Number(val)), + }); + + const dialog = new DialogWithForm({ + app: this.root.app, + title: "Set Goal", + desc: "", + formElements: [itemInput, rateInput], + buttons: ["cancel:bad:escape", "ok:good:enter"], + closeButton: false, + }); + this.root.hud.parts.dialogs.internalShowDialog(dialog); + + const closeHandler = () => { + if (this.isEntityStale(uid)) { + return; + } + + goalComp.item = this.parseItem(itemInput.getValue()); + goalComp.rate = this.parseRate(rateInput.getValue()); + }; + + dialog.buttonSignals.ok.add(closeHandler); + dialog.buttonSignals.cancel.add(() => { + if (this.isEntityStale(uid)) { + return; + } + + this.root.logic.tryDeleteBuilding(entity); + }); + } + + parseRate(value) { + return Number(value); + } + + parseItem(value) { + return this.root.systemMgr.systems.constantSignal.parseSignalCode(value); + } + + isEntityStale(uid) { + if (!this.root || !this.root.entityMgr) { + return true; + } + + const entity = this.root.entityMgr.findByUid(uid, false); + if (!entity) { + return true; + } + + const goalComp = entity.components.GoalAcceptor; + if (!goalComp) { + return true; + } + + return false; + } +} diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index 9775afde..17d64e4d 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -59,6 +59,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { [enumItemProcessorTypes.painterQuad]: this.process_PAINTER_QUAD, [enumItemProcessorTypes.hub]: this.process_HUB, [enumItemProcessorTypes.reader]: this.process_READER, + [enumItemProcessorTypes.goal]: this.process_GOAL, }; // Bind all handlers @@ -562,4 +563,13 @@ export class ItemProcessorSystem extends GameSystemWithFilter { this.root.hubGoals.handleDefinitionDelivered(item.definition); } } + + /** + * @param {ProcessorImplementationPayload} payload + */ + process_GOAL(payload) { + const readerComp = payload.entity.components.BeltReader; + readerComp.lastItemTimes.push(this.root.time.now()); + readerComp.lastItem = payload.items[payload.items.length - 1].item; + } } diff --git a/src/js/game/systems/item_producer.js b/src/js/game/systems/item_producer.js index a1ebb331..be78e4e8 100644 --- a/src/js/game/systems/item_producer.js +++ b/src/js/game/systems/item_producer.js @@ -2,33 +2,36 @@ import { GameRoot } from "../root"; /* typehints:end */ -import { enumItemProducerType, ItemProducerComponent } from "../components/item_producer"; +import { ItemProducerComponent } from "../components/item_producer"; import { GameSystemWithFilter } from "../game_system_with_filter"; export class ItemProducerSystem extends GameSystemWithFilter { /** @param {GameRoot} root */ constructor(root) { super(root, [ItemProducerComponent]); + this.item = null; } update() { for (let i = 0; i < this.allEntities.length; ++i) { const entity = this.allEntities[i]; + const producerComp = entity.components.ItemProducer; + const ejectorComp = entity.components.ItemEjector; - if (entity.components.ItemProducer.type === enumItemProducerType.wired) { - const pinsComp = entity.components.WiredPins; - const pin = pinsComp.slots[0]; - const network = pin.linkedNetwork; - - if (!network || !network.hasValue()) { - continue; - } - - const ejectorComp = entity.components.ItemEjector; - ejectorComp.tryEject(0, network.currentValue); - } else { - // TODO: entity w/ wireless item producer (e.g. ConstantProducer) + if (producerComp.isWireless()) { + continue; } + + const pinsComp = entity.components.WiredPins; + const pin = pinsComp.slots[0]; + const network = pin.linkedNetwork; + + if (!network || !network.hasValue()) { + continue; + } + + this.item = network.currentValue; + ejectorComp.tryEject(0, this.item); } } } diff --git a/src/js/game/systems/zone.js b/src/js/game/systems/zone.js index 01154602..f2fecd3f 100644 --- a/src/js/game/systems/zone.js +++ b/src/js/game/systems/zone.js @@ -39,15 +39,16 @@ export class ZoneSystem extends GameSystem { */ drawChunk(parameters, chunk) { const zone = this.root.gameMode.getZone().allScaled(globalConfig.tileSize); + const context = parameters.context; - parameters.context.globalAlpha = 0.1; - parameters.context.fillStyle = THEME.map.zone.background; - parameters.context.fillRect(zone.x, zone.y, zone.w, zone.h); + context.globalAlpha = 0.1; + context.fillStyle = THEME.map.zone.background; + context.fillRect(zone.x, zone.y, zone.w, zone.h); - parameters.context.globalAlpha = 0.9; - parameters.context.strokeStyle = THEME.map.zone.border; - parameters.context.strokeRect(zone.x, zone.y, zone.w, zone.h); + context.globalAlpha = 0.9; + context.strokeStyle = THEME.map.zone.border; + context.strokeRect(zone.x, zone.y, zone.w, zone.h); - parameters.context.globalAlpha = 1; + context.globalAlpha = 1; } } diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 3754ee36..b82252ff 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -209,7 +209,7 @@ export class MainMenuState extends GameState { if (G_IS_DEV && globalConfig.debug.fastGameEnter) { if (globalConfig.debug.testPuzzleMode) { - this.onPuzzlePlayButtonClicked(); + this.onPuzzleEditButtonClicked(); return; } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index e754db8a..e735c2a3 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -712,6 +712,11 @@ buildings: name: &constant_producer Constant Producer description: Outputs a shape, color or boolean (1 or 0) as specified. + goal_acceptor: + default: + name: &goal_acceptor Goal Acceptor + description: Accepts items and triggers a goal if the specified item and/or rate criteria are met. + storyRewards: # Those are the rewards gained from completing the store reward_cutter_and_trash: @@ -1140,6 +1145,7 @@ keybindings: comparator: *comparator item_producer: Item Producer (Sandbox) constant_producer: *constant_producer + goal_acceptor: *goal_acceptor # --- pipette: Pipette