diff --git a/res_raw/sprites/blueprints/wireless_display-remote_control.png b/res_raw/sprites/blueprints/wireless_display-remote_control.png new file mode 100644 index 00000000..304ad1af Binary files /dev/null and b/res_raw/sprites/blueprints/wireless_display-remote_control.png differ diff --git a/res_raw/sprites/blueprints/wireless_display.png b/res_raw/sprites/blueprints/wireless_display.png new file mode 100644 index 00000000..20c4948e Binary files /dev/null and b/res_raw/sprites/blueprints/wireless_display.png differ diff --git a/res_raw/sprites/buildings/wireless_display-remote_control.png b/res_raw/sprites/buildings/wireless_display-remote_control.png new file mode 100644 index 00000000..26e8dbd4 Binary files /dev/null and b/res_raw/sprites/buildings/wireless_display-remote_control.png differ diff --git a/res_raw/sprites/buildings/wireless_display.png b/res_raw/sprites/buildings/wireless_display.png new file mode 100644 index 00000000..0533d513 Binary files /dev/null and b/res_raw/sprites/buildings/wireless_display.png differ diff --git a/src/css/resources.scss b/src/css/resources.scss index 08bfa43f..9412c83e 100644 --- a/src/css/resources.scss +++ b/src/css/resources.scss @@ -1,5 +1,5 @@ $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, + constant_signal, logic_gate, lever, filter, wire_tunnel, display, wireless_display, virtual_processor, reader, storage, transistor, analyzer, comparator, item_producer; @each $building in $buildings { @@ -11,7 +11,7 @@ $buildings: belt, cutter, miner, mixer, painter, rotater, balancer, stacker, tra $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2, miner, miner-chainable, cutter, cutter-quad, rotater, rotater-ccw, stacker, mixer, painter-double, painter-quad, trash, storage, - reader, rotater-rotate180, display, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not, + reader, rotater-rotate180, display, wireless_display, wireless_display-remote_control, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not, logic_gate-xor, analyzer, virtual_processor-rotater, virtual_processor-unstacker, item_producer, virtual_processor-stacker, virtual_processor-painter, wire-second, painter, painter-mirrored, comparator; @each $building in $buildingsAndVariants { diff --git a/src/js/game/buildings/wire.js b/src/js/game/buildings/wire.js index 61b75073..c574ae4d 100644 --- a/src/js/game/buildings/wire.js +++ b/src/js/game/buildings/wire.js @@ -267,4 +267,4 @@ export class MetaWireBuilding extends MetaBuilding { rotationVariant: arrayWireRotationVariantToType.indexOf(targetType), }; } -} +} \ No newline at end of file diff --git a/src/js/game/buildings/wire_tunnel.js b/src/js/game/buildings/wire_tunnel.js index 2626dd12..bdc4b9c9 100644 --- a/src/js/game/buildings/wire_tunnel.js +++ b/src/js/game/buildings/wire_tunnel.js @@ -55,4 +55,4 @@ export class MetaWireTunnelBuilding extends MetaBuilding { setupEntityComponents(entity) { entity.addComponent(new WireTunnelComponent()); } -} +} \ No newline at end of file diff --git a/src/js/game/buildings/wireless_display.js b/src/js/game/buildings/wireless_display.js new file mode 100644 index 00000000..93a1bb54 --- /dev/null +++ b/src/js/game/buildings/wireless_display.js @@ -0,0 +1,87 @@ +import { enumDirection, Vector } from "../../core/vector"; +import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins"; +import { Entity } from "../entity"; +import { defaultBuildingVariant, MetaBuilding } from "../meta_building"; +import { GameRoot } from "../root"; +import { WirelessDisplayComponent } from "../components/wireless_display"; +import { enumHubGoalRewards } from "../tutorial_goals"; +import { formatItemsPerSecond, generateMatrixRotations } from "../../core/utils"; + + +/** @enum {string} */ +export const enumWirelessDisplayVariants = { + remote_control: "remote_control", +}; + +const overlayMatrices = { + [defaultBuildingVariant]: null, + [enumWirelessDisplayVariants.remote_control]: generateMatrixRotations([0, 1, 0, 0, 1, 1, 0, 1, 0]), +}; + +export class MetaWirelessDisplayBuilding extends MetaBuilding { + constructor() { + super("wireless_display"); + } + + getSilhouetteColor() { + return "#aaaaaa"; + } + + /** + * @param {GameRoot} root + */ + getIsUnlocked(root) { + return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_display); + } + + /** + * @param {GameRoot} root + */ + getAvailableVariants(root) { + let available = [defaultBuildingVariant]; + + if (true || root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_display)) { + available.push(enumWirelessDisplayVariants.remote_control); + } + + return available; + } + + getDimensions() { + return new Vector(1, 1); + } + + getShowWiresLayerPreview() { + return true; + } + + /** + * Creates the entity at the given location + * @param {Entity} entity + */ + setupEntityComponents(entity) { + entity.addComponent(new WirelessDisplayComponent({})); + } + + /** + * + * @param {Entity} entity + * @param {number} rotationVariant + * @param {string} variant + */ + updateVariants(entity, rotationVariant, variant) { + if (variant == enumWirelessDisplayVariants.remote_control && !entity.components.WiredPins) { + entity.addComponent( + new WiredPinsComponent({ + slots: [ + { + pos: new Vector(0, 0), + direction: enumDirection.bottom, + type: enumPinSlotType.logicalAcceptor, + }, + ], + }), + ); + } + } +} diff --git a/src/js/game/component_registry.js b/src/js/game/component_registry.js index f094e60d..de83a929 100644 --- a/src/js/game/component_registry.js +++ b/src/js/game/component_registry.js @@ -19,6 +19,8 @@ import { DisplayComponent } from "./components/display"; import { BeltReaderComponent } from "./components/belt_reader"; import { FilterComponent } from "./components/filter"; import { ItemProducerComponent } from "./components/item_producer"; +import { WirelessDisplayComponent } from "./components/wireless_display"; +import { WirelessCodeComponent } from "./components/wireless_code"; export function initComponentRegistry() { gComponentRegistry.register(StaticMapEntityComponent); @@ -38,9 +40,11 @@ export function initComponentRegistry() { gComponentRegistry.register(LeverComponent); gComponentRegistry.register(WireTunnelComponent); gComponentRegistry.register(DisplayComponent); + gComponentRegistry.register(WirelessDisplayComponent); gComponentRegistry.register(BeltReaderComponent); gComponentRegistry.register(FilterComponent); gComponentRegistry.register(ItemProducerComponent); + gComponentRegistry.register(WirelessCodeComponent); // IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS diff --git a/src/js/game/components/wire.js b/src/js/game/components/wire.js index d0e354e2..8c210abd 100644 --- a/src/js/game/components/wire.js +++ b/src/js/game/components/wire.js @@ -39,4 +39,4 @@ export class WireComponent extends Component { */ this.linkedNetwork = null; } -} +} \ No newline at end of file diff --git a/src/js/game/components/wire_tunnel.js b/src/js/game/components/wire_tunnel.js index 1c170484..17ac15ea 100644 --- a/src/js/game/components/wire_tunnel.js +++ b/src/js/game/components/wire_tunnel.js @@ -1,3 +1,4 @@ + import { Component } from "../component"; export class WireTunnelComponent extends Component { @@ -14,4 +15,4 @@ export class WireTunnelComponent extends Component { */ this.linkedNetworks = []; } -} +} \ No newline at end of file diff --git a/src/js/game/components/wireless_code.js b/src/js/game/components/wireless_code.js new file mode 100644 index 00000000..2c732e22 --- /dev/null +++ b/src/js/game/components/wireless_code.js @@ -0,0 +1,23 @@ +import { Component } from "../component"; +import { types } from "../../savegame/serialization"; + +export class WirelessCodeComponent extends Component { + static getId() { + return "WirelessCode"; + } + + static getSchema() { + return { + wireless_code: types.string + }; + } + + /** + * + * @param {object} id + */ + constructor(id) { + super(); + this.wireless_code = id; + } +} diff --git a/src/js/game/components/wireless_display.js b/src/js/game/components/wireless_display.js new file mode 100644 index 00000000..063e8990 --- /dev/null +++ b/src/js/game/components/wireless_display.js @@ -0,0 +1,36 @@ +import { Component } from "../component"; +import { types } from "../../savegame/serialization"; +import { BaseItem } from "../base_item"; +import { typeItemSingleton } from "../item_resolver"; + +export class WirelessDisplayComponent extends Component { + static getId() { + return "WirelessDisplay"; + } + + static getSchema() { + return { + signal: types.nullable(typeItemSingleton), + }; + } + + /** + * Copy the current state to another component + * @param {WirelessDisplayComponent} otherComponent + */ + copyAdditionalStateTo(otherComponent) { + otherComponent.signal = this.signal; + } + + /** + * + * @param {object} param0 + * @param {BaseItem=} param0.signal The signal to store + */ + constructor({ signal = null }) { + super(); + this.signal = signal; + } +} + + diff --git a/src/js/game/core.js b/src/js/game/core.js index 2df8989f..93871f62 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -89,7 +89,6 @@ export class GameCore { this.root.savegame = savegame; this.root.gameWidth = this.app.screenWidth; this.root.gameHeight = this.app.screenHeight; - // Initialize canvas element & context this.internalInitCanvas(); diff --git a/src/js/game/entity.js b/src/js/game/entity.js index d7dd715e..c15c88a8 100644 --- a/src/js/game/entity.js +++ b/src/js/game/entity.js @@ -96,6 +96,7 @@ export class Entity extends BasicSerializableObject { rotation: staticComp.rotation, rotationVariant: buildingData.rotationVariant, variant: buildingData.variant, + wireless_code: this.components.WirelessCode, }); for (const key in this.components) { diff --git a/src/js/game/entity_components.js b/src/js/game/entity_components.js index 7dee590a..801f0097 100644 --- a/src/js/game/entity_components.js +++ b/src/js/game/entity_components.js @@ -19,6 +19,8 @@ import { DisplayComponent } from "./components/display"; import { BeltReaderComponent } from "./components/belt_reader"; import { FilterComponent } from "./components/filter"; import { ItemProducerComponent } from "./components/item_producer"; +import { WirelessDisplayComponent } from "./components/wireless_display"; +import { WirelessCodeComponent } from "./components/wireless_code"; /* typehints:end */ /** @@ -80,6 +82,9 @@ export class EntityComponentStorage { /** @type {DisplayComponent} */ this.Display; + /** @type {WirelessDisplayComponent} */ + this.WirelessDisplay; + /** @type {BeltReaderComponent} */ this.BeltReader; @@ -89,6 +94,9 @@ export class EntityComponentStorage { /** @type {ItemProducerComponent} */ this.ItemProducer; + /** @type {WirelessCodeComponent} */ + this.WirelessCode; + /* typehints:end */ } } diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index 74ba798f..6bcc44b1 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -20,6 +20,7 @@ import { ConstantSignalSystem } from "./systems/constant_signal"; import { LogicGateSystem } from "./systems/logic_gate"; import { LeverSystem } from "./systems/lever"; import { DisplaySystem } from "./systems/display"; +import { WirelessDisplaySystem } from "./systems/wireless_display"; import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays"; import { BeltReaderSystem } from "./systems/belt_reader"; import { FilterSystem } from "./systems/filter"; @@ -88,6 +89,9 @@ export class GameSystemManager { /** @type {DisplaySystem} */ display: null, + /** @type {WirelessDisplaySystem} */ + wirelessDisplay: null, + /** @type {ItemProcessorOverlaysSystem} */ itemProcessorOverlays: null, @@ -162,6 +166,7 @@ export class GameSystemManager { add("beltReader", BeltReaderSystem); add("display", DisplaySystem); + add("wirelessDisplay", WirelessDisplaySystem); add("itemProcessorOverlays", ItemProcessorOverlaysSystem); diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index c9d9494f..9a945128 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -238,7 +238,8 @@ export class HubGoals extends BasicSerializableObject { return; } - const required = Math.min(200, 4 + (this.level - 27) * 0.25); + //Floor Required amount to remove confusion + const required = Math.min(200, Math.floor(4 + (this.level - 27) * 0.25)); this.currentGoal = { definition: this.computeFreeplayShape(this.level), required, diff --git a/src/js/game/hud/parts/buildings_toolbar.js b/src/js/game/hud/parts/buildings_toolbar.js index 05ffc795..bed5c80f 100644 --- a/src/js/game/hud/parts/buildings_toolbar.js +++ b/src/js/game/hud/parts/buildings_toolbar.js @@ -1,6 +1,7 @@ import { MetaBeltBuilding } from "../../buildings/belt"; import { MetaCutterBuilding } from "../../buildings/cutter"; import { MetaDisplayBuilding } from "../../buildings/display"; +import { MetaWirelessDisplayBuilding } from "../../buildings/wireless_display"; import { MetaFilterBuilding } from "../../buildings/filter"; import { MetaLeverBuilding } from "../../buildings/lever"; import { MetaMinerBuilding } from "../../buildings/miner"; @@ -39,6 +40,7 @@ export class HUDBuildingsToolbar extends HUDBaseToolbar { MetaLeverBuilding, MetaFilterBuilding, MetaDisplayBuilding, + MetaWirelessDisplayBuilding, ], visibilityCondition: () => !this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "regular", diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 08a11769..c6e03e2f 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -14,6 +14,7 @@ import { KEYMAPPINGS } from "../../key_action_mapper"; import { THEME } from "../../theme"; import { enumHubGoalRewards } from "../../tutorial_goals"; import { Blueprint } from "../../blueprint"; +import { drawRotatedSprite } from "../../../core/draw_utils"; const logger = createLogger("hud/mass_selector"); @@ -304,16 +305,9 @@ export class HUDMassSelector extends BaseHUDPart { renderedUids.add(uid); const staticComp = contents.components.StaticMapEntity; - const bounds = staticComp.getTileSpaceBounds(); - parameters.context.beginRoundedRect( - bounds.x * globalConfig.tileSize + boundsBorder, - bounds.y * globalConfig.tileSize + boundsBorder, - bounds.w * globalConfig.tileSize - 2 * boundsBorder, - bounds.h * globalConfig.tileSize - 2 * boundsBorder, - 2 - ); - parameters.context.fill(); + staticComp.drawSpriteOnBoundsClipped(parameters, staticComp.getBlueprintSprite(), 0); } + parameters.context.globalAlpha = 1; } } } @@ -322,15 +316,8 @@ export class HUDMassSelector extends BaseHUDPart { this.selectedUids.forEach(uid => { const entity = this.root.entityMgr.findByUid(uid); const staticComp = entity.components.StaticMapEntity; - const bounds = staticComp.getTileSpaceBounds(); - parameters.context.beginRoundedRect( - bounds.x * globalConfig.tileSize + boundsBorder, - bounds.y * globalConfig.tileSize + boundsBorder, - bounds.w * globalConfig.tileSize - 2 * boundsBorder, - bounds.h * globalConfig.tileSize - 2 * boundsBorder, - 2 - ); - parameters.context.fill(); + + staticComp.drawSpriteOnBoundsClipped(parameters, staticComp.getBlueprintSprite(), 0); }); } } diff --git a/src/js/game/hud/parts/wires_toolbar.js b/src/js/game/hud/parts/wires_toolbar.js index 5141bbeb..f5d0c5a3 100644 --- a/src/js/game/hud/parts/wires_toolbar.js +++ b/src/js/game/hud/parts/wires_toolbar.js @@ -11,6 +11,7 @@ import { MetaComparatorBuilding } from "../../buildings/comparator"; import { MetaReaderBuilding } from "../../buildings/reader"; import { MetaFilterBuilding } from "../../buildings/filter"; import { MetaDisplayBuilding } from "../../buildings/display"; +import { MetaWirelessDisplayBuilding } from "../../buildings/wireless_display"; import { MetaStorageBuilding } from "../../buildings/storage"; export class HUDWiresToolbar extends HUDBaseToolbar { @@ -32,6 +33,7 @@ export class HUDWiresToolbar extends HUDBaseToolbar { MetaLeverBuilding, MetaFilterBuilding, MetaDisplayBuilding, + MetaWirelessDisplayBuilding, ], visibilityCondition: () => !this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires", diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 9fa4ffe1..0c8bbac8 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -65,6 +65,7 @@ export const KEYMAPPINGS = { lever: { keyCode: key("I") }, filter: { keyCode: key("O") }, display: { keyCode: key("P") }, + wireless_display: { keyCode: key("K") }, // Wires toolbar wire: { keyCode: key("1") }, diff --git a/src/js/game/logic.js b/src/js/game/logic.js index 7ec7b8ab..9951ca36 100644 --- a/src/js/game/logic.js +++ b/src/js/game/logic.js @@ -193,6 +193,9 @@ export class GameLogic { * @param {enumDirection} param0.edge The edge to check for */ computeWireEdgeStatus({ wireVariant, tile, edge }) { + /** + * @type {Vector} + */ const offset = enumDirectionToVector[edge]; const targetTile = tile.add(offset); @@ -240,10 +243,10 @@ export class GameLogic { const targetStaticComp = targetEntity.components.StaticMapEntity; - // Check if its a crossing + // Check if its a tunnel const wireTunnelComp = targetEntity.components.WireTunnel; if (wireTunnelComp) { - return true; + return wireTunnelComp.CanConnectWorld(targetStaticComp, offset); } // Check if its a wire diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js index 848afbab..9548ac0b 100644 --- a/src/js/game/map_chunk_view.js +++ b/src/js/game/map_chunk_view.js @@ -68,6 +68,7 @@ export class MapChunkView extends MapChunk { systems.staticMapEntities.drawChunk(parameters, this); systems.lever.drawChunk(parameters, this); systems.display.drawChunk(parameters, this); + systems.wirelessDisplay.drawChunk(parameters, this); systems.storage.drawChunk(parameters, this); systems.itemProcessorOverlays.drawChunk(parameters, this); } diff --git a/src/js/game/meta_building.js b/src/js/game/meta_building.js index 9deee272..a2ba3307 100644 --- a/src/js/game/meta_building.js +++ b/src/js/game/meta_building.js @@ -199,7 +199,7 @@ export class MetaBuilding { * @param {number} param0.rotationVariant Rotation variant * @param {string} param0.variant */ - createEntity({ root, origin, rotation, originalRotation, rotationVariant, variant }) { + createEntity({ root, origin, rotation, originalRotation, rotationVariant, variant, wireless_code }) { const entity = new Entity(root); entity.layer = this.getLayer(); entity.addComponent( @@ -213,6 +213,11 @@ export class MetaBuilding { ); this.setupEntityComponents(entity, root); this.updateVariants(entity, rotationVariant, variant); + if (entity.components.WirelessDisplay && wireless_code) { + if (!entity.components.WirelessCode) { + entity.components.WirelessCode = wireless_code; + } + } return entity; } diff --git a/src/js/game/meta_building_registry.js b/src/js/game/meta_building_registry.js index 0613103e..105727ff 100644 --- a/src/js/game/meta_building_registry.js +++ b/src/js/game/meta_building_registry.js @@ -26,6 +26,7 @@ import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buil import { enumVirtualProcessorVariants, MetaVirtualProcessorBuilding } from "./buildings/virtual_processor"; import { MetaWireBuilding } from "./buildings/wire"; import { MetaWireTunnelBuilding } from "./buildings/wire_tunnel"; +import { MetaWirelessDisplayBuilding, enumWirelessDisplayVariants } from "./buildings/wireless_display"; import { buildBuildingCodeCache, gBuildingVariants, registerBuildingVariant } from "./building_codes"; import { enumWireVariant } from "./components/wire"; import { KEYMAPPINGS } from "./key_action_mapper"; @@ -59,6 +60,7 @@ export function initMetaBuildingRegistry() { gMetaBuildingRegistry.register(MetaAnalyzerBuilding); gMetaBuildingRegistry.register(MetaComparatorBuilding); gMetaBuildingRegistry.register(MetaItemProducerBuilding); + gMetaBuildingRegistry.register(MetaWirelessDisplayBuilding); // Belt registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0); @@ -121,7 +123,7 @@ export function initMetaBuildingRegistry() { registerBuildingVariant(52, MetaWireBuilding, enumWireVariant.second, 0); registerBuildingVariant(53, MetaWireBuilding, enumWireVariant.second, 1); registerBuildingVariant(54, MetaWireBuilding, enumWireVariant.second, 2); - registerBuildingVariant(55, MetaWireBuilding, enumWireVariant.second, 3); + registerBuildingVariant(55, MetaWireBuilding, enumWireVariant.second, 3); // Constant signal registerBuildingVariant(31, MetaConstantSignalBuilding); @@ -165,6 +167,10 @@ export function initMetaBuildingRegistry() { // Item producer registerBuildingVariant(61, MetaItemProducerBuilding); + // Wireless Display + registerBuildingVariant(62, MetaWirelessDisplayBuilding); + registerBuildingVariant(63, MetaWirelessDisplayBuilding, enumWirelessDisplayVariants.remote_control); + // Propagate instances for (const key in gBuildingVariants) { gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass( diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js index aaf31a19..e5c8c4f9 100644 --- a/src/js/game/systems/constant_signal.js +++ b/src/js/game/systems/constant_signal.js @@ -99,7 +99,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/item_processor_overlays.js b/src/js/game/systems/item_processor_overlays.js index 3ba44c7b..0377f779 100644 --- a/src/js/game/systems/item_processor_overlays.js +++ b/src/js/game/systems/item_processor_overlays.js @@ -1,6 +1,6 @@ import { globalConfig } from "../../core/config"; import { Loader } from "../../core/loader"; -import { smoothPulse } from "../../core/utils"; +import { round1DigitLocalized, smoothPulse } from "../../core/utils"; import { enumItemProcessorRequirements, enumItemProcessorTypes } from "../components/item_processor"; import { Entity } from "../entity"; import { GameSystem } from "../game_system"; @@ -92,7 +92,7 @@ export class ItemProcessorOverlaysSystem extends GameSystem { parameters.context.textAlign = "center"; parameters.context.font = "bold 10px GameFont"; parameters.context.fillText( - "" + Math.round(readerComp.lastThroughput * 10) / 10, + round1DigitLocalized(readerComp.lastThroughput), (staticComp.origin.x + 0.5) * globalConfig.tileSize, (staticComp.origin.y + 0.62) * globalConfig.tileSize ); diff --git a/src/js/game/systems/wire.js b/src/js/game/systems/wire.js index 4d0e6de4..73fcf3ac 100644 --- a/src/js/game/systems/wire.js +++ b/src/js/game/systems/wire.js @@ -316,6 +316,11 @@ export class WireSystem extends GameSystemWithFilter { } } + const tunnelComp = nextEntity.components.WireTunnel; + if (tunnelComp) { + //const outputDir = tunnelComp.GetOutputDirection(staticComp, offset); + } + if (newSearchTile) { // Find new surrounding wire targets const newTargets = this.findSurroundingWireTargets( @@ -364,7 +369,7 @@ export class WireSystem extends GameSystemWithFilter { * @param {Vector} initialTile * @param {Array} directions * @param {WireNetwork} network - * @param {enumWireVariant=} variantMask Only accept connections to this mask + * @param {enumWireVariant} variantMask Only accept connections to this mask * @returns {Array} */ findSurroundingWireTargets(initialTile, directions, network, variantMask = null) { @@ -386,9 +391,6 @@ export class WireSystem extends GameSystemWithFilter { const offset = enumDirectionToVector[direction]; const initialSearchTile = initialTile.add(offset); - // Store which tunnels we already visited to avoid infinite loops - const visitedTunnels = new Set(); - // First, find the initial connected entities const initialContents = this.root.map.getLayersContentsMultipleXY( initialSearchTile.x, @@ -396,17 +398,18 @@ export class WireSystem extends GameSystemWithFilter { ); // Link the initial tile to the initial entities, since it may change - /** @type {Array<{entity: Entity, tile: Vector}>} */ + /** @type {Array<{entity: Entity, tile: Vector, dir: Vector}>} */ const contents = []; for (let j = 0; j < initialContents.length; ++j) { contents.push({ entity: initialContents[j], tile: initialSearchTile, + dir: offset, }); } for (let k = 0; k < contents.length; ++k) { - const { entity, tile } = contents[k]; + const { entity, tile, dir } = contents[k]; const wireComp = entity.components.Wire; // Check for wire @@ -438,8 +441,17 @@ export class WireSystem extends GameSystemWithFilter { } // Check if the direction (inverted) matches - const pinDirection = staticComp.localDirectionToWorld(slot.direction); - if (pinDirection !== enumInvertedDirections[direction]) { + // const pinDirection = staticComp.localDirectionToWorld(slot.direction); + // if (pinDirection !== enumInvertedDirections[direction]) { + // continue; + // } + // /** + // * @type {Vector} + // */ + const worldDir = staticComp.localDirectionToWorld(slot.direction); + const invDir = enumInvertedDirections[worldDir]; + const pinDirection = enumDirectionToVector[invDir]; + if (!pinDirection.equals(dir)) { continue; } @@ -458,14 +470,20 @@ export class WireSystem extends GameSystemWithFilter { // Check if it's a tunnel, if so, go to the forwarded item const tunnelComp = entity.components.WireTunnel; if (tunnelComp) { - if (visitedTunnels.has(entity.uid)) { - continue; - } const staticComp = entity.components.StaticMapEntity; + //const localDir = staticComp.worldToLocalTile(tile.sub(offset)); + //staticComp.localDirectionToWorld(); + const outputDir = tunnelComp.GetOutputDirection(staticComp, dir); + if (!outputDir) { + continue; + } + const forwardedTile = staticComp.origin.add(outputDir); + + //TODO: Alter to Allow for different tunnel Types // Compute where this tunnel connects to - const forwardedTile = staticComp.origin.add(offset); + //const forwardedTile = staticComp.origin.add(offset); VERBOSE_WIRES && logger.log( " Found tunnel", @@ -487,6 +505,7 @@ export class WireSystem extends GameSystemWithFilter { contents.push({ entity: connectedContents[h], tile: forwardedTile, + dir: outputDir, }); } @@ -497,9 +516,6 @@ export class WireSystem extends GameSystemWithFilter { if (network.tunnels.indexOf(entity) < 0) { network.tunnels.push(entity); } - - // Remember this tunnel - visitedTunnels.add(entity.uid); } } } diff --git a/src/js/game/systems/wired_pins.js b/src/js/game/systems/wired_pins.js index e8bc1882..6c893caa 100644 --- a/src/js/game/systems/wired_pins.js +++ b/src/js/game/systems/wired_pins.js @@ -189,16 +189,17 @@ export class WiredPinsSystem extends GameSystemWithFilter { ); if (staticComp.getMetaBuilding().getRenderPins()) { + this.sprite = this.pinSprites[slot.type]; drawRotatedSprite({ parameters, - sprite: this.pinSprites[slot.type], + sprite: this.sprite, x: worldPos.x, y: worldPos.y, angle: effectiveRotation, size: globalConfig.tileSize + 2, offsetX: 0, offsetY: 0, - }); + }); } // Draw contained item to visualize whats emitted diff --git a/src/js/game/systems/wireless_display.js b/src/js/game/systems/wireless_display.js new file mode 100644 index 00000000..25e83d6c --- /dev/null +++ b/src/js/game/systems/wireless_display.js @@ -0,0 +1,273 @@ +import { globalConfig } from "../../core/config"; +import { Loader } from "../../core/loader"; +import { BaseItem } from "../base_item"; +import { enumColors } from "../colors"; +import { WirelessDisplayComponent } from "../components/wireless_display"; +import { GameSystemWithFilter } from "../game_system_with_filter"; +import { isTrueItem } from "../items/boolean_item"; +import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item"; +import { MapChunkView } from "../map_chunk_view"; +import { THIRDPARTY_URLS } 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 { Entity } from "../entity"; +import { WirelessCodeComponent } from "../components/wireless_code"; +import { THEME} from "../theme"; + +export class WirelessDisplaySystem extends GameSystemWithFilter { + constructor(root) { + super(root, [WirelessDisplayComponent]); + + this.root.signals.entityManuallyPlaced.add(this.channelSignalValue, this); + + /** @type {Object} */ + this.displaySprites = {}; + + for (const colorId in enumColors) { + if (colorId === enumColors.uncolored) { + continue; + } + this.displaySprites[colorId] = Loader.getSprite("sprites/wires/display/" + colorId + ".png"); + } + + this.wirelessMachineList = {}; + + this.displayNumber = 0; + this.entityCount = 0; + } + + update() { + if (this.entityCount != this.allEntities.length) { + for (let i = 0; i < this.allEntities.length; i++) { + const entity = this.allEntities[i]; + if (entity.components.WirelessDisplay && entity.components.WiredPins && entity.components.WirelessCode && !this.wirelessMachineList[entity.components.WirelessCode]) { + this.wirelessMachineList[entity.components.WirelessCode["wireless_code"]] = entity; + } + } + this.entityCount = this.allEntities.length; + } + const mousePos = this.root.app.mousePosition; + } + + /** + * Asks the entity to enter a valid signal code + * @param {Entity} entity + */ + channelSignalValue(entity) { + if (entity.components.WirelessDisplay) { + // Ok, query, but also save the uid because it could get stale + const uid = entity.uid; + + const signalValueInput = new FormElementInput({ + id: "channelValue", + label: fillInLinkIntoTranslation(T.dialogs.editChannel.descShortKey, THIRDPARTY_URLS.shapeViewer), + placeholder: "", + defaultValue: "", + validator: val => val, + }); + + const channeldialog = new DialogWithForm({ + app: this.root.app, + title: T.dialogs.editChannel.title, + desc: T.dialogs.editChannel.descItems, + formElements: [signalValueInput], + buttons: ["cancel:bad:escape", "ok:good:enter"], + closeButton: false, + }); + this.root.hud.parts.dialogs.internalShowDialog(channeldialog); + + // When confirmed, set the signal + const closeHandler = () => { + if (!this.root || !this.root.entityMgr) { + // Game got stopped + return; + } + + const entityRef = this.root.entityMgr.findByUid(uid, false); + if (!entityRef) { + // outdated + return; + } + + const constantComp = entityRef.components.WirelessDisplay; + if (!constantComp) { + // no longer interesting + return; + } + + if (signalValueInput.getValue() && !entity.components.WiredPins) { + entity.addComponent(new WirelessCodeComponent(signalValueInput.getValue())); + } else if (signalValueInput.getValue() && entity.components.WiredPins) { + entity.addComponent(new WirelessCodeComponent(signalValueInput.getValue())); + this.wirelessMachineList[entity.components.WirelessCode["wireless_code"]] = entity; + } + }; + + channeldialog.buttonSignals.ok.add(closeHandler); + channeldialog.valueChosen.add(closeHandler); + + // When cancelled, destroy the entity again + channeldialog.buttonSignals.cancel.add(() => { + if (!this.root || !this.root.entityMgr) { + // Game got stopped + return; + } + + const entityRef = this.root.entityMgr.findByUid(uid, false); + if (!entityRef) { + // outdated + return; + } + + const constantComp = entityRef.components.WirelessDisplay; + if (!constantComp) { + // no longer interesting + return; + } + + this.root.logic.tryDeleteBuilding(entityRef); + }); + } + } + + /** + * Returns the color / value a display should show + * @param {BaseItem} value + * @returns {BaseItem} + */ + getDisplayItem(value) { + if (!value) { + return null; + } + + switch (value.getItemType()) { + case "boolean": { + return isTrueItem(value) ? COLOR_ITEM_SINGLETONS[enumColors.white] : null; + } + + case "color": { + const item = /**@type {ColorItem} */ (value); + return item.color === enumColors.uncolored ? null : item; + } + + case "shape": { + return value; + } + + default: + assertAlways(false, "Unknown item type: " + value.getItemType()); + } + } + + /** + * Computes the color below the current tile + * @returns {number} + */ + computeColorBelowTile() { + const mousePosition = this.root.app.mousePosition; + if (!mousePosition) { + // Not on screen + return null; + } + + const worldPos = this.root.camera.screenToWorld(mousePosition); + const tile = worldPos.toTileSpace(); + const contents = this.root.map.getTileContent(tile, "regular"); + + if (contents && contents.components.WirelessDisplay) { + // We hovered a lower layer, show the color there + if (contents && contents.components.WirelessCode && contents.components.WirelessCode.wireless_code) { + return contents.components.WirelessCode.wireless_code; + } + } + + return null; + } + + /** + * Draws Text Storked + * @param {string} text + * @param {number} y + * @param {number} x + * @param {number=} width + */ + drawStroked(ctx, text, x, y, width = undefined) { + ctx.font = '15px Sans-serif'; + ctx.strokeStyle = 'black'; + ctx.lineWidth = 1; + ctx.miterLimit=2 + ctx.strokeText(text, x, y, width); + ctx.fillStyle = 'white'; + ctx.fillText(text, x, y, width); + } + + /** + * Draws a given chunk + * @param {import("../../core/draw_utils").DrawParameters} parameters + * @param {MapChunkView} chunk + */ + drawChunk(parameters, chunk) { + const contents = chunk.containedEntitiesByLayer.regular; + for (let i = 0; i < contents.length; ++i) { + const entity_a = contents[i]; + if (entity_a && entity_a.components.WirelessDisplay) { + const below = this.computeColorBelowTile(); + if (below) { + // We have something below our tile + const mousePosition = this.root.app.mousePosition; + const worldPos = this.root.camera.screenToWorld(mousePosition); + const tile = worldPos.toTileSpace().toWorldSpace(); + + this.drawStroked(parameters.context, below.toString(), worldPos.x + 5, worldPos.y + 5) + parameters.context.strokeStyle = THEME.map.colorBlindPickerTile; + parameters.context.beginPath(); + parameters.context.rect(tile.x, tile.y, globalConfig.tileSize, globalConfig.tileSize); + parameters.context.stroke(); + } + if (!entity_a.components.WiredPins) { + const entity_b = this.wirelessMachineList[entity_a.components.WirelessCode["wireless_code"]]; + if (entity_b) { + if (!this.allEntities.includes(entity_b)) { + this.wirelessMachineList[entity_b] = undefined; + return; + } + const origin = entity_a.components.StaticMapEntity.origin; + const pinsComp = entity_b.components.WiredPins; + const network = pinsComp.slots[0].linkedNetwork; + + if (!network) { + continue; + } + + const value = this.getDisplayItem(network.currentValue); + + if (!value) { + continue; + } + + if (value.getItemType()) { + if (value.getItemType() === "color") { + this.displaySprites[/** @type {ColorItem} */ (value).color].drawCachedCentered( + parameters, + (origin.x + 0.5) * globalConfig.tileSize, + (origin.y + 0.5) * globalConfig.tileSize, + globalConfig.tileSize + ); + } else if (value.getItemType() === "shape") { + value.drawItemCenteredClipped( + (origin.x + 0.5) * globalConfig.tileSize, + (origin.y + 0.5) * globalConfig.tileSize, + parameters, + 30 + ); + } + } + } + } + } + } + } +} + diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 9d145999..198e84b4 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -3,7 +3,7 @@ # # Contributing: # -# If you want to contribute, please make a pull request on this respository +# If you want to contribute, please make a pull request on this repository # and I will have a look. # # Placeholders: @@ -30,9 +30,11 @@ steamPage: intro: >- Do you like automation games? Then you are in the right place! - shapez.io is a relaxed game in which you have to build factories for the automated production of geometric shapes. As the level increases, the shapes become more and more complex, and you have to spread out on the infinite map. + shapez.io is a relaxed game in which you have to build factories for the automated production of geometric shapes. As the level increases, the shapes become more and more complex, and you + have to spread out on the infinite map. - And as if that wasn't enough, you also have to produce exponentially more to satisfy the demands - the only thing that helps is scaling! While you only have to process shapes at the beginning, you will later have to color them - by extracting and mixing colors! + And as if that wasn't enough, you also have to produce exponentially more to satisfy the demands - the only thing that helps is scaling! While you only have to process shapes at the + beginning, you will later have to color them - by extracting and mixing colors! Buying the game on Steam gives you access to the full version, but you can also play a demo at shapez.io first and decide later! @@ -40,7 +42,7 @@ steamPage: advantages: - 12 New Levels for a total of 26 levels - 18 New Buildings for a fully automated factory! - - 20 Upgrade Tiers for many hours of fun! + - Unlimited Upgrade Tiers for many hours of fun! - Wires Update for an entirely new dimension! - Dark Mode! - Unlimited Savegames @@ -79,7 +81,7 @@ global: # How big numbers are rendered, e.g. "10,000" thousandsDivider: "," - # What symbol to use to seperate the integer part from the fractional part of a number, e.g. "0.4" + # What symbol to use to separate the integer part from the fractional part of a number, e.g. "0.4" decimalSeparator: "." # The suffix for large numbers, e.g. 1.3k, 400.2M, etc. @@ -255,6 +257,12 @@ dialogs: titleEdit: Edit Marker desc: Give it a meaningful name, you can also include a short key of a shape (Which you can generate here) + editChannel: + title: Set Channel + descItems: >- + Enter used channel name + descShortKey: or enter new channel name + editSignal: title: Set Signal descItems: >- @@ -402,7 +410,9 @@ ingame: waypoints: waypoints: Markers hub: HUB - description: Left-click a marker to jump to it, right-click to delete it.

Press to create a marker from the current view, or right-click to create a marker at the selected location. + description: >- + Left-click a marker to jump to it, right-click to delete it.

Press to create a marker from the current view, or right-click to create a marker at the + selected location. creationSuccessNotification: Marker has been created. # Shape viewer @@ -420,7 +430,8 @@ ingame: Connect the extractor with a conveyor belt to your hub!

Tip: Click and drag the belt with your mouse! 1_3_expand: >- - This is NOT an idle game! Build more extractors and belts to finish the goal quicker.

Tip: Hold SHIFT to place multiple extractors, and use R to rotate them. + This is NOT an idle game! Build more extractors and belts to finish the goal quicker.

Tip: Hold SHIFT to place multiple extractors, and use + R to rotate them. 2_1_place_cutter: >- Now place a Cutter to cut the circles in two halves!

@@ -482,7 +493,7 @@ ingame: desc: As many as your heart desires! upgrades: - title: 20 Upgrade Tiers + title: ∞ Upgrade Tiers desc: This demo version has only 5! markers: @@ -499,7 +510,7 @@ ingame: support: title: Support me - desc: I develop it in my spare time! + desc: I develop the game in my spare time! # All shop upgrades shopUpgrades: @@ -581,13 +592,13 @@ buildings: rotater: default: - name: &rotater Rotate + name: &rotater Rotator description: Rotates shapes clockwise by 90 degrees. ccw: - name: Rotate (CCW) + name: Rotator (CCW) description: Rotates shapes counter-clockwise by 90 degrees. rotate180: - name: Rotate (180°) + name: Rotator (180°) description: Rotates shapes by 180 degrees. stacker: @@ -636,10 +647,23 @@ buildings: name: *wire description: *wire_desc + third: + name: *wire + description: *wire_desc + wire_tunnel: default: - name: &wire_tunnel Wire Crossing + name: &wire_tunnel Wire Tunnel description: Allows two wires to cross without connecting to each other. + elbow: + name: Elbow Tunnel + description: Allows a wire to turn a corner without connecting to anything else + straight: + name: Straight tunnel + description: Allows a wire to go straight without connecting to anything else + double_elbow: + name: Double Elbow Tunnel + description: Allows two wires to turn corners without connecting to each other. constant_signal: default: @@ -684,6 +708,14 @@ buildings: name: &display Display description: Connect a signal to show it on the display - It can be a shape, color or boolean. + wireless_display: + default: + name: &wireless_display Wireless Display + description: Connect to a channel and if channel connects, it receives the signal coming from Remote Control - Signal can be a shape, color or boolean. + remote_control: + name: Remote Control + description: Connect to a channel and if channel connects, it emits signal to Wireless Control - Signal can be a shape, color or boolean. + reader: default: name: &reader Belt Reader @@ -705,7 +737,7 @@ buildings: description: Virtually cuts the shape into two halves. rotater: - name: Virtual Rotater + name: Virtual Rotator description: Virtually rotates the shape clockwise. unstacker: @@ -729,16 +761,19 @@ storyRewards: # Those are the rewards gained from completing the store reward_cutter_and_trash: title: Cutting Shapes - desc: You just unlocked the cutter, which cuts shapes in half from top to bottom regardless of its orientation!

Be sure to get rid of the waste, or otherwise it will clog and stall - For this purpose I have given you the trash, which destroys everything you put into it! + desc: >- + You just unlocked the cutter, which cuts shapes in half from top to bottom regardless of its orientation!

Be sure to get rid of the waste, or + otherwise it will clog and stall - For this purpose I have given you the trash, which destroys everything you put into it! reward_rotater: title: Rotating - desc: The rotater has been unlocked! It rotates shapes clockwise by 90 degrees. + desc: The rotator has been unlocked! It rotates shapes clockwise by 90 degrees. reward_painter: title: Painting desc: >- - The painter has been unlocked - Extract some color veins (just as you do with shapes) and combine it with a shape in the painter to color them!

PS: If you are colorblind, there is a colorblind mode in the settings! + The painter has been unlocked - Extract some color veins (just as you do with shapes) and combine it with a shape in the painter to color them!

PS: If you are + colorblind, there is a colorblind mode in the settings! reward_mixer: title: Color Mixing @@ -746,7 +781,9 @@ storyRewards: reward_stacker: title: Stacker - desc: You can now combine shapes with the stacker! Both inputs are combined, and if they can be put next to each other, they will be fused. If not, the right input is stacked on top of the left input! + desc: >- + You can now combine shapes with the stacker! Both inputs are combined, and if they can be put next to each other, they will be fused. If not, the right + input is stacked on top of the left input! reward_balancer: title: Balancer @@ -758,7 +795,9 @@ storyRewards: reward_rotater_ccw: title: CCW Rotating - desc: You have unlocked a variant of the rotater - It allows you to rotate shapes counter-clockwise! To build it, select the rotater and press 'T' to cycle through its variants! + desc: >- + You have unlocked a variant of the rotator - It allows you to rotate shapes counter-clockwise! To build it, select the rotator and + press 'T' to cycle through its variants! reward_miner_chainable: title: Chaining Extractor @@ -791,7 +830,9 @@ storyRewards: reward_painter_double: title: Double Painting - desc: You have unlocked a variant of the painter - It works similar to the regular painter but processes two shapes at once, consuming just one color instead of two! + desc: >- + You have unlocked a variant of the painter - It works similar to the regular painter but processes two shapes at once, consuming just one color + instead of two! reward_storage: title: Storage @@ -801,11 +842,13 @@ storyRewards: reward_blueprints: title: Blueprints - desc: You can now copy and paste parts of your factory! Select an area (Hold CTRL, then drag with your mouse), and press 'C' to copy it.

Pasting it is not free, you need to produce blueprint shapes to afford it! (Those you just delivered). + desc: >- + You can now copy and paste parts of your factory! Select an area (Hold CTRL, then drag with your mouse), and press 'C' to copy it.

Pasting it is + not free, you need to produce blueprint shapes to afford it! (Those you just delivered). reward_rotater_180: - title: Rotater (180°) - desc: You just unlocked the 180 degrees rotater! - It allows you to rotate a shape by 180 degress (Surprise! :D) + title: Rotator (180°) + desc: You just unlocked the 180 degrees rotator! - It allows you to rotate a shape by 180 degrees (Surprise! :D) reward_wires_painter_and_levers: title: >- @@ -846,18 +889,19 @@ storyRewards: title: Virtual Processing desc: >- I just gave a whole bunch of new buildings which allow you to simulate the processing of shapes!

- You can now simulate a cutter, rotater, stacker and more on the wires layer! + You can now simulate a cutter, rotator, stacker and more on the wires layer! With this you now have three options to continue the game:

- Build an automated machine to create any possible shape requested by the HUB (I recommend to try it!).

- Build something cool with wires.

- - Continue to play regulary.

+ - Continue to play normally.

Whatever you choose, remember to have fun! # Special reward, which is shown when there is no reward actually no_reward: title: Next level desc: >- - This level gave you no reward, but the next one will!

PS: Better not destroy your existing factory - You'll need all those shapes later to unlock upgrades! + This level gave you no reward, but the next one will!

PS: Better not destroy your existing factory - You'll need all those shapes later to + unlock upgrades! no_reward_freeplay: title: Next level @@ -986,7 +1030,8 @@ settings: refreshRate: title: Tick Rate description: >- - This determines how many game ticks happen per second. In general, a higher tick rate means better precision but also worse performance. On lower tickrates, the throughput may not be exact. + This determines how many game ticks happen per second. In general, a higher tick rate means better precision but also worse performance. On lower tickrates, the throughput may not be + exact. alwaysMultiplace: title: Multiplace @@ -1037,7 +1082,8 @@ settings: clearCursorOnDeleteWhilePlacing: title: Clear Cursor on Right Click description: >- - Enabled by default, clears the cursor whenever you right click while you have a building selected for placement. If disabled, you can delete buildings by right-clicking while placing a building. + Enabled by default, clears the cursor whenever you right click while you have a building selected for placement. If disabled, you can delete buildings by right-clicking while placing + a building. lowQualityTextures: title: Low quality textures (Ugly) @@ -1132,6 +1178,7 @@ keybindings: filter: *filter wire_tunnel: *wire_tunnel display: *display + wireless_display: *wireless_display reader: *reader virtual_processor: *virtual_processor transistor: *transistor @@ -1211,7 +1258,7 @@ tips: - Cutters always cut vertically, regardless of their orientation. - The storage buffer prioritises the left output. - Invest time to build repeatable designs - it's worth it! - - Holding SHIFT lets you place multiple buildings in one click. + - Holding SHIFT lets you place multiple buildings at a time. - You can hold ALT to invert the direction of placed belts. - Efficiency is key! - Shape patches that are further away from the hub are more complex.