176 lines
6.9 KiB
TypeScript
176 lines
6.9 KiB
TypeScript
import type {QuestGenerationContext} from '../services/aiTypes';
|
||
import type {QuestOpportunity, QuestSceneSnapshot} from '../services/questTypes';
|
||
import { buildQuestVisibilitySlice } from '../services/storyEngine/visibilityEngine';
|
||
|
||
function describeWorld(worldType: QuestGenerationContext['worldType']) {
|
||
switch (worldType) {
|
||
case 'WUXIA':
|
||
return '边城模板';
|
||
case 'XIANXIA':
|
||
return '灵潮模板';
|
||
case 'CUSTOM':
|
||
return '自定义世界';
|
||
default:
|
||
return '未知世界';
|
||
}
|
||
}
|
||
|
||
function summarizeRecentStoryMoments(context: QuestGenerationContext) {
|
||
const moments = context.recentStoryMoments
|
||
.slice(-4)
|
||
.map(moment => `- ${moment.text}`)
|
||
.join('\n');
|
||
|
||
return moments || '- 暂无近期剧情记录';
|
||
}
|
||
|
||
function summarizeCurrentQuests(context: QuestGenerationContext) {
|
||
const summary = context.currentQuestSummary?.map(quest =>
|
||
`- ${quest.title}(${quest.status}),发布者 ${quest.issuerNpcId}`,
|
||
).join('\n');
|
||
|
||
return summary || '- 当前没有进行中的任务';
|
||
}
|
||
|
||
function summarizeCompanions(context: QuestGenerationContext) {
|
||
const active = context.activeCompanions?.map(companion => companion.characterId).join('、') || '无';
|
||
const roster = context.rosterCompanions?.map(companion => companion.characterId).join('、') || '无';
|
||
return `当前同行角色:${active}\n队伍名册:${roster}`;
|
||
}
|
||
|
||
function summarizePlayerState(context: QuestGenerationContext) {
|
||
const playerName = context.playerCharacter?.name ?? '未知角色';
|
||
const playerTitle = context.playerCharacter?.title ?? '未知称号';
|
||
const hp = `${context.playerHp ?? 0}/${context.playerMaxHp ?? 0}`;
|
||
const mana = `${context.playerMana ?? 0}/${context.playerMaxMana ?? 0}`;
|
||
const inventory = context.playerInventory?.slice(0, 8).map(item => item.name).join('、') || '无';
|
||
|
||
return [
|
||
`玩家:${playerName}(${playerTitle})`,
|
||
`生命:${hp}`,
|
||
`灵力:${mana}`,
|
||
`背包快照:${inventory}`,
|
||
].join('\n');
|
||
}
|
||
|
||
function summarizeScene(scene: QuestSceneSnapshot | null, context: QuestGenerationContext) {
|
||
const hostileNpcIds = context.currentSceneHostileNpcIds?.join('、') || '无';
|
||
const treasureHintCount = context.currentSceneTreasureHintCount ?? 0;
|
||
|
||
return [
|
||
`场景:${scene?.name ?? context.currentSceneName ?? '未知区域'}`,
|
||
`场景描述:${scene?.description ?? context.currentSceneDescription ?? '暂无'}`,
|
||
`敌对角色 ID:${hostileNpcIds}`,
|
||
`宝藏线索数量:${treasureHintCount}`,
|
||
].join('\n');
|
||
}
|
||
|
||
function summarizeActiveThreads(context: QuestGenerationContext) {
|
||
if (!context.activeThreadIds?.length) {
|
||
return '暂无明确激活线程';
|
||
}
|
||
|
||
const storyGraph = context.customWorldProfile?.storyGraph;
|
||
const labels = context.activeThreadIds.map((threadId) =>
|
||
[...(storyGraph?.visibleThreads ?? []), ...(storyGraph?.hiddenThreads ?? [])]
|
||
.find((thread) => thread.id === threadId)?.title ?? threadId,
|
||
);
|
||
|
||
return labels.join('、');
|
||
}
|
||
|
||
function summarizeIssuerNarrativeProfile(context: QuestGenerationContext) {
|
||
const profile = context.issuerNarrativeProfile;
|
||
if (!profile) {
|
||
return '暂无额外叙事档案';
|
||
}
|
||
|
||
return [
|
||
`公开面:${profile.publicMask}`,
|
||
`表层线:${profile.visibleLine}`,
|
||
`当前压力:${profile.immediatePressure}`,
|
||
profile.reactionHooks.length > 0
|
||
? `反应钩子:${profile.reactionHooks.join('、')}`
|
||
: null,
|
||
]
|
||
.filter(Boolean)
|
||
.join('\n');
|
||
}
|
||
|
||
function summarizeQuestVisibility(context: QuestGenerationContext) {
|
||
const slice = buildQuestVisibilitySlice({
|
||
issuerNarrativeProfile: context.issuerNarrativeProfile,
|
||
activeThreadIds: context.activeThreadIds,
|
||
});
|
||
|
||
return [
|
||
`可直接披露:${slice.sayableFactIds.join('、') || '无'}`,
|
||
`只宜写成推测:${slice.inferredFactIds.join('、') || '无'}`,
|
||
`禁止直接说破:${slice.forbiddenFactIds.join('、') || '无'}`,
|
||
].join('\n');
|
||
}
|
||
|
||
export const QUEST_INTENT_SYSTEM_PROMPT = `你是 AI 原生叙事 RPG 的任务导演。
|
||
只返回 JSON,不要输出 Markdown。
|
||
|
||
输出结构:
|
||
{
|
||
"intent": {
|
||
"title": "中文任务标题",
|
||
"description": "中文任务描述",
|
||
"summary": "中文短摘要",
|
||
"narrativeType": "bounty|escort|investigation|retrieval|relationship|trial",
|
||
"dramaticNeed": "string",
|
||
"issuerGoal": "string",
|
||
"playerHook": "string",
|
||
"worldReason": "string",
|
||
"recommendedObjectiveKinds": ["defeat_hostile_npc" | "inspect_treasure" | "spar_with_npc" | "talk_to_npc" | "reach_scene" | "deliver_item"],
|
||
"urgency": "low|medium|high",
|
||
"intimacy": "transactional|cooperative|trust_based",
|
||
"rewardTheme": "currency|resource|relationship|intel|rare_item",
|
||
"followupHooks": ["string"]
|
||
}
|
||
}
|
||
|
||
规则:
|
||
- 所有自然语言字段都必须使用中文。
|
||
- 任务必须扎根于当前场景、发布者和近期剧情。
|
||
- 不要编造奖励、ID、数量、状态或不受支持的规则变化。
|
||
- recommendedObjectiveKinds 只是语义建议,不是硬编码结果。
|
||
- 优先给出简洁、可玩的任务框架,不要堆砌华丽辞藻。
|
||
- title 必须是 4 到 10 个中文字符左右的短任务名,不要写成长句。
|
||
- description 解释任务为什么在当前剧情里成立,避免纯规则说明。
|
||
- summary 必须是清晰达成条件,优先使用“击败 / 调查 / 返回 / 前往 / 交付 / 切磋”等明确动词,不要写“处理一下”“看看情况”这类模糊表达。`;
|
||
|
||
export function buildQuestIntentPrompt(params: {
|
||
context: QuestGenerationContext;
|
||
scene: QuestSceneSnapshot | null;
|
||
opportunity: QuestOpportunity;
|
||
}) {
|
||
const {context, scene, opportunity} = params;
|
||
const customWorldSummary = context.customWorldProfile
|
||
? `${context.customWorldProfile.name}: ${context.customWorldProfile.summary}`
|
||
: '无';
|
||
|
||
return [
|
||
`世界:${describeWorld(context.worldType)}`,
|
||
`自定义世界摘要:${customWorldSummary}`,
|
||
`发布角色:${context.issuerNpcName ?? '未知'}(${context.issuerNpcId ?? '未知'})`,
|
||
`发布者身份:${context.issuerNpcContext ?? '暂无'}`,
|
||
`发布者好感:${context.issuerAffinity ?? 0}`,
|
||
`发布者信息揭示阶段:${context.issuerDisclosureStage ?? '未知'}`,
|
||
`发布者亲疏阶段:${context.issuerWarmthStage ?? '未知'}`,
|
||
`当前激活线程:${summarizeActiveThreads(context)}`,
|
||
`发布者叙事档案:\n${summarizeIssuerNarrativeProfile(context)}`,
|
||
`任务可见性切片:\n${summarizeQuestVisibility(context)}`,
|
||
`当前遭遇类型:${context.encounterKind ?? '无'}`,
|
||
summarizeScene(scene, context),
|
||
summarizePlayerState(context),
|
||
summarizeCompanions(context),
|
||
`当前任务机会:${opportunity.reason}`,
|
||
`当前任务列表:\n${summarizeCurrentQuests(context)}`,
|
||
`近期剧情片段:\n${summarizeRecentStoryMoments(context)}`,
|
||
'现在请基于这次具体局势,生成一个自然生长出来的任务意图。',
|
||
].join('\n\n');
|
||
}
|