Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,11 @@
"unit_captured_by_enemy": "Your {unit} was captured by {name}",
"captured_enemy_unit": "Captured {unit} from {name}",
"unit_destroyed": "Your {unit} was destroyed",
"no_boats_available": "No boats available, max {max}"
"no_boats_available": "No boats available, max {max}",
"atom_bomb_inbound": "{name} - atom bomb inbound",
"hydrogen_bomb_inbound": "{name} - hydrogen bomb inbound",
"naval_invasion_inbound": "Naval invasion incoming from {name} ({troops})",
"mirv_inbound": "⚠️⚠️⚠️ {name} - MIRV INBOUND ⚠️⚠️⚠️"
},
"player_type": {
"player": "Player",
Expand Down
7 changes: 6 additions & 1 deletion src/client/graphics/layers/EventsDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,8 +733,13 @@ export class EventsDisplay extends LitElement implements Layer {

const unitView = this.game.unit(event.unitID);

let description = event.message;
if (event.message.startsWith("events_display.")) {
description = translateText(event.message, event.params ?? {});
}

this.addEvent({
description: event.message,
description: description,
type: event.messageType,
unsafeDescription: false,
highlight: true,
Expand Down
30 changes: 18 additions & 12 deletions src/client/graphics/layers/PlayerPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,8 @@ export class PlayerPanel extends LitElement implements Layer {
if (!this.isVisible) return html``;

const my = this.g.myPlayer();
if (!my) return html``;
const isReplay = this.g.config().isReplay();
if (!my && !isReplay) return html``;
if (!this.tile) return html``;

const owner = this.g.owner(this.tile);
Expand All @@ -877,8 +878,10 @@ export class PlayerPanel extends LitElement implements Layer {
return html``;
}
const other = owner as PlayerView;
const myGoldNum = my.gold();
const myTroopsNum = Number(my.troops());
// In replay mode myPlayer() is null; use other as stand-in so read-only rendering works
const viewer = my ?? other;
const myGoldNum = viewer.gold();
const myTroopsNum = Number(viewer.troops());
Comment on lines +881 to +884
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Heads-up: in replay mode viewer becomes other, so a couple of "perspective" widgets compare the player to themselves.

When my is null (replay), viewer = other. That value then flows into:

  • Line 952 → renderIdentityRow(other, viewer)renderRelationPillIfNation(other, my=other) → reads this.otherProfile.relations?.[other.smallID()], i.e. the player's relation to themselves. You'll get a "Neutral" pill on every Nation in replay.
  • Line 1003 → renderStats(other, viewer)other.hasEmbargoAgainst(other), which is essentially always falsy, so the trading row will read "Active" for every player in replay regardless of the real embargo state.

The send-resource and moderation modals are safe because Actions is hidden in replay, so viewer never leaks there in practice. Still, the two display widgets above will show a small fib in replay mode.

Easiest fix: only render those perspective-dependent bits when there is a real "me", and let the helpers no-op otherwise. Something like:

♻️ Suggested tweak
-                    <div class="mb-1">${this.renderIdentityRow(other, viewer)}</div>
+                    <div class="mb-1">${this.renderIdentityRow(other, my)}</div>
@@
-                    ${this.renderStats(other, viewer)}
+                    ${my ? this.renderStats(other, my) : ""}

…and let renderIdentityRow / renderRelationPillIfNation accept my: PlayerView | null and bail early when it is null. A typed union (PlayerView | null) keeps the signatures honest without any extra class plumbing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/graphics/layers/PlayerPanel.ts` around lines 881 - 884, The
replay-path sets viewer = my ?? other causing perspective widgets to compare a
player to themselves; update the calls and helpers so they no-op when there is
no real "me": change renderIdentityRow and renderRelationPillIfNation signatures
to accept my: PlayerView | null and immediately return/do nothing if my is null,
and similarly guard renderStats (or its internal call to
other.hasEmbargoAgainst(other)) so it doesn't check embargoes when my is null;
then either pass my (which may be null) into those functions or only call them
when my != null to ensure replay rendering doesn't show self-relations or
incorrect embargo/trade status.


return html`
<style>
Expand Down Expand Up @@ -946,7 +949,7 @@ export class PlayerPanel extends LitElement implements Layer {
class="p-6 flex flex-col gap-2 font-sans antialiased text-[14.5px] leading-relaxed"
>
<!-- Identity (flag, name, type, traitor, relation) -->
<div class="mb-1">${this.renderIdentityRow(other, my)}</div>
<div class="mb-1">${this.renderIdentityRow(other, viewer)}</div>

${this.sendTarget
? html`
Expand All @@ -957,7 +960,7 @@ export class PlayerPanel extends LitElement implements Layer {
? myTroopsNum
: myGoldNum}
.uiState=${this.uiState}
.myPlayer=${my}
.myPlayer=${viewer}
.target=${this.sendTarget}
.gameView=${this.g}
.eventBus=${this.eventBus}
Expand All @@ -973,7 +976,7 @@ export class PlayerPanel extends LitElement implements Layer {
? html`
<player-moderation-modal
.open=${true}
.myPlayer=${my}
.myPlayer=${viewer}
.target=${this.moderationTarget}
.eventBus=${this.eventBus}
.isAdmin=${this.isAdminRole}
Expand All @@ -992,12 +995,12 @@ export class PlayerPanel extends LitElement implements Layer {
${this.renderResources(other)}

<!-- Rocket direction toggle -->
${other === my ? this.renderRocketDirectionToggle() : ""}
${other === viewer && !isReplay ? this.renderRocketDirectionToggle() : ""}

<ui-divider></ui-divider>

<!-- Stats: betrayals / trading -->
${this.renderStats(other, my)}
${this.renderStats(other, viewer)}

<ui-divider></ui-divider>

Expand All @@ -1007,10 +1010,13 @@ export class PlayerPanel extends LitElement implements Layer {
<!-- Alliance time remaining -->
${this.renderAllianceExpiry()}

<ui-divider></ui-divider>

<!-- Actions -->
${this.renderActions(my, other)}
${isReplay
? ""
: html`
<ui-divider></ui-divider>
<!-- Actions -->
${this.renderActions(viewer, other)}
`}
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/core/execution/MIRVExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ export class MirvExecution implements Execution {

this.mg.displayIncomingUnit(
this.nuke.id(),
// TODO TranslateText
`⚠️⚠️⚠️ ${this.player.displayName()} - MIRV INBOUND ⚠️⚠️⚠️`,
"events_display.mirv_inbound",
MessageType.MIRV_INBOUND,
this.targetPlayer.id(),
{ name: this.player.displayName() },
);
}

Expand Down
8 changes: 4 additions & 4 deletions src/core/execution/NukeExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,18 @@ export class NukeExecution implements Execution {
} else if (this.nukeType === UnitType.AtomBomb) {
this.mg.displayIncomingUnit(
this.nuke.id(),
// TODO TranslateText
`${this.player.displayName()} - atom bomb inbound`,
"events_display.atom_bomb_inbound",
MessageType.NUKE_INBOUND,
target.id(),
{ name: this.player.displayName() },
);
} else if (this.nukeType === UnitType.HydrogenBomb) {
this.mg.displayIncomingUnit(
this.nuke.id(),
// TODO TranslateText
`${this.player.displayName()} - hydrogen bomb inbound`,
"events_display.hydrogen_bomb_inbound",
MessageType.HYDROGEN_BOMB_INBOUND,
target.id(),
{ name: this.player.displayName() },
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/execution/TransportShipExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ export class TransportShipExecution implements Execution {
if (this.target.id() !== mg.terraNullius().id()) {
mg.displayIncomingUnit(
this.boat.id(),
// TODO TranslateText
`Naval invasion incoming from ${this.attacker.displayName()} (${renderTroops(this.boat.troops())})`,
"events_display.naval_invasion_inbound",
MessageType.NAVAL_INVASION_INBOUND,
this.target.id(),
{ name: this.attacker.displayName(), troops: renderTroops(this.boat.troops()) },
);
}

Expand Down
1 change: 1 addition & 0 deletions src/core/game/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ export interface Game extends GameMap {
message: string,
type: MessageType,
playerID: PlayerID | null,
params?: Record<string, string | number>,
): void;

displayChat(
Expand Down
2 changes: 2 additions & 0 deletions src/core/game/GameImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ export class GameImpl implements Game {
message: string,
type: MessageType,
playerID: PlayerID,
params?: Record<string, string | number>,
): void {
const id = this.player(playerID).smallID();

Expand All @@ -956,6 +957,7 @@ export class GameImpl implements Game {
message: message,
messageType: type,
playerID: id,
params: params,
});
}

Expand Down
1 change: 1 addition & 0 deletions src/core/game/GameUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export interface UnitIncomingUpdate {
message: string;
messageType: MessageType;
playerID: number;
params?: Record<string, string | number>;
}

export interface EmbargoUpdate {
Expand Down
Loading