From bd89c2cc9ead04111fda10648d20ef1905010e4d Mon Sep 17 00:00:00 2001 From: Tobias Springer Date: Sun, 10 May 2020 18:24:50 +0200 Subject: [PATCH] Mass deletion support --- res_built/atlas/atlas0_10.json | 10 +- res_built/atlas/atlas0_100.json | 10 +- res_built/atlas/atlas0_25.json | 10 +- res_built/atlas/atlas0_50.json | 10 +- res_built/atlas/atlas0_75.json | 10 +- res_raw/sprites/misc/deletion_marker.png | Bin 0 -> 4745 bytes src/css/ingame_hud/keybindings_overlay.scss | 4 +- src/css/ingame_hud/mass_selector.scss | 20 ++ src/css/main.scss | 3 +- src/js/game/hud/hud.js | 5 +- src/js/game/hud/parts/keybinding_overlay.js | 12 +- src/js/game/hud/parts/mass_selector.js | 195 +++++++++++++++++++- src/js/game/key_action_mapper.js | 2 + 13 files changed, 276 insertions(+), 15 deletions(-) create mode 100644 res_raw/sprites/misc/deletion_marker.png create mode 100644 src/css/ingame_hud/mass_selector.scss 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 0000000000000000000000000000000000000000..bb3a2b753c0d4326e577c8d4e9bf3536be21113a GIT binary patch literal 4745 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
- + + 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: {