diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index efbaad8..1cd1ade 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -2958,7 +2958,7 @@ {:else} {#each net.log as line, i (i)}
- {line.text} + {line.text} {#if line.notable && line.turn !== null && prefs.instantReplay && !net.spectating} @@ -3848,6 +3848,14 @@ min-height: 1.55rem; } .slip.creature-note { background: #e7edda; border-color: #7d8a5a; } + .score-row.active { + font-weight: 700; + background: rgba(58, 47, 31, 0.08); + border-left: 3px solid #3a2f1f; + margin-left: -0.45rem; + padding-left: 0.3rem; + border-radius: 2px; + } .score-row.active .score-name { font-weight: 700; } .score-temper { font-family: "Caveat", cursive; @@ -3916,6 +3924,17 @@ box-shadow: 0 2px 7px rgba(0, 0, 0, 0.45); } .chronicle div + div { margin-top: 0.05rem; } + /* the acting wizard's color, so one player's lines can be picked out at a glance */ + .log-mark { + display: inline-block; + width: 0.55em; + height: 0.55em; + margin-right: 0.45em; + border-radius: 2px; + border: 1px solid rgba(58, 47, 31, 0.35); + vertical-align: 0.05em; + } + .log-mark.blank { border-color: transparent; } .moment-eye { background: none; border: none; diff --git a/packages/web/src/net.svelte.ts b/packages/web/src/net.svelte.ts index 187049a..2469518 100644 --- a/packages/web/src/net.svelte.ts +++ b/packages/web/src/net.svelte.ts @@ -234,6 +234,22 @@ export interface LogLine { text: string; turn: number | null; notable: boolean; + /** The wizard acting, for the color mark beside the line: whoever + * cast, struck, walked, or countered — otherwise the turn's owner. */ + actor?: string; +} + +/** Events whose `player` is the one acting rather than the one acted on. */ +const ACTING = new Set([ + "moved", "warpStepped", "jumpedPit", "counteractionPlayed", "treasurePickedUp", "treasureDropped", + "numberPlayedForMovement", "cardsDiscarded", "turnStarted", "turnEnded", "doorUnlocked", "madDash", + "climbedFromPit", "spellTrapped", +]); +function actorOf(e: GameEvent, turnOwner: string | null): string | undefined { + const any = e as unknown as Record; + for (const k of ["caster", "attacker", "by", "owner"]) if (typeof any[k] === "string") return any[k] as string; + if (ACTING.has(e.type) && typeof any.player === "string") return any.player as string; + return turnOwner ?? undefined; } /** The boundaries the turn count walks — mirrored by the server's @@ -305,6 +321,8 @@ class Net { * socket that never fires onclose; the watchdog closes it by hand so * the reconnect loop (and the masthead banner) actually engage. */ private lastHeard = Date.now(); + /** Whose turn the chronicle is in, for the mark on lines with no actor of their own. */ + private turnOwner: string | null = null; private watchdog: ReturnType | null = null; roomId = $state(null); players = $state([]); @@ -510,6 +528,7 @@ class Net { this.openingRolls = { rolls: e.dieRolls, first: e.firstPlayer, players: e.players }; } if (TURN_BOUNDARY.has(e.type)) this.turnCounter++; + if (e.type === "turnStarted") this.turnOwner = e.player; const line = humanize(e); if (line) { // Every turn wears its eye, on the header line that opens @@ -517,7 +536,7 @@ class Net { // winning line carries its own besides. const notable = this.turnCounter >= 0 && (e.type === "gameWon" || TURN_BOUNDARY.has(e.type)); - this.log = [...this.log, { text: line, turn: this.turnCounter >= 0 ? this.turnCounter : null, notable }]; + this.log = [...this.log, { text: line, turn: this.turnCounter >= 0 ? this.turnCounter : null, notable, actor: actorOf(e, this.turnOwner) }]; } } if (talk > 0) { @@ -545,7 +564,7 @@ class Net { break; } case "chat": { - this.log = [...this.log, { text: `\u{1F4AC} ${msg.player}: ${msg.text}`, turn: null, notable: false }]; + this.log = [...this.log, { text: `\u{1F4AC} ${msg.player}: ${msg.text}`, turn: null, notable: false, actor: msg.player }]; this.chatCount += 1; if (this.roomId) this.markChatSeen(); break;