diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index fdc5743a..a541dc2e 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -74,6 +74,7 @@ export class GameHUD { shapeUnpinRequested: /** @type {TypedSignal<[string]>} */ (new Signal()), notification: /** @type {TypedSignal<[string, enumNotificationType]>} */ (new Signal()), buildingsSelectedForCopy: /** @type {TypedSignal<[Array]>} */ (new Signal()), + pasteBlueprintRequested: new Signal(), }; if (!IS_MOBILE) { diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js index 69523bd0..0ffff9b4 100644 --- a/src/js/game/hud/parts/blueprint_placer.js +++ b/src/js/game/hud/parts/blueprint_placer.js @@ -29,6 +29,8 @@ export class HUDBlueprintPlacer extends BaseHUDPart { /** @type {TypedTrackedState} */ this.currentBlueprint = new TrackedState(this.onBlueprintChanged, this); + /** @type {Blueprint?} */ + this.lastBlueprintUsed = null; const keyActionMapper = this.root.keyMapper; keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this); @@ -36,9 +38,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart { .getBinding(KEYMAPPINGS.placement.abortBuildingPlacement) .add(this.abortPlacement, this); keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this); - keyActionMapper - .getBinding(KEYMAPPINGS.placement.abortBuildingPlacement) - .add(this.abortPlacement, this); + keyActionMapper.getBinding(KEYMAPPINGS.massSelect.pasteLastBlueprint).add(this.pasteBlueprint, this); this.root.camera.downPreHandler.add(this.onMouseDown, this); this.root.camera.movePreHandler.add(this.onMouseMove, this); @@ -73,6 +73,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart { */ onBlueprintChanged(blueprint) { if (blueprint) { + this.lastBlueprintUsed = blueprint; this.costDisplayText.innerText = "" + blueprint.getCost(); } } @@ -144,6 +145,15 @@ export class HUDBlueprintPlacer extends BaseHUDPart { } } + pasteBlueprint() { + if (this.lastBlueprintUsed !== null) { + this.root.hud.signals.pasteBlueprintRequested.dispatch(); + this.currentBlueprint.set(this.lastBlueprintUsed); + } else { + this.root.soundProxy.playUiError(); + } + } + /** * * @param {DrawParameters} parameters diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index c1179f33..6da065b2 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -40,6 +40,7 @@ export class HUDBuildingPlacer extends BaseHUDPart { keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this); this.root.hud.signals.buildingsSelectedForCopy.add(this.abortPlacement, this); + this.root.hud.signals.pasteBlueprintRequested.add(this.abortPlacement, this); this.domAttach = new DynamicDomAttach(this.root, this.element, {}); diff --git a/src/js/game/hud/parts/keybinding_overlay.js b/src/js/game/hud/parts/keybinding_overlay.js index 25774305..0c3f3342 100644 --- a/src/js/game/hud/parts/keybinding_overlay.js +++ b/src/js/game/hud/parts/keybinding_overlay.js @@ -34,15 +34,15 @@ export class HUDKeybindingOverlay extends BaseHUDPart { ${getKeycode(KEYMAPPINGS.navigation.mapMoveDown)} ${getKeycode(KEYMAPPINGS.navigation.mapMoveRight)} - - - - + + + +
- +
@@ -56,13 +56,18 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
- - + +
+ ${getKeycode(KEYMAPPINGS.massSelect.pasteLastBlueprint)} + +
+ +
- +
${getKeycode(KEYMAPPINGS.placement.abortBuildingPlacement)} diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 5b5d53c3..b89108c0 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -22,6 +22,9 @@ export class HUDMassSelector extends BaseHUDPart { .getBinding(KEYMAPPINGS.massSelect.confirmMassDelete) .getKeyCodeString(); const abortKeybinding = this.root.keyMapper.getBinding(KEYMAPPINGS.general.back).getKeyCodeString(); + const cutKeybinding = this.root.keyMapper + .getBinding(KEYMAPPINGS.massSelect.massSelectCut) + .getKeyCodeString(); const copyKeybinding = this.root.keyMapper .getBinding(KEYMAPPINGS.massSelect.massSelectCopy) .getKeyCodeString(); @@ -32,6 +35,7 @@ export class HUDMassSelector extends BaseHUDPart { [], T.ingame.massSelect.infoText .replace("", `${removalKeybinding}`) + .replace("", `${cutKeybinding}`) .replace("", `${copyKeybinding}`) .replace("", `${abortKeybinding}`) ); @@ -54,6 +58,7 @@ export class HUDMassSelector extends BaseHUDPart { this.root.keyMapper .getBinding(KEYMAPPINGS.massSelect.confirmMassDelete) .add(this.confirmDelete, this); + this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCut).add(this.confirmCut, this); this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCopy).add(this.startCopy, this); this.domAttach = new DynamicDomAttach(this.root, this.element); @@ -123,6 +128,49 @@ export class HUDMassSelector extends BaseHUDPart { } } + confirmCut() { + if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) { + this.root.hud.parts.dialogs.showInfo( + T.dialogs.blueprintsNotUnlocked.title, + T.dialogs.blueprintsNotUnlocked.desc + ); + } else if (this.selectedUids.size > 100) { + const { ok } = this.root.hud.parts.dialogs.showWarning( + T.dialogs.massCutConfirm.title, + T.dialogs.massCutConfirm.desc.replace( + "", + "" + formatBigNumberFull(this.selectedUids.size) + ), + ["cancel:good", "ok:bad"] + ); + ok.add(() => this.doCut()); + } else { + this.doCut(); + } + } + + doCut() { + if (this.selectedUids.size > 0) { + const entityUids = Array.from(this.selectedUids); + + // copy code relies on entities still existing, so must copy before deleting. + this.root.hud.signals.buildingsSelectedForCopy.dispatch(entityUids); + + 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 cut, could not remove building"); + this.selectedUids.delete(uid); + } + } + + this.root.soundProxy.playUiClick(); + } else { + this.root.soundProxy.playUiError(); + } + } + /** * mouse down pre handler * @param {Vector} pos diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 9de75731..58077f01 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -65,7 +65,9 @@ export const KEYMAPPINGS = { massSelectStart: { keyCode: 17 }, // CTRL massSelectSelectMultiple: { keyCode: 16 }, // SHIFT massSelectCopy: { keyCode: key("C") }, + massSelectCut: { keyCode: key("X") }, confirmMassDelete: { keyCode: 46 }, // DEL + pasteLastBlueprint: { keyCode: key("V") }, }, placementModifiers: { diff --git a/translations/base-en.yaml b/translations/base-en.yaml index d10aea1d..4adee0bd 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -234,6 +234,11 @@ dialogs: desc: >- You are deleting a lot of buildings ( to be exact)! Are you sure you want to do this? + massCutConfirm: + title: Confirm cut + desc: >- + You are cutting a lot of buildings ( to be exact)! Are you sure you want to do this? + blueprintsNotUnlocked: title: Not unlocked yet desc: >- @@ -270,6 +275,7 @@ ingame: placeBuilding: Place building createMarker: Create Marker delete: Destroy + pasteLastBlueprint: Paste last blueprint # Everything related to placing buildings (I.e. as soon as you selected a building # from the toolbar) @@ -308,7 +314,7 @@ ingame: # Mass select information, this is when you hold CTRL and then drag with your mouse # to select multiple buildings massSelect: - infoText: Press to copy, to remove and to cancel. + infoText: Press to cut, to copy, to remove and to cancel. # The "Upgrades" window shop: @@ -697,11 +703,13 @@ keybindings: Modifier: Rotate CCW instead cycleBuildingVariants: Cycle Variants confirmMassDelete: Confirm Mass Delete + pasteLastBlueprint: Paste last blueprint cycleBuildings: Cycle Buildings massSelectStart: Hold and drag to start massSelectSelectMultiple: Select multiple areas massSelectCopy: Copy area + massSelectCut: Cut area placementDisableAutoOrientation: Disable automatic orientation placeMultiple: Stay in placement mode