diff --git a/res_built/atlas/atlas0_10.json b/res_built/atlas/atlas0_10.json index 93e62d33..676d8bb1 100644 --- a/res_built/atlas/atlas0_10.json +++ b/res_built/atlas/atlas0_10.json @@ -400,6 +400,14 @@ "spriteSourceSize": {"x":0,"y":0,"w":3,"h":3}, "sourceSize": {"w":3,"h":3} }, +"sprites/misc/deletion_marker.png": +{ + "frame": {"x":107,"y":95,"w":10,"h":10}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":10,"h":10}, + "sourceSize": {"w":10,"h":10} +}, "sprites/misc/slot_bad_arrow.png": { "frame": {"x":107,"y":95,"w":10,"h":10}, @@ -423,6 +431,6 @@ "format": "RGBA8888", "size": {"w":121,"h":240}, "scale": "0.1", - "smartupdate": "$TexturePacker:SmartUpdate:80df730947c1d83df455c7645ebd4dbd:29979cacc85be7fc1c890b96110d9771:f159918d23e5952766c6d23ab52278c6$" + "smartupdate": "$TexturePacker:SmartUpdate:e1aa43844bd41a62665cbb0c503ebb65:4482f83c3403490091eed64b07ce6591:f159918d23e5952766c6d23ab52278c6$" } } diff --git a/res_built/atlas/atlas0_100.json b/res_built/atlas/atlas0_100.json index 398253b9..d15c9c64 100644 --- a/res_built/atlas/atlas0_100.json +++ b/res_built/atlas/atlas0_100.json @@ -400,6 +400,14 @@ "spriteSourceSize": {"x":4,"y":4,"w":28,"h":28}, "sourceSize": {"w":32,"h":32} }, +"sprites/misc/deletion_marker.png": +{ + "frame": {"x":237,"y":1710,"w":82,"h":82}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":7,"w":82,"h":82}, + "sourceSize": {"w":96,"h":96} +}, "sprites/misc/slot_bad_arrow.png": { "frame": {"x":237,"y":1710,"w":82,"h":82}, @@ -423,6 +431,6 @@ "format": "RGBA8888", "size": {"w":1007,"h":1968}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:80df730947c1d83df455c7645ebd4dbd:29979cacc85be7fc1c890b96110d9771:f159918d23e5952766c6d23ab52278c6$" + "smartupdate": "$TexturePacker:SmartUpdate:e1aa43844bd41a62665cbb0c503ebb65:4482f83c3403490091eed64b07ce6591:f159918d23e5952766c6d23ab52278c6$" } } diff --git a/res_built/atlas/atlas0_25.json b/res_built/atlas/atlas0_25.json index 6ab93d35..68ff1c35 100644 --- a/res_built/atlas/atlas0_25.json +++ b/res_built/atlas/atlas0_25.json @@ -400,6 +400,14 @@ "spriteSourceSize": {"x":0,"y":0,"w":8,"h":8}, "sourceSize": {"w":8,"h":8} }, +"sprites/misc/deletion_marker.png": +{ + "frame": {"x":217,"y":159,"w":22,"h":22}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":1,"w":22,"h":22}, + "sourceSize": {"w":24,"h":24} +}, "sprites/misc/slot_bad_arrow.png": { "frame": {"x":217,"y":159,"w":22,"h":22}, @@ -423,6 +431,6 @@ "format": "RGBA8888", "size": {"w":591,"h":252}, "scale": "0.25", - "smartupdate": "$TexturePacker:SmartUpdate:80df730947c1d83df455c7645ebd4dbd:29979cacc85be7fc1c890b96110d9771:f159918d23e5952766c6d23ab52278c6$" + "smartupdate": "$TexturePacker:SmartUpdate:e1aa43844bd41a62665cbb0c503ebb65:4482f83c3403490091eed64b07ce6591:f159918d23e5952766c6d23ab52278c6$" } } diff --git a/res_built/atlas/atlas0_50.json b/res_built/atlas/atlas0_50.json index 64bf7ac2..737bce47 100644 --- a/res_built/atlas/atlas0_50.json +++ b/res_built/atlas/atlas0_50.json @@ -400,6 +400,14 @@ "spriteSourceSize": {"x":1,"y":1,"w":15,"h":15}, "sourceSize": {"w":16,"h":16} }, +"sprites/misc/deletion_marker.png": +{ + "frame": {"x":456,"y":460,"w":42,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":3,"w":42,"h":42}, + "sourceSize": {"w":48,"h":48} +}, "sprites/misc/slot_bad_arrow.png": { "frame": {"x":456,"y":460,"w":42,"h":42}, @@ -423,6 +431,6 @@ "format": "RGBA8888", "size": {"w":1023,"h":509}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:80df730947c1d83df455c7645ebd4dbd:29979cacc85be7fc1c890b96110d9771:f159918d23e5952766c6d23ab52278c6$" + "smartupdate": "$TexturePacker:SmartUpdate:e1aa43844bd41a62665cbb0c503ebb65:4482f83c3403490091eed64b07ce6591:f159918d23e5952766c6d23ab52278c6$" } } diff --git a/res_built/atlas/atlas0_75.json b/res_built/atlas/atlas0_75.json index 9d8ee63a..6ecb53d1 100644 --- a/res_built/atlas/atlas0_75.json +++ b/res_built/atlas/atlas0_75.json @@ -400,6 +400,14 @@ "spriteSourceSize": {"x":2,"y":2,"w":22,"h":22}, "sourceSize": {"w":24,"h":24} }, +"sprites/misc/deletion_marker.png": +{ + "frame": {"x":1050,"y":79,"w":62,"h":62}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":62,"h":62}, + "sourceSize": {"w":72,"h":72} +}, "sprites/misc/slot_bad_arrow.png": { "frame": {"x":1050,"y":79,"w":62,"h":62}, @@ -423,6 +431,6 @@ "format": "RGBA8888", "size": {"w":1125,"h":999}, "scale": "0.75", - "smartupdate": "$TexturePacker:SmartUpdate:80df730947c1d83df455c7645ebd4dbd:29979cacc85be7fc1c890b96110d9771:f159918d23e5952766c6d23ab52278c6$" + "smartupdate": "$TexturePacker:SmartUpdate:e1aa43844bd41a62665cbb0c503ebb65:4482f83c3403490091eed64b07ce6591:f159918d23e5952766c6d23ab52278c6$" } } diff --git a/res_raw/sprites/misc/deletion_marker.png b/res_raw/sprites/misc/deletion_marker.png new file mode 100644 index 00000000..bb3a2b75 Binary files /dev/null and b/res_raw/sprites/misc/deletion_marker.png differ diff --git a/src/css/ingame_hud/keybindings_overlay.scss b/src/css/ingame_hud/keybindings_overlay.scss index db05aacf..e345955b 100644 --- a/src/css/ingame_hud/keybindings_overlay.scss +++ b/src/css/ingame_hud/keybindings_overlay.scss @@ -64,7 +64,7 @@ } } - .shift .keybinding { + .keybinding.shift { transition: all 0.1s ease-in-out; transition-property: background-color, color, border-color; background: $colorRedBright; @@ -72,7 +72,7 @@ color: #fff; } - &.shiftDown .shift .keybinding { + &.shiftDown .keybinding.shift { border-color: darken($colorRedBright, 40); } } diff --git a/src/css/ingame_hud/mass_selector.scss b/src/css/ingame_hud/mass_selector.scss new file mode 100644 index 00000000..3ac4b763 --- /dev/null +++ b/src/css/ingame_hud/mass_selector.scss @@ -0,0 +1,20 @@ +#ingame_HUD_MassSelector { + position: absolute; + @include S(top, 50px); + left: 50%; + transform: translateX(-50%); + @include S(width, 300px); + background: #f77; + @include S(border-radius, 4px); + @include S(padding, 10px); + @include PlainText; + color: #fff; + + .keybinding { + position: relative; + top: unset; + left: unset; + right: unset; + bottom: unset; + } +} diff --git a/src/css/main.scss b/src/css/main.scss index 6d99f6a5..58c75b45 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -33,11 +33,12 @@ @import "ingame_hud/game_menu"; @import "ingame_hud/blur_overlay"; @import "ingame_hud/dialogs"; +@import "ingame_hud/mass_selector"; // Z-Index $elements: ingame_Canvas, ingame_HUD_building_placer_overlay, ingame_HUD_building_placer, ingame_HUD_buildings_toolbar, ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, ingame_HUD_Shop, - ingame_HUD_BetaOverlay, ingame_HUD_UnlockNotification; + ingame_HUD_BetaOverlay, ingame_HUD_MassSelector, ingame_HUD_UnlockNotification; $zindex: 100; diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index deae4591..3e4102d4 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -13,6 +13,7 @@ import { HUDUnlockNotification } from "./parts/unlock_notification"; import { HUDGameMenu } from "./parts/game_menu"; import { HUDShop } from "./parts/shop"; import { IS_MOBILE } from "../../core/config"; +import { HUDMassSelector } from "./parts/mass_selector"; export class GameHUD { /** @@ -40,6 +41,8 @@ export class GameHUD { gameMenu: new HUDGameMenu(this.root), + massSelector: new HUDMassSelector(this.root), + shop: new HUDShop(this.root), // betaOverlay: new HUDBetaOverlay(this.root), @@ -149,7 +152,7 @@ export class GameHUD { * @param {DrawParameters} parameters */ draw(parameters) { - const partsOrder = ["buildingPlacer"]; + const partsOrder = ["massSelector", "buildingPlacer"]; for (let i = 0; i < partsOrder.length; ++i) { if (this.parts[partsOrder[i]]) { diff --git a/src/js/game/hud/parts/keybinding_overlay.js b/src/js/game/hud/parts/keybinding_overlay.js index a83ef1ce..edf4d88c 100644 --- a/src/js/game/hud/parts/keybinding_overlay.js +++ b/src/js/game/hud/parts/keybinding_overlay.js @@ -44,7 +44,9 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
- + + ALT+ +
@@ -60,13 +62,13 @@ export class HUDKeybindingOverlay extends BaseHUDPart { -
- ⇧ SHIFT +
+ ⇧ SHIFT
-
- ALT +
+ ALT
` diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 658eb8ee..f8b0cb83 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -1,3 +1,196 @@ import { BaseHUDPart } from "../base_hud_part"; +import { Vector } from "../../../core/vector"; +import { STOP_PROPAGATION } from "../../../core/signal"; +import { DrawParameters } from "../../../core/draw_parameters"; +import { Entity } from "../../entity"; +import { Loader } from "../../../core/loader"; +import { globalConfig } from "../../../core/config"; +import { makeDiv } from "../../../core/utils"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; +import { createLogger } from "../../../core/logging"; -export class HUDMassSelector extends BaseHUDPart {} +const logger = createLogger("hud/mass_selector"); + +export class HUDMassSelector extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv( + parent, + "ingame_HUD_MassSelector", + [], + ` + Press DEL to remove selected buildings + and ESCAPE to cancel. + ` + ); + } + + initialize() { + this.deletionMarker = Loader.getSprite("sprites/misc/deletion_marker.png"); + + this.currentSelectionStart = null; + this.currentSelectionEnd = null; + this.entityUidsMarkedForDeletion = new Set(); + + this.root.signals.entityQueuedForDestroy.add(this.onEntityDestroyed, this); + + this.root.camera.downPreHandler.add(this.onMouseDown, this); + this.root.camera.movePreHandler.add(this.onMouseMove, this); + this.root.camera.upPostHandler.add(this.onMouseUp, this); + + this.root.gameState.keyActionMapper.getBinding("back").add(this.onBack, this); + this.root.gameState.keyActionMapper.getBinding("confirm_mass_delete").add(this.confirmDelete, this); + + this.domAttach = new DynamicDomAttach(this.root, this.element); + } + + /** + * Handles the destroy callback and makes sure we clean our list + * @param {Entity} entity + */ + onEntityDestroyed(entity) { + this.entityUidsMarkedForDeletion.delete(entity.uid); + } + + /** + * + */ + onBack() { + // Clear entities on escape + if (this.entityUidsMarkedForDeletion) { + this.entityUidsMarkedForDeletion = new Set(); + return STOP_PROPAGATION; + } + } + + confirmDelete() { + const entityUids = Array.from(this.entityUidsMarkedForDeletion); + for (let i = 0; i < entityUids.length; ++i) { + const uid = entityUids[i]; + const entity = this.root.entityMgr.findByUid(uid); + if (!this.root.logic.tryDeleteBuilding(entity)) { + logger.error("Error in mass delete, could not remove building"); + this.entityUidsMarkedForDeletion.delete(uid); + } + } + } + + /** + * mouse down pre handler + * @param {Vector} pos + */ + onMouseDown(pos) { + if (!this.root.app.inputMgr.altIsDown) { + return; + } + + if (!this.root.app.inputMgr.shiftIsDown) { + // Start new selection + this.entityUidsMarkedForDeletion = new Set(); + } + + this.currentSelectionStart = pos.copy(); + this.currentSelectionEnd = pos.copy(); + return STOP_PROPAGATION; + } + + /** + * mouse move pre handler + * @param {Vector} pos + */ + onMouseMove(pos) { + if (this.currentSelectionStart) { + this.currentSelectionEnd = pos.copy(); + } + } + + onMouseUp() { + if (this.currentSelectionStart) { + const worldStart = this.root.camera.screenToWorld(this.currentSelectionStart); + const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd); + + const tileStart = worldStart.toTileSpace(); + const tileEnd = worldEnd.toTileSpace(); + + const realTileStart = tileStart.min(tileEnd); + const realTileEnd = tileStart.max(tileEnd); + + for (let x = realTileStart.x; x <= realTileEnd.x; ++x) { + for (let y = realTileStart.y; y <= realTileEnd.y; ++y) { + const contents = this.root.map.getTileContentXY(x, y); + if (contents && this.root.logic.canDeleteBuilding(contents)) { + this.entityUidsMarkedForDeletion.add(contents.uid); + } + } + } + + this.currentSelectionStart = null; + this.currentSelectionEnd = null; + } + } + + update() { + this.domAttach.update(this.entityUidsMarkedForDeletion.size > 0); + } + + /** + * + * @param {DrawParameters} parameters + */ + draw(parameters) { + if (this.currentSelectionStart) { + const worldStart = this.root.camera.screenToWorld(this.currentSelectionStart); + const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd); + + const realWorldStart = worldStart.min(worldEnd); + const realWorldEnd = worldStart.max(worldEnd); + + const tileStart = worldStart.toTileSpace(); + const tileEnd = worldEnd.toTileSpace(); + + const realTileStart = tileStart.min(tileEnd); + const realTileEnd = tileStart.max(tileEnd); + + parameters.context.lineWidth = 1; + parameters.context.fillStyle = "rgba(255, 127, 127, 0.2)"; + parameters.context.strokeStyle = "rgba(255, 127, 127, 0.5)"; + parameters.context.beginPath(); + parameters.context.rect( + realWorldStart.x, + realWorldStart.y, + realWorldEnd.x - realWorldStart.x, + realWorldEnd.y - realWorldStart.y + ); + parameters.context.fill(); + parameters.context.stroke(); + + for (let x = realTileStart.x; x <= realTileEnd.x; ++x) { + for (let y = realTileStart.y; y <= realTileEnd.y; ++y) { + const contents = this.root.map.getTileContentXY(x, y); + if (contents && this.root.logic.canDeleteBuilding(contents)) { + const staticComp = contents.components.StaticMapEntity; + const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); + this.deletionMarker.drawCachedCentered( + parameters, + center.x, + center.y, + globalConfig.tileSize * 0.5 + ); + } + } + } + } + + this.entityUidsMarkedForDeletion.forEach(uid => { + const entity = this.root.entityMgr.findByUid(uid); + const staticComp = entity.components.StaticMapEntity; + const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); + + this.deletionMarker.drawCachedCentered( + parameters, + center.x, + center.y, + globalConfig.tileSize * 0.5 + ); + }); + } +} diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index dae0c57e..55adba47 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -29,6 +29,8 @@ export const defaultKeybindings = { menu_open_shop: { keyCode: key("F") }, menu_open_stats: { keyCode: key("G") }, + + confirm_mass_delete: { keyCode: 46 }, // DEL }, toolbar: {