// Live shard state derived from the WS feed: who is online, the gold-supply // series, and per-house decay stage. The ingest dispatcher calls the write // methods; the public read endpoints call the list/count methods. Writes take // camelCase semantic objects and map to the snake_case columns; only the keys // present are written (so a char.vitals refresh doesn't clobber login fields). const db = require('./shardState.db') const MAX_ECONOMY = 1000 // Map a camelCase online descriptor to DB columns, dropping undefined keys so a // partial refresh only touches the fields it carries. function onlineFields(data) { const map = { name: data.name, acct: data.acct, web_id: data.webId, map: data.map, x: data.x, y: data.y, z: data.z, hits: data.hits, hits_max: data.hitsMax, mana: data.mana, mana_max: data.manaMax, stam: data.stam, stam_max: data.stamMax, str: data.str, dex: data.dex, int: data.int, } const fields = {} for (const [k, v] of Object.entries(map)) if (v !== undefined) fields[k] = v return fields } // Upsert an online player (mob.login) or refresh their vitals (char.vitals). async function upsertOnline(data) { if (!data || !data.serial) return await db.upsertOnline(data.serial, onlineFields(data)) } const setOffline = (serial) => db.removeOnline(serial) const clearOnline = () => db.clearOnline() const onlineCount = () => db.countOnline() function shapeOnline(r) { return { serial: r.serial, name: r.name, acct: r.acct, webId: r.web_id, map: r.map, x: r.x, y: r.y, z: r.z, hits: r.hits, hitsMax: r.hits_max, mana: r.mana, manaMax: r.mana_max, stam: r.stam, stamMax: r.stam_max, str: r.str, dex: r.dex, int: r.int, updatedAt: r.updated_at, } } // Only players whose account is linked to a website user (opt-in visibility). async function listOnlineLinked() { const rows = await db.listOnlineLinked() return rows.map(shapeOnline) } async function listOnline() { const rows = await db.listOnline() return rows.map((r) => ({ serial: r.serial, name: r.name, acct: r.acct, webId: r.web_id, map: r.map, x: r.x, y: r.y, z: r.z, hits: r.hits, hitsMax: r.hits_max, mana: r.mana, manaMax: r.mana_max, stam: r.stam, stamMax: r.stam_max, str: r.str, dex: r.dex, int: r.int, updatedAt: r.updated_at, })) } // Append a gold-supply sample (economy.supply). async function addEconomySample({ accounts, gold, t }) { await db.insertEconomy({ accounts, gold, t }) } async function listEconomy(limit = 100) { const n = Math.min(Math.max(Number(limit) || 100, 1), MAX_ECONOMY) const rows = await db.listEconomy(n) // Return oldest → newest for charting. return rows .map((r) => ({ accounts: r.accounts, gold: r.gold == null ? null : Number(r.gold), t: r.t })) .reverse() } async function latestEconomy() { const r = await db.latestEconomy() return r ? { accounts: r.accounts, gold: r.gold == null ? null : Number(r.gold), t: r.t } : null } // Upsert a house's decay stage (house.decay). is_idoc is derived from the stage. async function upsertHouse(data) { if (!data || !data.serial) return const fields = { stage: data.stage ?? null, map: data.map ?? null, x: data.x ?? null, y: data.y ?? null, z: data.z ?? null, region: data.region ?? null, name: data.name ?? null, owner_serial: data.ownerSerial ?? null, owner_acct: data.ownerAcct ?? null, built_on: data.builtOn ? new Date(data.builtOn) : null, last_refreshed: data.lastRefreshed ? new Date(data.lastRefreshed) : null, is_idoc: String(data.stage).toUpperCase() === 'IDOC' ? 1 : 0, } await db.upsertHouse(data.serial, fields) } function shapeHouse(r) { return { serial: r.serial, stage: r.stage, map: r.map, x: r.x, y: r.y, z: r.z, region: r.region, name: r.name, ownerSerial: r.owner_serial, ownerAcct: r.owner_acct, builtOn: r.built_on, lastRefreshed: r.last_refreshed, isIdoc: Boolean(r.is_idoc), updatedAt: r.updated_at, } } async function listIdoc() { const rows = await db.listIdocHouses() return rows.map(shapeHouse) } // Houses owned by the given game accounts (admin: a user's linked accounts). async function listHousesForAccounts(accounts) { const rows = await db.listHousesByAccounts(accounts) return rows.map(shapeHouse) } // Online players on the given game accounts (admin: a user's linked accounts). async function listOnlineForAccounts(accounts) { const rows = await db.listOnlineByAccounts(accounts) return rows.map(shapeOnline) } module.exports = { upsertOnline, setOffline, clearOnline, onlineCount, listOnline, listOnlineLinked, listOnlineForAccounts, addEconomySample, listEconomy, latestEconomy, upsertHouse, listIdoc, listHousesForAccounts, }