// Shared formatting for shard events — used by the public Shard page, the // Activity feed, and the admin live feed. One place decides how each kind reads // and which category/badge it belongs to. function nameOf(who) { if (!who) return 'Someone' if (typeof who === 'string') return who return who.name || who.acct || 'Someone' } const n = (v) => Number(v || 0).toLocaleString() // A one-line human description of an event. Accepts either a stored event // (with .payload) or a raw live frame (fields at top level). export function describe(ev) { const p = ev.payload || ev switch (ev.kind) { case 'vendor.sale': return `${p.itemType || 'An item'}${p.amount > 1 ? ` ×${p.amount}` : ''} sold for ${n(p.price)}gp` case 'player.death': return `${nameOf(p.who)} was slain${p.killer ? ` by ${nameOf(p.killer)}` : ''}` case 'player.murdered': return `${nameOf(p.victim)} was murdered${p.murderer ? ` by ${nameOf(p.murderer)}` : ''}` case 'mob.killed': return `${nameOf(p.killer)} killed ${nameOf(p.killed)}` case 'skill.gain': return `${nameOf(p.who)} gained ${p.skill}${p.base != null ? ` (${p.base})` : ''}` case 'fame.change': return `${nameOf(p.who)}’s fame changed to ${n(p.new)}` case 'karma.change': return `${nameOf(p.who)}’s karma changed to ${n(p.new)}` case 'quest.complete': return `${nameOf(p.who)} completed “${p.quest}”` case 'house.decay': return `${p.name || 'A house'} is now ${p.to || p.stage}${p.region ? ` — ${p.region}` : ''}` case 'mob.login': return `${nameOf(p.who)} entered the world` case 'mob.logout': return `${nameOf(p.who)} left the world` case 'economy.supply': return `Gold supply: ${n(p.gold)} across ${n(p.accounts)} accounts` case 'server.hello': return `Shard online — ${n(p.accounts)} accounts, ${n(p.mobiles)} mobiles` case 'server.shutdown': return 'Shard shut down' case 'server.crashed': return `Shard crashed${p.error ? `: ${p.error}` : ''}` case 'champ.update': { const where = p.name || p.type || 'A champion spawn' if (p.status === 'active' && p.bossUp) return `${where}: boss is up${p.boss ? ` (${p.boss})` : ''}` if (p.status === 'active') return `${where} is active${p.level != null ? ` — level ${p.level}` : ''}` if (p.status === 'cooldown') return `${where} is on cooldown` return `${where} is ${p.status || 'idle'}` } case 'champ.remove': return `A champion spawn ended` // Support (help-page) queue + in-game moderation (admin channel only) case 'page.new': return `New ${p.type || 'help'} page from ${nameOf(p.sender)}` case 'page.updated': return `Help page from ${nameOf(p.sender)} updated${p.handled ? ' (claimed)' : ''}` case 'page.closed': return `Help page ${p.pageId || ''} closed` case 'admin.audit': return `${p.actor || 'Staff'} ${p.action || 'acted'}${p.target ? ` on ${p.target}` : ''}${p.origin ? ` [${p.origin}]` : ''}` // Staff / sensitive (admin channel only) case 'audit.set': return `${nameOf(p.staff) || 'Staff'} set ${p.prop} on ${p.target || p.targetSerial} (${p.old} → ${p.new})` case 'audit.command': return `${nameOf(p.staff) || 'Staff'} ran ${p.command}${p.args ? ` ${p.args}` : ''}` case 'cheat.fastwalk': return `Fast-walk flagged: ${nameOf(p.who)}${p.ip ? ` (${p.ip})` : ''}` case 'account.login.attempt': return `Login attempt: ${p.acct}${p.ip ? ` from ${p.ip}` : ''}` case 'gold.change': return `${p.acct}: gold ${p.delta >= 0 ? '+' : ''}${n(p.delta)} → ${n(p.new)}` default: return ev.kind } } // Category grouping for the filter tabs. // Vendor sales are intentionally NOT a public category — they are owner-private // (a linked player sees their own under the portal). The admin live feed still // describes vendor.sale via describe() below. export const CATEGORIES = [ { id: 'all', label: 'All', kinds: null }, { id: 'pvp', label: 'Deaths & PvP', kinds: ['player.death', 'player.murdered', 'mob.killed'] }, { id: 'progress', label: 'Progression', kinds: ['skill.gain', 'fame.change', 'karma.change', 'quest.complete'] }, { id: 'world', label: 'World', kinds: ['house.decay', 'mob.login', 'mob.logout', 'server.hello', 'server.shutdown', 'server.crashed', 'economy.supply'] }, ] const CATEGORY_OF = (() => { const m = {} for (const c of CATEGORIES) if (c.kinds) for (const k of c.kinds) m[k] = c.id return m })() export function categoryOf(kind) { return CATEGORY_OF[kind] || 'other' } // Short badge label for a kind (the part after the dot, title-cased-ish). export function kindLabel(kind) { return String(kind || '').replace(/[._]/g, ' ') }