Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -1,10 +1,18 @@
|
||||
import { buildCustomCampSceneName } from '../services/customWorldPresentation';
|
||||
import { resolveCustomWorldAnchorWorldType } from '../services/customWorldTheme';
|
||||
import {
|
||||
buildFallbackActorNarrativeProfile,
|
||||
normalizeActorNarrativeProfile,
|
||||
} from '../services/storyEngine/actorNarrativeProfile';
|
||||
import { buildSceneNarrativeResidues } from '../services/storyEngine/sceneResidueCompiler';
|
||||
import { buildThemePackFromWorldProfile } from '../services/storyEngine/themePack';
|
||||
import { buildFallbackWorldStoryGraph } from '../services/storyEngine/worldStoryGraph';
|
||||
import {
|
||||
CustomWorldProfile,
|
||||
Encounter,
|
||||
SceneConnectionInfo,
|
||||
SceneNpc,
|
||||
ScenePresetInfo,
|
||||
WorldType,
|
||||
} from '../types';
|
||||
import { buildRoleAttributeProfileFromLegacyData } from './attributeProfileGenerator';
|
||||
@@ -31,9 +39,9 @@ export interface ScenePreset {
|
||||
forwardSceneId?: string;
|
||||
connectedSceneIds: string[];
|
||||
connections: SceneConnectionInfo[];
|
||||
monsterIds: string[];
|
||||
npcs: SceneNpc[];
|
||||
treasureHints: string[];
|
||||
narrativeResidues?: ScenePresetInfo['narrativeResidues'];
|
||||
}
|
||||
|
||||
export type ScenePresetOverride = Partial<Omit<ScenePreset, 'id' | 'worldType' | 'npcs'>>;
|
||||
@@ -79,7 +87,7 @@ type SceneTemplate = {
|
||||
name: string;
|
||||
description: string;
|
||||
worldType: WorldType;
|
||||
monsterIds: string[];
|
||||
hostileNpcPresetIds: string[];
|
||||
connectedSceneIds: string[];
|
||||
forwardSceneId?: string;
|
||||
treasureHints: string[];
|
||||
@@ -229,7 +237,6 @@ function buildHostileSceneNpc(sceneId: string, worldType: WorldType, monsterId:
|
||||
avatar: preset.name.slice(0, 1) || '敌',
|
||||
description: preset.description,
|
||||
gender: inferCustomNpcGender(`${sceneId}:${preset.id}`, preset.name),
|
||||
hostileNpcPresetId: preset.id,
|
||||
monsterPresetId: preset.id,
|
||||
initialAffinity: -40,
|
||||
hostile: true,
|
||||
@@ -251,19 +258,25 @@ export function getSceneFriendlyNpcs(scene: { npcs?: SceneNpc[] } | null | undef
|
||||
return (scene?.npcs ?? []).filter(npc => !isHostileSceneNpc(npc));
|
||||
}
|
||||
|
||||
export function getSceneHostileNpcPresetIds(scene: { npcs?: SceneNpc[] } | null | undefined) {
|
||||
return [
|
||||
...new Set(
|
||||
getSceneHostileNpcs(scene)
|
||||
.map(npc => npc.monsterPresetId)
|
||||
.filter((monsterPresetId): monsterPresetId is string => Boolean(monsterPresetId)),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
export function buildEncounterFromSceneNpc(
|
||||
npc: SceneNpc,
|
||||
xMeters?: number,
|
||||
): Encounter {
|
||||
const hostileNpcPresetId = npc.hostileNpcPresetId ?? npc.monsterPresetId;
|
||||
const monsterPresetId = npc.monsterPresetId ?? npc.hostileNpcPresetId;
|
||||
|
||||
return {
|
||||
id: npc.id,
|
||||
kind: 'npc',
|
||||
characterId: npc.characterId,
|
||||
hostileNpcPresetId,
|
||||
monsterPresetId,
|
||||
monsterPresetId: npc.monsterPresetId,
|
||||
npcName: npc.name,
|
||||
npcDescription: npc.description,
|
||||
npcAvatar: npc.avatar,
|
||||
@@ -285,6 +298,7 @@ export function buildEncounterFromSceneNpc(
|
||||
initialItems: npc.initialItems,
|
||||
imageSrc: npc.imageSrc,
|
||||
visual: npc.visual,
|
||||
narrativeProfile: npc.narrativeProfile,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -293,6 +307,13 @@ function buildCustomSceneNpc(
|
||||
profile: CustomWorldProfile,
|
||||
anchorWorldType: WorldType,
|
||||
): SceneNpc {
|
||||
const themePack = profile.themePack ?? buildThemePackFromWorldProfile(profile);
|
||||
const storyGraph =
|
||||
profile.storyGraph ?? buildFallbackWorldStoryGraph(profile, themePack);
|
||||
const narrativeProfile = normalizeActorNarrativeProfile(
|
||||
npc.narrativeProfile,
|
||||
buildFallbackActorNarrativeProfile(npc, storyGraph, themePack),
|
||||
);
|
||||
const monsterPreset =
|
||||
npc.initialAffinity < 0
|
||||
? resolveCustomWorldNpcMonsterPreset(npc, anchorWorldType)
|
||||
@@ -324,24 +345,15 @@ function buildCustomSceneNpc(
|
||||
avatar: (npc.imageSrc ?? npc.name.slice(0, 1)) || '?',
|
||||
description: [
|
||||
npc.description,
|
||||
npc.backstoryReveal.publicSummary
|
||||
? `公开背景:${npc.backstoryReveal.publicSummary}`
|
||||
: '',
|
||||
npc.motivation ? `动机:${npc.motivation}` : '',
|
||||
npc.skills.length > 0
|
||||
? `技能:${npc.skills.map((skill) => skill.name).join('、')}`
|
||||
: '',
|
||||
npc.initialItems.length > 0
|
||||
? `随身物:${npc.initialItems
|
||||
.map((item) => `${item.name}x${item.quantity}`)
|
||||
.join('、')}`
|
||||
narrativeProfile.publicMask ? `公开面:${narrativeProfile.publicMask}` : '',
|
||||
narrativeProfile.immediatePressure
|
||||
? `当前压力:${narrativeProfile.immediatePressure}`
|
||||
: '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' '),
|
||||
gender: inferCustomNpcGender(npc.id, npc.name),
|
||||
monsterPresetId: monsterPreset?.id,
|
||||
hostileNpcPresetId: monsterPreset?.id,
|
||||
initialAffinity: npc.initialAffinity,
|
||||
hostile,
|
||||
recruitable: !hostile,
|
||||
@@ -363,6 +375,7 @@ function buildCustomSceneNpc(
|
||||
})),
|
||||
imageSrc: npc.imageSrc,
|
||||
visual: npc.visual,
|
||||
narrativeProfile,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -375,7 +388,7 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
|
||||
const imageOffset = hashText(profile.id || profile.name) % Math.max(1, allImages.length);
|
||||
const anchorWorldType = resolveCustomWorldAnchorWorldType(profile);
|
||||
const baseMonsterPool: string[] = getScenePresetsByWorld(anchorWorldType)
|
||||
.flatMap((scene: ScenePreset) => scene.monsterIds)
|
||||
.flatMap((scene: ScenePreset) => getSceneHostileNpcPresetIds(scene))
|
||||
.filter((monsterId: string, index: number, array: string[]) => array.indexOf(monsterId) === index);
|
||||
const fallbackMonsterIds: string[] = baseMonsterPool.length > 0 ? baseMonsterPool : [];
|
||||
const playableCharacters = buildCustomWorldPlayableCharacters(profile);
|
||||
@@ -423,11 +436,15 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
|
||||
connectedSceneIds: campConnections.map((connection) => connection.sceneId),
|
||||
connections: campConnections,
|
||||
forwardSceneId: pickForwardSceneIdFromConnections(campConnections),
|
||||
monsterIds: [],
|
||||
treasureHints: [
|
||||
`${profile.name}地图残页`,
|
||||
...profile.landmarks.slice(0, 3).map(landmark => `${landmark.name}的旧线索`),
|
||||
].slice(0, 4),
|
||||
narrativeResidues: buildSceneNarrativeResidues({
|
||||
sceneId: campSceneId,
|
||||
sceneName: buildCustomCampSceneName(profile),
|
||||
profile,
|
||||
}),
|
||||
npcs: campNpcs,
|
||||
},
|
||||
...profile.landmarks.map((landmark, index): ScenePreset => {
|
||||
@@ -483,10 +500,11 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
|
||||
connections.map((connection) => connection.sceneId),
|
||||
);
|
||||
const monsterSliceStart = (index * 2) % Math.max(1, fallbackMonsterIds.length || 1);
|
||||
const monsterIds: string[] = fallbackMonsterIds.slice(monsterSliceStart, monsterSliceStart + 2);
|
||||
const hostileNpcs = monsterIds
|
||||
const seedMonsterIds: string[] = fallbackMonsterIds.slice(monsterSliceStart, monsterSliceStart + 2);
|
||||
const hostileNpcs = seedMonsterIds
|
||||
.map((monsterId: string) => buildHostileSceneNpc(buildCustomSceneId('landmark', index), anchorWorldType, monsterId))
|
||||
.filter(Boolean) as SceneNpc[];
|
||||
const combinedNpcs = [...sceneNpcs, ...hostileNpcs];
|
||||
|
||||
return {
|
||||
id: buildCustomSceneId('landmark', index),
|
||||
@@ -497,13 +515,20 @@ function buildCustomScenePresets(profile: CustomWorldProfile): ScenePreset[] {
|
||||
connectedSceneIds,
|
||||
connections,
|
||||
forwardSceneId: pickForwardSceneIdFromConnections(connections),
|
||||
monsterIds,
|
||||
treasureHints: [
|
||||
`${landmark.name}的旧线索`,
|
||||
`${profile.name}相关遗物`,
|
||||
profile.storyNpcs[index]?.name ? `${profile.storyNpcs[index]!.name}留下的痕迹` : `${profile.playerGoal.slice(0, 10)}相关痕迹`,
|
||||
],
|
||||
npcs: [...sceneNpcs, ...hostileNpcs],
|
||||
narrativeResidues:
|
||||
landmark.narrativeResidues && landmark.narrativeResidues.length > 0
|
||||
? landmark.narrativeResidues
|
||||
: buildSceneNarrativeResidues({
|
||||
sceneId: buildCustomSceneId('landmark', index),
|
||||
sceneName: landmark.name,
|
||||
profile,
|
||||
}),
|
||||
npcs: combinedNpcs,
|
||||
};
|
||||
}),
|
||||
];
|
||||
@@ -609,7 +634,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '竹林古道',
|
||||
description: '风过竹叶如刀鸣,窄道蜿蜒向深处,最适合藏伏毒物和游侠。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-13', 'monster-08'],
|
||||
hostileNpcPresetIds: ['monster-13', 'monster-08'],
|
||||
connectedSceneIds: ['wuxia-mountain-gate', 'wuxia-mist-woods', 'wuxia-ferry-bridge'],
|
||||
forwardSceneId: 'wuxia-mountain-gate',
|
||||
treasureHints: ['竹根旁半埋的刀鞘', '倒竹间的旧药囊'],
|
||||
@@ -622,7 +647,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '山门石阶',
|
||||
description: '青石阶层层向上,旧山门半开半掩,守山人与伏兽都能藏得很稳。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-04', 'monster-06'],
|
||||
hostileNpcPresetIds: ['monster-04', 'monster-06'],
|
||||
connectedSceneIds: ['wuxia-temple-forecourt', 'wuxia-border-camp', 'wuxia-bamboo-road'],
|
||||
forwardSceneId: 'wuxia-temple-forecourt',
|
||||
treasureHints: ['裂缝里的铜钥', '石狮座下遗落的令牌'],
|
||||
@@ -635,7 +660,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '雨夜长街',
|
||||
description: '长街积水映灯,屋檐下尽是藏身空隙,最易碰见追踪者与夜行客。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-11', 'monster-07'],
|
||||
hostileNpcPresetIds: ['monster-11', 'monster-07'],
|
||||
connectedSceneIds: ['wuxia-ferry-bridge', 'wuxia-palace-court', 'wuxia-ruined-village'],
|
||||
forwardSceneId: 'wuxia-ferry-bridge',
|
||||
treasureHints: ['灯檐下浸湿的布包', '排水沟边翻起的账册残页'],
|
||||
@@ -648,7 +673,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '荒村断垣',
|
||||
description: '残墙和空屋挤成一团,风里总像夹着旧哭声与游荡脚步。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-03', 'monster-07'],
|
||||
hostileNpcPresetIds: ['monster-03', 'monster-07'],
|
||||
connectedSceneIds: ['wuxia-mist-woods', 'wuxia-rain-street', 'wuxia-border-camp'],
|
||||
forwardSceneId: 'wuxia-border-camp',
|
||||
treasureHints: ['断墙后压着的木匣', '枯井边散落的旧簪'],
|
||||
@@ -661,7 +686,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '古桥渡口',
|
||||
description: '桥面潮湿,渡口雾重,来往之人不多,但每个身影都藏着故事。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-04', 'monster-11'],
|
||||
hostileNpcPresetIds: ['monster-04', 'monster-11'],
|
||||
connectedSceneIds: ['wuxia-rain-street', 'wuxia-bamboo-road', 'wuxia-border-camp'],
|
||||
forwardSceneId: 'wuxia-border-camp',
|
||||
treasureHints: ['桥柱缝里的油纸包', '渡船板下藏着的旧钱袋'],
|
||||
@@ -674,7 +699,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '雾林小径',
|
||||
description: '晨雾久久不散,树影像一层层压下来,适合毒蛇与潜伏兽狩猎。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-08', 'monster-13', 'monster-07'],
|
||||
hostileNpcPresetIds: ['monster-08', 'monster-13', 'monster-07'],
|
||||
connectedSceneIds: ['wuxia-bamboo-road', 'wuxia-ruined-village', 'wuxia-temple-forecourt'],
|
||||
forwardSceneId: 'wuxia-ruined-village',
|
||||
treasureHints: ['缠在树根上的锦囊', '被雾水泡湿的地图残页'],
|
||||
@@ -687,7 +712,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '边关营地',
|
||||
description: '营火与旌旗都带着风沙味,士卒、斥候和异兽都可能在这里短暂停留。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-18', 'monster-11'],
|
||||
hostileNpcPresetIds: ['monster-18', 'monster-11'],
|
||||
connectedSceneIds: ['wuxia-ferry-bridge', 'wuxia-mountain-gate', 'wuxia-ruined-village'],
|
||||
forwardSceneId: 'wuxia-rain-street',
|
||||
treasureHints: ['废营帐里的箭囊', '火盆旁埋着的军需匣'],
|
||||
@@ -700,7 +725,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '地宫通道',
|
||||
description: '地砖尽头传来回声,石壁上的裂隙像无数只正在张望的眼。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-03', 'monster-06'],
|
||||
hostileNpcPresetIds: ['monster-03', 'monster-06'],
|
||||
connectedSceneIds: ['wuxia-temple-forecourt', 'wuxia-mine-depths', 'wuxia-palace-court'],
|
||||
forwardSceneId: 'wuxia-mine-depths',
|
||||
treasureHints: ['砖缝里的陪葬铜匣', '石灯底座后的残卷'],
|
||||
@@ -713,7 +738,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '寺庙前庭',
|
||||
description: '香灰、古钟和石灯挤在一处,清净里始终藏着不安的回响。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-04', 'monster-03'],
|
||||
hostileNpcPresetIds: ['monster-04', 'monster-03'],
|
||||
connectedSceneIds: ['wuxia-mountain-gate', 'wuxia-crypt-passage', 'wuxia-mist-woods'],
|
||||
forwardSceneId: 'wuxia-crypt-passage',
|
||||
treasureHints: ['香炉灰里的玉珠', '石灯下压着的签牌'],
|
||||
@@ -726,7 +751,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '矿道深处',
|
||||
description: '碎石与矿灯照出曲折坑道,深处总有重物挪动与甲壳摩擦声。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-06', 'monster-18'],
|
||||
hostileNpcPresetIds: ['monster-06', 'monster-18'],
|
||||
connectedSceneIds: ['wuxia-crypt-passage', 'wuxia-forge-works', 'wuxia-border-camp'],
|
||||
forwardSceneId: 'wuxia-forge-works',
|
||||
treasureHints: ['矿车夹层里的银匣', '埋在碎矿中的精铁'],
|
||||
@@ -739,7 +764,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '铸坊工场',
|
||||
description: '火星、铁水与重锤声混在一起,热浪里最容易引来重甲怪物与寻刀之人。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-18', 'monster-04'],
|
||||
hostileNpcPresetIds: ['monster-18', 'monster-04'],
|
||||
connectedSceneIds: ['wuxia-mine-depths', 'wuxia-palace-court', 'wuxia-border-camp'],
|
||||
forwardSceneId: 'wuxia-palace-court',
|
||||
treasureHints: ['淬火池旁的铁匣', '风箱后压着的旧兵谱'],
|
||||
@@ -752,7 +777,7 @@ const WUXIA_SCENES: SceneTemplate[] = [
|
||||
name: '宫苑内庭',
|
||||
description: '回廊深处静得过分,花木修得齐整,却处处像埋着王庭旧案。',
|
||||
worldType: WorldType.WUXIA,
|
||||
monsterIds: ['monster-11', 'monster-13'],
|
||||
hostileNpcPresetIds: ['monster-11', 'monster-13'],
|
||||
connectedSceneIds: ['wuxia-forge-works', 'wuxia-rain-street', 'wuxia-crypt-passage'],
|
||||
forwardSceneId: 'wuxia-rain-street',
|
||||
treasureHints: ['回廊暗格里的香囊', '花圃石座下的旧金牌'],
|
||||
@@ -768,7 +793,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '云海仙门',
|
||||
description: '云阶在脚下翻涌,门阙后方灵光不断,来客与守门异物都极显眼。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-02', 'monster-16'],
|
||||
hostileNpcPresetIds: ['monster-02', 'monster-16'],
|
||||
connectedSceneIds: ['xianxia-floating-isle', 'xianxia-celestial-corridor', 'xianxia-star-vessel'],
|
||||
forwardSceneId: 'xianxia-celestial-corridor',
|
||||
treasureHints: ['云阶尽头的灵符匣', '门阙阴影里的玉牌'],
|
||||
@@ -781,7 +806,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '悬空仙岛',
|
||||
description: '浮岛边缘风大云急,灵禽与飞蛾总绕着岛沿的光带盘旋。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-12', 'monster-16'],
|
||||
hostileNpcPresetIds: ['monster-12', 'monster-16'],
|
||||
connectedSceneIds: ['xianxia-cloud-gate', 'xianxia-waterfall-cliff', 'xianxia-moon-lake'],
|
||||
forwardSceneId: 'xianxia-moon-lake',
|
||||
treasureHints: ['浮岛边缘的灵羽匣', '云藤下悬着的小玉瓶'],
|
||||
@@ -794,7 +819,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '天宫长廊',
|
||||
description: '廊柱之间回响着空灵风声,禁制和书妖都喜欢寄在这类高处回廊里。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-02', 'monster-14'],
|
||||
hostileNpcPresetIds: ['monster-02', 'monster-14'],
|
||||
connectedSceneIds: ['xianxia-cloud-gate', 'xianxia-thunder-altar', 'xianxia-ancient-ruins'],
|
||||
forwardSceneId: 'xianxia-thunder-altar',
|
||||
treasureHints: ['廊柱暗槽里的玉简', '风铃后藏着的封签'],
|
||||
@@ -807,7 +832,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '灵药花圃',
|
||||
description: '灵草灵花层层叠开,香气诱人,却也最容易养出食灵的怪物。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-15', 'monster-05'],
|
||||
hostileNpcPresetIds: ['monster-15', 'monster-05'],
|
||||
connectedSceneIds: ['xianxia-jade-cavern', 'xianxia-sacred-tree', 'xianxia-moon-lake'],
|
||||
forwardSceneId: 'xianxia-sacred-tree',
|
||||
treasureHints: ['药圃深处的灵壶', '花架下压着的采录册'],
|
||||
@@ -820,7 +845,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '寒玉洞天',
|
||||
description: '洞壁结着寒玉光泽,地面湿滑,水灵和阴性异物都爱停在这里。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-10', 'monster-12', 'monster-20'],
|
||||
hostileNpcPresetIds: ['monster-10', 'monster-12', 'monster-20'],
|
||||
connectedSceneIds: ['xianxia-herb-garden', 'xianxia-moon-lake', 'xianxia-ancient-ruins'],
|
||||
forwardSceneId: 'xianxia-moon-lake',
|
||||
treasureHints: ['寒玉裂隙里的灵髓', '冰面下闪着光的贝匣'],
|
||||
@@ -833,7 +858,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '熔岩秘境',
|
||||
description: '热浪裹着赤光翻涌,附近的异章与泥灵都容易被灼气激得发狂。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-14', 'monster-10'],
|
||||
hostileNpcPresetIds: ['monster-14', 'monster-10'],
|
||||
connectedSceneIds: ['xianxia-thunder-altar', 'xianxia-waterfall-cliff', 'xianxia-jade-cavern'],
|
||||
forwardSceneId: 'xianxia-waterfall-cliff',
|
||||
treasureHints: ['熔岩边冷却的矿匣', '焦岩后藏着的火纹石'],
|
||||
@@ -846,7 +871,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '雷殿祭坛',
|
||||
description: '祭坛上方雷纹未散,灵书、飞蛾与雷意余波总会把来者围在中心。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-02', 'monster-16'],
|
||||
hostileNpcPresetIds: ['monster-02', 'monster-16'],
|
||||
connectedSceneIds: ['xianxia-celestial-corridor', 'xianxia-molten-realm', 'xianxia-star-vessel'],
|
||||
forwardSceneId: 'xianxia-star-vessel',
|
||||
treasureHints: ['祭坛角落的雷纹匣', '断碑背面的青铜铃'],
|
||||
@@ -859,7 +884,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '星舟甲板',
|
||||
description: '甲板横在高天之上,风压和星光都很强,飞行异物最爱在这里盘旋。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-12', 'monster-16', 'monster-02'],
|
||||
hostileNpcPresetIds: ['monster-12', 'monster-16', 'monster-02'],
|
||||
connectedSceneIds: ['xianxia-thunder-altar', 'xianxia-cloud-gate', 'xianxia-floating-isle'],
|
||||
forwardSceneId: 'xianxia-floating-isle',
|
||||
treasureHints: ['舵台后的星图匣', '甲板缝里卡着的灵罗盘'],
|
||||
@@ -872,7 +897,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '月湖仙洲',
|
||||
description: '湖光像铺开的镜面,水灵、章灵与花影都可能从月色里浮出来。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-20', 'monster-14', 'monster-15'],
|
||||
hostileNpcPresetIds: ['monster-20', 'monster-14', 'monster-15'],
|
||||
connectedSceneIds: ['xianxia-jade-cavern', 'xianxia-floating-isle', 'xianxia-herb-garden'],
|
||||
forwardSceneId: 'xianxia-herb-garden',
|
||||
treasureHints: ['湖岸边漂来的玉匣', '月色下若隐若现的银铃'],
|
||||
@@ -885,7 +910,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '古仙遗迹',
|
||||
description: '残碑、断墙与旧阵纹密密叠在一起,最容易招来书妖和骨灵残念。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-02', 'monster-05', 'monster-12'],
|
||||
hostileNpcPresetIds: ['monster-02', 'monster-05', 'monster-12'],
|
||||
connectedSceneIds: ['xianxia-celestial-corridor', 'xianxia-jade-cavern', 'xianxia-sacred-tree'],
|
||||
forwardSceneId: 'xianxia-sacred-tree',
|
||||
treasureHints: ['残阵中心埋着的玉简', '倒塌碑柱里的小匣'],
|
||||
@@ -898,7 +923,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '神木秘境',
|
||||
description: '古树根系盘踞成殿,枝叶遮天,最易孕出噬灵花与窥视灵眼。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-15', 'monster-05'],
|
||||
hostileNpcPresetIds: ['monster-15', 'monster-05'],
|
||||
connectedSceneIds: ['xianxia-herb-garden', 'xianxia-ancient-ruins', 'xianxia-waterfall-cliff'],
|
||||
forwardSceneId: 'xianxia-waterfall-cliff',
|
||||
treasureHints: ['盘根间的木纹匣', '树洞深处垂着的灵种'],
|
||||
@@ -911,7 +936,7 @@ const XIANXIA_SCENES: SceneTemplate[] = [
|
||||
name: '飞瀑仙崖',
|
||||
description: '瀑声压住一切杂音,崖边潮气浓重,飞蝠、水灵与章影都很容易现身。',
|
||||
worldType: WorldType.XIANXIA,
|
||||
monsterIds: ['monster-12', 'monster-20', 'monster-16'],
|
||||
hostileNpcPresetIds: ['monster-12', 'monster-20', 'monster-16'],
|
||||
connectedSceneIds: ['xianxia-sacred-tree', 'xianxia-molten-realm', 'xianxia-floating-isle'],
|
||||
forwardSceneId: 'xianxia-cloud-gate',
|
||||
treasureHints: ['瀑幕后闪着光的石匣', '崖边藤上挂着的护身铃'],
|
||||
@@ -926,9 +951,10 @@ function buildScenePoolFromTemplates(templates: SceneTemplate[]): ScenePreset[]
|
||||
|
||||
return templates.map((template, index) => {
|
||||
const characterNpcs = buildCharacterNpcPool(template.id, template.worldType);
|
||||
const hostileNpcs = template.monsterIds
|
||||
.map(monsterId => buildHostileSceneNpc(template.id, template.worldType, monsterId))
|
||||
.filter(Boolean) as SceneNpc[];
|
||||
const hostileNpcs = template.hostileNpcPresetIds
|
||||
.map(monsterId => buildHostileSceneNpc(template.id, template.worldType, monsterId))
|
||||
.filter(Boolean) as SceneNpc[];
|
||||
const mergedSceneNpcs = mergeNpcs(characterNpcs, [...hostileNpcs, ...template.extraNpcs], template.worldType);
|
||||
const sceneOverride = SCENE_OVERRIDES[template.id] ?? {};
|
||||
return {
|
||||
...template,
|
||||
@@ -938,7 +964,14 @@ function buildScenePoolFromTemplates(templates: SceneTemplate[]): ScenePreset[]
|
||||
sceneOverride.connectedSceneIds ?? template.connectedSceneIds,
|
||||
sceneOverride.forwardSceneId ?? template.forwardSceneId,
|
||||
),
|
||||
npcs: mergeNpcs(characterNpcs, [...hostileNpcs, ...template.extraNpcs], template.worldType),
|
||||
narrativeResidues: template.treasureHints.slice(0, 2).map((hint, residueIndex) => ({
|
||||
id: `residue:${template.id}:${residueIndex + 1}`,
|
||||
title: `${template.name}的残痕 ${residueIndex + 1}`,
|
||||
visibleClue: hint,
|
||||
linkedFactIds: [],
|
||||
linkedThreadIds: [],
|
||||
})),
|
||||
npcs: mergedSceneNpcs,
|
||||
} satisfies ScenePreset;
|
||||
});
|
||||
}
|
||||
@@ -1027,8 +1060,9 @@ export function buildSceneEntityCatalogText(worldType: WorldType, sceneId: strin
|
||||
return '当前区域暂无可用实体目录。';
|
||||
}
|
||||
|
||||
const monsterText = scene.monsterIds.length > 0
|
||||
? scene.monsterIds
|
||||
const hostileNpcPresetIds = getSceneHostileNpcPresetIds(scene);
|
||||
const monsterText = hostileNpcPresetIds.length > 0
|
||||
? hostileNpcPresetIds
|
||||
.map(monsterId => getMonsterPresetById(worldType, monsterId)?.name ?? monsterId)
|
||||
.join('、')
|
||||
: '暂无明确怪物';
|
||||
@@ -1044,12 +1078,16 @@ export function buildSceneEntityCatalogText(worldType: WorldType, sceneId: strin
|
||||
const treasureText = scene.treasureHints.length > 0
|
||||
? scene.treasureHints.join('、')
|
||||
: '暂无明确宝藏线索';
|
||||
const residueText = (scene.narrativeResidues?.length ?? 0) > 0
|
||||
? scene.narrativeResidues!.map((residue: NonNullable<ScenePresetInfo['narrativeResidues']>[number]) => `${residue.title}:${residue.visibleClue}`).join('、')
|
||||
: '暂无明显场景残痕';
|
||||
|
||||
return [
|
||||
`当前怪物:${monsterText}`,
|
||||
`当前敌对角色:${hostileNpcText}`,
|
||||
`当前场景角色:${friendlyNpcText}`,
|
||||
`当前宝藏线索:${treasureText}`,
|
||||
`当前场景残痕:${residueText}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user