UNPKG

endfield-mcp

Version:

MCP Server for Arknights: Endfield (TypeScript / stdio + Streamable HTTP)

116 lines • 4.24 kB
/** * Story chapter derivation and listing. * * There is no explicit chapter list in the upstream data. Chapters are * derived by grouping catalog entries on the episode-prefix portion of * the mission id: * e1m1, e1m2 → chapter "e1" * sm1l1m1 → chapter "sm1" * c6m1 → chapter "c6" (character story) * Display names come from missions.json when available; the prefix is * used as fallback. * * Depends on `./storyCore.js` for the catalog loaders (`entries`, * `missionNames`). */ import { entries, missionNames } from "./storyCore.js"; // --------------------------------------------------------------------------- // Chapter cache (private to this module) // --------------------------------------------------------------------------- let _chapterCache = null; /** Reset the chapter grouping cache. Called by the barrel orchestrator. */ export function clearChapterCache() { _chapterCache = null; } // --------------------------------------------------------------------------- // Chapter derivation // --------------------------------------------------------------------------- /** * Extract the chapter prefix from a mission id. * * Mission ids follow patterns like: * e1m1, e1m2 → "e1" (main story episode 1) * e10m4 → "e10" * sm1l1m1 → "sm1" (side mission layer 1) * c6m1 → "c6" (character 6's story) * gm01m1 → "gm01" (gate story) * a1m6d2 → "a1" (activity) * f1m1 → "f1" (facility) * * Strategy: take the leading non-digit run + the following digit run. * "e1m1" → "e1", "sm1l1m1" → "sm1", "gm01m1" → "gm01". */ export function chapterPrefix(mission) { const match = mission.match(/^([a-z]+)(\d+)/i); return match ? `${match[1]}${match[2]}` : mission; } function chaptersByPrefix() { if (_chapterCache !== null) return _chapterCache; const map = new Map(); for (const e of entries()) { const prefix = chapterPrefix(e.mission); const bucket = map.get(prefix); if (bucket) { bucket.push(e); } else { map.set(prefix, [e]); } } _chapterCache = map; return map; } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /** List all story chapters with scene counts and display names. */ export function listStoryChapters() { const names = missionNames(); const grouped = chaptersByPrefix(); const out = []; for (const [chapterId, sceneList] of grouped) { // Try to find a display name: look for any mission in this chapter // that has a name in missions.json, prefer the first one. let displayName = chapterId; for (const e of sceneList) { const name = names[e.mission]; if (name) { displayName = name; break; } } out.push({ chapterId, displayName, entryCount: sceneList.length, }); } // Sort: main story first (e1, e2, ...), then by chapter id. out.sort((a, b) => { if (a.chapterId.startsWith("e") && !b.chapterId.startsWith("e")) return -1; if (!a.chapterId.startsWith("e") && b.chapterId.startsWith("e")) return 1; return a.chapterId.localeCompare(b.chapterId, undefined, { numeric: true }); }); return out; } /** List scenes within a chapter (by chapter prefix like "e1", "sm1"). */ export function listStories(chapterId) { const grouped = chaptersByPrefix(); // Normalize: user might pass "e1" or "E1" — case-insensitive match. const lower = chapterId.toLowerCase(); for (const [prefix, list] of grouped) { if (prefix.toLowerCase() === lower) { return [...list].sort((a, b) => { if (a.mission !== b.mission) { return a.mission.localeCompare(b.mission, undefined, { numeric: true }); } return a.scene - b.scene; }); } } return []; } //# sourceMappingURL=storyChapters.js.map