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
149 lines
4.7 KiB
JavaScript
149 lines
4.7 KiB
JavaScript
// ── The nav glyph for this module's player-portal row ─────────────────────
|
|
//
|
|
// `icon` is part of the nav-item contract (MODULE_API.md §3.3, 1.3.0): core
|
|
// renders whatever component a row carries, exactly as it renders its own rows'
|
|
// icons — and core's player portal draws a glyph on every row, so a row without
|
|
// one reads as breakage rather than as a design. The client suite asserts it.
|
|
//
|
|
// The public header is text buttons and carries no icons, which is why this file
|
|
// arrives with the player row and not before it.
|
|
//
|
|
// **The frame is copied from core's `PlayerPortalLayout`, deliberately and by
|
|
// copy rather than by import** — 16px, `currentColor`, stroke 2. Four attributes
|
|
// of presentation are not a component: putting them in the shared kit would
|
|
// freeze core's icon sizing into the contract, where changing it later would be a
|
|
// major bump. A module that wants to look like the nav it is in matches that nav.
|
|
|
|
const Icon = ({ children }) => (
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
aria-hidden="true"
|
|
focusable="false"
|
|
>
|
|
{children}
|
|
</svg>
|
|
)
|
|
|
|
/**
|
|
* A chain link — what the row is for.
|
|
*
|
|
* Not a gem, a person or a server: the portal's rows say what a player does
|
|
* there, and what a player does at `/player/rust` is link an account. Core's own
|
|
* neighbours are a gear (account), a shield (appeals) and a bell (notifications),
|
|
* so the row has to read as a verb in that company.
|
|
*/
|
|
export const IconLink = () => (
|
|
<Icon>
|
|
<path d="M10 13a5 5 0 007.07 0l2.83-2.83a5 5 0 00-7.07-7.07L11.5 4.5" />
|
|
<path d="M14 11a5 5 0 00-7.07 0L4.1 13.83a5 5 0 007.07 7.07L12.5 19.5" />
|
|
</Icon>
|
|
)
|
|
|
|
/**
|
|
* A key — the admin sidebar's row for the permission mirror.
|
|
*
|
|
* Core's admin groups are labelled by subject and drawn with glyphs of the same
|
|
* weight, so this is the same 16px frame as the portal's. A key rather than a
|
|
* shield: a shield is protection from something, and this row is about handing
|
|
* somebody the right to do something.
|
|
*/
|
|
export const IconKey = () => (
|
|
<Icon>
|
|
<circle cx="7.5" cy="15.5" r="4.5" />
|
|
<path d="M10.7 12.3L20 3" />
|
|
<path d="M17 6l2.5 2.5" />
|
|
</Icon>
|
|
)
|
|
|
|
/**
|
|
* Sliders — the admin sidebar's row for the mod-configuration editor.
|
|
*
|
|
* Not a gear: core's account row is a gear, and two gears in one sidebar say
|
|
* "settings" twice without saying whose. Sliders read as values being tuned,
|
|
* which is exactly what that page does to somebody else's game host.
|
|
*/
|
|
export const IconSliders = () => (
|
|
<Icon>
|
|
<path d="M4 6h10" />
|
|
<path d="M18 6h2" />
|
|
<circle cx="16" cy="6" r="2" />
|
|
<path d="M4 12h4" />
|
|
<path d="M12 12h8" />
|
|
<circle cx="10" cy="12" r="2" />
|
|
<path d="M4 18h10" />
|
|
<path d="M18 18h2" />
|
|
<circle cx="16" cy="18" r="2" />
|
|
</Icon>
|
|
)
|
|
|
|
/**
|
|
* An eye — the admin sidebar's row for who may see who is online.
|
|
*
|
|
* The page decides what the public can SEE, so the glyph is the act of seeing.
|
|
*/
|
|
export const IconEye = () => (
|
|
<Icon>
|
|
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" />
|
|
<circle cx="12" cy="12" r="3" />
|
|
</Icon>
|
|
)
|
|
|
|
/**
|
|
* Two stacked units — the admin sidebar's row for the servers themselves.
|
|
*
|
|
* The one row that is about the machines rather than what happens on them: the
|
|
* sidecar each one answers through, and when each one wipes.
|
|
*/
|
|
export const IconServer = () => (
|
|
<Icon>
|
|
<rect x="3" y="4" width="18" height="7" rx="1.5" />
|
|
<rect x="3" y="13" width="18" height="7" rx="1.5" />
|
|
<path d="M7 7.5h.01" />
|
|
<path d="M7 16.5h.01" />
|
|
</Icon>
|
|
)
|
|
|
|
export default { IconLink, IconKey, IconSliders, IconEye, IconServer }
|
|
|
|
/**
|
|
* A circle inside a dashed ring — the admin sidebar's row for zone presets.
|
|
*
|
|
* A zone is an area with rules at its edge, so the glyph is a place and its
|
|
* boundary rather than a map pin, which the live map already means.
|
|
*/
|
|
export const IconZone = () => (
|
|
<Icon>
|
|
<circle cx="12" cy="12" r="9" strokeDasharray="3 3" />
|
|
<circle cx="12" cy="12" r="3" />
|
|
</Icon>
|
|
)
|
|
|
|
/**
|
|
* A figure — the admin sidebar's row for RunicNPC's profiles (runicnpc stage 4).
|
|
* A head and shoulders: the page is about who the NPCs are.
|
|
*/
|
|
export const IconNpc = () => (
|
|
<Icon>
|
|
<circle cx="12" cy="8" r="3.5" />
|
|
<path d="M5 20c0-3.9 3.1-7 7-7s7 3.1 7 7" />
|
|
</Icon>
|
|
)
|
|
|
|
/**
|
|
* A figure on a spot — the admin sidebar's row for NPC placements: where they stand.
|
|
*/
|
|
export const IconPlacement = () => (
|
|
<Icon>
|
|
<circle cx="12" cy="6" r="2.5" />
|
|
<path d="M8 15c0-2.2 1.8-4 4-4s4 1.8 4 4" />
|
|
<ellipse cx="12" cy="19" rx="7" ry="2" />
|
|
</Icon>
|
|
)
|