feat(rust): NPC sides, the faction table, an event's escort, ally and tether (runicnpc stage 5)
RunicNPC stage 5 on the site (docs runicnpc/PLAN.md, D253-D272):
- profiles gain the guard role, faction, relations (its own exceptions),
alertRadius, turrets, hurtByPlayers, hurtsPlayers and kitUse, checked in
RunicNPC's order and words, and defaulting to "players only" (D255);
- the faction table: one site-wide table, one row per pair and both ways
(D254, D268), stored in rust_npc_factions, edited on the NPC profiles page
(PUT /admin/rust/npcs/factions), pushed to every server with its
profiles and hashed with them, and a standalone server's own pairs
adopted at its first push with the site's winning (D244, D251);
- the Place NPCs step takes escort (a Steam id or a {placeholder}), an ally
(a clan from the new rust.options.clans source, or the team of a player)
and tether (the zone this event made), for a RunicNPC profile only
(D269, D270, D272);
- a placement may be held inside a zone (tether, D272);
- the bridge's RunicNPC API floor is 4.
Tests: 488 server, 66 client. routes.manifest.json regenerated against the
pinned core (one route added).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -12,6 +12,7 @@ const PROFILES = 'rust_npc_profiles'
|
||||
const PROFILE_SERVERS = 'rust_npc_profile_servers'
|
||||
const SYNC = 'rust_npc_sync'
|
||||
const KILLS = 'rust_npc_kills'
|
||||
const FACTIONS = 'rust_npc_factions'
|
||||
const SERVERS = 'rust_servers'
|
||||
const STATE = 'rust_server_state'
|
||||
const PLAYERS = 'rust_players'
|
||||
@@ -194,6 +195,29 @@ async function markDirty(serverIds = null) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── The faction table (stage 5, D254) ─────────────────────────────────────
|
||||
|
||||
/** Every pair, `a` before `b`, sorted. */
|
||||
async function listFactions() {
|
||||
return (await core.query(`SELECT a, b, relation FROM ${FACTIONS} ORDER BY a ASC, b ASC`)).map((r) => ({ a: r.a, b: r.b, relation: r.relation }))
|
||||
}
|
||||
|
||||
/** Replaces the whole table, in one transaction; the caller has checked it (`checkFactions`). */
|
||||
async function replaceFactions(rows) {
|
||||
const conn = await core.pool.getConnection()
|
||||
try {
|
||||
await conn.beginTransaction()
|
||||
await conn.query(`DELETE FROM ${FACTIONS}`)
|
||||
for (const r of rows) await conn.query(`INSERT INTO ${FACTIONS} (a, b, relation) VALUES (?, ?, ?)`, [r.a, r.b, r.relation])
|
||||
await conn.commit()
|
||||
} catch (err) {
|
||||
await conn.rollback().catch(() => {})
|
||||
throw err
|
||||
} finally {
|
||||
conn.release()
|
||||
}
|
||||
}
|
||||
|
||||
// ── Kills (D247) ────────────────────────────────────────────────────────────
|
||||
|
||||
async function addKills({ serverId, wipeId, steamId }, profile, siteProfileId, kills) {
|
||||
@@ -300,6 +324,8 @@ module.exports = {
|
||||
markAdopted,
|
||||
putSync,
|
||||
markDirty,
|
||||
listFactions,
|
||||
replaceFactions,
|
||||
addKills,
|
||||
scopeWhere,
|
||||
ranking,
|
||||
|
||||
Reference in New Issue
Block a user