diff --git a/package-lock.json b/package-lock.json index ffaa98120..89b41aa45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1049,7 +1049,6 @@ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2922,7 +2921,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -2946,7 +2944,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -5101,7 +5098,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -6420,6 +6416,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -6436,6 +6433,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -6452,6 +6450,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -6468,6 +6467,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -6484,6 +6484,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -6500,6 +6501,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -6516,6 +6518,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -6532,6 +6535,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -6548,6 +6552,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -6564,6 +6569,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -6599,6 +6605,7 @@ "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -7253,7 +7260,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz", "integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -7488,7 +7494,6 @@ "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/types": "8.34.1", @@ -8259,7 +8264,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8318,7 +8322,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8884,7 +8887,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", @@ -9063,7 +9065,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1" @@ -9085,7 +9086,6 @@ "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -9690,7 +9690,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -10278,7 +10277,6 @@ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -11021,7 +11019,6 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -11191,7 +11188,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11550,7 +11546,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -12451,7 +12446,6 @@ "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -14981,7 +14975,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -16695,7 +16688,6 @@ "integrity": "sha512-dyuThzncsgEgJZnvd/A/5x6IkUERbK+phXqUQrI+0C6WE+8xqGH5VChRTLecemhgZF0kQ+gZOM3tJTX9937xpg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@pixi/colord": "^2.9.6", "@types/css-font-loading-module": "^0.0.12", @@ -16753,7 +16745,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17092,7 +17083,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -18362,7 +18352,6 @@ "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^13.0.5", @@ -19075,7 +19064,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19307,7 +19295,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -19495,7 +19482,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -19578,8 +19564,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsx": { "version": "4.20.3", @@ -19668,7 +19653,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19963,7 +19947,6 @@ "integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -20013,7 +19996,6 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -20097,7 +20079,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -20213,7 +20194,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -20306,7 +20286,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 1ab056d17..eb0f5bdb0 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -153,16 +153,33 @@ export class EventsDisplay extends LitElement implements Layer { /** * Find the boat unit associated with an attack. - * Matches boats by owner, target territory, and optionally troop count. + * Uses unit ID if available, otherwise falls back to matching by owner, target territory, and troop count. */ private findBoatForAttack( attackerID: number, targetID: number, troops: number, + unitID?: number, ): UnitView | null { const attacker = this.game.playerBySmallID(attackerID) as PlayerView; + if (!attacker) return null; + + // If unit ID is provided, try to find the boat directly by ID + if (unitID !== undefined) { + const boat = this.game.unit(unitID); + if ( + boat && + boat.type() === UnitType.TransportShip && + boat.owner() === attacker && + boat.isActive() + ) { + return boat; + } + } + + // Fallback to matching by owner, target territory, and troop count const target = this.game.playerBySmallID(targetID) as PlayerView; - if (!attacker || !target) return null; + if (!target) return null; const boats = attacker .units(UnitType.TransportShip) @@ -897,6 +914,7 @@ export class EventsDisplay extends LitElement implements Layer { myPlayer.smallID(), attack.targetID, attack.troops, + attack.unitID, ) : null; const timeRemaining = diff --git a/src/core/execution/AttackExecution.ts b/src/core/execution/AttackExecution.ts index 13099b7b6..04f812034 100644 --- a/src/core/execution/AttackExecution.ts +++ b/src/core/execution/AttackExecution.ts @@ -26,6 +26,7 @@ export class AttackExecution implements Execution { private mg: Game; private attack: Attack | null = null; + private _unitID: number | undefined; constructor( private startTroops: number | null = null, @@ -33,7 +34,10 @@ export class AttackExecution implements Execution { private _targetID: PlayerID | null, private sourceTile: TileRef | null = null, private removeTroops: boolean = true, - ) {} + unitID?: number, + ) { + this._unitID = unitID; + } public targetID(): PlayerID | null { return this._targetID; @@ -114,6 +118,7 @@ export class AttackExecution implements Execution { this.startTroops, this.sourceTile, new Set(), + this._unitID, ); if (this.sourceTile !== null) { diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 7bca7931d..cbc47c24f 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -204,6 +204,7 @@ export class TransportShipExecution implements Execution { if (this.target.isPlayer() && this.attacker.isFriendly(this.target)) { this.attacker.addTroops(this.boat.troops()); } else { + const boatID = this.boat.id(); this.mg.addExecution( new AttackExecution( this.boat.troops(), @@ -211,6 +212,7 @@ export class TransportShipExecution implements Execution { this.targetID, this.dst, false, + boatID, ), ); } diff --git a/src/core/game/AttackImpl.ts b/src/core/game/AttackImpl.ts index 952ed6933..65b295c30 100644 --- a/src/core/game/AttackImpl.ts +++ b/src/core/game/AttackImpl.ts @@ -8,6 +8,7 @@ export class AttackImpl implements Attack { private _borderSize = 0; public _retreating = false; public _retreated = false; + private _unitID: number | undefined; constructor( private _id: string, @@ -17,7 +18,10 @@ export class AttackImpl implements Attack { private _sourceTile: TileRef | null, private _border: Set, private _mg: GameImpl, - ) {} + unitID?: number, + ) { + this._unitID = unitID; + } sourceTile(): TileRef | null { return this._sourceTile; @@ -44,6 +48,10 @@ export class AttackImpl implements Attack { return this._id; } + unitID(): number | undefined { + return this._unitID; + } + delete() { if (this._target.isPlayer()) { (this._target as PlayerImpl)._incomingAttacks = ( diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 362fcc5d8..1ae631926 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -369,6 +369,8 @@ export interface Attack { delete(): void; // The tile the attack originated from, mostly used for boat attacks. sourceTile(): TileRef | null; + // Unit ID for boat attacks (TransportShip), undefined for land attacks. + unitID(): number | undefined; addBorderTile(tile: TileRef): void; removeBorderTile(tile: TileRef): void; clearBorder(): void; @@ -652,6 +654,7 @@ export interface Player { troops: number, sourceTile: TileRef | null, border: Set, + unitID?: number, ): Attack; outgoingAttacks(): Attack[]; incomingAttacks(): Attack[]; diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 12be736f0..2c7d8c098 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -143,6 +143,7 @@ export interface AttackUpdate { troops: number; id: string; retreating: boolean; + unitID?: number; // Unit ID for boat attacks (TransportShip) } export interface PlayerUpdate { diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index d5e182cac..f773c7a73 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -152,6 +152,7 @@ export class PlayerImpl implements Player { troops: a.troops(), id: a.id(), retreating: a.retreating(), + unitID: a.unitID(), } satisfies AttackUpdate; }), incomingAttacks: this._incomingAttacks.map((a) => { @@ -161,6 +162,7 @@ export class PlayerImpl implements Player { troops: a.troops(), id: a.id(), retreating: a.retreating(), + unitID: a.unitID(), } satisfies AttackUpdate; }), outgoingAllianceRequests: outgoingAllianceRequests, @@ -1153,6 +1155,7 @@ export class PlayerImpl implements Player { troops: number, sourceTile: TileRef | null, border: Set, + unitID?: number, ): Attack { const attack = new AttackImpl( this._pseudo_random.nextID(), @@ -1162,6 +1165,7 @@ export class PlayerImpl implements Player { sourceTile, border, this.mg, + unitID, ); this._outgoingAttacks.push(attack); if (target.isPlayer()) {