diff --git a/resources/lang/en.json b/resources/lang/en.json index a2c7324eb..cd881bee7 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -749,6 +749,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.", + "retaliate_attack": "Retaliate", + "retaliate_attack_desc": "Send a retaliation attack to blunt/negate the force of the most recent active attacker. Only available when you are being attacked.", "ally_keybinds": "Ally Keybinds", "request_alliance": "Request Alliance", "request_alliance_desc": "Send an alliance request to the player whose tile is under your cursor.", diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index bbcc5c9f8..51f29e6fd 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -13,7 +13,12 @@ import { import { createPartialGameRecord, findClosestBy, replacer } from "../core/Util"; import { ServerConfig } from "../core/configuration/Config"; import { getGameLogicConfig } from "../core/configuration/ConfigLoader"; -import { BuildableUnit, Structures, UnitType } from "../core/game/Game"; +import { + BuildableUnit, + PlayerType, + Structures, + UnitType, +} from "../core/game/Game"; import { TileRef } from "../core/game/GameMap"; import { GameMapLoader } from "../core/game/GameMapLoader"; import { @@ -34,6 +39,7 @@ import { DoBreakAllianceEvent, DoGroundAttackEvent, DoRequestAllianceEvent, + DoRetaliateAttackEvent, InputHandler, MouseMoveEvent, MouseUpEvent, @@ -391,6 +397,10 @@ export class ClientGameRunner { DoGroundAttackEvent, this.doGroundAttackUnderCursor.bind(this), ); + this.eventBus.on( + DoRetaliateAttackEvent, + this.doRetaliateAttackMostRecent.bind(this), + ); this.eventBus.on( DoRequestAllianceEvent, this.doRequestAllianceUnderCursor.bind(this), @@ -783,6 +793,41 @@ export class ClientGameRunner { }); } + private doRetaliateAttackMostRecent(): void { + if (!this.isActive || this.gameView.inSpawnPhase()) { + return; + } + + if (this.myPlayer === null) { + if (!this.clientID) return; + const myPlayer = this.gameView.playerByClientID(this.clientID); + if (myPlayer === null) return; + this.myPlayer = myPlayer; + } + + const incomingAttacks = this.myPlayer.incomingAttacks().filter((a) => { + const t = ( + this.gameView.playerBySmallID(a.attackerID) as PlayerView + ).type(); + return t !== PlayerType.Bot; + }); + + if (incomingAttacks.length === 0) return; + + const mostRecentAttack = incomingAttacks[incomingAttacks.length - 1]; + + const attacker = this.gameView.playerBySmallID( + mostRecentAttack.attackerID, + ) as PlayerView; + if (!attacker) return; + + const counterTroops = Math.min( + mostRecentAttack.troops, + this.renderer.uiState.attackRatio * this.myPlayer.troops(), + ); + this.eventBus.emit(new SendAttackIntentEvent(attacker.id(), counterTroops)); + } + private doRequestAllianceUnderCursor(): void { const tile = this.getTileUnderCursor(); if (tile === null) return; diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index c5778968d..d2d2de2a2 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -153,6 +153,8 @@ export class DoBoatAttackEvent implements GameEvent {} export class DoGroundAttackEvent implements GameEvent {} +export class DoRetaliateAttackEvent implements GameEvent {} + export class DoRequestAllianceEvent implements GameEvent {} export class DoBreakAllianceEvent implements GameEvent {} @@ -496,6 +498,11 @@ export class InputHandler { this.eventBus.emit(new DoGroundAttackEvent()); } + if (this.keybindMatchesEvent(e, this.keybinds.retaliateAttack)) { + e.preventDefault(); + this.eventBus.emit(new DoRetaliateAttackEvent()); + } + if (this.keybindMatchesEvent(e, this.keybinds.attackRatioDown)) { e.preventDefault(); const increment = this.userSettings.attackRatioIncrement(); diff --git a/src/client/UserSettingModal.ts b/src/client/UserSettingModal.ts index 1d599ad0d..bbf31cca9 100644 --- a/src/client/UserSettingModal.ts +++ b/src/client/UserSettingModal.ts @@ -647,6 +647,16 @@ export class UserSettingModal extends BaseModal { @change=${this.handleKeybindChange} > + + { attackRatioUp: "KeyY", boatAttack: "KeyB", groundAttack: "KeyG", + retaliateAttack: "Shift+KeyR", requestAlliance: "KeyK", breakAlliance: "KeyL", swapDirection: "KeyU",