@@ -23,6 +23,7 @@ const EDITABLE_SECTION_IDS = {
|
||||
thread: new Set(['title', 'summary', 'conflictType', 'stakes']),
|
||||
chapter: new Set(['title', 'summary', 'openingEvent', 'playerGoal', 'understandingShift']),
|
||||
camp: new Set(['name', 'description', 'dangerLevel']),
|
||||
sceneChapter: new Set(['title', 'summary']),
|
||||
} as const;
|
||||
|
||||
function normalizePatches(sections: DraftSectionPatch[]) {
|
||||
@@ -52,6 +53,17 @@ function parseStringList(value: string) {
|
||||
return [...new Set(value.split(/[\n;;]+/u).map((item) => item.trim()).filter(Boolean))];
|
||||
}
|
||||
|
||||
function parseReferenceList(value: string) {
|
||||
return [
|
||||
...new Set(
|
||||
value
|
||||
.split(/[\n,,、;;]+/u)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function resolveThreadType(value: string) {
|
||||
if (value.includes('暗') || value.toLowerCase() === 'hidden') {
|
||||
return 'hidden' as const;
|
||||
@@ -60,6 +72,61 @@ function resolveThreadType(value: string) {
|
||||
return 'main' as const;
|
||||
}
|
||||
|
||||
function parseSceneActSectionId(sectionId: string) {
|
||||
const match = sectionId.match(
|
||||
/^act:([^:]+):(title|summary|backgroundImageSrc|encounterNpcIds|actGoal|transitionHook)$/u,
|
||||
);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
actId: match[1],
|
||||
field: match[2] as
|
||||
| 'title'
|
||||
| 'summary'
|
||||
| 'backgroundImageSrc'
|
||||
| 'encounterNpcIds'
|
||||
| 'actGoal'
|
||||
| 'transitionHook',
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCharacterIdByReference(
|
||||
value: string,
|
||||
draftProfile: NonNullable<ReturnType<typeof normalizeFoundationDraftProfile>>,
|
||||
) {
|
||||
const characters = [...draftProfile.playableNpcs, ...draftProfile.storyNpcs];
|
||||
return (
|
||||
characters.find((entry) => entry.id === value)?.id ||
|
||||
characters.find((entry) => entry.name === value)?.id ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
function parseEncounterNpcIds(
|
||||
value: string,
|
||||
draftProfile: NonNullable<ReturnType<typeof normalizeFoundationDraftProfile>>,
|
||||
) {
|
||||
const references = parseReferenceList(value);
|
||||
if (references.length === 0) {
|
||||
throw badRequest('scene act requires at least one encounter NPC');
|
||||
}
|
||||
|
||||
const unresolvedReferences = references.filter(
|
||||
(reference) => !resolveCharacterIdByReference(reference, draftProfile),
|
||||
);
|
||||
if (unresolvedReferences.length > 0) {
|
||||
throw badRequest(
|
||||
`unknown scene act NPC reference: ${unresolvedReferences.join('、')}`,
|
||||
);
|
||||
}
|
||||
|
||||
return references.map((reference) =>
|
||||
resolveCharacterIdByReference(reference, draftProfile),
|
||||
);
|
||||
}
|
||||
|
||||
export function updateDraftCardSections(params: UpdateDraftCardSectionsParams) {
|
||||
const draftProfile = normalizeFoundationDraftProfile(params.draftProfile);
|
||||
if (!draftProfile) {
|
||||
@@ -293,6 +360,70 @@ export function updateDraftCardSections(params: UpdateDraftCardSectionsParams) {
|
||||
return draftProfile as unknown as Record<string, unknown>;
|
||||
}
|
||||
|
||||
const sceneChapter = draftProfile.sceneChapters.find(
|
||||
(entry) => entry.id === params.cardId,
|
||||
);
|
||||
if (sceneChapter) {
|
||||
patches.forEach(({ sectionId, value }) => {
|
||||
if (EDITABLE_SECTION_IDS.sceneChapter.has(sectionId as never)) {
|
||||
if (sectionId === 'title') {
|
||||
sceneChapter.title = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sectionId === 'summary') {
|
||||
sceneChapter.summary = value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedSceneActSection = parseSceneActSectionId(sectionId);
|
||||
if (!parsedSceneActSection) {
|
||||
throw badRequest(`section ${sectionId} is not editable for scene_chapter`);
|
||||
}
|
||||
|
||||
const targetAct = sceneChapter.acts.find(
|
||||
(entry) => entry.id === parsedSceneActSection.actId,
|
||||
);
|
||||
if (!targetAct) {
|
||||
throw notFound(`scene act ${parsedSceneActSection.actId} not found`);
|
||||
}
|
||||
|
||||
if (parsedSceneActSection.field === 'title') {
|
||||
targetAct.title = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedSceneActSection.field === 'summary') {
|
||||
targetAct.summary = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedSceneActSection.field === 'backgroundImageSrc') {
|
||||
targetAct.backgroundImageSrc = value || null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedSceneActSection.field === 'encounterNpcIds') {
|
||||
const encounterNpcIds = parseEncounterNpcIds(value, draftProfile);
|
||||
targetAct.encounterNpcIds = encounterNpcIds;
|
||||
targetAct.primaryNpcId = encounterNpcIds[0] || '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedSceneActSection.field === 'actGoal') {
|
||||
targetAct.actGoal = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedSceneActSection.field === 'transitionHook') {
|
||||
targetAct.transitionHook = value;
|
||||
}
|
||||
});
|
||||
|
||||
return draftProfile as unknown as Record<string, unknown>;
|
||||
}
|
||||
|
||||
if (draftProfile.camp?.id === params.cardId) {
|
||||
patches.forEach(({ sectionId, value }) => {
|
||||
if (!EDITABLE_SECTION_IDS.camp.has(sectionId as never)) {
|
||||
|
||||
Reference in New Issue
Block a user