From 87fd9c3219f439d7b1c6ffa96a3bf1e5db6dd819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Kosman?= Date: Wed, 14 May 2025 23:18:46 +0200 Subject: [PATCH] Feat : focus unit when clicking on warnings in chat (#699) ## Description: Enables you to click on the `Naval invasion incoming from X` or `X - atom bomb inbound` messages to focus the camera on the incoming unit (boat or nuke). Works for boats, atom bombs, hydrogen bombs and MIRVs. Nothing changes in looks, only the fact that the messages are clickable. closes #641 ## Please complete the following: - [x] I have added screenshots for all UI updates - [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: leo21_ --- src/client/graphics/layers/EventsDisplay.ts | 35 +++++++++++++++++++- src/core/execution/MIRVExecution.ts | 3 +- src/core/execution/NukeExecution.ts | 8 +++-- src/core/execution/TransportShipExecution.ts | 19 ++++++----- src/core/game/Game.ts | 6 ++++ src/core/game/GameImpl.ts | 18 ++++++++++ src/core/game/GameUpdates.ts | 12 ++++++- 7 files changed, 86 insertions(+), 15 deletions(-) diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index d1d705502..34eb7ee02 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -21,6 +21,7 @@ import { EmojiUpdate, GameUpdateType, TargetPlayerUpdate, + UnitIncomingUpdate, } from "../../../core/game/GameUpdates"; import { ClientID } from "../../../core/Schemas"; import { @@ -53,6 +54,7 @@ interface Event { priority?: number; duration?: Tick; focusID?: number; + unitView?: UnitView; } @customElement("events-display") @@ -89,6 +91,7 @@ export class EventsDisplay extends LitElement implements Layer { [GameUpdateType.BrokeAlliance, (u) => this.onBrokeAllianceEvent(u)], [GameUpdateType.TargetPlayer, (u) => this.onTargetPlayerEvent(u)], [GameUpdateType.Emoji, (u) => this.onEmojiMessageEvent(u)], + [GameUpdateType.UnitIncoming, (u) => this.onUnitIncomingEvent(u)], ]); constructor() { @@ -421,6 +424,28 @@ export class EventsDisplay extends LitElement implements Layer { } } + onUnitIncomingEvent(event: UnitIncomingUpdate) { + const myPlayer = this.game.playerByClientID(this.clientID); + + if ( + event.playerID != null && + (!myPlayer || myPlayer.smallID() !== event.playerID) + ) { + return; + } + + const unitView = this.game.unit(event.unitID); + + this.addEvent({ + description: event.message, + type: event.messageType, + unsafeDescription: false, + highlight: true, + createdAt: this.game.ticks(), + unitView: unitView, + }); + } + private getMessageTypeClasses(type: MessageType): string { switch (type) { case MessageType.SUCCESS: @@ -645,7 +670,15 @@ export class EventsDisplay extends LitElement implements Layer { > ${this.getEventDescription(event)} ` - : this.getEventDescription(event)} + : event.unitView + ? html`` + : this.getEventDescription(event)} ${event.buttons ? html`
diff --git a/src/core/execution/MIRVExecution.ts b/src/core/execution/MIRVExecution.ts index 61b38df81..3a960d86a 100644 --- a/src/core/execution/MIRVExecution.ts +++ b/src/core/execution/MIRVExecution.ts @@ -81,7 +81,8 @@ export class MirvExecution implements Execution { this.separateDst = this.mg.ref(x, y); this.pathFinder.computeControlPoints(spawn, this.separateDst); - this.mg.displayMessage( + this.mg.displayIncomingUnit( + this.nuke.id(), `⚠️⚠️⚠️ ${this.player.name()} - MIRV INBOUND ⚠️⚠️⚠️`, MessageType.ERROR, this.targetPlayer.id(), diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 4572eefc5..e0333f626 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -106,14 +106,16 @@ export class NukeExecution implements Execution { if (this.mg.hasOwner(this.dst)) { const target = this.mg.owner(this.dst) as Player; if (this.type == UnitType.AtomBomb) { - this.mg.displayMessage( + this.mg.displayIncomingUnit( + this.nuke.id(), `${this.player.name()} - atom bomb inbound`, MessageType.ERROR, target.id(), ); } if (this.type == UnitType.HydrogenBomb) { - this.mg.displayMessage( + this.mg.displayIncomingUnit( + this.nuke.id(), `${this.player.name()} - hydrogen bomb inbound`, MessageType.ERROR, target.id(), @@ -129,7 +131,7 @@ export class NukeExecution implements Execution { ); } - // after sending an nuke set the missilesilo on cooldown + // after sending a nuke set the missilesilo on cooldown const silo = this.player .units(UnitType.MissileSilo) .find((silo) => silo.tile() === spawn); diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 0194c895d..9b8dfc27d 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -67,15 +67,6 @@ export class TransportShipExecution implements Execution { this.attacker = mg.player(this.attackerID); - // Notify the target player about the incoming naval invasion - if (this.targetID && this.targetID !== mg.terraNullius().id()) { - mg.displayMessage( - `Naval invasion incoming from ${this.attacker.displayName()}`, - MessageType.WARN, - this.targetID, - ); - } - if ( this.attacker.units(UnitType.TransportShip).length >= mg.config().boatMaxNumber() @@ -142,6 +133,16 @@ export class TransportShipExecution implements Execution { this.boat = this.attacker.buildUnit(UnitType.TransportShip, this.src, { troops: this.troops, }); + + // Notify the target player about the incoming naval invasion + if (this.targetID && this.targetID !== mg.terraNullius().id()) { + mg.displayIncomingUnit( + this.boat.id(), + `Naval invasion incoming from ${this.attacker.displayName()}`, + MessageType.WARN, + this.targetID, + ); + } } tick(ticks: number) { diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 8eec8c263..bb57ca831 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -544,6 +544,12 @@ export interface Game extends GameMap { type: MessageType, playerID: PlayerID | null, ): void; + displayIncomingUnit( + unitID: number, + message: string, + type: MessageType, + playerID: PlayerID, + ): void; displayChat( message: string, diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 698689537..1b88abad0 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -628,6 +628,24 @@ export class GameImpl implements Game { recipient: recipient, }); } + + displayIncomingUnit( + unitID: number, + message: string, + type: MessageType, + playerID: PlayerID, + ): void { + const id = this.player(playerID).smallID(); + + this.addUpdate({ + type: GameUpdateType.UnitIncoming, + unitID: unitID, + message: message, + messageType: type, + playerID: id, + }); + } + addUnit(u: Unit) { this.unitGrid.addUnit(u); } diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index dc9b3e313..f9b3087d9 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -38,6 +38,7 @@ export enum GameUpdateType { Emoji, Win, Hash, + UnitIncoming, } export type GameUpdate = @@ -53,7 +54,8 @@ export type GameUpdate = | TargetPlayerUpdate | EmojiUpdate | WinUpdate - | HashUpdate; + | HashUpdate + | UnitIncomingUpdate; export interface TileUpdateWrapper { type: GameUpdateType.Tile; @@ -182,3 +184,11 @@ export interface HashUpdate { tick: Tick; hash: number; } + +export interface UnitIncomingUpdate { + type: GameUpdateType.UnitIncoming; + unitID: number; + message: string; + messageType: MessageType; + playerID: number; +}