feat(rust): NPC profile and placement routes, the event picker, triggers and titles (runicnpc stage 4)
Admin: /admin/rust/npcs for profiles (create, change, delete, restore a replaced one, push now) and each server's placements (list, add from a map point, change, remove, rename, respawn). Public: the profiles a leaderboard ranks by, one profile's ranking counted as the profile says (D247, D250), and one player's kills by profile (D252). Player: your own kills by profile. The Place NPCs step offers the site's profiles first, then Rust's own (D243). rust.npc.died and rust.npc.health are triggers a phase can wait on. A title rule can rank a profile's kills. Swagger fragment, engagement and route manifests regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -16,7 +16,9 @@ const events = require('../../model/events/events.model')
|
||||
const map = require('../../model/map/map.model')
|
||||
const mapDb = require('../../model/map/map.db')
|
||||
const mapLive = require('../../mapLive')
|
||||
const npcs = require('../../model/npcs/npcs.model')
|
||||
const servers = require('../../model/servers/servers.model')
|
||||
const serversDb = require('../../model/servers/servers.db')
|
||||
const titles = require('../../model/titles/titles.model')
|
||||
const visibility = require('../../model/visibility/visibility.model')
|
||||
|
||||
@@ -130,6 +132,51 @@ async function listLeaderboard(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /servers/:id/npc-profiles — what the leaderboard's profile picker offers (D250). */
|
||||
async function listNpcProfiles(req, res) {
|
||||
try {
|
||||
res.json({ profiles: await npcs.boardProfiles(req.params.id) })
|
||||
} catch (err) {
|
||||
log.error('failed to read the NPC profiles', { server: req.params.id, error: err.message })
|
||||
res.status(500).json({ message: 'Failed to read the NPC profiles' })
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /servers/:id/npc-leaderboard?profile= — one profile's kills, counted as it says (D247, D250). */
|
||||
async function npcLeaderboard(req, res) {
|
||||
const profileId = Number(req.query.profile)
|
||||
if (!Number.isInteger(profileId) || profileId < 1) return res.status(400).json({ message: 'profile is a profile id' })
|
||||
|
||||
try {
|
||||
const state = await serversDb.getState(req.params.id)
|
||||
const wipeId = req.query.wipe ? String(req.query.wipe) : null
|
||||
const currentWipe = Boolean(wipeId && state && state.wipeId === wipeId)
|
||||
const [ranked, held] = await Promise.all([
|
||||
npcs.ranking({ serverId: req.params.id, profileId, wipeId, currentWipe, limit: req.query.limit }),
|
||||
titles.currentFor(req.params.id),
|
||||
])
|
||||
res.json({ profile: ranked.profile, leaderboard: ranked.rows.map((row) => ({ ...row, kills: row.value, titles: held.get(row.steamId) || [] })) })
|
||||
} catch (err) {
|
||||
if (err instanceof npcs.NpcError) return res.status(err.status).json({ message: err.message })
|
||||
log.error('failed to read an NPC leaderboard', { server: req.params.id, error: err.message })
|
||||
return res.status(500).json({ message: 'Failed to read the leaderboard' })
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /servers/:id/players/:steamId/npc-kills — one leaderboard row, opened (D252). */
|
||||
async function playerNpcKills(req, res) {
|
||||
const steamId = String(req.params.steamId || '')
|
||||
if (!/^\d{1,20}$/.test(steamId)) return res.status(400).json({ message: 'steamId is a Steam id' })
|
||||
|
||||
try {
|
||||
const wipeId = req.query.wipe ? String(req.query.wipe) : null
|
||||
res.json({ steamId, kills: await npcs.playerKills({ serverId: req.params.id, steamId, wipeId }) })
|
||||
} catch (err) {
|
||||
log.error('failed to read a player’s NPC kills', { server: req.params.id, error: err.message })
|
||||
res.status(500).json({ message: 'Failed to read the kills' })
|
||||
}
|
||||
}
|
||||
|
||||
async function listWipes(req, res) {
|
||||
try {
|
||||
res.json({ wipes: await events.wipes(req.params.id) })
|
||||
@@ -353,6 +400,9 @@ module.exports = {
|
||||
getServer,
|
||||
listEvents,
|
||||
listLeaderboard,
|
||||
listNpcProfiles,
|
||||
npcLeaderboard,
|
||||
playerNpcKills,
|
||||
listWipes,
|
||||
listOnline,
|
||||
listClans,
|
||||
|
||||
@@ -92,6 +92,45 @@ rustRouter.get(
|
||||
servers.listLeaderboard,
|
||||
)
|
||||
|
||||
rustRouter.get(
|
||||
'/servers/:id/npc-profiles',
|
||||
// #swagger.tags = ['Public · Rust']
|
||||
// #swagger.summary = 'The NPC profiles a server’s leaderboard can rank by'
|
||||
// #swagger.description = 'The site’s RunicNPC profiles pushed to this server (docs/runicnpc/PLAN.md stage 4, D250): each one’s id, its name, a label (the first of its NPC names), and how its kills are counted (D247) — `server` (this server’s kills of that name), `name` (every server’s kills of that name) or `profile` (this site profile’s, wherever it was pushed). Empty on a site without RunicNPC.'
|
||||
// #swagger.parameters['id'] = { in: 'path', required: true, description: 'The server’s slug', schema: { type: 'string' } }
|
||||
/* #swagger.responses[200] = { description: 'The profiles', content: { "application/json": { schema: { $ref: "#/components/schemas/RustNpcBoardProfiles" } } } } */
|
||||
siteMode,
|
||||
servers.listNpcProfiles,
|
||||
)
|
||||
|
||||
rustRouter.get(
|
||||
'/servers/:id/npc-leaderboard',
|
||||
// #swagger.tags = ['Public · Rust']
|
||||
// #swagger.summary = 'Who has killed the most NPCs of one profile'
|
||||
// #swagger.description = 'One RunicNPC profile’s kills, most first, counted as the profile says (D247, D250). Per wipe when `wipe` is given, all time otherwise. A profile counted across servers reaches every server’s current wipe when `wipe` is this server’s current one, and only this server’s rows for an older wipe. Each row carries the chat `titles` the player holds now, as the main leaderboard’s do.'
|
||||
// #swagger.parameters['id'] = { in: 'path', required: true, description: 'The server’s slug', schema: { type: 'string' } }
|
||||
// #swagger.parameters['profile'] = { in: 'query', required: true, description: 'The profile’s id, from npc-profiles', schema: { type: 'integer' } }
|
||||
// #swagger.parameters['wipe'] = { in: 'query', required: false, description: 'Restrict to one wipe id', schema: { type: 'string' } }
|
||||
// #swagger.parameters['limit'] = { in: 'query', required: false, description: 'Rows to return, capped at 200', schema: { type: 'integer' } }
|
||||
/* #swagger.responses[200] = { description: 'The ranking', content: { "application/json": { schema: { $ref: "#/components/schemas/RustNpcLeaderboard" } } } } */
|
||||
/* #swagger.responses[404] = { description: 'No such profile on this server' } */
|
||||
siteMode,
|
||||
servers.npcLeaderboard,
|
||||
)
|
||||
|
||||
rustRouter.get(
|
||||
'/servers/:id/players/:steamId/npc-kills',
|
||||
// #swagger.tags = ['Public · Rust']
|
||||
// #swagger.summary = 'One player’s kills of each NPC profile on a server'
|
||||
// #swagger.description = 'What opening a leaderboard row shows (D252): the player’s kills of RunicNPC’s NPCs by profile on this server, for the wipe the page shows (`wipe`), or all time without it. Counted by profile name on this server, whatever a profile’s leaderboard counts; labelled with the site’s name for the profile where it has one. The same numbers the leaderboard already makes public, split by profile.'
|
||||
// #swagger.parameters['id'] = { in: 'path', required: true, description: 'The server’s slug', schema: { type: 'string' } }
|
||||
// #swagger.parameters['steamId'] = { in: 'path', required: true, description: 'The player’s Steam id', schema: { type: 'string' } }
|
||||
// #swagger.parameters['wipe'] = { in: 'query', required: false, description: 'Restrict to one wipe id', schema: { type: 'string' } }
|
||||
/* #swagger.responses[200] = { description: 'The kills', content: { "application/json": { schema: { $ref: "#/components/schemas/RustNpcPlayerKills" } } } } */
|
||||
siteMode,
|
||||
servers.playerNpcKills,
|
||||
)
|
||||
|
||||
rustRouter.get(
|
||||
'/servers/:id/wipes',
|
||||
// #swagger.tags = ['Public · Rust']
|
||||
|
||||
Reference in New Issue
Block a user