-
Notifications
You must be signed in to change notification settings - Fork 1k
feat: make PlayerInfoPanel available in replays #3855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,14 @@ const swordIcon = assetUrl("images/SwordIconWhite.svg"); | |
|
|
||
| import { ContextMenuEvent } from "../../InputHandler"; | ||
|
|
||
| function emptyPlayerActions(): PlayerActions { | ||
| return { | ||
| canAttack: false, | ||
| buildableUnits: [], | ||
| canSendEmojiAllPlayers: false, | ||
| }; | ||
| } | ||
|
|
||
| @customElement("main-radial-menu") | ||
| export class MainRadialMenu extends LitElement implements Layer { | ||
| private radialMenu: RadialMenu; | ||
|
|
@@ -87,27 +95,40 @@ export class MainRadialMenu extends LitElement implements Layer { | |
| if (!this.game.isValidCoord(worldCoords.x, worldCoords.y)) { | ||
| return; | ||
| } | ||
| if (this.game.myPlayer() === null) { | ||
| const myPlayer = this.game.myPlayer(); | ||
| const isReplay = this.game.config().isReplay(); | ||
| if (myPlayer === null && !isReplay) { | ||
| return; | ||
| } | ||
| this.clickedTile = this.game.ref(worldCoords.x, worldCoords.y); | ||
| this.game | ||
| .myPlayer()! | ||
| .actions(this.clickedTile) | ||
| .then((actions) => { | ||
| this.updatePlayerActions( | ||
| this.game.myPlayer()!, | ||
| actions, | ||
| this.clickedTile!, | ||
| event.x, | ||
| event.y, | ||
| ); | ||
| }); | ||
| if (myPlayer === null) { | ||
| // Replay: only show the info-only radial when right-clicking on a player | ||
| if (!this.game.owner(this.clickedTile).isPlayer()) { | ||
| return; | ||
| } | ||
| this.updatePlayerActions( | ||
| null, | ||
| emptyPlayerActions(), | ||
| this.clickedTile, | ||
| event.x, | ||
| event.y, | ||
| ); | ||
| return; | ||
| } | ||
| myPlayer.actions(this.clickedTile).then((actions) => { | ||
| this.updatePlayerActions( | ||
| myPlayer, | ||
| actions, | ||
| this.clickedTile!, | ||
| event.x, | ||
| event.y, | ||
| ); | ||
| }); | ||
|
Comment on lines
+118
to
+126
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Capture tile locally before async callbacks to avoid stale-menu updates At Line 118 and Line 188, the callback reads Proposed fix- this.clickedTile = this.game.ref(worldCoords.x, worldCoords.y);
+ this.clickedTile = this.game.ref(worldCoords.x, worldCoords.y);
+ const clickedTile = this.clickedTile;
...
- myPlayer.actions(this.clickedTile).then((actions) => {
+ myPlayer.actions(clickedTile).then((actions) => {
this.updatePlayerActions(
myPlayer,
actions,
- this.clickedTile!,
+ clickedTile,
event.x,
event.y,
);
});
...
- myPlayer.actions(this.clickedTile).then((actions) => {
- this.updatePlayerActions(myPlayer, actions, this.clickedTile!);
+ const clickedTile = this.clickedTile;
+ myPlayer.actions(clickedTile).then((actions) => {
+ this.updatePlayerActions(myPlayer, actions, clickedTile);
});Also applies to: 188-190 🤖 Prompt for AI Agents |
||
| }); | ||
| } | ||
|
|
||
| private async updatePlayerActions( | ||
| myPlayer: PlayerView, | ||
| myPlayer: PlayerView | null, | ||
| actions: PlayerActions, | ||
| tile: TileRef, | ||
| screenX: number | null = null, | ||
|
|
@@ -139,6 +160,7 @@ export class MainRadialMenu extends LitElement implements Layer { | |
| }; | ||
|
|
||
| const isFriendlyTarget = | ||
| myPlayer !== null && | ||
| recipient !== null && | ||
| recipient.isFriendly(myPlayer) && | ||
| !recipient.isDisconnected(); | ||
|
|
@@ -161,16 +183,11 @@ export class MainRadialMenu extends LitElement implements Layer { | |
|
|
||
| async tick() { | ||
| if (!this.radialMenu.isMenuVisible() || this.clickedTile === null) return; | ||
| this.game | ||
| .myPlayer()! | ||
| .actions(this.clickedTile) | ||
| .then((actions) => { | ||
| this.updatePlayerActions( | ||
| this.game.myPlayer()!, | ||
| actions, | ||
| this.clickedTile!, | ||
| ); | ||
| }); | ||
| const myPlayer = this.game.myPlayer(); | ||
| if (myPlayer === null) return; // replay mode: nothing to refresh | ||
| myPlayer.actions(this.clickedTile).then((actions) => { | ||
| this.updatePlayerActions(myPlayer, actions, this.clickedTile!); | ||
| }); | ||
| } | ||
|
|
||
| renderLayer(context: CanvasRenderingContext2D) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it maybe be cleaner to create: this.g.isSpectator() and it just does: isSpectator() { |
||
| if (!my && !isReplay) return html``; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so in this case we are in a live match but haven'ts spawned in? so we don't render the player panel? |
||
| if (!this.tile) return html``; | ||
|
|
||
| const owner = this.g.owner(this.tile); | ||
|
|
@@ -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()); | ||
|
|
||
| return html` | ||
| <style> | ||
|
|
@@ -946,7 +949,9 @@ 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` | ||
|
|
@@ -957,7 +962,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} | ||
|
|
@@ -973,7 +978,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} | ||
|
|
@@ -992,12 +997,14 @@ 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> | ||
|
|
||
|
|
@@ -1006,11 +1013,13 @@ export class PlayerPanel extends LitElement implements Layer { | |
|
|
||
| <!-- Alliance time remaining --> | ||
| ${this.renderAllianceExpiry()} | ||
|
|
||
| <ui-divider></ui-divider> | ||
|
|
||
| <!-- Actions --> | ||
| ${this.renderActions(my, other)} | ||
| ${isReplay | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, instead of isReplay, it's isSpectator because being dead is the equivalent of watching a replay |
||
| ? "" | ||
| : html` | ||
| <ui-divider></ui-divider> | ||
| <!-- Actions --> | ||
| ${this.renderActions(viewer, other)} | ||
| `} | ||
|
Comment on lines
1015
to
+1022
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hide or clear alliance-expiry state in replay mode Line 1015 still renders alliance expiry in replay, but replay does not refresh that value (live-only update path), so stale text can leak from previous state. Proposed fix- <!-- Alliance time remaining -->
- ${this.renderAllianceExpiry()}
+ <!-- Alliance time remaining -->
+ ${isReplay ? "" : this.renderAllianceExpiry()}Optionally also clear on replay entry in 🤖 Prompt for AI Agents |
||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so in this case we haven't spawned yet?