// Roll the sidecar's raw presence.online `byRegion` map (many named ServUO // regions) up into a handful of labelled display buckets for the "Players Online" // widget. This is the ONE place to retune the grouping — edit BUCKETS (order + // membership) and the widget follows. Anything not matched lands in "Wilderness" // so the bucket counts always reconcile to the true total. // Named cities/towns, matched as a prefix on the (space/apostrophe-stripped) // region name so "skara brae", "serpent's hold", etc. all resolve. Kept as a // list rather than one giant alternation regex (simpler to read and retune). const TOWN_PREFIXES = [ 'moonglow', 'minoc', 'trinsic', 'jhelom', 'yew', 'skarabrae', 'magincia', 'newmagincia', 'vesper', 'nujelm', 'cove', 'ocllo', 'serpenthold', 'serpentshold', 'wind', 'delucia', 'papua', ] const normalizeRegion = (r) => String(r).toLowerCase().replace(/['’\s]/g, '') // Ordered list of buckets. `label` shows in the widget; `match(region)` decides // membership. First matching bucket wins; the last bucket is the catch-all. export const BUCKETS = [ { id: 'britain', label: 'Britain', // Passthrough for the capital + its immediate surrounds. match: (r) => /^britain/i.test(r), }, { id: 'towns', label: 'Towns', // The other named cities/towns. match: (r) => { const norm = normalizeRegion(r) return TOWN_PREFIXES.some((t) => norm.startsWith(t)) }, }, { id: 'dungeons', label: 'Dungeons', match: (r) => /(despise|destard|deceit|shame|hythloth|covetous|wrong|terathan|fire|ice|orc cave|dungeon|abyss|doom|khaldun|wrong|blackthorn|exodus|labyrinth|underworld)/i.test( r, ), }, { id: 'housing', label: 'Housing', // House regions expose themselves as named house/townhouse regions. match: (r) => /(house|townhouse|homestead|tent)/i.test(r), }, { id: 'wilderness', label: 'Wilderness', // Catch-all: the unnamed "Wilderness" region + anything unmatched above. match: () => true, }, ] // Given a raw { region: count } map, return [{ id, label, count }] in BUCKETS // order, dropping empty buckets, with the summed total also returned. export function bucketize(byRegion = {}) { const totals = new Map(BUCKETS.map((b) => [b.id, 0])) let total = 0 for (const [region, n] of Object.entries(byRegion || {})) { const count = Number(n) || 0 total += count const bucket = BUCKETS.find((b) => b.match(String(region))) || BUCKETS[BUCKETS.length - 1] totals.set(bucket.id, totals.get(bucket.id) + count) } const rows = BUCKETS.map((b) => ({ id: b.id, label: b.label, count: totals.get(b.id) })).filter( (r) => r.count > 0, ) return { rows, total } }