This commit is contained in:
168
src/services/customWorldScenePresentation.ts
Normal file
168
src/services/customWorldScenePresentation.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
resolveCustomWorldCampSceneImage,
|
||||
resolveCustomWorldLandmarkImageMap,
|
||||
} from '../data/customWorldVisuals';
|
||||
import type {
|
||||
CustomWorldLandmark,
|
||||
CustomWorldProfile,
|
||||
SceneChapterBlueprint,
|
||||
} from '../types';
|
||||
import { resolveCustomWorldCampScene } from './customWorldCamp';
|
||||
|
||||
export type CustomWorldSceneKind = 'camp' | 'landmark';
|
||||
|
||||
export type CustomWorldSceneActImagePreview = {
|
||||
id: string;
|
||||
title: string;
|
||||
imageSrc: string;
|
||||
};
|
||||
|
||||
export type CustomWorldScenePresentation = {
|
||||
id: string;
|
||||
kind: CustomWorldSceneKind;
|
||||
name: string;
|
||||
description: string;
|
||||
imageSrc: string;
|
||||
sceneChapters: SceneChapterBlueprint[];
|
||||
actPreviews: CustomWorldSceneActImagePreview[];
|
||||
};
|
||||
|
||||
function normalizeText(value: string | null | undefined) {
|
||||
return value?.trim() ?? '';
|
||||
}
|
||||
|
||||
export function resolveScenePresentationChapters(params: {
|
||||
sceneChapters: CustomWorldProfile['sceneChapterBlueprints'];
|
||||
sceneId: string;
|
||||
sceneName: string;
|
||||
}) {
|
||||
const sceneChapters = params.sceneChapters ?? [];
|
||||
const normalizedSceneId = params.sceneId.trim();
|
||||
const normalizedSceneName = params.sceneName.trim();
|
||||
|
||||
const directMatches = sceneChapters.filter(
|
||||
(chapter) => chapter.sceneId.trim() === normalizedSceneId,
|
||||
);
|
||||
if (directMatches.length > 0) {
|
||||
return directMatches;
|
||||
}
|
||||
|
||||
const linkedMatches = sceneChapters.filter((chapter) =>
|
||||
chapter.linkedLandmarkIds.some(
|
||||
(landmarkId) => landmarkId.trim() === normalizedSceneId,
|
||||
),
|
||||
);
|
||||
if (linkedMatches.length > 0) {
|
||||
return linkedMatches;
|
||||
}
|
||||
|
||||
return sceneChapters.filter((chapter) => {
|
||||
const chapterTitle = chapter.title.trim();
|
||||
return (
|
||||
chapterTitle === normalizedSceneName ||
|
||||
chapter.summary.includes(normalizedSceneName) ||
|
||||
chapter.acts.some(
|
||||
(act) =>
|
||||
act.title.includes(normalizedSceneName) ||
|
||||
act.summary.includes(normalizedSceneName),
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveScenePresentationImage(params: {
|
||||
sceneImageSrc?: string | null;
|
||||
sceneChapters: SceneChapterBlueprint[];
|
||||
}) {
|
||||
const firstActImageSrc =
|
||||
params.sceneChapters
|
||||
.flatMap((chapter) => chapter.acts)
|
||||
.map((act) => normalizeText(act.backgroundImageSrc))
|
||||
.find(Boolean) || '';
|
||||
|
||||
return firstActImageSrc || normalizeText(params.sceneImageSrc);
|
||||
}
|
||||
|
||||
export function collectSceneActImagePreviews(params: {
|
||||
sceneChapters: SceneChapterBlueprint[];
|
||||
sceneImageSrc?: string | null;
|
||||
}) {
|
||||
const sceneImageSrc = normalizeText(params.sceneImageSrc);
|
||||
const actPreviews = params.sceneChapters.flatMap((chapter) =>
|
||||
chapter.acts
|
||||
.map((act, index) => {
|
||||
// 中文注释:幕预览图片必须优先读取当前幕背景;场景图只给缺图的旧数据兜底,避免开局场景和普通场景在列表、详情里显示不同图片。
|
||||
const imageSrc = normalizeText(act.backgroundImageSrc) || sceneImageSrc;
|
||||
return {
|
||||
id: act.id.trim() || `${chapter.id}-act-${index}`,
|
||||
title: act.title.trim() || `第${index + 1}幕`,
|
||||
imageSrc,
|
||||
};
|
||||
})
|
||||
.filter((act) => act.imageSrc),
|
||||
);
|
||||
|
||||
if (actPreviews.length > 0 || !sceneImageSrc) {
|
||||
return actPreviews;
|
||||
}
|
||||
|
||||
return [1, 2, 3].map((actNumber) => ({
|
||||
id: `fallback-scene-act-${actNumber}`,
|
||||
title: `第${actNumber}幕`,
|
||||
imageSrc: sceneImageSrc,
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildScenePresentation(params: {
|
||||
profile: CustomWorldProfile;
|
||||
scene: CustomWorldLandmark;
|
||||
kind: CustomWorldSceneKind;
|
||||
sceneImageSrc?: string | null;
|
||||
}) {
|
||||
const sceneChapters = resolveScenePresentationChapters({
|
||||
sceneChapters: params.profile.sceneChapterBlueprints,
|
||||
sceneId: params.scene.id,
|
||||
sceneName: params.scene.name,
|
||||
});
|
||||
const imageSrc = resolveScenePresentationImage({
|
||||
sceneImageSrc: params.sceneImageSrc ?? params.scene.imageSrc,
|
||||
sceneChapters,
|
||||
});
|
||||
|
||||
return {
|
||||
id: params.scene.id,
|
||||
kind: params.kind,
|
||||
name: params.scene.name,
|
||||
description: params.scene.description,
|
||||
imageSrc,
|
||||
sceneChapters,
|
||||
actPreviews: collectSceneActImagePreviews({
|
||||
sceneChapters,
|
||||
sceneImageSrc: imageSrc,
|
||||
}),
|
||||
} satisfies CustomWorldScenePresentation;
|
||||
}
|
||||
|
||||
export function buildCustomWorldScenePresentations(profile: CustomWorldProfile) {
|
||||
const landmarkImageById = resolveCustomWorldLandmarkImageMap(profile);
|
||||
const campScene = resolveCustomWorldCampScene(profile);
|
||||
const campPresentation = buildScenePresentation({
|
||||
profile,
|
||||
scene: campScene,
|
||||
kind: 'camp',
|
||||
sceneImageSrc: resolveCustomWorldCampSceneImage(profile),
|
||||
});
|
||||
const landmarkPresentations = profile.landmarks.map((landmark) =>
|
||||
buildScenePresentation({
|
||||
profile,
|
||||
scene: landmark,
|
||||
kind: 'landmark',
|
||||
sceneImageSrc: landmarkImageById.get(landmark.id) || landmark.imageSrc,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
camp: campPresentation,
|
||||
landmarks: landmarkPresentations,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user