+ ${this.indicators.map((indicator) => {
+ const elapsed = this.game.ticks() - indicator.createdAt;
+ const remaining = indicator.duration - elapsed;
+ const remainingSeconds = Math.max(0, Math.ceil(remaining / 10));
+ const percentExpired = Math.min(
+ 100,
+ (elapsed / indicator.duration) * 100,
+ );
+
+ // Generate pattern preview if player has a pattern
+ const patternUrl = indicator.playerPattern
+ ? generatePatternPreviewDataUrl(indicator.playerPattern, 48)
+ : null;
+
+ return html`
+
(this.hoveredIndicator = indicator.id)}
+ @mouseleave=${() => {
+ if (!this.popupHovered) {
+ this.hoveredIndicator = null;
+ }
+ }}
+ >
+
this.handleFocus(indicator)}
+ >
+
+ ${patternUrl
+ ? html`

`
+ : ""}
+
+
+ ${indicator.type === "request"
+ ? html`
?
`
+ : html`
+ ⏳
+
`}
+ ${this.hoveredIndicator === indicator.id
+ ? html`
+
+
+
+
+ ${indicator.playerName}
+
+
${remainingSeconds}s
+ ${indicator.type === "renewal" &&
+ indicator.otherPlayerWantsRenewal
+ ? html`
+ ${translateText("events_display.wants_to_renew")}
+
`
+ : ""}
+
+ `
+ : ""}
+
+ `;
+ })}
+ ${this.pendingIndicators.length > 0
+ ? html`
+
+ +${this.pendingIndicators.length}
+ >
+
+ `
+ : ""}
+
+ `;
+ }
+
+ createRenderRoot() {
+ return this;
+ }
+}
diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts
index 2b66f33367..fab6fe9a0e 100644
--- a/src/client/graphics/layers/EventsDisplay.ts
+++ b/src/client/graphics/layers/EventsDisplay.ts
@@ -28,8 +28,6 @@ import {
import {
CancelAttackIntentEvent,
CancelBoatIntentEvent,
- SendAllianceExtensionIntentEvent,
- SendAllianceReplyIntentEvent,
SendAttackIntentEvent,
} from "../../Transport";
import { Layer } from "./Layer";
@@ -82,8 +80,6 @@ export class EventsDisplay extends LitElement implements Layer {
private active: boolean = false;
private events: GameEvent[] = [];
- // allianceID -> last checked at tick
- private alliancesCheckedAt = new Map