From dd4de1cdeac4e7233ff6f1765af99729fd994e7b Mon Sep 17 00:00:00 2001 From: Moppler Date: Fri, 10 Jul 2020 23:38:53 +0100 Subject: [PATCH] Initial attempt at couter building --- src/js/game/buildings/counter.js | 62 ++++++++++++++ src/js/game/component_registry.js | 4 +- src/js/game/components/counter.js | 98 ++++++++++++++++++++++ src/js/game/core.js | 2 + src/js/game/entity_components.js | 4 + src/js/game/game_system_manager.js | 6 ++ src/js/game/hud/parts/buildings_toolbar.js | 2 + src/js/game/key_action_mapper.js | 1 + src/js/game/meta_building_registry.js | 2 + src/js/game/systems/counter.js | 77 +++++++++++++++++ src/js/game/systems/item_ejector.js | 16 +++- translations/base-en.yaml | 6 ++ 12 files changed, 276 insertions(+), 4 deletions(-) create mode 100644 src/js/game/buildings/counter.js create mode 100644 src/js/game/components/counter.js create mode 100644 src/js/game/systems/counter.js diff --git a/src/js/game/buildings/counter.js b/src/js/game/buildings/counter.js new file mode 100644 index 00000000..f3e938ae --- /dev/null +++ b/src/js/game/buildings/counter.js @@ -0,0 +1,62 @@ +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 { Entity } from "../entity"; +import { MetaBuilding } from "../meta_building"; +import { GameRoot } from "../root"; +import { enumItemType } from "../base_item"; +import { ItemCounterComponent } from "../components/counter"; + +export class MetaCounterBuilding extends MetaBuilding { + constructor() { + super("counter"); + } + + getSilhouetteColor() { + return "#7dc6cd"; + } + + /** + * @param {GameRoot} root + * @param {string} variant + * @returns {Array<[string, string]>} + */ + getAdditionalStatistics(root, variant) { + const speed = 2; + return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; + } + + /** + * @param {GameRoot} root + */ + getIsUnlocked(root) { + return true; //root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_rotater); + } + + /** + * Creates the entity at the given location + * @param {Entity} entity + */ + setupEntityComponents(entity) { + entity.addComponent(new ItemCounterComponent()); + + 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: enumItemType.shape, + }, + ], + }) + ); + } +} diff --git a/src/js/game/component_registry.js b/src/js/game/component_registry.js index d3398937..5a019923 100644 --- a/src/js/game/component_registry.js +++ b/src/js/game/component_registry.js @@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage"; import { EnergyGeneratorComponent } from "./components/energy_generator"; import { WiredPinsComponent } from "./components/wired_pins"; import { EnergyConsumerComponent } from "./components/energy_consumer"; +import { ItemCounterComponent } from "./components/counter"; export function initComponentRegistry() { gComponentRegistry.register(StaticMapEntityComponent); @@ -29,6 +30,7 @@ export function initComponentRegistry() { gComponentRegistry.register(EnergyGeneratorComponent); gComponentRegistry.register(WiredPinsComponent); gComponentRegistry.register(EnergyConsumerComponent); + gComponentRegistry.register(ItemCounterComponent); // IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS @@ -37,7 +39,7 @@ export function initComponentRegistry() { assert( // @ts-ignore require.context("./components", false, /.*\.js/i).keys().length === - gComponentRegistry.getNumEntries(), + gComponentRegistry.getNumEntries(), "Not all components are registered" ); diff --git a/src/js/game/components/counter.js b/src/js/game/components/counter.js new file mode 100644 index 00000000..a0148179 --- /dev/null +++ b/src/js/game/components/counter.js @@ -0,0 +1,98 @@ +import { types } from "../../savegame/serialization"; +import { Component } from "../component"; +import { BaseItem } from "../base_item"; +import { gItemRegistry } from "../../core/global_registries"; +import { GameTime } from "../time/game_time"; + +export class ItemCounterComponent extends Component { + static getId() { + return "Counter"; + } + + static getSchema() { + return { + inputSlots: types.array( + types.structured({ + item: types.obj(gItemRegistry), + sourceSlot: types.uint, + }) + ), + }; + } + + duplicateWithoutContents() { + return new ItemCounterComponent(); + } + + constructor() { + super(); + + /** + * Our current inputs + * @type {Array<{ item: BaseItem, sourceSlot: number }>} + */ + this.inputSlots = []; + + this.currentCount = 0; + + this.lastResetTime = 0; + + /** @typedef {object} TickCount + * @property {number} gameTimeSeconds + * @property {number} count + */ + /** @type {TickCount[]} */ + this.tickHistory = []; + + this.averageItemsPerSecond = 0; + } + + /** + * Called every time an item leaves the counter + */ + countNewItem() { + this.currentCount++; + } + + /** + * Called on every counter entity .update() call + * @param {GameTime} gameTime + */ + tick(gameTime) { + const count = this.currentCount; + this.currentCount = 0; + + this.tickHistory.push({ + gameTimeSeconds: gameTime.timeSeconds, + count: count, + }); + + // Only keep history for the last second. + this.tickHistory = this.tickHistory.filter(tick => gameTime.timeSeconds - tick.gameTimeSeconds <= 1); + + const delta = gameTime.timeSeconds - this.lastResetTime; + if (delta > 1) { + const sum = this.tickHistory.reduce((a, b) => a + b.count, 0); + this.averageItemsPerSecond = sum; + this.lastResetTime = gameTime.timeSeconds; + } + } + + /** + * Tries to take the item + * @param {BaseItem} item + * @param {number} sourceSlot + */ + tryTakeItem(item, sourceSlot) { + // 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; + } + } + + this.inputSlots.push({ item, sourceSlot }); + return true; + } +} diff --git a/src/js/game/core.js b/src/js/game/core.js index 960a83e3..ef3387de 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -423,6 +423,8 @@ export class GameCore { // Energy consumer (Battery icons) systems.energyConsumer.draw(params); + + systems.counter.draw(params); } // Green wires overlay (not within the if because it can fade) diff --git a/src/js/game/entity_components.js b/src/js/game/entity_components.js index 24430dd2..6d07ad04 100644 --- a/src/js/game/entity_components.js +++ b/src/js/game/entity_components.js @@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage"; import { EnergyGeneratorComponent } from "./components/energy_generator"; import { WiredPinsComponent } from "./components/wired_pins"; import { EnergyConsumerComponent } from "./components/energy_consumer"; +import { ItemCounterComponent } from "./components/counter"; /* typehints:end */ /** @@ -65,6 +66,9 @@ export class EntityComponentStorage { /** @type {EnergyConsumerComponent} */ this.EnergyConsumer; + /** @type {ItemCounterComponent} */ + this.Counter; + /* typehints:end */ } } diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index ed9d1155..d0538109 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -16,6 +16,7 @@ import { StorageSystem } from "./systems/storage"; import { EnergyGeneratorSystem } from "./systems/energy_generator"; import { WiredPinsSystem } from "./systems/wired_pins"; import { EnergyConsumerSystem } from "./systems/energy_consumer"; +import { CounterSystem } from "./systems/counter"; const logger = createLogger("game_system_manager"); @@ -68,6 +69,9 @@ export class GameSystemManager { /** @type {EnergyConsumerSystem} */ energyConsumer: null, + /** @type {CounterSystem} */ + counter: null, + /* typehints:end */ }; this.systemUpdateOrder = []; @@ -110,6 +114,8 @@ export class GameSystemManager { add("energyConsumer", EnergyConsumerSystem); + add("counter", CounterSystem); + // IMPORTANT: Must be after belt system since belt system can change the // orientation of an entity after it is placed -> the item acceptor cache // then would be invalid diff --git a/src/js/game/hud/parts/buildings_toolbar.js b/src/js/game/hud/parts/buildings_toolbar.js index c46a5c98..41df709b 100644 --- a/src/js/game/hud/parts/buildings_toolbar.js +++ b/src/js/game/hud/parts/buildings_toolbar.js @@ -4,6 +4,7 @@ import { MetaEnergyGenerator } from "../../buildings/energy_generator"; import { MetaMinerBuilding } from "../../buildings/miner"; import { MetaMixerBuilding } from "../../buildings/mixer"; import { MetaPainterBuilding } from "../../buildings/painter"; +import { MetaCounterBuilding } from "../../buildings/counter"; import { MetaRotaterBuilding } from "../../buildings/rotater"; import { MetaSplitterBuilding } from "../../buildings/splitter"; import { MetaStackerBuilding } from "../../buildings/stacker"; @@ -19,6 +20,7 @@ const supportedBuildings = [ MetaUndergroundBeltBuilding, MetaMinerBuilding, MetaCutterBuilding, + MetaCounterBuilding, MetaRotaterBuilding, MetaStackerBuilding, MetaMixerBuilding, diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 2dfc5bb1..090d6066 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -49,6 +49,7 @@ export const KEYMAPPINGS = { underground_belt: { keyCode: key("3") }, miner: { keyCode: key("4") }, cutter: { keyCode: key("5") }, + counter: { keyCode: key("~") }, rotater: { keyCode: key("6") }, stacker: { keyCode: key("7") }, mixer: { keyCode: key("8") }, diff --git a/src/js/game/meta_building_registry.js b/src/js/game/meta_building_registry.js index 4e1fdd81..3617fdd4 100644 --- a/src/js/game/meta_building_registry.js +++ b/src/js/game/meta_building_registry.js @@ -4,6 +4,7 @@ import { MetaCutterBuilding } from "./buildings/cutter"; import { MetaMinerBuilding } from "./buildings/miner"; import { MetaMixerBuilding } from "./buildings/mixer"; import { MetaPainterBuilding } from "./buildings/painter"; +import { MetaCounterBuilding } from "./buildings/counter"; import { MetaRotaterBuilding } from "./buildings/rotater"; import { MetaSplitterBuilding } from "./buildings/splitter"; import { MetaStackerBuilding } from "./buildings/stacker"; @@ -20,6 +21,7 @@ export function initMetaBuildingRegistry() { gMetaBuildingRegistry.register(MetaSplitterBuilding); gMetaBuildingRegistry.register(MetaMinerBuilding); gMetaBuildingRegistry.register(MetaCutterBuilding); + gMetaBuildingRegistry.register(MetaCounterBuilding); gMetaBuildingRegistry.register(MetaRotaterBuilding); gMetaBuildingRegistry.register(MetaStackerBuilding); gMetaBuildingRegistry.register(MetaMixerBuilding); diff --git a/src/js/game/systems/counter.js b/src/js/game/systems/counter.js new file mode 100644 index 00000000..316c8c14 --- /dev/null +++ b/src/js/game/systems/counter.js @@ -0,0 +1,77 @@ +import { DrawParameters } from "../../core/draw_parameters"; +import { ItemCounterComponent } from "../components/counter"; +import { Entity } from "../entity"; +import { GameSystemWithFilter } from "../game_system_with_filter"; +import { ShapeItem } from "../items/shape_item"; +import { Loader } from "../../core/loader"; + +export class CounterSystem extends GameSystemWithFilter { + constructor(root) { + super(root, [ItemCounterComponent]); + + this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png"); + } + + update() { + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; + const counterComp = entity.components.Counter; + const ejectorComp = entity.components.ItemEjector; + + const items = counterComp.inputSlots; + + let outItem = null; + + if (items.length > 0) { + const inputItem = /** @type {ShapeItem} */ (items[0].item); + assert(inputItem instanceof ShapeItem, "Input for counting is not a shape"); + + outItem = inputItem; + let slot = ejectorComp.getFirstFreeSlot(entity.layer); + + if (slot !== null) { + if (!ejectorComp.tryEject(slot, outItem)) { + assert(false, "Failed to eject"); + } else { + counterComp.countNewItem(); + counterComp.inputSlots = []; + } + } + } + + counterComp.tick(this.root.time); + } + } + + draw(parameters) { + this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this)); + } + + /** + * @param {DrawParameters} parameters + * @param {Entity} entity + */ + drawEntity(parameters, entity) { + const context = parameters.context; + const staticComp = entity.components.StaticMapEntity; + + if (!staticComp.shouldBeDrawn(parameters)) { + return; + } + + const counterComp = entity.components.Counter; + + context.globalAlpha = 1; + const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); + + this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15); + + context.font = "bold 10px GameFont"; + context.textAlign = "center"; + context.fillStyle = "#64666e"; + context.fillText(counterComp.averageItemsPerSecond, center.x, center.y + 25.5); + + context.textAlign = "left"; + context.globalAlpha = 1; + } +} diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index 316dc053..97a45749 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -219,9 +219,9 @@ export class ItemEjectorSystem extends GameSystemWithFilter { sourceSlot.progress = Math.min( 1, sourceSlot.progress + - progressGrowth * - this.root.hubGoals.getBeltBaseSpeed(sourceSlot.layer) * - globalConfig.beltItemSpacingByLayer[sourceSlot.layer] + progressGrowth * + this.root.hubGoals.getBeltBaseSpeed(sourceSlot.layer) * + globalConfig.beltItemSpacingByLayer[sourceSlot.layer] ); // Check if we are still in the process of ejecting, can't proceed then @@ -332,6 +332,16 @@ export class ItemEjectorSystem extends GameSystemWithFilter { return false; } + const counterComp = receiver.components.Counter; + if (counterComp) { + if (counterComp.tryTakeItem(item, slotIndex)) { + // Passed it over + return true; + } + + return false; + } + return false; } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 64b1777a..f3bab4f1 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -504,6 +504,11 @@ buildings: name: Rotate (CCW) description: Rotates shapes counter-clockwise by 90 degrees. + counter: + default: + name: &counter Counter + description: COUNTER + stacker: default: name: &stacker Stacker @@ -818,6 +823,7 @@ keybindings: miner: *miner cutter: *cutter advanced_processor: *advanced_processor + counter: *counter rotater: *rotater stacker: *stacker mixer: *mixer