diff --git a/resources/lang/en.json b/resources/lang/en.json index f2c01b89b..4b17ff124 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -497,6 +497,8 @@ "boat_attack_desc": "Send a boat attack to the tile under your cursor.", "ground_attack": "Ground Attack", "ground_attack_desc": "Send a ground attack to the tile under your cursor.", + "local_attack": "Localized Attack (Hold)", + "local_attack_desc": "Hold to start attacks from the nearest border tile to the target.", "zoom_controls": "Zoom Controls", "zoom_out": "Zoom Out", "zoom_out_desc": "Zoom out the map", diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 946c5851f..62e16b655 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -688,12 +688,15 @@ export class ClientGameRunner { if (!this.myPlayer) return; const targetId = this.gameView.owner(tile).id(); - const sourceTile = await resolveAttackSourceTile( - this.gameView, - this.myPlayer, - targetId, - tile, - ); + const useLocalAttack = this.renderer.uiState.localAttackHeld; + const sourceTile = useLocalAttack + ? await resolveAttackSourceTile( + this.gameView, + this.myPlayer, + targetId, + tile, + ) + : null; this.eventBus.emit( new SendAttackIntentEvent( targetId, diff --git a/src/client/HelpModal.ts b/src/client/HelpModal.ts index edcb5765c..8d4fbff8f 100644 --- a/src/client/HelpModal.ts +++ b/src/client/HelpModal.ts @@ -49,6 +49,7 @@ export class HelpModal extends BaseModal { zoomIn: "KeyE", attackRatioDown: "KeyT", attackRatioUp: "KeyY", + localAttack: "KeyL", shiftKey: "ShiftLeft", modifierKey: isMac ? "MetaLeft" : "ControlLeft", altKey: "AltLeft", diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index 3bd9378b0..b8f76e978 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -212,6 +212,7 @@ export class InputHandler { attackRatioUp: "KeyY", boatAttack: "KeyB", groundAttack: "KeyG", + localAttack: "KeyL", swapDirection: "KeyU", modifierKey: isMac ? "MetaLeft" : "ControlLeft", altKey: "AltLeft", @@ -338,6 +339,7 @@ export class InputHandler { "Equal", this.keybinds.attackRatioDown, this.keybinds.attackRatioUp, + this.keybinds.localAttack, this.keybinds.centerCamera, "ControlLeft", "ControlRight", @@ -347,6 +349,10 @@ export class InputHandler { ) { this.activeKeys.add(e.code); } + + if (e.code === this.keybinds.localAttack) { + this.uiState.localAttackHeld = true; + } }); window.addEventListener("keyup", (e) => { const isTextInput = this.isTextInputTarget(e.target); @@ -385,6 +391,10 @@ export class InputHandler { this.eventBus.emit(new AttackRatioEvent(10)); } + if (e.code === this.keybinds.localAttack) { + this.uiState.localAttackHeld = false; + } + if (e.code === this.keybinds.centerCamera) { e.preventDefault(); this.eventBus.emit(new CenterCameraEvent()); @@ -631,6 +641,7 @@ export class InputHandler { clearInterval(this.moveInterval); } this.activeKeys.clear(); + this.uiState.localAttackHeld = false; } isModifierKeyPressed(event: PointerEvent): boolean { diff --git a/src/client/KeybindsModal.ts b/src/client/KeybindsModal.ts index 2eadfd4c4..c631f5d38 100644 --- a/src/client/KeybindsModal.ts +++ b/src/client/KeybindsModal.ts @@ -21,6 +21,7 @@ const DefaultKeybinds: Record = { attackRatioUp: "KeyY", boatAttack: "KeyB", groundAttack: "KeyG", + localAttack: "KeyL", zoomOut: "KeyQ", zoomIn: "KeyE", centerCamera: "KeyC", @@ -432,6 +433,16 @@ export class KeybindsModal extends BaseModal { @change=${this.handleKeybindChange} > + +

diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 41bfe13b3..06f1b9202 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -56,6 +56,7 @@ export function createRenderer( attackRatio: 20, ghostStructure: null, rocketDirectionUp: true, + localAttackHeld: false, } as UIState; //hide when the game renders diff --git a/src/client/graphics/UIState.ts b/src/client/graphics/UIState.ts index f47acef88..6172cccb5 100644 --- a/src/client/graphics/UIState.ts +++ b/src/client/graphics/UIState.ts @@ -4,4 +4,5 @@ export interface UIState { attackRatio: number; ghostStructure: UnitType | null; rocketDirectionUp: boolean; + localAttackHeld: boolean; } diff --git a/src/client/graphics/layers/RadialMenuElements.ts b/src/client/graphics/layers/RadialMenuElements.ts index 88e11aace..82ff52079 100644 --- a/src/client/graphics/layers/RadialMenuElements.ts +++ b/src/client/graphics/layers/RadialMenuElements.ts @@ -601,12 +601,15 @@ export const centerButtonElement: CenterButtonElement = { } } else { const targetId = params.selected?.id() ?? null; - const sourceTile = await resolveAttackSourceTile( - params.game, - params.myPlayer, - targetId, - params.tile, - ); + const useLocalAttack = params.uiState?.localAttackHeld ?? false; + const sourceTile = useLocalAttack + ? await resolveAttackSourceTile( + params.game, + params.myPlayer, + targetId, + params.tile, + ) + : null; params.playerActionHandler.handleAttack( params.myPlayer, targetId,