endfield-mcp
Version:
MCP Server for Arknights: Endfield (TypeScript / stdio + Streamable HTTP)
92 lines • 3.73 kB
JavaScript
/**
* Character table core — store binding, table cache, and entry resolution.
*
* Owns the single source of truth for the parsed `CharacterTable.json`:
* the store reference, the lazy parse cache, and the multi-key resolver
* (id / CN name / EN name / engName). The operations module
* (`./characters.js`) and the profiles module (`./characterProfiles.js`)
* both resolve entries through `resolveCharacterEntry` so lookup
* precedence stays consistent across the character domain.
*
* ## Table shape
*
* CharacterTable.json: `{ [characterId: string]: CharacterEntry }`.
* The characterId format is `chr_NNNN_slug` (e.g. `chr_0002_endminm`).
* There are ~29 entries (as of the first export, 2026-06).
*
* Name / CV fields come through the i18n resolver (`./texts.ts`) so
* callers get actual strings, not int64 hashes.
*/
import { resolveText, } from "./texts.js";
// ---------------------------------------------------------------------------
// Lazy cache + store binding
// ---------------------------------------------------------------------------
let _characterTable = null;
export function clearCharacterCaches() {
_characterTable = null;
}
let _store = null;
export function bindCharacterStore(store) {
_store = store;
clearCharacterCaches();
}
function store() {
if (_store === null) {
throw new Error("Character reader used before bindCharacterStore() — call it once at startup.");
}
return _store;
}
function table() {
if (_characterTable !== null)
return _characterTable;
// Int64-safe parse: CharacterTable.name.id and cvName.*.id are int64
// localization hashes that exceed Number.MAX_SAFE_INTEGER. Plain
// readJson would truncate them and break i18n lookups.
_characterTable = store().readJsonInt64Safe("tables/CharacterTable.json");
return _characterTable;
}
// ---------------------------------------------------------------------------
// Internal accessor — exported for the operations module (same domain)
// ---------------------------------------------------------------------------
/**
* Return the parsed table sorted by `sortOrder` (ascending, stable).
* The operations module uses this to produce deterministic list/search output.
*/
export function sortedEntries() {
return Object.entries(table()).sort((a, b) => (a[1].sortOrder ?? 9999) - (b[1].sortOrder ?? 9999));
}
// ---------------------------------------------------------------------------
// Public resolver
// ---------------------------------------------------------------------------
/**
* Resolve a character by id, CN name, requested-lang name, or engName.
* Returns `{id, entry}` on hit, `null` on miss. Shared by getCharacterInfo
* (operations) and getCharacterArchives/getCharacterVoices (profiles) so
* both use the same lookup precedence.
*/
export function resolveCharacterEntry(idOrName, lang = "CN") {
const t = table();
// Try exact id first.
let entry = t[idOrName];
let resolvedId = idOrName;
// Fall back to matching on resolved name (CN/EN) or engName.
if (entry === undefined) {
for (const [cid, e] of Object.entries(t)) {
const cnName = resolveText(e.name, "CN", "");
const targetName = resolveText(e.name, lang, "");
const engName = e.engName ?? "";
if (cid === idOrName ||
cnName === idOrName ||
targetName === idOrName ||
engName === idOrName) {
entry = e;
resolvedId = cid;
break;
}
}
}
if (entry === undefined)
return null;
return { id: resolvedId, entry };
}
//# sourceMappingURL=characterTable.js.map