Move warship by a touch (of magic) (#2408)

## Description:

This adds moving warships by tapping them on a touchscreen. Now you can
steer them just like you already could with a mouse.

Also has some earlier returns, doing checks only when needed, prevent as
much duplicate checks and a bugfix.

In onMouseUp:
- early return if no OceanTile. Before, function findWarshipsNearCell
would still go look for warships even if ocean wasn't clicked.
- Move const nearbyWarShips down. It isn't needed when this.selectedUnit
is true.
- Remove unnecessary const clickedWarship.
- Move getting clickRef from function findWarshipsNearCell into
onMouseUp. Because it is needed in case of this.selectedUnit too, within
onMouseUp.
Getting a valid clickRef for this.selectedUnit fixes: Runtime error when
clicking outside the map after selecting a warship. The isValidCoord/Ref
check was missing for this.selectedUnit.

For findWarshipsNearCell:
- moved the cell/tile checks out to onMouseUp, the only caller of the
function.
- did NOT rename findWarshipsNearCell. Although it now uses tileRef as
input. Renaming can cause merge issues so i only do this when needed.

Added onTouch:
- Tests if we need to look for warships to select/move or if we can open
Radial Menu.
- Prevent as much duplicated checks as possible. So if no there's no
Ocean Tile found, just send the radial menu event, which checks
isValidCoord anyway. isOceanTile itself works fine even if it's no valid
cell (proven by this.selectedUnit working all this time in onMouseUp
without an isValidCoord test).

Screencap on mobile, shows selecting and moving warships, no runtime
error when clicking outside the map after selecting a warship, while
radial menu still opens as normal:


https://github.com/user-attachments/assets/1300d557-ae2f-46e3-92bd-d434c523aae7

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

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

tryout33
This commit is contained in:
VariableVince
2025-11-08 05:09:03 +01:00
committed by GitHub
parent d16a2485e3
commit d07f84f801
2 changed files with 74 additions and 25 deletions
+7 -1
View File
@@ -18,6 +18,12 @@ export class MouseOverEvent implements GameEvent {
public readonly y: number,
) {}
}
export class TouchEvent implements GameEvent {
constructor(
public readonly x: number,
public readonly y: number,
) {}
}
/**
* Event emitted when a unit is selected or deselected
@@ -476,7 +482,7 @@ export class InputHandler {
Math.abs(event.y - this.lastPointerDownY);
if (dist < 10) {
if (event.pointerType === "touch") {
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY));
this.eventBus.emit(new TouchEvent(event.x, event.y));
event.preventDefault();
return;
}
+67 -24
View File
@@ -7,7 +7,9 @@ import { GameView, UnitView } from "../../../core/game/GameView";
import { BezenhamLine } from "../../../core/utilities/Line";
import {
AlternateViewEvent,
ContextMenuEvent,
MouseUpEvent,
TouchEvent,
UnitSelectionEvent,
} from "../../InputHandler";
import { MoveWarshipIntentEvent } from "../../Transport";
@@ -73,6 +75,7 @@ export class UnitLayer implements Layer {
init() {
this.eventBus.on(AlternateViewEvent, (e) => this.onAlternativeViewEvent(e));
this.eventBus.on(MouseUpEvent, (e) => this.onMouseUp(e));
this.eventBus.on(TouchEvent, (e) => this.onTouch(e));
this.eventBus.on(UnitSelectionEvent, (e) => this.onUnitSelectionChange(e));
this.redraw();
@@ -81,16 +84,10 @@ export class UnitLayer implements Layer {
/**
* Find player-owned warships near the given cell within a configurable radius
* @param cell The cell to check
* @param clickRef The tile to check
* @returns Array of player's warships in range, sorted by distance (closest first)
*/
private findWarshipsNearCell(cell: { x: number; y: number }): UnitView[] {
if (!this.game.isValidCoord(cell.x, cell.y)) {
// The cell coordinate were invalid (user probably clicked outside the map), therefore no warships can be found
return [];
}
const clickRef = this.game.ref(cell.x, cell.y);
private findWarshipsNearCell(clickRef: TileRef): UnitView[] {
// Only select warships owned by the player
return this.game
.units(UnitType.Warship)
@@ -109,29 +106,75 @@ export class UnitLayer implements Layer {
});
}
private onMouseUp(event: MouseUpEvent) {
// Convert screen coordinates to world coordinates
private onMouseUp(
event: MouseUpEvent,
clickRef?: TileRef,
nearbyWarships?: UnitView[],
) {
if (clickRef === undefined) {
// Convert screen coordinates to world coordinates
const cell = this.transformHandler.screenToWorldCoordinates(
event.x,
event.y,
);
if (!this.game.isValidCoord(cell.x, cell.y)) return;
clickRef = this.game.ref(cell.x, cell.y);
}
if (!this.game.isOcean(clickRef)) return;
if (this.selectedUnit) {
this.eventBus.emit(
new MoveWarshipIntentEvent(this.selectedUnit.id(), clickRef),
);
// Deselect
this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false));
return;
}
// Find warships near this tile, sorted by distance
nearbyWarships ??= this.findWarshipsNearCell(clickRef);
if (nearbyWarships.length > 0) {
// Toggle selection of the closest warship
this.eventBus.emit(new UnitSelectionEvent(nearbyWarships[0], true));
}
}
private onTouch(event: TouchEvent) {
const cell = this.transformHandler.screenToWorldCoordinates(
event.x,
event.y,
);
// Find warships near this cell, sorted by distance
const nearbyWarships = this.findWarshipsNearCell(cell);
const clickRef = this.game.ref(cell.x, cell.y);
if (!this.game.isOcean(clickRef)) {
// No isValidCoord/Ref check yet, that is done for ContextMenuEvent later
// No warship to find because no Ocean tile, open Radial Menu
this.eventBus.emit(new ContextMenuEvent(event.x, event.y));
return;
}
if (!this.game.isValidRef(clickRef)) {
return;
}
if (this.selectedUnit) {
const clickRef = this.game.ref(cell.x, cell.y);
if (this.game.isOcean(clickRef)) {
this.eventBus.emit(
new MoveWarshipIntentEvent(this.selectedUnit.id(), clickRef),
);
}
// Deselect
this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false));
} else if (nearbyWarships.length > 0) {
// Toggle selection of the closest warship
const clickedUnit = nearbyWarships[0];
this.eventBus.emit(new UnitSelectionEvent(clickedUnit, true));
// Reuse the mouse logic, send clickRef to avoid fetching it again
this.onMouseUp(new MouseUpEvent(event.x, event.y), clickRef);
return;
}
const nearbyWarships = this.findWarshipsNearCell(clickRef);
if (nearbyWarships.length > 0) {
this.onMouseUp(
new MouseUpEvent(event.x, event.y),
clickRef,
nearbyWarships,
);
} else {
// No warships selected or nearby, open Radial Menu
this.eventBus.emit(new ContextMenuEvent(event.x, event.y));
}
}