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'); }