feat(rust): NPC profiles and placements pages, the profile leaderboard (runicnpc stage 4)
Admin: Rust NPC profiles (the form RunicNPC reads, per server, shared or fleet, each server's push state and refusals, replaced profiles with Restore) and Rust NPC placements (the live map: click to place, pins for every placement; edit, rename, respawn, remove). The live map takes a pick handler and pins, unchanged for the public page. Public: the leaderboard ranks by a profile's kills (D250), and opening a row shows that player's kills by profile (D252); the killfeed names a RunicNPC NPC by its own name. Player: your own kills by profile. Titles: a rule on a profile's kills picks the profile. npcs.test.js covers the profile checks, adoption, the push, the picker and verb, the triggers, kill crediting, titles and placements (481 server tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -261,6 +261,37 @@ function Held({ accounts }) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* RunicNPC (runicnpc stage 4, D252): your kills of each NPC profile, this wipe,
|
||||
* by server, across every Steam account you have linked. Nothing without a link.
|
||||
*/
|
||||
function NpcKills() {
|
||||
const { data, loading, error } = useAsync(() => api.playerNpcKills.list(), [])
|
||||
if (loading) return <Loading />
|
||||
if (error) return <ErrorState error={error} />
|
||||
const rows = (data && data.kills) || []
|
||||
if (rows.length === 0) {
|
||||
return <p className="sans dim" style={{ fontSize: '0.8rem', margin: 0 }}>None this wipe.</p>
|
||||
}
|
||||
const byServer = new Map()
|
||||
for (const r of rows) byServer.set(r.server || r.serverId, [...(byServer.get(r.server || r.serverId) || []), r])
|
||||
return (
|
||||
<ul className="sans" style={{ listStyle: 'none', margin: 0, padding: 0, fontSize: '0.86rem', display: 'grid', gap: 6 }}>
|
||||
{[...byServer.entries()].map(([serverId, list]) => (
|
||||
<li key={serverId}>
|
||||
<span className="dim">{serverId}: </span>
|
||||
{list.map((k, i) => (
|
||||
<span key={k.profile}>
|
||||
{i > 0 && <span className="dim"> · </span>}
|
||||
{k.label} <strong>{k.kills}</strong>
|
||||
</span>
|
||||
))}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Account() {
|
||||
// `useAsync` rather than this module's `usePolled`: nothing here changes unless
|
||||
// the person looking at it changes it, and a page that re-asked every twenty
|
||||
@@ -326,6 +357,9 @@ export default function Account() {
|
||||
<div className="field-label" style={{ margin: '30px 0 12px' }}>What you can do in game</div>
|
||||
|
||||
{data && <Held accounts={links.length} />}
|
||||
|
||||
<div className="field-label" style={{ margin: '30px 0 12px' }}>NPCs you have killed</div>
|
||||
{data && links.length > 0 ? <NpcKills /> : <p className="sans dim" style={{ fontSize: '0.8rem', margin: 0 }}>Link a Steam account to see them.</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user