// The game feed: a chat-like column of short-lived messages pinned to the // lower-left corner of the map. Newest lines sit at the bottom and each line // retires on its own, so the map is never permanently obstructed. export const FEED_TTL_MS = 4500; export const FEED_ALERT_TTL_MS = 8000; const FEED_FADE_MS = 400; export class GameFeed { constructor($container, options = {}) { this.$container = $container; this.ttl = options.ttl !== undefined ? options.ttl : FEED_TTL_MS; this.alertTtl = options.alertTtl !== undefined ? options.alertTtl : FEED_ALERT_TTL_MS; this.fade = options.fade !== undefined ? options.fade : FEED_FADE_MS; this._timers = new Set(); } // Appends one line at the bottom of the feed. `alert` marks a message the // player must not miss (being declared war on) and lets it linger longer. // A negative `ttl` keeps the line until `clear` or a later retirement. push(text, { alert = false, ttl } = {}) { if (!this.$container || !this.$container.length) return null; const life = ttl !== undefined ? ttl : alert ? this.alertTtl : this.ttl; const $line = $("
").text(text); if (alert) $line.addClass("feed-alert"); this.$container.append($line); if (life >= 0) { const timer = setTimeout(() => { this._timers.delete(timer); this._retire($line); }, life); this._timers.add(timer); } return $line; } _retire($line) { $line.addClass("leaving"); if (this.fade <= 0) { $line.remove(); return; } const timer = setTimeout(() => { this._timers.delete(timer); $line.remove(); }, this.fade); this._timers.add(timer); } clear() { for (const timer of this._timers) clearTimeout(timer); this._timers.clear(); if (this.$container) this.$container.empty(); } }