The website half of the protocol-5 bump. Engagement Phase 10.
Schema — twelve columns and two indexes.
shard_houses gains next_stage, estimated_collapse, decay_period_sec and
dynamic_decay. estimated_collapse is nullable and stays null far more often than
not, deliberately: under dynamic decay ServUO draws each stage at random on entry,
so collapse is knowable only at IDOC. A null means "not knowable", never "not yet
read".
shard_vendors gains owner_acct plus seven fee columns and an index on dismissal_at.
owner_acct is the structural one — the table has carried owner_name since protocol
3, but a character name joins to nothing, and only the game account reaches
shard_account_links. Until now a vendor row named an owner the site could not
resolve to a person. dismissal_at + owner_acct are what let Phase 11's
uo.vendor.expiring find "vendors about to be dismissed" and turn each into a
person, without scanning every shop.
Ingest.
Both new field groups arrive NESTED and are flattened into columns on the way in,
then re-nested on the way out — the same trick shardMarket already uses for
`location`. That is not stylistic: the visibility projection matches literal JSON
keys, so the stored read model and the live wire frame have to spell a group
identically or one admin rule covers only one of the two paths. It also means a
field added inside a group later inherits the group's gate instead of defaulting to
visible; there is a test that adds an imaginary future fee field and asserts exactly
that.
Two write-back asymmetries, both load-bearing:
* ownerName is written ONLY when the frame carries one. house.update also writes
that column, from a different sweep, and a pre-v5 overlay's house.decay carries
no ownerName at all — coalescing to null would let every decay transition erase
a name the registry had already resolved.
* The schedule and fee columns are written UNCONDITIONALLY, including as nulls. A
schedule is a claim about the future and goes stale on its own: roll a shard
back to a pre-v5 overlay, or let a house leave IDOC, and the right stored value
is nothing. A dismissal date nobody is maintaining is worse than none.
dismissalAt is taken from the shard rather than recomputed. The shard resolved it
against ServUO's two vendor systems, whose charge, funds and pay interval all
differ; re-deriving it here would be a second implementation of PlayerVendor's own
rule.
Visibility — three classifications, each chosen rather than inherited.
* house.decay's `schedule` defaults to `anonymous`. The countdown IS the public
IDOC page's content and a house at IDOC is already announced in game. Listed
anyway so a shard that considers a precise collapse time an unfair advantage can
raise it — and one nested rule takes the whole schedule with it.
* vendor.listing's `fees` defaults to `admin`, the only default in the market
feature that does not reproduce prior behaviour, because there is no prior
behaviour to reproduce. Shop name, owner and location are already visible to any
player through the in-game Vendor Search gump, which is the argument for
publishing them. Held gold, daily charge and dismissal date are visible to the
OWNER only, on that vendor's own gump. Publishing them anonymously would be a
new disclosure and a targeting aid — which shops are about to be abandoned, and
how much coin is in each.
* account.login.result is admin-only BY OMISSION. KIND_FEATURE is the map of kinds
an admin may widen, and there is no rung below admin that an IP plus an auth
verdict belongs on. The omission is the decision, and a test says so by name.
owner_acct needs no rule: rule 1 locks it by suffix. And the new columns are in no
REST read model's column list — they exist for Phase 11's server-side trigger and
reach no client at all.
The pin, and the protocol-4 bug seen from the other side.
Both declaration sites go to 5 (the model constant and schema.sql's CREATE default),
plus the one-shot migration, guarded `protocol < 5` so an install that missed an
earlier step is carried the whole way.
The schema test used to assert `DEFAULT 4` at each site. That is exactly how
protocol 4 shipped with the emitters moved and one site left behind: every site
agreed with itself and the test passed. It now reads DEFAULT_PROTOCOL from the
model, so the assertion is "the declarations AGREE", and the one-shot migration
test is written once against the current version instead of being hand-copied per
bump.
470 tests pass, 16 new. Verified end to end on the live rig against a real ServUO
and the release sidecar.
Docs: RunicGateway/docs link/v5.md.
Co-Authored-By: Claude <noreply@anthropic.com>
424 lines
18 KiB
JavaScript
424 lines
18 KiB
JavaScript
const { query } = require('../../core')
|
|
|
|
// Shared upsert builder for the shard-state tables. Each is keyed on a single
|
|
// primary column (`pkCol` = pk); `fields` carries only the columns the model
|
|
// wants to write, so a partial refresh touches nothing else. `coalesce` keeps
|
|
// the prior column value when the incoming one is NULL (used by shard_online so a
|
|
// vitals frame that omits acct/name doesn't blank what mob.login set); otherwise
|
|
// the incoming value wins (VALUES()).
|
|
function upsertRow(table, pkCol, pk, fields, { coalesce = false } = {}) {
|
|
const cols = Object.keys(fields)
|
|
const allCols = [pkCol, ...cols]
|
|
const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
|
|
const placeholders = allCols.map(() => '?').join(', ')
|
|
const rhs = coalesce
|
|
? (c) => `\`${c}\` = COALESCE(VALUES(\`${c}\`), \`${c}\`)`
|
|
: (c) => `\`${c}\` = VALUES(\`${c}\`)`
|
|
const updates = cols.map(rhs).join(', ')
|
|
return query(
|
|
`INSERT INTO ${table} (${insertCols}) VALUES (${placeholders})
|
|
ON DUPLICATE KEY UPDATE ${updates}`,
|
|
[pk, ...cols.map((c) => fields[c])],
|
|
)
|
|
}
|
|
|
|
// ── Online players ─────────────────────────────────────────────────────────
|
|
const ONLINE_COLS =
|
|
'serial, name, acct, web_id, map, x, y, z, hits, hits_max, mana, mana_max, stam, stam_max, str, dex, `int`, updated_at'
|
|
|
|
// Upsert one online player. `fields` already prepared by the model (only the
|
|
// columns it wants to write); serial is required and is the primary key.
|
|
// COALESCE variant: a char.vitals frame that omits acct/name must not blank what
|
|
// mob.login set, so an incoming NULL keeps the prior column value.
|
|
const upsertOnline = (serial, fields) =>
|
|
upsertRow('shard_online', 'serial', serial, fields, { coalesce: true })
|
|
|
|
const removeOnline = (serial) => query('DELETE FROM shard_online WHERE serial = ?', [serial])
|
|
const clearOnline = () => query('DELETE FROM shard_online')
|
|
|
|
async function countOnline() {
|
|
const rows = await query('SELECT COUNT(*) AS n FROM shard_online')
|
|
return rows[0] ? Number(rows[0].n) : 0
|
|
}
|
|
|
|
const listOnline = () =>
|
|
query(`SELECT ${ONLINE_COLS} FROM shard_online ORDER BY name ASC`)
|
|
|
|
// Online players on any of the given game accounts (admin: a user's linked
|
|
// accounts). Empty list short-circuits so we never emit `IN ()`.
|
|
const listOnlineByAccounts = (accounts) =>
|
|
accounts.length === 0
|
|
? Promise.resolve([])
|
|
: query(
|
|
`SELECT ${ONLINE_COLS} FROM shard_online
|
|
WHERE acct IN (${accounts.map(() => '?').join(', ')})
|
|
ORDER BY name ASC`,
|
|
accounts,
|
|
)
|
|
|
|
// Staff roles whose online presence is shown on the public Shard page. Players
|
|
// who link an account are NOT surfaced publicly — only staff opt into visibility
|
|
// by virtue of being staff.
|
|
const PUBLIC_ONLINE_ROLES = ['admin', 'editor', 'moderator']
|
|
|
|
// Online players whose game account is linked to a STAFF website user. Joined
|
|
// against shard_account_links (not the sidecar-supplied web_id) so a link takes
|
|
// effect immediately, regardless of whether the player has re-logged since
|
|
// linking, then through to users so only staff roles are surfaced publicly.
|
|
const listOnlineLinked = () => {
|
|
const cols = ONLINE_COLS.split(', ')
|
|
.map((c) => `o.${c}`)
|
|
.join(', ')
|
|
return query(
|
|
`SELECT ${cols}
|
|
FROM shard_online o
|
|
JOIN shard_account_links l ON l.account = o.acct
|
|
JOIN users u ON u.id = l.user_id
|
|
WHERE u.role IN (${PUBLIC_ONLINE_ROLES.map(() => '?').join(', ')})
|
|
ORDER BY o.name ASC`,
|
|
PUBLIC_ONLINE_ROLES,
|
|
)
|
|
}
|
|
|
|
// ── Economy supply series ────────────────────────────────────────────────
|
|
const insertEconomy = ({ accounts, gold, t }) =>
|
|
query('INSERT INTO shard_economy (accounts, gold, t) VALUES (?, ?, ?)', [
|
|
accounts ?? null,
|
|
gold ?? null,
|
|
t,
|
|
])
|
|
|
|
const listEconomy = (limit) =>
|
|
query('SELECT accounts, gold, t FROM shard_economy ORDER BY t DESC LIMIT ?', [limit])
|
|
|
|
async function latestEconomy() {
|
|
const rows = await query('SELECT accounts, gold, t FROM shard_economy ORDER BY t DESC LIMIT 1')
|
|
return rows[0] || null
|
|
}
|
|
|
|
// ── Houses / IDOC ────────────────────────────────────────────────────────
|
|
const HOUSE_COLS =
|
|
'serial, stage, map, x, y, z, region, name, owner_serial, owner_acct, built_on, last_refreshed, is_idoc, updated_at' +
|
|
// Protocol 5's decay schedule. Added to the BASE column list rather than to
|
|
// HOUSE_REG_COLS because it arrives on house.decay, so a decay-only row -- one the
|
|
// registry sweep has never seen -- carries it too, and the public IDOC page reads
|
|
// exactly those rows.
|
|
', next_stage, estimated_collapse, decay_period_sec, dynamic_decay'
|
|
|
|
const upsertHouse = (serial, fields) => upsertRow('shard_houses', 'serial', serial, fields)
|
|
|
|
const listIdocHouses = () =>
|
|
query(`SELECT ${HOUSE_COLS} FROM shard_houses WHERE is_idoc = 1 ORDER BY updated_at DESC`)
|
|
|
|
// Houses owned by any of the given game accounts (admin: a user's linked
|
|
// accounts). IDOC houses first, then newest-refreshed. Empty list short-circuits.
|
|
const listHousesByAccounts = (accounts) =>
|
|
accounts.length === 0
|
|
? Promise.resolve([])
|
|
: query(
|
|
`SELECT ${HOUSE_REG_COLS} FROM shard_houses
|
|
WHERE owner_acct IN (${accounts.map(() => '?').join(', ')})
|
|
ORDER BY is_idoc DESC, updated_at DESC`,
|
|
accounts,
|
|
)
|
|
|
|
// ── House registry (Protocol 2.0 house.update / house.remove) ──────────────
|
|
// The registry columns extend HOUSE_COLS; a registry row is one we've seen via
|
|
// house.update (in_registry = 1), as opposed to a decay-only transition row.
|
|
const HOUSE_REG_COLS = `${HOUSE_COLS}, owner_name, co_owners, friends, price, decay, in_registry`
|
|
|
|
const removeHouse = (serial) => query('DELETE FROM shard_houses WHERE serial = ?', [serial])
|
|
|
|
// The full registered-house browser: every row we've seen via house.update.
|
|
const listRegistryHouses = () =>
|
|
query(`SELECT ${HOUSE_REG_COLS} FROM shard_houses WHERE in_registry = 1 ORDER BY name ASC`)
|
|
|
|
// ── Champion spawns ────────────────────────────────────────────────────────
|
|
const CHAMP_COLS =
|
|
'serial, category, type, name, status, active, map, x, y, z, boss_up, payload, t, updated_at'
|
|
|
|
const upsertChamp = (serial, fields) => upsertRow('shard_champs', 'serial', serial, fields)
|
|
|
|
const removeChamp = (serial) => query('DELETE FROM shard_champs WHERE serial = ?', [serial])
|
|
const clearChamps = () => query('DELETE FROM shard_champs')
|
|
// Ordered by name (matches the sidecar's /champs ordering).
|
|
const listChamps = () => query(`SELECT ${CHAMP_COLS} FROM shard_champs ORDER BY name ASC`)
|
|
|
|
// ── Help-page (support) queue ──────────────────────────────────────────────
|
|
const PAGE_COLS =
|
|
'page_id, type, sender_name, sender_acct, web_id, message, map, x, y, z, sent_ms, handled, handler, payload, updated_at'
|
|
|
|
async function upsertPage(pageId, fields) {
|
|
const cols = Object.keys(fields)
|
|
const allCols = ['page_id', ...cols]
|
|
const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
|
|
const placeholders = allCols.map(() => '?').join(', ')
|
|
const updates = cols.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ')
|
|
await query(
|
|
`INSERT INTO shard_pages (${insertCols}) VALUES (${placeholders})
|
|
ON DUPLICATE KEY UPDATE ${updates}`,
|
|
[pageId, ...cols.map((c) => fields[c])],
|
|
)
|
|
}
|
|
|
|
const removePage = (pageId) => query('DELETE FROM shard_pages WHERE page_id = ?', [pageId])
|
|
const clearPages = () => query('DELETE FROM shard_pages')
|
|
// Oldest-open first so the queue reads like a work list.
|
|
const listPages = () => query(`SELECT ${PAGE_COLS} FROM shard_pages ORDER BY sent_ms ASC`)
|
|
|
|
// ── Guild board (Protocol 2.0) ─────────────────────────────────────────────
|
|
const GUILD_COLS =
|
|
'id, name, abbr, members, online, alliance, leader_serial, leader_name, leader_acct, leader_web_id, payload, t, updated_at'
|
|
|
|
const upsertGuild = (id, fields) => upsertRow('shard_guilds', 'id', id, fields)
|
|
|
|
const removeGuild = (id) => query('DELETE FROM shard_guilds WHERE id = ?', [id])
|
|
const clearGuilds = () => query('DELETE FROM shard_guilds')
|
|
const listGuilds = () => query(`SELECT ${GUILD_COLS} FROM shard_guilds ORDER BY name ASC`)
|
|
|
|
// ── Guild membership (Protocol 4) ──────────────────────────────────────────
|
|
// `rank` is backticked wherever it is written, like `int` on shard_online: it is a
|
|
// reserved word in MySQL 8 and merely a keyword in MariaDB, so it parses bare here
|
|
// and must not be relied on to.
|
|
const MEMBER_COLS = 'guild_id, serial, name, acct, web_id, is_player, `rank`, rank_cliloc, rank_name, t'
|
|
|
|
// Upsert rather than plain insert: a roster frame can be redelivered (the /history
|
|
// backfill replays stored frames on every reconnect), and a redelivery must be a
|
|
// no-op rather than a duplicate-key error.
|
|
//
|
|
// The rank columns are assigned unconditionally, NULL included. A member whose rank
|
|
// the shard withheld — a staff account, whose GuildRank getter reports Leader
|
|
// regardless of the truth — must go back to "not known" rather than keeping a rank
|
|
// from before they were promoted.
|
|
const upsertGuildMembers = (rows) => {
|
|
if (!rows.length) return Promise.resolve()
|
|
const values = rows.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ')
|
|
const params = rows.flatMap((r) => [
|
|
r.guild_id, r.serial, r.name, r.acct, r.web_id, r.is_player,
|
|
r.rank, r.rank_cliloc, r.rank_name, r.t,
|
|
])
|
|
return query(
|
|
`INSERT INTO shard_guild_members (${MEMBER_COLS}) VALUES ${values}
|
|
ON DUPLICATE KEY UPDATE name = VALUES(name), acct = VALUES(acct),
|
|
web_id = VALUES(web_id), is_player = VALUES(is_player),
|
|
\`rank\` = VALUES(\`rank\`), rank_cliloc = VALUES(rank_cliloc),
|
|
rank_name = VALUES(rank_name), t = VALUES(t)`,
|
|
params,
|
|
)
|
|
}
|
|
|
|
const clearGuildMembers = (guildId) =>
|
|
query('DELETE FROM shard_guild_members WHERE guild_id = ?', [guildId])
|
|
|
|
const removeGuildMember = (guildId, serial) =>
|
|
query('DELETE FROM shard_guild_members WHERE guild_id = ? AND serial = ?', [guildId, serial])
|
|
|
|
const clearAllGuildMembers = () => query('DELETE FROM shard_guild_members')
|
|
|
|
const listGuildMembers = (guildId) =>
|
|
query(`SELECT ${MEMBER_COLS} FROM shard_guild_members WHERE guild_id = ? ORDER BY name ASC`, [
|
|
guildId,
|
|
])
|
|
|
|
// The guild an actor LEADS — matched on the current board (leader_serial or the
|
|
// linked leader_acct), so it reflects live state. Guild MEMBERSHIP for non-leaders
|
|
// is not modelled (the board carries only counts + leader), so we don't guess it.
|
|
const findGuildLedByActor = (serial, acct) =>
|
|
query(
|
|
`SELECT id, name, abbr, alliance, leader_name FROM shard_guilds
|
|
WHERE leader_serial = ? OR (leader_acct IS NOT NULL AND leader_acct = ?)
|
|
LIMIT 1`,
|
|
[serial ?? null, acct ?? null],
|
|
)
|
|
|
|
// Guilds led by any of the given game accounts (admin: a user's linked accounts).
|
|
const listGuildsLedByAccounts = (accounts) =>
|
|
accounts.length === 0
|
|
? Promise.resolve([])
|
|
: query(
|
|
`SELECT id, name, abbr, alliance, leader_name FROM shard_guilds
|
|
WHERE leader_acct IN (${accounts.map(() => '?').join(', ')})
|
|
ORDER BY name ASC`,
|
|
accounts,
|
|
)
|
|
|
|
// ── Governor board + term history (Protocol 2.0) ───────────────────────────
|
|
const GOV_COLS =
|
|
'city, governor_serial, governor_name, governor_acct, governor_web_id, elect_serial, elect_name, elect_acct, election_phase, candidates, auto_pick_at, payload, t, updated_at'
|
|
|
|
const upsertGovernor = (city, fields) => upsertRow('shard_governors', 'city', city, fields)
|
|
|
|
const listGovernors = () => query(`SELECT ${GOV_COLS} FROM shard_governors ORDER BY city ASC`)
|
|
|
|
// Cities whose current governor is one of the given game accounts (cross-link:
|
|
// does this user hold a governorship?). Empty list short-circuits.
|
|
const listGovernorshipsByAccounts = (accounts) =>
|
|
accounts.length === 0
|
|
? Promise.resolve([])
|
|
: query(
|
|
`SELECT ${GOV_COLS} FROM shard_governors
|
|
WHERE governor_acct IN (${accounts.map(() => '?').join(', ')})
|
|
ORDER BY city ASC`,
|
|
accounts,
|
|
)
|
|
|
|
// The single open term (ended_at IS NULL) for a city, if any.
|
|
async function currentGovernorTerm(city) {
|
|
const rows = await query(
|
|
'SELECT id, city, governor_serial, governor_name, governor_acct, governor_web_id, started_at, ended_at, votes FROM shard_governor_terms WHERE city = ? AND ended_at IS NULL ORDER BY started_at DESC LIMIT 1',
|
|
[city],
|
|
)
|
|
return rows[0] || null
|
|
}
|
|
|
|
const closeGovernorTerm = (id, endedAt) =>
|
|
query('UPDATE shard_governor_terms SET ended_at = ? WHERE id = ?', [endedAt, id])
|
|
|
|
const openGovernorTerm = ({ city, serial, name, acct, webId, startedAt }) =>
|
|
query(
|
|
`INSERT INTO shard_governor_terms
|
|
(city, governor_serial, governor_name, governor_acct, governor_web_id, started_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
[city, serial ?? null, name ?? null, acct ?? null, webId ?? null, startedAt],
|
|
)
|
|
|
|
const listGovernorTerms = (city, limit) =>
|
|
query(
|
|
'SELECT id, city, governor_serial, governor_name, governor_acct, governor_web_id, started_at, ended_at, votes FROM shard_governor_terms WHERE city = ? ORDER BY started_at DESC LIMIT ?',
|
|
[city, limit],
|
|
)
|
|
|
|
// ── Online-population snapshot (Protocol 2.0 presence.online) ───────────────
|
|
async function setPresence({ count, byFacet, byRegion, t }) {
|
|
await query(
|
|
`INSERT INTO shard_presence (id, count, by_facet, by_region, t) VALUES (1, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE count = VALUES(count), by_facet = VALUES(by_facet),
|
|
by_region = VALUES(by_region), t = VALUES(t)`,
|
|
[
|
|
Number.isFinite(count) ? count : 0,
|
|
byFacet ? JSON.stringify(byFacet) : null,
|
|
byRegion ? JSON.stringify(byRegion) : null,
|
|
Number.isFinite(t) ? t : null,
|
|
],
|
|
)
|
|
}
|
|
|
|
async function latestPresence() {
|
|
const rows = await query('SELECT count, by_facet, by_region, t FROM shard_presence WHERE id = 1')
|
|
return rows[0] || null
|
|
}
|
|
|
|
// ── Shard ruleset (Protocol 3.0 world.ruleset) ─────────────────────────────
|
|
// Singleton, same shape as shard_presence: the shard re-emits the whole frame on
|
|
// every connect, so there is nothing to merge — the latest one wins outright.
|
|
async function setRuleset({ rev, expansion, payload, t }) {
|
|
await query(
|
|
`INSERT INTO shard_ruleset (id, rev, expansion, payload, t) VALUES (1, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE rev = VALUES(rev), expansion = VALUES(expansion),
|
|
payload = VALUES(payload), t = VALUES(t)`,
|
|
[rev ?? null, expansion ?? null, payload, Number.isFinite(t) ? t : null],
|
|
)
|
|
}
|
|
|
|
async function getRuleset() {
|
|
const rows = await query(
|
|
'SELECT rev, expansion, payload, t, updated_at FROM shard_ruleset WHERE id = 1',
|
|
)
|
|
return rows[0] || null
|
|
}
|
|
|
|
// ── Points/loyalty boards (Protocol 3.0 points.board) ──────────────────────
|
|
// One row per point system. The shard only emits a system whose top N actually
|
|
// moved, so this is a sparse stream of overwrites; there is no delete, because
|
|
// the shard's set of systems is fixed at startup.
|
|
async function upsertPointsBoard({ system, name, nameCliloc, maxPoints, players, showOnGump, payload, t }) {
|
|
await query(
|
|
`INSERT INTO shard_points_boards
|
|
(system, name, name_cliloc, max_points, players, show_on_gump, payload, t)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE name = VALUES(name), name_cliloc = VALUES(name_cliloc),
|
|
max_points = VALUES(max_points), players = VALUES(players),
|
|
show_on_gump = VALUES(show_on_gump), payload = VALUES(payload), t = VALUES(t)`,
|
|
[
|
|
system,
|
|
name ?? null,
|
|
Number.isFinite(nameCliloc) ? nameCliloc : null,
|
|
Number.isFinite(maxPoints) ? maxPoints : null,
|
|
Number.isFinite(players) ? players : null,
|
|
showOnGump ? 1 : 0,
|
|
payload,
|
|
Number.isFinite(t) ? t : null,
|
|
],
|
|
)
|
|
}
|
|
|
|
// Ordered by display name, falling back to the system key for a board whose name
|
|
// arrived as a bare cliloc — otherwise every unresolved board would sort together
|
|
// under NULL.
|
|
async function listPointsBoards() {
|
|
return query(
|
|
`SELECT system, name, name_cliloc, max_points, players, show_on_gump, payload, t, updated_at
|
|
FROM shard_points_boards ORDER BY COALESCE(name, system), system`,
|
|
)
|
|
}
|
|
|
|
async function getPointsBoard(system) {
|
|
const rows = await query(
|
|
`SELECT system, name, name_cliloc, max_points, players, show_on_gump, payload, t, updated_at
|
|
FROM shard_points_boards WHERE system = ?`,
|
|
[system],
|
|
)
|
|
return rows[0] || null
|
|
}
|
|
|
|
module.exports = {
|
|
upsertOnline,
|
|
removeOnline,
|
|
clearOnline,
|
|
countOnline,
|
|
listOnline,
|
|
listOnlineLinked,
|
|
listOnlineByAccounts,
|
|
insertEconomy,
|
|
listEconomy,
|
|
latestEconomy,
|
|
upsertHouse,
|
|
listIdocHouses,
|
|
listHousesByAccounts,
|
|
removeHouse,
|
|
listRegistryHouses,
|
|
upsertGuild,
|
|
removeGuild,
|
|
clearGuilds,
|
|
listGuilds,
|
|
upsertGuildMembers,
|
|
clearGuildMembers,
|
|
removeGuildMember,
|
|
clearAllGuildMembers,
|
|
listGuildMembers,
|
|
findGuildLedByActor,
|
|
listGuildsLedByAccounts,
|
|
upsertGovernor,
|
|
listGovernors,
|
|
listGovernorshipsByAccounts,
|
|
currentGovernorTerm,
|
|
closeGovernorTerm,
|
|
openGovernorTerm,
|
|
listGovernorTerms,
|
|
setPresence,
|
|
latestPresence,
|
|
setRuleset,
|
|
getRuleset,
|
|
upsertPointsBoard,
|
|
listPointsBoards,
|
|
getPointsBoard,
|
|
upsertChamp,
|
|
removeChamp,
|
|
clearChamps,
|
|
listChamps,
|
|
upsertPage,
|
|
removePage,
|
|
clearPages,
|
|
listPages,
|
|
}
|