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
154 lines
4.9 KiB
JavaScript
154 lines
4.9 KiB
JavaScript
// ── Admin · Rust · NPCs: the handlers (docs/runicnpc/PLAN.md stage 4) ──────
|
|
//
|
|
// Thin: the model decides, and a refusal it throws carries its own status and
|
|
// sentence. Every write is logged in core's activity log, a placement's too,
|
|
// because a placement puts armed NPCs in the world.
|
|
|
|
const core = require('../../core')
|
|
|
|
const npcs = require('../../model/npcs/npcs.model')
|
|
const npcSync = require('../../npcSync')
|
|
|
|
const log = core.logger('npcs')
|
|
|
|
const by = (req) => (req.user ? req.user.id : null)
|
|
|
|
/** A model refusal answers with its own status; anything else is ours to log. */
|
|
function fail(res, err, what) {
|
|
if (err instanceof npcs.NpcError) return res.status(err.status).json({ message: err.message })
|
|
log.error(`failed to ${what}`, { error: err.message })
|
|
return res.status(500).json({ message: `Failed to ${what}` })
|
|
}
|
|
|
|
async function describe(req, res) {
|
|
try {
|
|
res.json(await npcs.describe())
|
|
} catch (err) {
|
|
fail(res, err, 'read the NPC profiles')
|
|
}
|
|
}
|
|
|
|
async function create(req, res) {
|
|
try {
|
|
const profile = await npcs.create(req.body || {}, by(req))
|
|
await core.activity.log({ req, action: 'rust.npcs.profile.create', detail: { id: profile.id, name: profile.name } })
|
|
res.status(201).json(profile)
|
|
} catch (err) {
|
|
fail(res, err, 'save the NPC profile')
|
|
}
|
|
}
|
|
|
|
async function update(req, res) {
|
|
try {
|
|
const profile = await npcs.update(Number(req.params.pid), req.body || {})
|
|
await core.activity.log({ req, action: 'rust.npcs.profile.update', detail: { id: profile.id, name: profile.name } })
|
|
res.json(profile)
|
|
} catch (err) {
|
|
fail(res, err, 'save the NPC profile')
|
|
}
|
|
}
|
|
|
|
async function remove(req, res) {
|
|
try {
|
|
await npcs.remove(Number(req.params.pid))
|
|
await core.activity.log({ req, action: 'rust.npcs.profile.delete', detail: { id: Number(req.params.pid) } })
|
|
res.json({ ok: true })
|
|
} catch (err) {
|
|
fail(res, err, 'delete the NPC profile')
|
|
}
|
|
}
|
|
|
|
async function restore(req, res) {
|
|
try {
|
|
const profile = await npcs.restore(Number(req.params.pid))
|
|
await core.activity.log({ req, action: 'rust.npcs.profile.restore', detail: { id: profile.id, name: profile.name } })
|
|
res.json(profile)
|
|
} catch (err) {
|
|
fail(res, err, 'restore the NPC profile')
|
|
}
|
|
}
|
|
|
|
async function push(req, res) {
|
|
try {
|
|
const [result] = await npcSync.tick({ force: req.params.id })
|
|
if (!result) return res.status(404).json({ message: 'No such server, or it is switched off' })
|
|
await core.activity.log({ req, action: 'rust.npcs.push', detail: { server: req.params.id, outcome: result.outcome } })
|
|
res.json(result)
|
|
} catch (err) {
|
|
fail(res, err, 'push the NPC profiles')
|
|
}
|
|
}
|
|
|
|
async function listPlacements(req, res) {
|
|
try {
|
|
res.json(await npcs.listPlacements(req.params.id))
|
|
} catch (err) {
|
|
fail(res, err, 'read the placements')
|
|
}
|
|
}
|
|
|
|
async function addPlacement(req, res) {
|
|
try {
|
|
const placed = await npcs.addPlacement(req.params.id, req.body || {})
|
|
await core.activity.log({ req, action: 'rust.npcs.placement.add', detail: { server: req.params.id, id: placed.id, profile: req.body.profile, position: placed.position } })
|
|
res.status(201).json(placed)
|
|
} catch (err) {
|
|
fail(res, err, 'place the NPCs')
|
|
}
|
|
}
|
|
|
|
async function setPlacement(req, res) {
|
|
try {
|
|
const out = await npcs.setPlacement(req.params.id, req.params.placement, req.body || {})
|
|
await core.activity.log({ req, action: 'rust.npcs.placement.update', detail: { server: req.params.id, id: req.params.placement } })
|
|
res.json(out)
|
|
} catch (err) {
|
|
fail(res, err, 'change the placement')
|
|
}
|
|
}
|
|
|
|
async function removePlacement(req, res) {
|
|
try {
|
|
await npcs.removePlacement(req.params.id, req.params.placement)
|
|
await core.activity.log({ req, action: 'rust.npcs.placement.delete', detail: { server: req.params.id, id: req.params.placement } })
|
|
res.json({ ok: true })
|
|
} catch (err) {
|
|
fail(res, err, 'remove the placement')
|
|
}
|
|
}
|
|
|
|
async function renamePlacement(req, res) {
|
|
try {
|
|
const out = await npcs.renamePlacement(req.params.id, req.params.placement, req.body.to)
|
|
await core.activity.log({ req, action: 'rust.npcs.placement.rename', detail: { server: req.params.id, from: req.params.placement, to: req.body.to } })
|
|
res.json({ id: out.id || req.body.to, previous: req.params.placement })
|
|
} catch (err) {
|
|
fail(res, err, 'rename the placement')
|
|
}
|
|
}
|
|
|
|
async function respawnPlacement(req, res) {
|
|
try {
|
|
const out = await npcs.respawnPlacement(req.params.id, req.params.placement)
|
|
await core.activity.log({ req, action: 'rust.npcs.placement.respawn', detail: { server: req.params.id, id: req.params.placement } })
|
|
res.json({ id: req.params.placement, respawned: Number(out.respawned) || 0 })
|
|
} catch (err) {
|
|
fail(res, err, 'respawn the placement')
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
describe,
|
|
create,
|
|
update,
|
|
remove,
|
|
restore,
|
|
push,
|
|
listPlacements,
|
|
addPlacement,
|
|
setPlacement,
|
|
removePlacement,
|
|
renamePlacement,
|
|
respawnPlacement,
|
|
}
|