fix: preserve rpg custom world detail profiles

This commit is contained in:
kdletters
2026-05-22 03:14:11 +08:00
parent a9d23a8a44
commit d74457faa2
19 changed files with 2726 additions and 109 deletions

View File

@@ -28,6 +28,7 @@ import {
CustomWorldNpcVisualGearType,
CustomWorldNpcVisualRace,
CustomWorldOpeningCgProfile,
CustomWorldCoverProfile,
CustomWorldPlayableNpc,
CustomWorldProfile,
CustomWorldRoleInitialItem,
@@ -864,6 +865,7 @@ function normalizeLandmark(
id: toText(value.id, `saved-landmark-${index + 1}`),
name,
description: toText(value.description),
visualDescription: toText(value.visualDescription) || undefined,
imageSrc: toText(value.imageSrc) || undefined,
narrativeResidues:
preserveStructuredRecordArray<SceneNarrativeResidue>(
@@ -909,7 +911,10 @@ function normalizeCampScene(
summary: toText(connection.summary) || toText(connection.description),
}))
.filter((connection) => connection.targetLandmarkId),
narrativeResidues: null,
narrativeResidues:
preserveStructuredRecordArray<SceneNarrativeResidue>(
value.narrativeResidues,
) ?? null,
};
}
@@ -989,6 +994,13 @@ function normalizeSceneActBlueprint(
const advanceRule = toText(value.advanceRule);
const title = toText(value.title);
const summary = toText(value.summary);
const backgroundImageSrc = toText(value.backgroundImageSrc);
const backgroundPromptText = toText(value.backgroundPromptText);
const backgroundAssetId = toText(value.backgroundAssetId);
const eventDescription = toText(value.eventDescription);
const linkedThreadIds = toStringArray(value.linkedThreadIds);
const actGoal = toText(value.actGoal);
const transitionHook = toText(value.transitionHook);
const primaryNpcId = resolveCustomWorldRoleIdReference(
profileRoles,
toText(value.primaryNpcId, encounterNpcIds[0] ?? ''),
@@ -998,7 +1010,18 @@ function normalizeSceneActBlueprint(
toText(value.oppositeNpcId, primaryNpcId),
);
if (!title && !summary && encounterNpcIds.length === 0) {
if (
!title &&
!summary &&
encounterNpcIds.length === 0 &&
!backgroundImageSrc &&
!backgroundPromptText &&
!backgroundAssetId &&
!eventDescription &&
linkedThreadIds.length === 0 &&
!actGoal &&
!transitionHook
) {
return null;
}
@@ -1011,26 +1034,26 @@ function normalizeSceneActBlueprint(
stageCoverage.length > 0
? stageCoverage
: index === 0
? ['opening']
: ['climax', 'aftermath'],
backgroundImageSrc: toText(value.backgroundImageSrc) || undefined,
backgroundPromptText: toText(value.backgroundPromptText) || undefined,
backgroundAssetId: toText(value.backgroundAssetId) || undefined,
? ['opening']
: ['climax', 'aftermath'],
backgroundImageSrc: backgroundImageSrc || undefined,
backgroundPromptText: backgroundPromptText || undefined,
backgroundAssetId: backgroundAssetId || undefined,
encounterNpcIds,
primaryNpcId,
oppositeNpcId,
eventDescription: toText(
value.eventDescription,
eventDescription,
oppositeNpcId
? `${index + 1} 幕中,玩家与${oppositeNpcId}正面接触,推动当前场景事件升级。`
: `${index + 1} 幕中,玩家处理当前场景的关键事件。`,
),
linkedThreadIds: toStringArray(value.linkedThreadIds),
linkedThreadIds,
advanceRule: SCENE_ACT_ADVANCE_RULES.has(advanceRule as never)
? (advanceRule as SceneActBlueprint['advanceRule'])
: 'after_active_step_complete',
actGoal: toText(value.actGoal),
transitionHook: toText(value.transitionHook),
actGoal,
transitionHook,
};
}
@@ -1159,6 +1182,9 @@ function normalizeProfile(value: unknown): CustomWorldProfile | null {
const openingCg = preserveStructuredRecord<CustomWorldOpeningCgProfile>(
value.openingCg,
);
const cover = preserveStructuredRecord<CustomWorldCoverProfile>(
value.cover,
);
const normalizedProfile = {
id: toText(value.id, `saved-custom-world-${Date.now().toString(36)}`),
settingText,
@@ -1184,6 +1210,7 @@ function normalizeProfile(value: unknown): CustomWorldProfile | null {
.map((entry, index) => normalizeItem(entry, index))
.filter((entry): entry is CustomWorldItem => Boolean(entry))
: [],
cover,
openingCg,
camp,
landmarks: normalizeCustomWorldLandmarks({