Keybind Ground Attack (#1258)

## Description:

- Implement ground attack logic in ClientGameRunner
- Move cursor location logic from doBoatAttackUnderCursor to
getTileUnderCursor for reusability.
- New keybind G for Ground Attack
- Add translations for ground attack labels in English

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory*
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [X] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

<img width="902" alt="image"
src="https://github.com/user-attachments/assets/54bb3178-af2e-4715-bfa2-82b7fe0e8e85"
/>

*The getTileUnderCursor() and doGroundAttackUnderCursor() methods are
private with many dependencies. I did not do isolated unit testing and
thus zero tests where added.

## Please put your Discord username so you can be contacted if a bug or
regression is found:

eng.la

---------

Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
Daniel
2025-06-25 17:21:54 +02:00
committed by GitHub
parent a9e1dc853e
commit b181c6c15d
4 changed files with 74 additions and 15 deletions
+2
View File
@@ -269,6 +269,8 @@
"attack_keybinds": "Attack Keybinds",
"boat_attack": "Boat Attack",
"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.",
"zoom_controls": "Zoom Controls",
"zoom_out": "Zoom Out",
"zoom_out_desc": "Zoom out the map",
+55 -15
View File
@@ -27,6 +27,7 @@ import { UserSettings } from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
import {
DoBoatAttackEvent,
DoGroundAttackEvent,
InputHandler,
MouseMoveEvent,
MouseUpEvent,
@@ -246,9 +247,16 @@ export class ClientGameRunner {
1000,
);
}, 20000);
this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e));
this.eventBus.on(MouseMoveEvent, (e) => this.onMouseMove(e));
this.eventBus.on(DoBoatAttackEvent, (e) => this.doBoatAttackUnderCursor());
this.eventBus.on(MouseUpEvent, this.inputEvent.bind(this));
this.eventBus.on(MouseMoveEvent, this.onMouseMove.bind(this));
this.eventBus.on(
DoBoatAttackEvent,
this.doBoatAttackUnderCursor.bind(this),
);
this.eventBus.on(
DoGroundAttackEvent,
this.doGroundAttackUnderCursor.bind(this),
);
this.renderer.initialize();
this.input.initialize();
@@ -418,20 +426,10 @@ export class ClientGameRunner {
}
private doBoatAttackUnderCursor(): void {
if (!this.isActive || !this.lastMousePosition) {
const tile = this.getTileUnderCursor();
if (tile === null) {
return;
}
if (this.gameView.inSpawnPhase()) {
return;
}
const cell = this.renderer.transformHandler.screenToWorldCoordinates(
this.lastMousePosition.x,
this.lastMousePosition.y,
);
if (!this.gameView.isValidCoord(cell.x, cell.y)) {
return;
}
const tile = this.gameView.ref(cell.x, cell.y);
if (this.myPlayer === null) {
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
@@ -446,6 +444,48 @@ export class ClientGameRunner {
});
}
private doGroundAttackUnderCursor(): void {
const tile = this.getTileUnderCursor();
if (tile === null) {
return;
}
if (this.myPlayer === null) {
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
if (myPlayer === null) return;
this.myPlayer = myPlayer;
}
this.myPlayer.actions(tile).then((actions) => {
if (this.myPlayer === null) return;
if (actions.canAttack) {
this.eventBus.emit(
new SendAttackIntentEvent(
this.gameView.owner(tile).id(),
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
),
);
}
});
}
private getTileUnderCursor(): TileRef | null {
if (!this.isActive || !this.lastMousePosition) {
return null;
}
if (this.gameView.inSpawnPhase()) {
return null;
}
const cell = this.renderer.transformHandler.screenToWorldCoordinates(
this.lastMousePosition.x,
this.lastMousePosition.y,
);
if (!this.gameView.isValidCoord(cell.x, cell.y)) {
return null;
}
return this.gameView.ref(cell.x, cell.y);
}
private canBoatAttack(actions: PlayerActions, tile: TileRef): boolean {
const bu = actions.buildableUnits.find(
(bu) => bu.type === UnitType.TransportShip,
+8
View File
@@ -79,6 +79,8 @@ export class ShowEmojiMenuEvent implements GameEvent {
export class DoBoatAttackEvent implements GameEvent {}
export class DoGroundAttackEvent implements GameEvent {}
export class AttackRatioEvent implements GameEvent {
constructor(public readonly attackRatio: number) {}
}
@@ -133,6 +135,7 @@ export class InputHandler {
attackRatioDown: "Digit1",
attackRatioUp: "Digit2",
boatAttack: "KeyB",
groundAttack: "KeyG",
modifierKey: "ControlLeft",
altKey: "AltLeft",
...JSON.parse(localStorage.getItem("settings.keybinds") ?? "{}"),
@@ -265,6 +268,11 @@ export class InputHandler {
this.eventBus.emit(new DoBoatAttackEvent());
}
if (e.code === this.keybinds.groundAttack) {
e.preventDefault();
this.eventBus.emit(new DoGroundAttackEvent());
}
if (e.code === this.keybinds.attackRatioDown) {
e.preventDefault();
this.eventBus.emit(new AttackRatioEvent(-10));
+9
View File
@@ -416,6 +416,15 @@ export class UserSettingModal extends LitElement {
@change=${this.handleKeybindChange}
></setting-keybind>
<setting-keybind
action="groundAttack"
label=${translateText("user_setting.ground_attack")}
description=${translateText("user_setting.ground_attack_desc")}
defaultKey="KeyG"
.value=${this.keybinds["groundAttack"] ?? ""}
@change=${this.handleKeybindChange}
></setting-keybind>
<div class="text-center text-white text-base font-semibold mt-5 mb-2">
${translateText("user_setting.zoom_controls")}
</div>