import { useMemo, useState } from "react"; import { enterGmManastormTest, enterRandomGmReview } from "../app/gmManastormRuntime"; import { installDungeonShellRuntime } from "../app/dungeonShellRuntime"; import { useShellStore } from "../app/shellStore"; import { bossAdminRecordForStatus, GM_REVIEW_NOTE_MAX_LENGTH, isGameMasterSession, resolvedBossAdminCatalog, type ManastormBossStatus, type ManastormGlobalOptions, } from "../game/manastormAdminConfig"; import { useManastormAdminStore } from "../game/manastormAdminStore"; import { useGameStore } from "../game/store"; import { configWithGmReviewNote, gmReviewQueue, type GmReviewKind, } from "../game/gmContentReview"; import { BrandMark, FrontSurface } from "./FrontSurface"; const STATUS_LABELS: Readonly> = { ready: "Ready", "needs-tested": "Needs tested", "not-ready": "Not ready", }; const GLOBAL_OPTIONS: readonly { readonly key: keyof ManastormGlobalOptions; readonly label: string; readonly detail: string }[] = [ { key: "enabled", label: "Manastorms", detail: "Master switch for ordinary player entry." }, { key: "levelingEnabled", label: "Leveling mode", detail: "Allow characters below the end-game threshold to enter." }, { key: "endgameEnabled", label: "End-game mode", detail: "Allow level-cap characters to enter the end-game pool." }, { key: "affixesEnabled", label: "Affixes", detail: "Apply catalog and modifier-derived affixes." }, { key: "chaoticLinkEnabled", label: "Chaotic Link", detail: "Empower bosses until their linked kill requirement is met." }, { key: "milestoneEncountersEnabled", label: "Milestone bosses", detail: "Force the milestone pool every five levels." }, ]; installDungeonShellRuntime(); export function GmAdminScreen() { const session = useShellStore((state) => state.session); const returnToMainMenu = useShellStore((state) => state.returnToMainMenu); const enterDungeon = useShellStore((state) => state.enterDungeon); const draft = useManastormAdminStore((state) => state.draft); const revision = useManastormAdminStore((state) => state.revision); const dirty = useManastormAdminStore((state) => state.dirty); const saving = useManastormAdminStore((state) => state.saving); const error = useManastormAdminStore((state) => state.error); const publish = useManastormAdminStore((state) => state.publish); const replaceDraft = useManastormAdminStore((state) => state.replaceDraft); const resetDraft = useManastormAdminStore((state) => state.resetDraft); const setEditingBossKey = useManastormAdminStore((state) => state.setEditingBossKey); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [dungeonFilter, setDungeonFilter] = useState("all"); const [sourceFilter, setSourceFilter] = useState<"all" | "dungeon" | "installed-manastorm">("all"); const [runnableFilter, setRunnableFilter] = useState<"all" | "runnable" | "incomplete">("all"); const [poolFilter, setPoolFilter] = useState<"all" | "public" | "excluded">("all"); const [selectedKey, setSelectedKey] = useState(null); const catalog = useMemo(() => resolvedBossAdminCatalog(draft), [draft]); const randomQueueCounts = useMemo(() => Object.fromEntries( (["dungeon", "boss", "manastorm"] as const).map((kind) => [kind, gmReviewQueue(kind, draft).length]), ) as Readonly>, [draft]); const dungeonOptions = useMemo(() => [...new Map(catalog.map((entry) => [entry.dungeonTitle, entry.dungeonTitle])).keys()].sort(), [catalog]); const filtered = useMemo(() => { const query = search.trim().toLowerCase(); return catalog.filter((entry) => ( (statusFilter === "all" || entry.status === statusFilter) && (dungeonFilter === "all" || entry.dungeonTitle === dungeonFilter) && (sourceFilter === "all" || entry.source.includes(sourceFilter)) && (runnableFilter === "all" || (runnableFilter === "runnable" ? entry.runnable : !entry.runnable)) && (poolFilter === "all" || (poolFilter === "public" ? entry.inPublicPool : !entry.inPublicPool)) && (!query || `${entry.name} ${entry.dungeonTitle} ${entry.source.join(" ")} ${entry.reviewNote}`.toLowerCase().includes(query)) )); }, [catalog, dungeonFilter, poolFilter, runnableFilter, search, sourceFilter, statusFilter]); const selected = catalog.find((entry) => entry.key === selectedKey) ?? filtered[0] ?? null; if (!isGameMasterSession(session)) { return (

Game Master access required

This administration surface is available only to server-authorized accounts.

); } const setGlobal = (key: keyof ManastormGlobalOptions, value: boolean) => replaceDraft({ ...draft, global: { ...draft.global, [key]: value }, }); const setStatus = (key: string, status: ManastormBossStatus) => { const entry = catalog.find((candidate) => candidate.key === key); if (status === "ready" && !entry?.runnable) return; if (!entry) return; replaceDraft({ ...draft, bosses: { ...draft.bosses, [key]: bossAdminRecordForStatus(entry, draft, status), }, }); }; const setReviewNote = (key: string, note: string) => { const next = configWithGmReviewNote(draft, { kind: "boss", bossKey: key }, note); if (next) replaceDraft(next); }; const editInDungeon = () => { if (!selected?.dungeonId) return; setEditingBossKey(selected.key); if (enterDungeon(selected.dungeonId)) { useGameStore.getState().openOverlay("gm"); } }; const enterSelectedDungeon = () => { if (!selected?.dungeonId) return; setEditingBossKey(null); enterDungeon(selected.dungeonId); }; return (
Server authority / revision {revision}Manastorm GM Tools

Unified catalog

All bosses

setSearch(event.currentTarget.value)} placeholder="Search boss or dungeon" />
{filtered.map((entry) => ( ))}