diff --git a/gulp/gulpfile.js b/gulp/gulpfile.js index 72d5b699..a4b63dec 100644 --- a/gulp/gulpfile.js +++ b/gulp/gulpfile.js @@ -1,5 +1,11 @@ /* eslint-disable */ +const nodeVersion = process.versions.node.split(".")[0]; +if (nodeVersion !== "10") { + console.error("This cli requires exactly Node 10. You are using node " + nodeVersion); + process.exit(1); +} + require("colors"); const gulp = require("gulp"); diff --git a/res_raw/atlas.tps b/res_raw/atlas.tps index 9d94a757..36f93c3d 100644 --- a/res_raw/atlas.tps +++ b/res_raw/atlas.tps @@ -304,6 +304,24 @@ scale9FromFile + sprites/blueprints/cutter-quad.png + sprites/blueprints/painter-quad.png + sprites/buildings/cutter-quad.png + sprites/buildings/painter-quad.png + + pivotPoint + 0.5,0.5 + spriteScale + 1 + scale9Enabled + + scale9Borders + 192,48,384,96 + scale9Paddings + 192,48,384,96 + scale9FromFile + + sprites/blueprints/cutter.png sprites/blueprints/mixer.png sprites/blueprints/painter.png @@ -323,12 +341,22 @@ scale9FromFile + sprites/blueprints/miner-chainable.png sprites/blueprints/miner.png + sprites/blueprints/rotater-ccw.png sprites/blueprints/rotater.png + sprites/blueprints/splitter-compact.png sprites/blueprints/trash.png + sprites/blueprints/underground_belt_entry-tier2.png sprites/blueprints/underground_belt_entry.png + sprites/blueprints/underground_belt_exit-tier2.png sprites/blueprints/underground_belt_exit.png + sprites/buildings/miner-chainable.png + sprites/buildings/rotater-ccw.png + sprites/buildings/splitter-compact.png + sprites/buildings/underground_belt_entry-tier2.png sprites/buildings/underground_belt_entry.png + sprites/buildings/underground_belt_exit-tier2.png sprites/buildings/underground_belt_exit.png pivotPoint @@ -344,6 +372,22 @@ scale9FromFile + sprites/blueprints/painter-double.png + sprites/buildings/painter-double.png + + pivotPoint + 0.5,0.5 + spriteScale + 1 + scale9Enabled + + scale9Borders + 96,96,192,192 + scale9Paddings + 96,96,192,192 + scale9FromFile + + sprites/buildings/cutter.png sprites/buildings/mixer.png sprites/buildings/painter.png diff --git a/src/css/ingame_hud/mass_selector.scss b/src/css/ingame_hud/mass_selector.scss index 0e8bdcb2..99027735 100644 --- a/src/css/ingame_hud/mass_selector.scss +++ b/src/css/ingame_hud/mass_selector.scss @@ -3,20 +3,21 @@ @include S(top, 50px); left: 50%; transform: translateX(-50%); - background: rgba(#f77, 0.8); - @include S(border-radius, 4px); - @include S(padding, 9px); - @include PlainText; + background: rgba(lighten(#f77, 5), 0.95); + @include S(border-radius, 2px); + @include S(padding, 6px, 10px); + @include SuperSmallText; color: #fff; - display: flex; - align-items: center; + // color: #f77; .keybinding { + vertical-align: middle; @include S(margin, 0, 4px); position: relative; top: unset; left: unset; right: unset; bottom: unset; + @include S(margin-top, -2px); } } diff --git a/src/css/main.scss b/src/css/main.scss index a974e2e0..1bb07d65 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -54,8 +54,8 @@ ingame_HUD_PinnedShapes, ingame_HUD_buildings_toolbar, ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, -ingame_HUD_MassSelector, ingame_HUD_Notifications, +ingame_HUD_MassSelector, // Overlays ingame_HUD_BetaOverlay, diff --git a/src/js/core/config.js b/src/js/core/config.js index 05cad1c8..36821b58 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -38,7 +38,7 @@ export const globalConfig = { // Belt speeds // NOTICE: Update webpack.production.config too! - beltSpeedItemsPerSecond: 5, + beltSpeedItemsPerSecond: 1, itemSpacingOnBelts: 0.63, minerSpeedItemsPerSecond: 0, // COMPUTED diff --git a/src/js/game/buildings/belt_base.js b/src/js/game/buildings/belt_base.js index 8a0f5490..cfd1f299 100644 --- a/src/js/game/buildings/belt_base.js +++ b/src/js/game/buildings/belt_base.js @@ -93,6 +93,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding { directions: [enumDirection.bottom], }, ], + animated: false, }) ); diff --git a/src/js/game/buildings/hub.js b/src/js/game/buildings/hub.js index 6186597e..218144ee 100644 --- a/src/js/game/buildings/hub.js +++ b/src/js/game/buildings/hub.js @@ -44,6 +44,10 @@ export class MetaHubBuilding extends MetaBuilding { processorType: enumItemProcessorTypes.hub, }) ); + + // We render the sprite ourself + entity.components.StaticMapEntity.spriteKey = null; + entity.addComponent(new UnremovableComponent()); entity.addComponent( new ItemAcceptorComponent({ diff --git a/src/js/game/buildings/splitter.js b/src/js/game/buildings/splitter.js index 77d35604..d83375f5 100644 --- a/src/js/game/buildings/splitter.js +++ b/src/js/game/buildings/splitter.js @@ -112,7 +112,7 @@ export class MetaSplitterBuilding extends MetaBuilding { { pos: new Vector(1, 0), direction: enumDirection.top }, ]); - entity.components.ItemProcessor.beltUnderlays = [ + entity.components.ItemAcceptor.beltUnderlays = [ { pos: new Vector(0, 0), direction: enumDirection.top }, { pos: new Vector(1, 0), direction: enumDirection.top }, ]; @@ -135,7 +135,7 @@ export class MetaSplitterBuilding extends MetaBuilding { { pos: new Vector(0, 0), direction: enumDirection.top }, ]); - entity.components.ItemProcessor.beltUnderlays = [ + entity.components.ItemAcceptor.beltUnderlays = [ { pos: new Vector(0, 0), direction: enumDirection.top }, ]; diff --git a/src/js/game/camera.js b/src/js/game/camera.js index 9c0559e8..1fd235e5 100644 --- a/src/js/game/camera.js +++ b/src/js/game/camera.js @@ -29,6 +29,15 @@ const velocityFade = 0.98; const velocityStrength = 0.4; const velocityMax = 20; +/** + * @enum {string} + */ +export const enumMouseButton = { + left: "left", + middle: "middle", + right: "right", +}; + export class Camera extends BasicSerializableObject { constructor(root) { super(); @@ -81,10 +90,10 @@ export class Camera extends BasicSerializableObject { this.touchPostMoveVelocity = new Vector(0, 0); // Handlers - this.downPreHandler = new Signal(/* pos */); - this.movePreHandler = new Signal(/* pos */); - this.pinchPreHandler = new Signal(/* pos */); - this.upPostHandler = new Signal(/* pos */); + this.downPreHandler = /** @type {TypedSignal<[Vector, enumMouseButton]>} */ (new Signal()); + this.movePreHandler = /** @type {TypedSignal<[Vector]>} */ (new Signal()); + // this.pinchPreHandler = /** @type {TypedSignal<[Vector]>} */ (new Signal()); + this.upPostHandler = /** @type {TypedSignal<[Vector]>} */ (new Signal()); this.internalInitEvents(); this.clampZoomLevel(); @@ -411,6 +420,10 @@ export class Camera extends BasicSerializableObject { this.touchPostMoveVelocity = new Vector(0, 0); if (event.which === 1) { this.combinedSingleTouchStartHandler(event.clientX, event.clientY); + } else if (event.which === 2) { + this.downPreHandler.dispatch(new Vector(event.clientX, event.clientY), enumMouseButton.middle); + } else if (event.which === 3) { + this.downPreHandler.dispatch(new Vector(event.clientX, event.clientY), enumMouseButton.right); } return false; } @@ -496,10 +509,10 @@ export class Camera extends BasicSerializableObject { const touch = event.touches[0]; this.combinedSingleTouchStartHandler(touch.clientX, touch.clientY); } else if (event.touches.length === 2) { - if (this.pinchPreHandler.dispatch() === STOP_PROPAGATION) { - // Something prevented pinching - return false; - } + // if (this.pinchPreHandler.dispatch() === STOP_PROPAGATION) { + // // Something prevented pinching + // return false; + // } const touch1 = event.touches[0]; const touch2 = event.touches[1]; @@ -620,7 +633,7 @@ export class Camera extends BasicSerializableObject { */ combinedSingleTouchStartHandler(x, y) { const pos = new Vector(x, y); - if (this.downPreHandler.dispatch(pos) === STOP_PROPAGATION) { + if (this.downPreHandler.dispatch(pos, enumMouseButton.left) === STOP_PROPAGATION) { // Somebody else captured it return; } diff --git a/src/js/game/colors.js b/src/js/game/colors.js index 8926dd23..6d483771 100644 --- a/src/js/game/colors.js +++ b/src/js/game/colors.js @@ -45,7 +45,7 @@ export const enumColorsToHexCode = { [enumColors.purple]: "#dd66ff", // blue + green - [enumColors.cyan]: "#87fff5", + [enumColors.cyan]: "#00fcff", // blue + green + red [enumColors.white]: "#ffffff", diff --git a/src/js/game/components/belt.js b/src/js/game/components/belt.js index 339610a1..3f890b9b 100644 --- a/src/js/game/components/belt.js +++ b/src/js/game/components/belt.js @@ -59,9 +59,8 @@ export class BeltComponent extends Component { /** * Returns if the belt can currently accept an item from the given direction - * @param {enumDirection} direction */ - canAcceptNewItem(direction) { + canAcceptNewItem() { const firstItem = this.sortedItems[0]; if (!firstItem) { return true; @@ -73,9 +72,8 @@ export class BeltComponent extends Component { /** * Pushes a new item to the belt * @param {BaseItem} item - * @param {enumDirection} direction */ - takeNewItem(item, direction) { + takeNewItem(item) { this.sortedItems.unshift([0, item]); } diff --git a/src/js/game/components/item_acceptor.js b/src/js/game/components/item_acceptor.js index f696c0e4..d5546d4b 100644 --- a/src/js/game/components/item_acceptor.js +++ b/src/js/game/components/item_acceptor.js @@ -1,5 +1,5 @@ import { Component } from "../component"; -import { Vector, enumDirection, enumDirectionToAngle, enumInvertedDirections } from "../../core/vector"; +import { Vector, enumDirection, enumInvertedDirections } from "../../core/vector"; import { BaseItem } from "../base_item"; import { ShapeItem } from "../items/shape_item"; import { ColorItem } from "../items/color_item"; @@ -34,6 +34,23 @@ export class ItemAcceptorComponent extends Component { filter: types.nullable(types.enum(enumItemAcceptorItemFilter)), }) ), + animated: types.bool, + beltUnderlays: types.array( + types.structured({ + pos: types.vector, + direction: types.enum(enumDirection), + }) + ), + + // We don't actually need to store the animations + // itemConsumptionAnimations: types.array( + // types.structured({ + // item: types.obj(gItemRegistry), + // slotIndex: types.uint, + // animProgress: types.float, + // direction: types.enum(enumDirection), + // }) + // ), }; } @@ -41,10 +58,23 @@ export class ItemAcceptorComponent extends Component { * * @param {object} param0 * @param {Array<{pos: Vector, directions: enumDirection[], filter?: enumItemAcceptorItemFilter}>} param0.slots The slots from which we accept items + * @param {boolean=} param0.animated Whether to animate item consumption + * @param {Array<{pos: Vector, direction: enumDirection}>=} param0.beltUnderlays Where to render belt underlays */ - constructor({ slots = [] }) { + constructor({ slots = [], beltUnderlays = [], animated = true }) { super(); + this.animated = animated; + + /** + * Fixes belt animations + * @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection}>} + */ + this.itemConsumptionAnimations = []; + + /* Which belt underlays to render */ + this.beltUnderlays = beltUnderlays; + this.setSlots(slots); } @@ -86,6 +116,23 @@ export class ItemAcceptorComponent extends Component { } } + /** + * Called when an item has been accepted so that + * @param {number} slotIndex + * @param {enumDirection} direction + * @param {BaseItem} item + */ + onItemAccepted(slotIndex, direction, item) { + if (this.animated) { + this.itemConsumptionAnimations.push({ + item, + slotIndex, + direction, + animProgress: 0.0, + }); + } + } + /** * Tries to find a slot which accepts the current item * @param {Vector} targetLocalTile @@ -106,12 +153,6 @@ export class ItemAcceptorComponent extends Component { for (let slotIndex = 0; slotIndex < this.slots.length; ++slotIndex) { const slot = this.slots[slotIndex]; - // const acceptorLocalPosition = targetStaticComp.applyRotationToVector( - // slot.pos - // ); - - // const acceptorGlobalPosition = acceptorLocalPosition.add(targetStaticComp.origin); - // Make sure the acceptor slot is on the right position if (!slot.pos.equals(targetLocalTile)) { continue; @@ -130,7 +171,6 @@ export class ItemAcceptorComponent extends Component { } } - // && this.canAcceptItem(slotIndex, ejectingItem) return null; } } diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js index 6eaaa755..0c4e90c6 100644 --- a/src/js/game/components/item_processor.js +++ b/src/js/game/components/item_processor.js @@ -30,12 +30,7 @@ export class ItemProcessorComponent extends Component { nextOutputSlot: types.uint, type: types.enum(enumItemProcessorTypes), inputsPerCharge: types.uint, - beltUnderlays: types.array( - types.structured({ - pos: types.vector, - direction: types.enum(enumDirection), - }) - ), + inputSlots: types.array( types.structured({ item: types.obj(gItemRegistry), @@ -50,14 +45,6 @@ export class ItemProcessorComponent extends Component { }) ), secondsUntilEject: types.float, - itemConsumptionAnimations: types.array( - types.structured({ - item: types.obj(gItemRegistry), - slotIndex: types.uint, - animProgress: types.float, - direction: types.enum(enumDirection), - }) - ), }; } @@ -66,14 +53,9 @@ export class ItemProcessorComponent extends Component { * @param {object} param0 * @param {enumItemProcessorTypes=} param0.processorType Which type of processor this is * @param {number=} param0.inputsPerCharge How many items this machine needs until it can start working - * @param {Array<{pos: Vector, direction: enumDirection}>=} param0.beltUnderlays Where to render belt underlays * */ - constructor({ - processorType = enumItemProcessorTypes.splitter, - inputsPerCharge = 1, - beltUnderlays = [], - }) { + constructor({ processorType = enumItemProcessorTypes.splitter, inputsPerCharge = 1 }) { super(); // Which slot to emit next, this is only a preference and if it can't emit @@ -87,9 +69,6 @@ export class ItemProcessorComponent extends Component { // How many inputs we need for one charge this.inputsPerCharge = inputsPerCharge; - // Which belt underlays to render - this.beltUnderlays = beltUnderlays; - /** * Our current inputs * @type {Array<{ item: BaseItem, sourceSlot: number }>} @@ -108,19 +87,14 @@ export class ItemProcessorComponent extends Component { * How long it takes until we are done with the current items */ this.secondsUntilEject = 0; - - /** - * Fixes belt animations - * @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection}>} - */ - this.itemConsumptionAnimations = []; } /** * Tries to take the item * @param {BaseItem} item + * @param {number} sourceSlot */ - tryTakeItem(item, sourceSlot, sourceDirection) { + 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]; @@ -130,12 +104,6 @@ export class ItemProcessorComponent extends Component { } this.inputSlots.push({ item, sourceSlot }); - this.itemConsumptionAnimations.push({ - item, - slotIndex: sourceSlot, - direction: sourceDirection, - animProgress: 0.0, - }); return true; } } diff --git a/src/js/game/components/underground_belt.js b/src/js/game/components/underground_belt.js index 7093e4b3..92d15203 100644 --- a/src/js/game/components/underground_belt.js +++ b/src/js/game/components/underground_belt.js @@ -34,6 +34,9 @@ export class UndergroundBeltComponent extends Component { this.mode = mode; this.tier = tier; + /** @type {Array<{ item: BaseItem, progress: number }>} */ + this.consumptionAnimations = []; + /** * Used on both receiver and sender. * Reciever: Used to store the next item to transfer, and to block input while doing this diff --git a/src/js/game/core.js b/src/js/game/core.js index db577110..b69d8227 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -398,10 +398,10 @@ export class GameCore { // systems.mapResources.draw(params); if (!this.root.camera.getIsMapOverlayActive()) { - systems.itemProcessor.drawUnderlays(params); + systems.itemAcceptor.drawUnderlays(params); systems.belt.draw(params); systems.itemEjector.draw(params); - systems.itemProcessor.draw(params); + systems.itemAcceptor.draw(params); } root.map.drawForeground(params); diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index 810ddec0..7cde296e 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -11,6 +11,7 @@ import { ItemProcessorSystem } from "./systems/item_processor"; import { UndergroundBeltSystem } from "./systems/underground_belt"; import { HubSystem } from "./systems/hub"; import { StaticMapEntitySystem } from "./systems/static_map_entity"; +import { ItemAcceptorSystem } from "./systems/item_acceptor"; const logger = createLogger("game_system_manager"); @@ -48,6 +49,9 @@ export class GameSystemManager { /** @type {StaticMapEntitySystem} */ staticMapEntities: null, + /** @type {ItemAcceptorSystem} */ + itemAcceptor: null, + /* typehints:end */ }; this.systemUpdateOrder = []; @@ -82,6 +86,8 @@ export class GameSystemManager { add("staticMapEntities", StaticMapEntitySystem); + add("itemAcceptor", ItemAcceptorSystem); + logger.log("📦 There are", this.systemUpdateOrder.length, "game systems"); } diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index 2fc3ba5e..d33cf559 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -17,6 +17,7 @@ import { Math_abs, Math_radians, Math_degrees } from "../../../core/builtins"; import { Loader } from "../../../core/loader"; import { drawRotatedSprite } from "../../../core/draw_utils"; import { Entity } from "../../entity"; +import { enumMouseButton } from "../../camera"; export class HUDBuildingPlacer extends BaseHUDPart { initialize() { @@ -45,6 +46,11 @@ export class HUDBuildingPlacer extends BaseHUDPart { this.variantsAttach = new DynamicDomAttach(this.root, this.variantsElement, {}); + /** + * Whether we are currently drag-deleting + */ + this.currentlyDeleting = false; + /** * Stores which variants for each building we prefer, this is based on what * the user last selected @@ -87,14 +93,17 @@ export class HUDBuildingPlacer extends BaseHUDPart { /** * mouse down pre handler * @param {Vector} pos + * @param {enumMouseButton} button */ - onMouseDown(pos) { + onMouseDown(pos, button) { if (this.root.camera.getIsMapOverlayActive()) { return; } - if (this.currentMetaBuilding.get()) { + // Placement + if (button === enumMouseButton.left && this.currentMetaBuilding.get()) { this.currentlyDragging = true; + this.currentlyDeleting = false; this.lastDragTile = this.root.camera.screenToWorld(pos).toTileSpace(); // Place initial building @@ -102,6 +111,15 @@ export class HUDBuildingPlacer extends BaseHUDPart { return STOP_PROPAGATION; } + + // Deletion + if (button === enumMouseButton.right && !this.currentMetaBuilding.get()) { + this.currentlyDragging = true; + this.currentlyDeleting = true; + this.lastDragTile = this.root.camera.screenToWorld(pos).toTileSpace(); + this.currentMetaBuilding.set(null); + return STOP_PROPAGATION; + } } /** @@ -114,7 +132,7 @@ export class HUDBuildingPlacer extends BaseHUDPart { } const metaBuilding = this.currentMetaBuilding.get(); - if (metaBuilding && this.lastDragTile) { + if ((metaBuilding || this.currentlyDeleting) && this.lastDragTile) { const oldPos = this.lastDragTile; const newPos = this.root.camera.screenToWorld(pos).toTileSpace(); @@ -126,6 +144,7 @@ export class HUDBuildingPlacer extends BaseHUDPart { if (!oldPos.equals(newPos)) { if ( + metaBuilding && metaBuilding.getRotateAutomaticallyWhilePlacing(this.currentVariant.get()) && !this.root.app.inputMgr.ctrlIsDown ) { @@ -152,8 +171,15 @@ export class HUDBuildingPlacer extends BaseHUDPart { var sy = y0 < y1 ? 1 : -1; var err = dx - dy; - while (this.currentMetaBuilding.get()) { - this.tryPlaceCurrentBuildingAt(new Vector(x0, y0)); + while (this.currentlyDeleting || this.currentMetaBuilding.get()) { + if (this.currentlyDeleting) { + const contents = this.root.map.getTileContentXY(x0, y0); + if (contents && !contents.queuedForDestroy && !contents.destroyed) { + this.root.logic.tryDeleteBuilding(contents); + } + } else { + this.tryPlaceCurrentBuildingAt(new Vector(x0, y0)); + } if (x0 === x1 && y0 === y1) break; var e2 = 2 * err; if (e2 > -dy) { @@ -186,6 +212,7 @@ export class HUDBuildingPlacer extends BaseHUDPart { */ abortDragging() { this.currentlyDragging = true; + this.currentlyDeleting = false; this.lastDragTile = null; } @@ -201,6 +228,7 @@ export class HUDBuildingPlacer extends BaseHUDPart { * @param {MetaBuilding} metaBuilding */ onSelectedMetaBuildingChanged(metaBuilding) { + this.abortDragging(); this.root.hud.signals.selectedPlacementBuildingChanged.dispatch(metaBuilding); if (metaBuilding) { this.buildingInfoElements.label.innerHTML = metaBuilding.getName(); @@ -215,7 +243,8 @@ export class HUDBuildingPlacer extends BaseHUDPart { this.currentVariant.set(variant); this.fakeEntity = new Entity(null); - metaBuilding.setupEntityComponents(this.fakeEntity, null, variant); + metaBuilding.setupEntityComponents(this.fakeEntity, null); + this.fakeEntity.addComponent( new StaticMapEntityComponent({ origin: new Vector(0, 0), @@ -223,13 +252,13 @@ export class HUDBuildingPlacer extends BaseHUDPart { tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(), }) ); + metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get()); this.buildingInfoElements.tutorialImage.setAttribute( "data-icon", "building_tutorials/" + metaBuilding.getId() + ".png" ); } else { - this.currentlyDragging = false; this.fakeEntity = null; } diff --git a/src/js/game/hud/parts/buildings_toolbar.js b/src/js/game/hud/parts/buildings_toolbar.js index 4965a0da..6c2aec81 100644 --- a/src/js/game/hud/parts/buildings_toolbar.js +++ b/src/js/game/hud/parts/buildings_toolbar.js @@ -33,7 +33,13 @@ export class HUDBuildingsToolbar extends BaseHUDPart { constructor(root) { super(root); - /** @type {Object.} */ + /** @type {Object.} */ this.buildingHandles = {}; this.sigBuildingSelected = new Signal(); @@ -76,6 +82,7 @@ export class HUDBuildingsToolbar extends BaseHUDPart { element: itemContainer, unlocked: false, selected: false, + index: i, }; } @@ -83,6 +90,9 @@ export class HUDBuildingsToolbar extends BaseHUDPart { this.onSelectedPlacementBuildingChanged, this ); + + this.lastSelectedIndex = 0; + actionMapper.getBinding("cycle_buildings").add(this.cycleBuildings, this); } update() { @@ -98,6 +108,13 @@ export class HUDBuildingsToolbar extends BaseHUDPart { } } + cycleBuildings() { + const newIndex = (this.lastSelectedIndex + 1) % toolbarBuildings.length; + const metaBuildingClass = toolbarBuildings[newIndex]; + const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass); + this.selectBuildingForPlacement(metaBuilding); + } + /** * @param {MetaBuilding} metaBuilding */ @@ -109,6 +126,9 @@ export class HUDBuildingsToolbar extends BaseHUDPart { handle.selected = newStatus; handle.element.classList.toggle("selected", newStatus); } + if (handle.selected) { + this.lastSelectedIndex = handle.index; + } } this.element.classList.toggle("buildingSelected", !!metaBuilding); diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 25e37306..33ade806 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -8,6 +8,7 @@ import { globalConfig } from "../../../core/config"; import { makeDiv } from "../../../core/utils"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { createLogger } from "../../../core/logging"; +import { enumMouseButton } from "../../camera"; const logger = createLogger("hud/mass_selector"); @@ -82,12 +83,17 @@ export class HUDMassSelector extends BaseHUDPart { /** * mouse down pre handler * @param {Vector} pos + * @param {enumMouseButton} mouseButton */ - onMouseDown(pos) { + onMouseDown(pos, mouseButton) { if (!this.root.app.inputMgr.ctrlIsDown) { return; } + if (mouseButton !== enumMouseButton.left) { + return; + } + if (!this.root.app.inputMgr.shiftIsDown) { // Start new selection this.entityUidsMarkedForDeletion = new Set(); diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index d5972820..19bc5b2a 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -50,6 +50,8 @@ export const defaultKeybindings = { rotate_while_placing: { keyCode: key("R") }, cycle_variants: { keyCode: key("T") }, + + cycle_buildings: { keyCode: 9 }, }, }; diff --git a/src/js/game/systems/hub.js b/src/js/game/systems/hub.js index 55ce3544..ee26b71b 100644 --- a/src/js/game/systems/hub.js +++ b/src/js/game/systems/hub.js @@ -4,10 +4,13 @@ import { DrawParameters } from "../../core/draw_parameters"; import { Entity } from "../entity"; import { formatBigNumber } from "../../core/utils"; import { enumHubGoalRewardToString } from "../tutorial_goals"; +import { Loader } from "../../core/loader"; export class HubSystem extends GameSystemWithFilter { constructor(root) { super(root, [HubComponent]); + + this.hubSprite = Loader.getSprite("sprites/buildings/hub.png"); } draw(parameters) { @@ -40,6 +43,9 @@ export class HubSystem extends GameSystemWithFilter { const pos = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); + // Background + staticComp.drawSpriteOnFullEntityBounds(parameters, this.hubSprite, 2.2); + const definition = this.root.hubGoals.currentGoal.definition; definition.draw(pos.x - 25, pos.y - 10, parameters, 40); diff --git a/src/js/game/systems/item_acceptor.js b/src/js/game/systems/item_acceptor.js new file mode 100644 index 00000000..961b7203 --- /dev/null +++ b/src/js/game/systems/item_acceptor.js @@ -0,0 +1,114 @@ +import { GameSystemWithFilter } from "../game_system_with_filter"; +import { globalConfig } from "../../core/config"; +import { DrawParameters } from "../../core/draw_parameters"; +import { Entity } from "../entity"; +import { enumDirectionToVector, enumDirectionToAngle } from "../../core/vector"; +import { ItemAcceptorComponent } from "../components/item_acceptor"; +import { Loader } from "../../core/loader"; +import { drawRotatedSprite } from "../../core/draw_utils"; +import { Math_radians } from "../../core/builtins"; + +export class ItemAcceptorSystem extends GameSystemWithFilter { + constructor(root) { + super(root, [ItemAcceptorComponent]); + + this.underlayBeltSprites = [ + Loader.getSprite("sprites/belt/forward_0.png"), + Loader.getSprite("sprites/belt/forward_1.png"), + Loader.getSprite("sprites/belt/forward_2.png"), + Loader.getSprite("sprites/belt/forward_3.png"), + Loader.getSprite("sprites/belt/forward_4.png"), + Loader.getSprite("sprites/belt/forward_5.png"), + ]; + } + + update() { + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; + const aceptorComp = entity.components.ItemAcceptor; + + // Process item consumption animations to avoid items popping from the belts + for (let animIndex = 0; animIndex < aceptorComp.itemConsumptionAnimations.length; ++animIndex) { + const anim = aceptorComp.itemConsumptionAnimations[animIndex]; + anim.animProgress += + globalConfig.physicsDeltaSeconds * this.root.hubGoals.getBeltBaseSpeed() * 2; + if (anim.animProgress > 1) { + aceptorComp.itemConsumptionAnimations.splice(animIndex, 1); + animIndex -= 1; + } + } + } + } + + draw(parameters) { + this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this)); + } + + drawUnderlays(parameters) { + this.forEachMatchingEntityOnScreen(parameters, this.drawEntityUnderlays.bind(this)); + } + + /** + * @param {DrawParameters} parameters + * @param {Entity} entity + */ + drawEntity(parameters, entity) { + const staticComp = entity.components.StaticMapEntity; + const acceptorComp = entity.components.ItemAcceptor; + + for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) { + const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[ + animIndex + ]; + + const slotData = acceptorComp.slots[slotIndex]; + const slotWorldPos = staticComp.applyRotationToVector(slotData.pos).add(staticComp.origin); + + const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)]; + const finalTile = slotWorldPos.subScalars( + fadeOutDirection.x * (animProgress / 2 - 0.5), + fadeOutDirection.y * (animProgress / 2 - 0.5) + ); + item.draw( + (finalTile.x + 0.5) * globalConfig.tileSize, + (finalTile.y + 0.5) * globalConfig.tileSize, + parameters + ); + } + } + + /** + * @param {DrawParameters} parameters + * @param {Entity} entity + */ + drawEntityUnderlays(parameters, entity) { + const staticComp = entity.components.StaticMapEntity; + const acceptorComp = entity.components.ItemAcceptor; + + const underlays = acceptorComp.beltUnderlays; + for (let i = 0; i < underlays.length; ++i) { + const { pos, direction } = underlays[i]; + + const transformedPos = staticComp.localTileToWorld(pos); + const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)]; + + // SYNC with systems/belt.js:drawSingleEntity! + const animationIndex = Math.floor( + (this.root.time.now() * + this.root.hubGoals.getBeltBaseSpeed() * + this.underlayBeltSprites.length * + 126) / + 42 + ); + + drawRotatedSprite({ + parameters, + sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length], + x: (transformedPos.x + 0.5) * globalConfig.tileSize, + y: (transformedPos.y + 0.5) * globalConfig.tileSize, + angle: Math_radians(angle), + size: globalConfig.tileSize, + }); + } + } +} diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index cc5cb82d..dec116d7 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -72,14 +72,12 @@ export class ItemEjectorSystem extends GameSystemWithFilter { continue; } - if ( - this.tryPassOverItem( - ejectingItem, - targetEntity, + if (this.tryPassOverItem(ejectingItem, targetEntity, matchingSlot.index)) { + targetAcceptorComp.onItemAccepted( matchingSlot.index, - matchingSlot.acceptedDirection - ) - ) { + matchingSlot.acceptedDirection, + ejectingItem + ); ejectorSlot.item = null; continue; } @@ -92,9 +90,8 @@ export class ItemEjectorSystem extends GameSystemWithFilter { * @param {BaseItem} item * @param {Entity} receiver * @param {number} slotIndex - * @param {string} localDirection */ - tryPassOverItem(item, receiver, slotIndex, localDirection) { + tryPassOverItem(item, receiver, slotIndex) { // Try figuring out how what to do with the item // TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell. // Also its just a few cases (hope it stays like this .. :x). @@ -102,8 +99,8 @@ export class ItemEjectorSystem extends GameSystemWithFilter { const beltComp = receiver.components.Belt; if (beltComp) { // Ayy, its a belt! - if (beltComp.canAcceptNewItem(localDirection)) { - beltComp.takeNewItem(item, localDirection); + if (beltComp.canAcceptNewItem()) { + beltComp.takeNewItem(item); return true; } } @@ -111,7 +108,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter { const itemProcessorComp = receiver.components.ItemProcessor; if (itemProcessorComp) { // Its an item processor .. - if (itemProcessorComp.tryTakeItem(item, slotIndex, localDirection)) { + if (itemProcessorComp.tryTakeItem(item, slotIndex)) { return true; } } diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index 5a8d01d8..d8757294 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -1,37 +1,16 @@ +import { Math_max } from "../../core/builtins"; import { globalConfig } from "../../core/config"; -import { DrawParameters } from "../../core/draw_parameters"; -import { Loader } from "../../core/loader"; +import { BaseItem } from "../base_item"; +import { enumColorMixingResults } from "../colors"; +import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; -import { ItemProcessorComponent, enumItemProcessorTypes } from "../components/item_processor"; -import { Math_max, Math_radians } from "../../core/builtins"; -import { BaseItem } from "../base_item"; -import { ShapeItem } from "../items/shape_item"; -import { enumDirectionToVector, enumDirection, enumDirectionToAngle } from "../../core/vector"; import { ColorItem } from "../items/color_item"; -import { enumColorMixingResults } from "../colors"; -import { drawRotatedSprite } from "../../core/draw_utils"; +import { ShapeItem } from "../items/shape_item"; export class ItemProcessorSystem extends GameSystemWithFilter { constructor(root) { super(root, [ItemProcessorComponent]); - - this.underlayBeltSprites = [ - Loader.getSprite("sprites/belt/forward_0.png"), - Loader.getSprite("sprites/belt/forward_1.png"), - Loader.getSprite("sprites/belt/forward_2.png"), - Loader.getSprite("sprites/belt/forward_3.png"), - Loader.getSprite("sprites/belt/forward_4.png"), - Loader.getSprite("sprites/belt/forward_5.png"), - ]; - } - - draw(parameters) { - this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this)); - } - - drawUnderlays(parameters) { - this.forEachMatchingEntityOnScreen(parameters, this.drawEntityUnderlays.bind(this)); } update() { @@ -47,17 +26,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter { processorComp.secondsUntilEject - globalConfig.physicsDeltaSeconds ); - // Also, process item consumption animations to avoid items popping from the belts - for (let animIndex = 0; animIndex < processorComp.itemConsumptionAnimations.length; ++animIndex) { - const anim = processorComp.itemConsumptionAnimations[animIndex]; - anim.animProgress += - globalConfig.physicsDeltaSeconds * this.root.hubGoals.getBeltBaseSpeed() * 2; - if (anim.animProgress > 1) { - processorComp.itemConsumptionAnimations.splice(animIndex, 1); - animIndex -= 1; - } - } - // Check if we have any finished items we can eject if ( processorComp.secondsUntilEject === 0 && // it was processed in time @@ -363,68 +331,4 @@ export class ItemProcessorSystem extends GameSystemWithFilter { processorComp.itemsToEject = outItems; } - - /** - * @param {DrawParameters} parameters - * @param {Entity} entity - */ - drawEntity(parameters, entity) { - const staticComp = entity.components.StaticMapEntity; - const processorComp = entity.components.ItemProcessor; - const acceptorComp = entity.components.ItemAcceptor; - - for (let animIndex = 0; animIndex < processorComp.itemConsumptionAnimations.length; ++animIndex) { - const { item, slotIndex, animProgress, direction } = processorComp.itemConsumptionAnimations[ - animIndex - ]; - - const slotData = acceptorComp.slots[slotIndex]; - const slotWorldPos = staticComp.applyRotationToVector(slotData.pos).add(staticComp.origin); - - const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)]; - const finalTile = slotWorldPos.subScalars( - fadeOutDirection.x * (animProgress / 2 - 0.5), - fadeOutDirection.y * (animProgress / 2 - 0.5) - ); - item.draw( - (finalTile.x + 0.5) * globalConfig.tileSize, - (finalTile.y + 0.5) * globalConfig.tileSize, - parameters - ); - } - } - /** - * @param {DrawParameters} parameters - * @param {Entity} entity - */ - drawEntityUnderlays(parameters, entity) { - const staticComp = entity.components.StaticMapEntity; - const processorComp = entity.components.ItemProcessor; - - const underlays = processorComp.beltUnderlays; - for (let i = 0; i < underlays.length; ++i) { - const { pos, direction } = underlays[i]; - - const transformedPos = staticComp.localTileToWorld(pos); - const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)]; - - // SYNC with systems/belt.js:drawSingleEntity! - const animationIndex = Math.floor( - (this.root.time.now() * - this.root.hubGoals.getBeltBaseSpeed() * - this.underlayBeltSprites.length * - 126) / - 42 - ); - - drawRotatedSprite({ - parameters, - sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length], - x: (transformedPos.x + 0.5) * globalConfig.tileSize, - y: (transformedPos.y + 0.5) * globalConfig.tileSize, - angle: Math_radians(angle), - size: globalConfig.tileSize, - }); - } - } } diff --git a/src/js/game/systems/static_map_entity.js b/src/js/game/systems/static_map_entity.js index 751315e6..d50b134d 100644 --- a/src/js/game/systems/static_map_entity.js +++ b/src/js/game/systems/static_map_entity.js @@ -26,7 +26,6 @@ export class StaticMapEntitySystem extends GameSystem { return; } - const drawEntitiesOutside = parameters.zoomLevel < globalConfig.mapChunkPrerenderMinZoom; const drawOutlinesOnly = parameters.zoomLevel < globalConfig.mapChunkOverviewMinZoom; const contents = chunk.contents; @@ -54,17 +53,8 @@ export class StaticMapEntitySystem extends GameSystem { } else { const spriteKey = staticComp.spriteKey; if (spriteKey) { - // Check if origin is contained to avoid drawing entities multiple times - if ( - drawEntitiesOutside || - (staticComp.origin.x >= chunk.tileX && - staticComp.origin.x < chunk.tileX + globalConfig.mapChunkSize && - staticComp.origin.y >= chunk.tileY && - staticComp.origin.y < chunk.tileY + globalConfig.mapChunkSize) - ) { - const sprite = Loader.getSprite(spriteKey); - staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2, false); - } + const sprite = Loader.getSprite(spriteKey); + staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2, false); } } } diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts index 71e2db5c..7e84d86a 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -199,10 +199,10 @@ declare class TypedTrackedState { declare const STOP_PROPAGATION = "stop_propagation"; declare interface TypedSignal> { - add(receiver: (...args: T) => typeof STOP_PROPAGATION | void, scope?: object); - remove(receiver: (...args: T) => typeof STOP_PROPAGATION | void); + add(receiver: (...args: T) => /* STOP_PROPAGATION */ string | void, scope?: object); + remove(receiver: (...args: T) => /* STOP_PROPAGATION */ string | void); - dispatch(...args: T): typeof STOP_PROPAGATION | void; + dispatch(...args: T): /* STOP_PROPAGATION */ string | void; removeAll(); }