endfield-mcp
Version:
MCP Server for Arknights: Endfield (TypeScript / stdio + Streamable HTTP)
126 lines • 5.26 kB
JavaScript
/**
* Character domain — operations facade (list / get / search projections).
*
* Thin layer over `./characterTable.js` (table cache + resolver) and
* `./characterEnums.js` (profession/charType/weaponType display names).
* Produces tool-friendly shapes (`CharacterListItem`, `CharacterInfo`)
* with names resolved through `./texts.ts` so callers get actual strings,
* not int64 hashes.
*
* Lifecycle + resolver symbols (`bindCharacterStore`, `clearCharacterCaches`,
* `resolveCharacterEntry`, `CharacterEntry`) are re-exported from
* `./characterTable.js` so existing consumers (server, startupSync,
* characterProfiles, tests, smoke scripts) keep importing from
* `./characters.js` unchanged.
*/
import { resolveText } from "./texts.js";
import { sortedEntries, resolveCharacterEntry, } from "./characterTable.js";
import { PROFESSION_NAMES_CN, CHARTYPE_NAMES_CN, WEAPON_TYPE_NAMES_CN, } from "./characterEnums.js";
// ---------------------------------------------------------------------------
// Re-exports — preserve the public surface from before the split
// ---------------------------------------------------------------------------
export { bindCharacterStore, clearCharacterCaches, resolveCharacterEntry, } from "./characterTable.js";
// ---------------------------------------------------------------------------
// Public projections
// ---------------------------------------------------------------------------
/** List all characters with default-language names resolved. */
export function listCharacters(lang = "CN") {
return sortedEntries().map(([id, e]) => ({
id,
name: resolveText(e.name, lang, e.engName ?? id),
engName: e.engName ?? "",
profession: PROFESSION_NAMES_CN[e.profession] ?? `未知(${e.profession})`,
rarity: e.rarity,
charType: e.charTypeId
? CHARTYPE_NAMES_CN[e.charTypeId] ?? e.charTypeId
: "未知",
department: e.department ?? "未知",
}));
}
export function getCharacterInfo(idOrName, lang = "CN") {
const resolved = resolveCharacterEntry(idOrName, lang);
if (resolved === null)
return null;
const { id: resolvedId, entry } = resolved;
return {
id: resolvedId,
name: resolveText(entry.name, lang, entry.engName ?? resolvedId),
engName: entry.engName ?? "",
phoneticName: entry.phoneticName ?? "",
profession: PROFESSION_NAMES_CN[entry.profession] ??
`未知(${entry.profession})`,
professionCode: entry.profession,
rarity: entry.rarity,
department: entry.department ?? "未知",
charType: entry.charTypeId
? CHARTYPE_NAMES_CN[entry.charTypeId] ?? entry.charTypeId
: "未知",
weaponType: entry.weaponType !== undefined
? WEAPON_TYPE_NAMES_CN[entry.weaponType] ?? `未知(${entry.weaponType})`
: "未知",
weaponTypeCode: entry.weaponType ?? -1,
cvNames: {
chinese: resolveText(entry.cvName?.ChiCVName, "CN", ""),
english: resolveText(entry.cvName?.EngCVName, "EN", ""),
japanese: resolveText(entry.cvName?.JapCVName, "JP", ""),
korean: resolveText(entry.cvName?.KorCVName, "KR", ""),
},
sortOrder: entry.sortOrder ?? 9999,
defaultWeaponId: entry.defaultWeaponId ?? "",
};
}
/**
* Regex search across names and key fields.
*
* Searches resolved names (CN + engName), charId, profession, department,
* and charType. Returns compact matches with a snippet showing where the
* pattern hit.
*/
export function searchCharacters(pattern, maxResults = 30, lang = "CN") {
let re;
try {
re = new RegExp(pattern, "i");
}
catch {
// Fall back to literal substring if the regex is malformed.
const literal = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
re = new RegExp(literal, "i");
}
const out = [];
for (const [id, e] of sortedEntries()) {
const name = resolveText(e.name, lang, e.engName ?? id);
const engName = e.engName ?? "";
const profession = PROFESSION_NAMES_CN[e.profession] ?? "";
const charTypeId = e.charTypeId ?? "";
// charType field is searchable by both the raw English id (e.g.
// "Physical") and the Chinese-mapped name (e.g. "物理"), so users
// searching in either language hit.
const charTypeCN = charTypeId
? CHARTYPE_NAMES_CN[charTypeId] ?? ""
: "";
const charTypeSearchable = [charTypeId, charTypeCN]
.filter(Boolean)
.join("/");
const dept = e.department ?? "";
const fields = [
["名称", name],
["英文名", engName],
["ID", id],
["职业", profession],
["属性", charTypeSearchable],
["阵营", dept],
];
const hit = fields.find(([, v]) => re.test(v));
if (hit) {
out.push({
id,
name,
snippet: `${hit[0]}: ${hit[1]}`,
});
if (out.length >= maxResults)
break;
}
}
return out;
}
//# sourceMappingURL=characters.js.map