250 lines
12 KiB
TypeScript
250 lines
12 KiB
TypeScript
type ParsedRole = {
|
||
id: string;
|
||
name: string;
|
||
title: string;
|
||
role: string;
|
||
description: string;
|
||
visualDescription: string;
|
||
actionDescription: string;
|
||
sceneVisualDescription: string;
|
||
backstory: string;
|
||
personality: string;
|
||
motivation: string;
|
||
tags: string[];
|
||
};
|
||
|
||
type ParsedLandmarkConnection = {
|
||
targetLandmarkId: string;
|
||
summary: string;
|
||
relativePosition: string;
|
||
};
|
||
|
||
type ParsedLandmark = {
|
||
id: string;
|
||
name: string;
|
||
description: string;
|
||
visualDescription: string;
|
||
dangerLevel: string;
|
||
sceneNpcIds: string[];
|
||
connections: ParsedLandmarkConnection[];
|
||
};
|
||
|
||
type ParsedProfile = {
|
||
name: string;
|
||
settingText: string;
|
||
summary: string;
|
||
tone: string;
|
||
playerGoal: string;
|
||
playableNpcs: ParsedRole[];
|
||
storyNpcs: ParsedRole[];
|
||
landmarks: ParsedLandmark[];
|
||
};
|
||
|
||
export const CUSTOM_WORLD_ENTITY_GENERATOR_SYSTEM_PROMPT =
|
||
'你是游戏世界编辑器的实体生成器。你必须只返回可解析 JSON,不要输出解释、前言或 Markdown。';
|
||
|
||
function buildRoleReferenceText(roles: ParsedRole[], emptyText: string) {
|
||
if (roles.length === 0) {
|
||
return emptyText;
|
||
}
|
||
|
||
return roles
|
||
.slice(0, 12)
|
||
.map(
|
||
(role, index) =>
|
||
`${index + 1}. ${role.name} / ${role.title || role.role} / 身份:${
|
||
role.role || '未写'
|
||
} / 描述:${role.description || '未写'} / 背景:${
|
||
role.backstory || '未写'
|
||
} / 性格:${role.personality || '未写'} / 动机:${
|
||
role.motivation || '未写'
|
||
} / 形象:${role.visualDescription || '未写'} / 动作表现:${
|
||
role.actionDescription || '未写'
|
||
} / 场景画面:${role.sceneVisualDescription || '未写'} / 标签:${
|
||
role.tags.join('、') || '暂无'
|
||
}`,
|
||
)
|
||
.join('\n');
|
||
}
|
||
|
||
function buildLandmarkReferenceText(profile: ParsedProfile) {
|
||
if (profile.landmarks.length === 0) {
|
||
return '当前还没有场景设定。';
|
||
}
|
||
|
||
const storyNpcById = new Map(profile.storyNpcs.map((npc) => [npc.id, npc]));
|
||
const landmarkById = new Map(
|
||
profile.landmarks.map((landmark) => [landmark.id, landmark]),
|
||
);
|
||
|
||
return profile.landmarks
|
||
.slice(0, 12)
|
||
.map((landmark, index) => {
|
||
const sceneNpcNames = landmark.sceneNpcIds
|
||
.map((npcId) => storyNpcById.get(npcId)?.name ?? '')
|
||
.filter(Boolean)
|
||
.join('、');
|
||
const connectionNames = landmark.connections
|
||
.map((connection) => {
|
||
const targetName =
|
||
landmarkById.get(connection.targetLandmarkId)?.name ||
|
||
connection.targetLandmarkId;
|
||
return `${targetName}(${connection.relativePosition} / ${
|
||
connection.summary || '无说明'
|
||
})`;
|
||
})
|
||
.join('、');
|
||
|
||
return `${index + 1}. ${landmark.name} / 危险度:${
|
||
landmark.dangerLevel || 'medium'
|
||
} / 描述:${landmark.description || '未写'} / 画面:${
|
||
landmark.visualDescription || '未写'
|
||
} / 场景角色:${
|
||
sceneNpcNames || '暂无'
|
||
} / 连接:${connectionNames || '暂无'}`;
|
||
})
|
||
.join('\n');
|
||
}
|
||
|
||
export function buildPlayablePrompt(profile: ParsedProfile) {
|
||
return [
|
||
`世界名:${profile.name}`,
|
||
`当前世界设定:${profile.settingText || profile.summary || '未提供额外设定。'}`,
|
||
`世界摘要:${profile.summary || '未填写'}`,
|
||
`世界基调:${profile.tone || '未填写'}`,
|
||
`玩家主线目标:${profile.playerGoal || '未填写'}`,
|
||
`当前可扮演角色设定:\n${buildRoleReferenceText(profile.playableNpcs, '当前还没有可扮演角色。')}`,
|
||
`当前场景角色设定:\n${buildRoleReferenceText(profile.storyNpcs, '当前还没有场景角色。')}`,
|
||
`当前场景设定:\n${buildLandmarkReferenceText(profile)}`,
|
||
'请基于上面全部上下文,生成 1 名新的“可扮演角色”。',
|
||
'要求:',
|
||
'- 必须与当前世界设定、已有可扮演角色、已有场景角色、已有场景形成互补,不要重复定位。',
|
||
'- 必须保留明确的协作价值、成长空间和入队理由。',
|
||
'- 不要生成泛用模板角色,必须让角色与当前世界的具体势力、地点、冲突或禁忌发生绑定。',
|
||
'- visualDescription 只写与角色设定相关的外形、服装、材质、武器、体态、色彩和识别特征,禁止写角色以外的周边环境等与角色不想管的设定。',
|
||
'- actionDescription 只写这个角色的动作表现与战斗气质,不要写镜头切换或参数。',
|
||
'- sceneVisualDescription 只写该角色首次登场或主要活动区域的场景画面感,不要写“提示词”字样。',
|
||
'- 只返回 JSON,不要输出解释或 Markdown。',
|
||
'JSON 结构:',
|
||
'{',
|
||
' "playableNpc": {',
|
||
' "name": "角色名",',
|
||
' "title": "称号",',
|
||
' "role": "身份",',
|
||
' "description": "一句到两句定位描述",',
|
||
' "visualDescription": "角色形象描述",',
|
||
' "actionDescription": "动作表现描述",',
|
||
' "sceneVisualDescription": "角色关联场景画面描述",',
|
||
' "backstory": "背景经历",',
|
||
' "personality": "性格特点",',
|
||
' "motivation": "当前动机",',
|
||
' "combatStyle": "战斗风格",',
|
||
' "initialAffinity": 22,',
|
||
' "relationshipHooks": ["关系钩子1", "关系钩子2", "关系钩子3"],',
|
||
' "tags": ["标签1", "标签2", "标签3"],',
|
||
' "publicSummary": "公开背景摘要",',
|
||
' "chapterTeasers": ["表层来意", "旧事裂痕", "隐藏执念", "最终底牌"],',
|
||
' "chapterContents": ["对应正文1", "对应正文2", "对应正文3", "对应正文4"],',
|
||
' "skills": [',
|
||
' { "name": "技能1", "summary": "说明", "style": "风格" },',
|
||
' { "name": "技能2", "summary": "说明", "style": "风格" },',
|
||
' { "name": "技能3", "summary": "说明", "style": "风格" }',
|
||
' ],',
|
||
' "initialItems": [',
|
||
' { "name": "物品1", "category": "武器", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },',
|
||
' { "name": "物品2", "category": "专属物品", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },',
|
||
' { "name": "物品3", "category": "消耗品", "quantity": 2, "rarity": "uncommon", "description": "说明", "tags": ["标签"] }',
|
||
' ]',
|
||
' }',
|
||
'}',
|
||
].join('\n');
|
||
}
|
||
|
||
export function buildStoryPrompt(profile: ParsedProfile) {
|
||
return [
|
||
`世界名:${profile.name}`,
|
||
`当前世界设定:${profile.settingText || profile.summary || '未提供额外设定。'}`,
|
||
`世界摘要:${profile.summary || '未填写'}`,
|
||
`世界基调:${profile.tone || '未填写'}`,
|
||
`玩家主线目标:${profile.playerGoal || '未填写'}`,
|
||
`当前可扮演角色设定:\n${buildRoleReferenceText(profile.playableNpcs, '当前还没有可扮演角色。')}`,
|
||
`当前场景角色设定:\n${buildRoleReferenceText(profile.storyNpcs, '当前还没有场景角色。')}`,
|
||
`当前场景设定:\n${buildLandmarkReferenceText(profile)}`,
|
||
'请基于上面全部上下文,生成 1 名新的“场景角色”。',
|
||
'要求:',
|
||
'- 必须与当前世界设定、已有可扮演角色、已有场景角色、已有场景形成互补,不要重复定位。',
|
||
'- 必须像能直接进入游戏的场景角色,而不是抽象设定条目。',
|
||
'- 角色应与具体场景、关系链或局势变化发生绑定。',
|
||
'- visualDescription 只写与角色设定匹配的外形、服装、材质、武器、体态、色彩和识别特征,不要写“提示词”、镜头参数或构图规则。',
|
||
'- actionDescription 只写这个角色的动作表现与战斗气质,不要写镜头切换或参数。',
|
||
'- sceneVisualDescription 只写该角色首次登场或主要活动区域的场景画面感,不要写“提示词”字样。',
|
||
'- 只返回 JSON,不要输出解释或 Markdown。',
|
||
'JSON 结构:',
|
||
'{',
|
||
' "storyNpc": {',
|
||
' "name": "角色名",',
|
||
' "title": "称号",',
|
||
' "role": "身份",',
|
||
' "description": "一句到两句定位描述",',
|
||
' "visualDescription": "角色形象描述",',
|
||
' "actionDescription": "动作表现描述",',
|
||
' "sceneVisualDescription": "角色关联场景画面描述",',
|
||
' "backstory": "背景经历",',
|
||
' "personality": "性格特点",',
|
||
' "motivation": "当前动机",',
|
||
' "combatStyle": "战斗风格",',
|
||
' "initialAffinity": 6,',
|
||
' "relationshipHooks": ["关系钩子1", "关系钩子2", "关系钩子3"],',
|
||
' "tags": ["标签1", "标签2", "标签3"],',
|
||
' "publicSummary": "公开背景摘要",',
|
||
' "chapterTeasers": ["表层来意", "旧事裂痕", "隐藏执念", "最终底牌"],',
|
||
' "chapterContents": ["对应正文1", "对应正文2", "对应正文3", "对应正文4"],',
|
||
' "skills": [',
|
||
' { "name": "技能1", "summary": "说明", "style": "风格" },',
|
||
' { "name": "技能2", "summary": "说明", "style": "风格" },',
|
||
' { "name": "技能3", "summary": "说明", "style": "风格" }',
|
||
' ],',
|
||
' "initialItems": [',
|
||
' { "name": "物品1", "category": "武器", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },',
|
||
' { "name": "物品2", "category": "专属物品", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },',
|
||
' { "name": "物品3", "category": "消耗品", "quantity": 2, "rarity": "uncommon", "description": "说明", "tags": ["标签"] }',
|
||
' ]',
|
||
' }',
|
||
'}',
|
||
].join('\n');
|
||
}
|
||
|
||
export function buildLandmarkPrompt(profile: ParsedProfile) {
|
||
return [
|
||
`世界名:${profile.name}`,
|
||
`当前世界设定:${profile.settingText || profile.summary || '未提供额外设定。'}`,
|
||
`世界摘要:${profile.summary || '未填写'}`,
|
||
`世界基调:${profile.tone || '未填写'}`,
|
||
`玩家主线目标:${profile.playerGoal || '未填写'}`,
|
||
`当前可扮演角色设定:\n${buildRoleReferenceText(profile.playableNpcs, '当前还没有可扮演角色。')}`,
|
||
`当前场景角色设定:\n${buildRoleReferenceText(profile.storyNpcs, '当前还没有场景角色。')}`,
|
||
`当前场景设定:\n${buildLandmarkReferenceText(profile)}`,
|
||
'请基于上面全部上下文,生成 1 个新的“场景”。',
|
||
'要求:',
|
||
'- 必须与当前世界设定、已有可扮演角色、已有场景角色、已有场景形成互补,不要重复。',
|
||
'- 必须给出适合出现在这个新场景里的 sceneNpcNames,且只能从已有场景角色里选择至少 3 个名字。',
|
||
'- 必须给出 connections,且 targetLandmarkName 只能引用已有场景名,不要连向自己。',
|
||
'- visualDescription 只写这个场景的空间层次、地面、主体建筑或自然景观、氛围、色彩和可见装置,不要写“提示词”、镜头参数或构图规则。',
|
||
'- 只返回 JSON,不要输出解释或 Markdown。',
|
||
'JSON 结构:',
|
||
'{',
|
||
' "landmark": {',
|
||
' "name": "场景名",',
|
||
' "description": "场景描述",',
|
||
' "visualDescription": "场景画面描述",',
|
||
' "dangerLevel": "low|medium|high|extreme",',
|
||
' "sceneNpcNames": ["场景角色1", "场景角色2", "场景角色3"],',
|
||
' "connections": [',
|
||
' { "targetLandmarkName": "已有场景名", "relativePosition": "forward", "summary": "通路说明" },',
|
||
' { "targetLandmarkName": "已有场景名", "relativePosition": "inside", "summary": "通路说明" }',
|
||
' ]',
|
||
' }',
|
||
'}',
|
||
].join('\n');
|
||
}
|