This commit is contained in:
2026-04-27 14:23:19 +08:00
parent 09d3fe59b3
commit fa2dbb310b
75 changed files with 7363 additions and 1487 deletions

View File

@@ -381,6 +381,40 @@ function buildCustomSceneId(kind: 'camp' | 'landmark', index = 0) {
return kind === 'camp' ? 'custom-scene-camp' : `custom-scene-landmark-${index + 1}`;
}
function collectSceneActNpcIdsForScene(
profile: CustomWorldProfile,
sceneAliases: string[],
) {
const aliasSet = new Set(sceneAliases.map((alias) => alias.trim()).filter(Boolean));
const npcIds: string[] = [];
const pushNpcId = (npcId: string | null | undefined) => {
const normalizedNpcId = npcId?.trim() ?? '';
if (normalizedNpcId && !npcIds.includes(normalizedNpcId)) {
npcIds.push(normalizedNpcId);
}
};
profile.sceneChapterBlueprints?.forEach((chapter) => {
const chapterSceneIds = [
chapter.sceneId,
...chapter.linkedLandmarkIds,
...chapter.acts.map((act) => act.sceneId),
].map((sceneId) => sceneId.trim()).filter(Boolean);
if (!chapterSceneIds.some((sceneId) => aliasSet.has(sceneId))) {
return;
}
chapter.acts.forEach((act) => {
pushNpcId(act.primaryNpcId);
pushNpcId(act.oppositeNpcId);
act.encounterNpcIds.forEach(pushNpcId);
});
});
return npcIds;
}
function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
const campSceneProfile = resolveCustomWorldCampScene(profile);
const landmarkImageMap = resolveCustomWorldLandmarkImageMap(profile);
@@ -403,6 +437,37 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
const customStoryNpcById = new Map(
profile.storyNpcs.map((npc) => [npc.id, npc]),
);
const buildCustomSceneNpcByRoleId = (roleId: string) => {
const storyNpc = customStoryNpcById.get(roleId);
if (storyNpc) {
return buildCustomSceneNpc(storyNpc, profile);
}
const playableNpc = profile.playableNpcs.find((npc) => npc.id === roleId);
if (!playableNpc) {
return null;
}
return buildCharacterNpc(playableNpc.id, WorldType.CUSTOM, profile);
};
const pushUniqueSceneNpc = (sceneNpcs: SceneNpc[], npc: SceneNpc | null) => {
if (!npc) {
return;
}
const candidateIds = [npc.id, npc.characterId].filter(Boolean);
if (
sceneNpcs.some((sceneNpc) =>
[sceneNpc.id, sceneNpc.characterId]
.filter(Boolean)
.some((sceneNpcId) => candidateIds.includes(sceneNpcId)),
)
) {
return;
}
sceneNpcs.push(npc);
};
const campNpcs = playableCharacters.slice(1).map(character => {
const npc = buildCharacterNpc(character.id, WorldType.CUSTOM, profile);
return npc
@@ -413,6 +478,12 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
}
: null;
}).filter(Boolean) as SceneNpc[];
collectSceneActNpcIdsForScene(profile, [
campSceneId,
profile.camp?.id ?? '',
]).forEach((npcId) =>
pushUniqueSceneNpc(campNpcs, buildCustomSceneNpcByRoleId(npcId)),
);
const campConnections = profile.landmarks
.slice(0, 3)
@@ -445,12 +516,20 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
npcs: campNpcs,
},
...profile.landmarks.map((landmark, index): ScenePreset => {
const sceneNpcs = landmark.sceneNpcIds
const runtimeSceneId = buildCustomSceneId('landmark', index);
const sceneActNpcIds = collectSceneActNpcIdsForScene(profile, [
landmark.id,
runtimeSceneId,
]);
const sceneNpcs = [...sceneActNpcIds, ...landmark.sceneNpcIds]
.map((npcId) => customStoryNpcById.get(npcId))
.filter(Boolean)
.map((npc) =>
buildCustomSceneNpc(npc!, profile),
);
sceneActNpcIds.forEach((npcId) =>
pushUniqueSceneNpc(sceneNpcs, buildCustomSceneNpcByRoleId(npcId)),
);
if (sceneNpcs.length < 3) {
profile.storyNpcs
.filter(
@@ -499,12 +578,12 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
const monsterSliceStart = (index * 2) % Math.max(1, fallbackMonsterIds.length || 1);
const seedMonsterIds: string[] = fallbackMonsterIds.slice(monsterSliceStart, monsterSliceStart + 2);
const hostileNpcs = seedMonsterIds
.map((monsterId: string) => buildHostileSceneNpc(buildCustomSceneId('landmark', index), WorldType.CUSTOM, monsterId))
.map((monsterId: string) => buildHostileSceneNpc(runtimeSceneId, WorldType.CUSTOM, monsterId))
.filter(Boolean) as SceneNpc[];
const combinedNpcs = [...sceneNpcs, ...hostileNpcs];
return {
id: buildCustomSceneId('landmark', index),
id: runtimeSceneId,
name: landmark.name,
description: landmark.description,
worldType: WorldType.CUSTOM,
@@ -521,7 +600,7 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
landmark.narrativeResidues && landmark.narrativeResidues.length > 0
? landmark.narrativeResidues
: buildSceneNarrativeResidues({
sceneId: buildCustomSceneId('landmark', index),
sceneId: runtimeSceneId,
sceneName: landmark.name,
profile,
}),