Add Boat hotkey (#1060)

## Description:
Pressing B to fast boat

![Screenshot_2025-06-06-09-32-53-865_com android
chrome](https://github.com/user-attachments/assets/cdd4aefe-0088-4334-bf69-7cf3b32b6db6)

## 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

## Please put your Discord username so you can be contacted if a bug or
regression is found:
dovg
This commit is contained in:
tnhnblgl
2025-06-07 00:11:39 +03:00
committed by GitHub
parent 49b01d8014
commit 4367bacf71
4 changed files with 101 additions and 34 deletions
+77 -34
View File
@@ -12,7 +12,7 @@ import {
import { createGameRecord } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
import { getConfig } from "../core/configuration/ConfigLoader";
import { Cell, UnitType } from "../core/game/Game";
import { Cell, PlayerActions, UnitType } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import {
ErrorUpdate,
@@ -25,7 +25,12 @@ import { GameView, PlayerView } from "../core/game/GameView";
import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader";
import { UserSettings } from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
import { InputHandler, MouseMoveEvent, MouseUpEvent } from "./InputHandler";
import {
DoBoatAttackEvent,
InputHandler,
MouseMoveEvent,
MouseUpEvent,
} from "./InputHandler";
import { endGame, startGame, startTime } from "./LocalPersistantStats";
import { getPersistentID } from "./Main";
import {
@@ -231,6 +236,7 @@ export class ClientGameRunner {
}, 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.renderer.initialize();
this.input.initialize();
@@ -363,13 +369,6 @@ export class ClientGameRunner {
}
this.myPlayer.actions(tile).then((actions) => {
if (this.myPlayer === null) return;
const bu = actions.buildableUnits.find(
(bu) => bu.type === UnitType.TransportShip,
);
if (bu === undefined) {
console.warn(`no transport ship buildable units`);
return;
}
if (actions.canAttack) {
this.eventBus.emit(
new SendAttackIntentEvent(
@@ -377,31 +376,8 @@ export class ClientGameRunner {
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
),
);
} else if (
bu.canBuild !== false &&
this.shouldBoat(tile, bu.canBuild) &&
this.gameView.isLand(tile)
) {
this.myPlayer
.bestTransportShipSpawn(this.gameView.ref(cell.x, cell.y))
.then((spawn: number | false) => {
if (this.myPlayer === null) throw new Error("not initialized");
let spawnCell: Cell | null = null;
if (spawn !== false) {
spawnCell = new Cell(
this.gameView.x(spawn),
this.gameView.y(spawn),
);
}
this.eventBus.emit(
new SendBoatAttackIntentEvent(
this.gameView.owner(tile).id(),
cell,
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
spawnCell,
),
);
});
} else if (this.canBoatAttack(actions, tile)) {
this.sendBoatAttackIntent(tile, cell);
}
const owner = this.gameView.owner(tile);
@@ -413,6 +389,73 @@ export class ClientGameRunner {
});
}
private doBoatAttackUnderCursor(): void {
if (!this.isActive || !this.lastMousePosition) {
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.gameView.inSpawnPhase()) {
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 (!actions.canAttack && this.canBoatAttack(actions, tile)) {
this.sendBoatAttackIntent(tile, cell);
}
});
}
private canBoatAttack(actions: PlayerActions, tile: TileRef): boolean {
const bu = actions.buildableUnits.find(
(bu) => bu.type === UnitType.TransportShip,
);
if (bu === undefined) {
console.warn(`no transport ship buildable units`);
return false;
}
return (
bu.canBuild !== false &&
this.shouldBoat(tile, bu.canBuild) &&
this.gameView.isLand(tile)
);
}
private sendBoatAttackIntent(tile: TileRef, cell: Cell) {
if (!this.myPlayer) return;
this.myPlayer
.bestTransportShipSpawn(this.gameView.ref(cell.x, cell.y))
.then((spawn: number | false) => {
if (this.myPlayer === null) throw new Error("not initialized");
let spawnCell: Cell | null = null;
if (spawn !== false) {
spawnCell = new Cell(this.gameView.x(spawn), this.gameView.y(spawn));
}
this.eventBus.emit(
new SendBoatAttackIntentEvent(
this.gameView.owner(tile).id(),
cell,
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
spawnCell,
),
);
});
}
private shouldBoat(tile: TileRef, src: TileRef) {
// TODO: Global enable flag
// TODO: Global limit autoboat to nearby shore flag