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",