From 735671cef63a4cb3defe6adab70a39c9dbecdd13 Mon Sep 17 00:00:00 2001 From: Rj Manhas Date: Mon, 10 Nov 2025 22:05:12 -0700 Subject: [PATCH] fixed name leak --- resources/lang/en.json | 19 +++- src/client/graphics/layers/EventsDisplay.ts | 103 +++++++++++++++--- src/core/execution/MIRVExecution.ts | 6 +- src/core/execution/NukeExecution.ts | 12 +- src/core/execution/TradeShipExecution.ts | 18 ++- src/core/execution/TransportShipExecution.ts | 6 +- .../alliance/AllianceExtensionExecution.ts | 6 +- src/core/game/Game.ts | 1 + src/core/game/GameImpl.ts | 10 +- src/core/game/GameUpdates.ts | 1 + src/core/game/PlayerImpl.ts | 27 ++++- src/core/game/UnitImpl.ts | 14 ++- tests/AllianceExtensionExecution.test.ts | 2 +- 13 files changed, 185 insertions(+), 40 deletions(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index aac1034c2..dea450823 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -577,10 +577,23 @@ "focus": "Focus", "accept_alliance": "Accept", "reject_alliance": "Reject", - "alliance_renewed": "Your alliance with {name} has been renewed", - "wants_to_renew_alliance": "{name} wants to renew your alliance", + "alliance_renewed": "Your alliance with {otherPlayerName} has been renewed", + "wants_to_renew_alliance": "{otherPlayerName} wants to renew your alliance", "ignore": "Ignore", - "unit_voluntarily_deleted": "Unit voluntarily deleted" + "unit_voluntarily_deleted": "Unit voluntarily deleted", + "sent_troops_to_player": "Sent {troops} troops to {recipientPlayerName}", + "received_troops_from_player": "Received {troops} troops from {senderPlayerName}", + "sent_gold_to_player": "Sent {gold} gold to {recipientPlayerName}", + "received_gold_from_player": "Received {gold} gold from {senderPlayerName}", + "unit_captured_by_enemy": "Your {unitType} was captured by {captorPlayerName}", + "captured_enemy_unit": "Captured {unitType} from {previousOwnerPlayerName}", + "conquered_player": "Conquered {conqueredPlayerName} received {gold} gold", + "received_gold_from_trade": "Received {gold} gold from trade with {tradingPartnerPlayerName}", + "received_gold_from_captured_ship": "Received {gold} gold from ship captured from {originalOwnerPlayerName}", + "mirv_inbound": "{attackerPlayerName} - MIRV INBOUND", + "atom_bomb_inbound": "{attackerPlayerName} - atom bomb inbound", + "hydrogen_bomb_inbound": "{attackerPlayerName} - hydrogen bomb inbound", + "naval_invasion_inbound": "Naval invasion incoming from {attackerPlayerName}" }, "unit_info_modal": { "structure_info": "Structure Info", diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 1141643ef..396762ab4 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -286,7 +286,7 @@ export class EventsDisplay extends LitElement implements Layer { this.addEvent({ description: translateText("events_display.about_to_expire", { - name: other.name(), + name: other.displayName(), }), type: MessageType.RENEW_ALLIANCE, duration: this.game.config().allianceExtensionPromptOffset() - 3 * 10, // 3 second buffer @@ -299,7 +299,7 @@ export class EventsDisplay extends LitElement implements Layer { }, { text: translateText("events_display.renew_alliance", { - name: other.name(), + name: other.displayName(), }), className: "btn", action: () => @@ -373,7 +373,43 @@ export class EventsDisplay extends LitElement implements Layer { let description: string = event.message; if (event.message.startsWith("events_display.")) { - description = translateText(event.message, event.params ?? {}); + // Resolve player IDs in params to displayName() for hidden names support + // Pattern: Server sends params with keys ending in "PlayerID" (e.g., "recipientPlayerID") + // Client resolves these to displayName() and replaces "ID" with "Name" in the key + // (e.g., "recipientPlayerID" -> "recipientPlayerName") for translation + // This ensures hidden names work correctly and messages are translatable + const resolvedParams: Record = {}; + if (event.params) { + for (const [key, value] of Object.entries(event.params)) { + // If param ends with "PlayerID" or "PlayerId", resolve it to displayName + if ( + (key.endsWith("PlayerID") || + key.endsWith("PlayerId") || + key.endsWith("playerID") || + key.endsWith("playerId")) && + (typeof value === "string" || typeof value === "number") + ) { + try { + const player = + typeof value === "string" + ? this.game.player(value) + : this.game.playerBySmallID(value); + if (player && player instanceof PlayerView) { + resolvedParams[key.replace(/ID$/i, "Name")] = + player.displayName(); + } else { + resolvedParams[key] = value; + } + } catch (e) { + // If player not found, keep original value + resolvedParams[key] = value; + } + } else { + resolvedParams[key] = value; + } + } + } + description = translateText(event.message, resolvedParams); } this.addEvent({ @@ -445,7 +481,7 @@ export class EventsDisplay extends LitElement implements Layer { this.addEvent({ description: translateText("events_display.request_alliance", { - name: requestor.name(), + name: requestor.displayName(), }), buttons: [ { @@ -512,7 +548,7 @@ export class EventsDisplay extends LitElement implements Layer { ) as PlayerView; this.addEvent({ description: translateText("events_display.alliance_request_status", { - name: recipient.name(), + name: recipient.displayName(), status: update.accepted ? translateText("events_display.alliance_accepted") : translateText("events_display.alliance_rejected"), @@ -552,7 +588,7 @@ export class EventsDisplay extends LitElement implements Layer { this.addEvent({ description: translateText("events_display.betrayal_description", { - name: betrayed.name(), + name: betrayed.displayName(), malusPercent: malusPercent, durationText: durationText, }), @@ -572,7 +608,7 @@ export class EventsDisplay extends LitElement implements Layer { ]; this.addEvent({ description: translateText("events_display.betrayed_you", { - name: traitor.name(), + name: traitor.displayName(), }), type: MessageType.ALLIANCE_BROKEN, highlight: true, @@ -599,7 +635,7 @@ export class EventsDisplay extends LitElement implements Layer { this.addEvent({ description: translateText("events_display.alliance_expired", { - name: other.name(), + name: other.displayName(), }), type: MessageType.ALLIANCE_EXPIRED, highlight: true, @@ -617,8 +653,8 @@ export class EventsDisplay extends LitElement implements Layer { this.addEvent({ description: translateText("events_display.attack_request", { - name: other.name(), - target: target.name(), + name: other.displayName(), + target: target.displayName(), }), type: MessageType.ATTACK_REQUEST, highlight: true, @@ -698,8 +734,49 @@ export class EventsDisplay extends LitElement implements Layer { const unitView = this.game.unit(event.unitID); + let description: string = event.message; + // Resolve player IDs in params to displayName() for hidden names support + if (event.message.startsWith("events_display.")) { + const resolvedParams: Record = {}; + if (event.params) { + for (const [key, value] of Object.entries(event.params)) { + // If param ends with "PlayerID" or "PlayerId", resolve it to displayName + if ( + (key.endsWith("PlayerID") || + key.endsWith("PlayerId") || + key.endsWith("playerID") || + key.endsWith("playerId")) && + (typeof value === "string" || typeof value === "number") + ) { + try { + const player = + typeof value === "string" + ? this.game.player(value) + : this.game.playerBySmallID(value); + if (player && player instanceof PlayerView) { + resolvedParams[key.replace(/ID$/i, "Name")] = + player.displayName(); + } else { + resolvedParams[key] = value; + } + } catch (e) { + // If player not found, keep original value + resolvedParams[key] = value; + } + } else { + resolvedParams[key] = value; + } + } + } + description = translateText(event.message, resolvedParams); + // Add emojis for MIRV messages in code (not in translation) + if (event.message === "events_display.mirv_inbound") { + description = `⚠️⚠️⚠️ ${description} ⚠️⚠️⚠️`; + } + } + this.addEvent({ - description: event.message, + description: description, type: event.messageType, unsafeDescription: false, highlight: true, @@ -747,7 +824,7 @@ export class EventsDisplay extends LitElement implements Layer { ${renderTroops(attack.troops)} ${( this.game.playerBySmallID(attack.attackerID) as PlayerView - )?.name()} + )?.displayName()} ${attack.retreating ? `(${translateText("events_display.retreating")}...)` : ""} @@ -778,7 +855,7 @@ export class EventsDisplay extends LitElement implements Layer { this.game.playerBySmallID( attack.targetID, ) as PlayerView - )?.name()} + )?.displayName()} `, onClick: async () => this.attackWarningOnClick(attack), className: "text-left text-blue-400", diff --git a/src/core/execution/MIRVExecution.ts b/src/core/execution/MIRVExecution.ts index b79a4e1ef..8707a2755 100644 --- a/src/core/execution/MIRVExecution.ts +++ b/src/core/execution/MIRVExecution.ts @@ -80,10 +80,12 @@ export class MirvExecution implements Execution { this.mg.displayIncomingUnit( this.nuke.id(), - // TODO TranslateText - `⚠️⚠️⚠️ ${this.player.name()} - MIRV INBOUND ⚠️⚠️⚠️`, + "events_display.mirv_inbound", MessageType.MIRV_INBOUND, this.targetPlayer.id(), + { + attackerPlayerID: this.player.id(), + }, ); } diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 1fd9222bb..992b4282f 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -128,18 +128,22 @@ export class NukeExecution implements Execution { } else if (this.nukeType === UnitType.AtomBomb) { this.mg.displayIncomingUnit( this.nuke.id(), - // TODO TranslateText - `${this.player.name()} - atom bomb inbound`, + "events_display.atom_bomb_inbound", MessageType.NUKE_INBOUND, target.id(), + { + attackerPlayerID: this.player.id(), + }, ); } else if (this.nukeType === UnitType.HydrogenBomb) { this.mg.displayIncomingUnit( this.nuke.id(), - // TODO TranslateText - `${this.player.name()} - hydrogen bomb inbound`, + "events_display.hydrogen_bomb_inbound", MessageType.HYDROGEN_BOMB_INBOUND, target.id(), + { + attackerPlayerID: this.player.id(), + }, ); } diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 0da513b8e..e21b3fefc 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -143,10 +143,14 @@ export class TradeShipExecution implements Execution { if (this.wasCaptured) { this.tradeShip!.owner().addGold(gold, this._dstPort.tile()); this.mg.displayMessage( - `Received ${renderNumber(gold)} gold from ship captured from ${this.origOwner.displayName()}`, + "events_display.received_gold_from_captured_ship", MessageType.CAPTURED_ENEMY_UNIT, this.tradeShip!.owner().id(), gold, + { + gold: renderNumber(gold), + originalOwnerPlayerID: this.origOwner.id(), + }, ); // Record stats this.mg @@ -156,16 +160,24 @@ export class TradeShipExecution implements Execution { this.srcPort.owner().addGold(gold); this._dstPort.owner().addGold(gold, this._dstPort.tile()); this.mg.displayMessage( - `Received ${renderNumber(gold)} gold from trade with ${this.srcPort.owner().displayName()}`, + "events_display.received_gold_from_trade", MessageType.RECEIVED_GOLD_FROM_TRADE, this._dstPort.owner().id(), gold, + { + gold: renderNumber(gold), + tradingPartnerPlayerID: this.srcPort.owner().id(), + }, ); this.mg.displayMessage( - `Received ${renderNumber(gold)} gold from trade with ${this._dstPort.owner().displayName()}`, + "events_display.received_gold_from_trade", MessageType.RECEIVED_GOLD_FROM_TRADE, this.srcPort.owner().id(), gold, + { + gold: renderNumber(gold), + tradingPartnerPlayerID: this._dstPort.owner().id(), + }, ); // Record stats this.mg diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 913abb817..644dbf7ca 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -143,10 +143,12 @@ export class TransportShipExecution implements Execution { if (this.targetID && this.targetID !== mg.terraNullius().id()) { mg.displayIncomingUnit( this.boat.id(), - // TODO TranslateText - `Naval invasion incoming from ${this.attacker.displayName()}`, + "events_display.naval_invasion_inbound", MessageType.NAVAL_INVASION_INBOUND, this.targetID, + { + attackerPlayerID: this.attacker.id(), + }, ); } diff --git a/src/core/execution/alliance/AllianceExtensionExecution.ts b/src/core/execution/alliance/AllianceExtensionExecution.ts index 699d4182d..697806d6b 100644 --- a/src/core/execution/alliance/AllianceExtensionExecution.ts +++ b/src/core/execution/alliance/AllianceExtensionExecution.ts @@ -50,14 +50,14 @@ export class AllianceExtensionExecution implements Execution { MessageType.ALLIANCE_ACCEPTED, this.from.id(), undefined, - { name: to.displayName() }, + { otherPlayerID: to.id() }, ); mg.displayMessage( "events_display.alliance_renewed", MessageType.ALLIANCE_ACCEPTED, this.toID, undefined, - { name: this.from.displayName() }, + { otherPlayerID: this.from.id() }, ); } else if (alliance.onlyOneAgreedToExtend() && !wasOnlyOneAgreed) { // Send message to the other player that someone wants to renew @@ -67,7 +67,7 @@ export class AllianceExtensionExecution implements Execution { MessageType.RENEW_ALLIANCE, this.toID, undefined, - { name: this.from.displayName() }, + { otherPlayerID: this.from.id() }, ); } } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 8ce4822b3..ef2aed5dd 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -723,6 +723,7 @@ export interface Game extends GameMap { message: string, type: MessageType, playerID: PlayerID | null, + params?: Record, ): void; displayChat( diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index cccb25902..67993c6bf 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -738,6 +738,7 @@ export class GameImpl implements Game { message: string, type: MessageType, playerID: PlayerID, + params?: Record, ): void { const id = this.player(playerID).smallID(); @@ -747,6 +748,7 @@ export class GameImpl implements Game { message: message, messageType: type, playerID: id, + params: params, }); } @@ -897,12 +899,14 @@ export class GameImpl implements Game { conquerPlayer(conqueror: Player, conquered: Player) { const gold = conquered.gold(); this.displayMessage( - `Conquered ${conquered.displayName()} received ${renderNumber( - gold, - )} gold`, + "events_display.conquered_player", MessageType.CONQUERED_PLAYER, conqueror.id(), gold, + { + conqueredPlayerID: conquered.id(), + gold: renderNumber(gold), + }, ); conqueror.addGold(gold); conquered.removeGold(gold); diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 706be36d8..e91045bb9 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -260,6 +260,7 @@ export interface UnitIncomingUpdate { message: string; messageType: MessageType; playerID: number; + params?: Record; } export interface EmbargoUpdate { diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index d5e182cac..efc90e127 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -651,14 +651,24 @@ export class PlayerImpl implements Player { this.sentDonations.push(new Donation(recipient, this.mg.ticks())); this.mg.displayMessage( - `Sent ${renderTroops(troops)} troops to ${recipient.name()}`, + "events_display.sent_troops_to_player", MessageType.SENT_TROOPS_TO_PLAYER, this.id(), + undefined, + { + troops: renderTroops(troops), + recipientPlayerID: recipient.id(), + }, ); this.mg.displayMessage( - `Received ${renderTroops(troops)} troops from ${this.name()}`, + "events_display.received_troops_from_player", MessageType.RECEIVED_TROOPS_FROM_PLAYER, recipient.id(), + undefined, + { + troops: renderTroops(troops), + senderPlayerID: this.id(), + }, ); return true; } @@ -671,15 +681,24 @@ export class PlayerImpl implements Player { this.sentDonations.push(new Donation(recipient, this.mg.ticks())); this.mg.displayMessage( - `Sent ${renderNumber(gold)} gold to ${recipient.name()}`, + "events_display.sent_gold_to_player", MessageType.SENT_GOLD_TO_PLAYER, this.id(), + undefined, + { + gold: renderNumber(gold), + recipientPlayerID: recipient.id(), + }, ); this.mg.displayMessage( - `Received ${renderNumber(gold)} gold from ${this.name()}`, + "events_display.received_gold_from_player", MessageType.RECEIVED_GOLD_FROM_PLAYER, recipient.id(), gold, + { + gold: renderNumber(gold), + senderPlayerID: this.id(), + }, ); return true; } diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 917d0af67..2f42b2b63 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -202,14 +202,24 @@ export class UnitImpl implements Unit { this._owner._units.push(this); this.mg.addUpdate(this.toUpdate()); this.mg.displayMessage( - `Your ${this.type()} was captured by ${newOwner.displayName()}`, + "events_display.unit_captured_by_enemy", MessageType.UNIT_CAPTURED_BY_ENEMY, this._lastOwner.id(), + undefined, + { + unitType: this.type(), + captorPlayerID: newOwner.id(), + }, ); this.mg.displayMessage( - `Captured ${this.type()} from ${this._lastOwner.displayName()}`, + "events_display.captured_enemy_unit", MessageType.CAPTURED_ENEMY_UNIT, newOwner.id(), + undefined, + { + unitType: this.type(), + previousOwnerPlayerID: this._lastOwner.id(), + }, ); } diff --git a/tests/AllianceExtensionExecution.test.ts b/tests/AllianceExtensionExecution.test.ts index 4c4b8b7b9..0cf11e226 100644 --- a/tests/AllianceExtensionExecution.test.ts +++ b/tests/AllianceExtensionExecution.test.ts @@ -152,7 +152,7 @@ describe("AllianceExtensionExecution", () => { MessageType.RENEW_ALLIANCE, player2.id(), undefined, - { name: player1.displayName() }, + { otherPlayerID: player1.id() }, ); expect(displayMessageSpy).toHaveBeenCalledTimes(1);