This commit is contained in:
34
src/data/functionCatalog/npc/index.ts
Normal file
34
src/data/functionCatalog/npc/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
import { NPC_CHAT_FUNCTION } from './npcChat';
|
||||
import {
|
||||
NPC_CHAT_QUEST_OFFER_ABANDON_FUNCTION,
|
||||
NPC_CHAT_QUEST_OFFER_REPLACE_FUNCTION,
|
||||
NPC_CHAT_QUEST_OFFER_VIEW_FUNCTION,
|
||||
} from './npcChatQuestOffer';
|
||||
import { NPC_FIGHT_FUNCTION } from './npcFight';
|
||||
import { NPC_GIFT_FUNCTION } from './npcGift';
|
||||
import { NPC_HELP_FUNCTION } from './npcHelp';
|
||||
import { NPC_LEAVE_FUNCTION } from './npcLeave';
|
||||
import { NPC_PREVIEW_TALK_FUNCTION } from './npcPreviewTalk';
|
||||
import { NPC_QUEST_ACCEPT_FUNCTION } from './npcQuestAccept';
|
||||
import { NPC_QUEST_TURN_IN_FUNCTION } from './npcQuestTurnIn';
|
||||
import { NPC_RECRUIT_FUNCTION } from './npcRecruit';
|
||||
import { NPC_SPAR_FUNCTION } from './npcSpar';
|
||||
import { NPC_TRADE_FUNCTION } from './npcTrade';
|
||||
|
||||
export const NPC_FUNCTION_DOCUMENTATION: FunctionDocumentationEntry[] = [
|
||||
NPC_PREVIEW_TALK_FUNCTION,
|
||||
NPC_TRADE_FUNCTION,
|
||||
NPC_FIGHT_FUNCTION,
|
||||
NPC_SPAR_FUNCTION,
|
||||
NPC_HELP_FUNCTION,
|
||||
NPC_CHAT_FUNCTION,
|
||||
NPC_CHAT_QUEST_OFFER_VIEW_FUNCTION,
|
||||
NPC_CHAT_QUEST_OFFER_REPLACE_FUNCTION,
|
||||
NPC_CHAT_QUEST_OFFER_ABANDON_FUNCTION,
|
||||
NPC_GIFT_FUNCTION,
|
||||
NPC_RECRUIT_FUNCTION,
|
||||
NPC_QUEST_ACCEPT_FUNCTION,
|
||||
NPC_QUEST_TURN_IN_FUNCTION,
|
||||
NPC_LEAVE_FUNCTION,
|
||||
];
|
||||
34
src/data/functionCatalog/npc/npcChat.ts
Normal file
34
src/data/functionCatalog/npc/npcChat.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_chat
|
||||
*
|
||||
* 与眼前 NPC 围绕当前话题继续交谈的 function。
|
||||
*/
|
||||
export const NPC_CHAT_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_chat',
|
||||
domain: 'npc',
|
||||
title: '继续交谈',
|
||||
source: 'src/data/functionCatalog/npc/npcChat.ts',
|
||||
summary: '围绕当前话题展开聊天并累积关系推进。',
|
||||
detailedDescription:
|
||||
'它会先生成一段聊天正文,再在后台继续生成新的冒险选项。当前 UI 中,新选项通常会被延后到 story_continue_adventure 之后再展示。',
|
||||
trigger:
|
||||
'在 NPC 交互菜单里按不同话题重复出现,functionId 相同但 actionText 和 detailText 可不同。',
|
||||
execution:
|
||||
'点击后先进入流式聊天,再触发一次新的剧情推理,并把真正的新 options 放入 deferredOptions。',
|
||||
result:
|
||||
'玩家会看到对话正文、关系变化和后续继续冒险入口,而不是立刻显示新一轮选项。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'stream_then_defer',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts -> commitNpcChatState',
|
||||
animationNote: '重点在流式对白和轻量站场,不额外打开窗口。',
|
||||
storyNote:
|
||||
'先生成聊天正文,再把真正的新选项放入 deferredOptions,等待 continue adventure。',
|
||||
uiNote: '不弹 modal,直接进入聊天流。',
|
||||
compactDetailText: '聊聊并试探口风',
|
||||
},
|
||||
};
|
||||
86
src/data/functionCatalog/npc/npcChatQuestOffer.ts
Normal file
86
src/data/functionCatalog/npc/npcChatQuestOffer.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_chat_quest_offer_*
|
||||
*
|
||||
* NPC 聊天态里的临时委托处理 function。它们不是新的任务系统,
|
||||
* 而是高好感聊天中 pending quest offer 的查看、更换和放弃入口。
|
||||
*/
|
||||
const QUEST_OFFER_SOURCE = 'src/data/functionCatalog/npc/npcChatQuestOffer.ts';
|
||||
const QUEST_OFFER_EXECUTOR =
|
||||
'server-rs/crates/api-server/src/runtime_story/compat.rs -> resolve_runtime_story_choice_action';
|
||||
|
||||
export const NPC_CHAT_QUEST_OFFER_VIEW_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_chat_quest_offer_view',
|
||||
domain: 'npc',
|
||||
title: '查看委托',
|
||||
source: QUEST_OFFER_SOURCE,
|
||||
summary: '查看当前聊天中 NPC 刚提出但尚未领取的委托。',
|
||||
detailedDescription:
|
||||
'它用于 pending quest offer 阶段,只打开或返回当前待领取任务详情,不把任务写入正式 quest log。',
|
||||
trigger: 'NPC 聊天触发待领取委托后,任务处理态选项中出现。',
|
||||
execution:
|
||||
'后端读取当前 pending quest offer,并返回可展示的任务详情与领取入口。',
|
||||
result: '玩家可以查看任务目标和奖励,确认领取前不会改变正式任务日志。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'local_only',
|
||||
uiMode: 'none',
|
||||
executor: QUEST_OFFER_EXECUTOR,
|
||||
animationNote: '不触发角色位移动画,重点是切换任务详情展示。',
|
||||
storyNote: '只保留当前委托上下文,不生成新的聊天剧情。',
|
||||
uiNote: '展示待领取任务详情,等待玩家领取、替换或返回聊天。',
|
||||
compactDetailText: '查看这份委托',
|
||||
},
|
||||
};
|
||||
|
||||
export const NPC_CHAT_QUEST_OFFER_REPLACE_FUNCTION: FunctionDocumentationEntry =
|
||||
{
|
||||
id: 'npc_chat_quest_offer_replace',
|
||||
domain: 'npc',
|
||||
title: '更换委托',
|
||||
source: QUEST_OFFER_SOURCE,
|
||||
summary: '让 NPC 重新生成一份聊天内待领取委托。',
|
||||
detailedDescription:
|
||||
'它不会本地改写现有任务文案,而是重新走任务生成链,替换当前 pending quest offer。',
|
||||
trigger: 'NPC 聊天任务处理态中,玩家不满意当前委托时出现。',
|
||||
execution:
|
||||
'后端调用任务生成链生成新 quest offer,并覆盖当前聊天态 pending offer。',
|
||||
result:
|
||||
'当前待领取委托被替换,聊天仍停留在任务处理态,正式 quest log 不变。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'local_effect_then_generate',
|
||||
uiMode: 'none',
|
||||
executor: QUEST_OFFER_EXECUTOR,
|
||||
animationNote: '不触发战斗或移动演出,只追加轻量聊天反馈。',
|
||||
storyNote: '重新生成 pending quest offer,并说明 NPC 换了一个委托。',
|
||||
uiNote: '继续显示查看、更换、放弃这组任务处理选项。',
|
||||
compactDetailText: '换一个委托',
|
||||
},
|
||||
};
|
||||
|
||||
export const NPC_CHAT_QUEST_OFFER_ABANDON_FUNCTION: FunctionDocumentationEntry =
|
||||
{
|
||||
id: 'npc_chat_quest_offer_abandon',
|
||||
domain: 'npc',
|
||||
title: '放弃委托',
|
||||
source: QUEST_OFFER_SOURCE,
|
||||
summary: '丢弃当前聊天中尚未领取的委托。',
|
||||
detailedDescription:
|
||||
'它只清理 pending quest offer,不影响已经写入 quest log 的正式任务,也不会扣除奖励或结算任务失败。',
|
||||
trigger: 'NPC 聊天任务处理态中,玩家暂时不想接这份委托时出现。',
|
||||
execution:
|
||||
'后端清空当前聊天态 pending quest offer,并恢复普通 NPC 聊天选项。',
|
||||
result: '待领取委托消失,玩家回到自由聊天或离开 NPC 的正常流程。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'local_only',
|
||||
uiMode: 'none',
|
||||
executor: QUEST_OFFER_EXECUTOR,
|
||||
animationNote: '不触发额外演出,只回到普通聊天态。',
|
||||
storyNote: '追加玩家暂时不接委托的轻量反馈。',
|
||||
uiNote: '恢复普通 npc_chat 建议和自定义输入。',
|
||||
compactDetailText: '暂时不接',
|
||||
},
|
||||
};
|
||||
32
src/data/functionCatalog/npc/npcFight.ts
Normal file
32
src/data/functionCatalog/npc/npcFight.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_fight
|
||||
*
|
||||
* 与眼前 NPC 直接开战的强制冲突 function。
|
||||
*/
|
||||
export const NPC_FIGHT_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_fight',
|
||||
domain: 'npc',
|
||||
title: '与对方战斗',
|
||||
source: 'src/data/functionCatalog/npc/npcFight.ts',
|
||||
summary: '把当前 NPC 交互直接导向敌对战斗。',
|
||||
detailedDescription:
|
||||
'无论对方原本是中立还是敌对,选择这个 function 都表示玩家主动接受或制造正面冲突,后续会切到 NPC 战斗模式。',
|
||||
trigger: '在敌对 NPC 遭遇或普通 NPC 交互菜单里都可能出现。',
|
||||
execution:
|
||||
'点击后会切换 currentBattleNpcId / currentNpcBattleMode,并进入本地战斗结算链路。',
|
||||
result:
|
||||
'交互界面转为战斗,战后会按 fight_victory 等结果处理掉落、好感和任务推进。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'special_sequence',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts -> handleNpcInteraction',
|
||||
animationNote: '切到 NPC 战斗模式后,由战斗播放链路驱动后续动画。',
|
||||
storyNote: '不会先弹窗,直接把当前 encounter 切成战斗态并进入后续结算。',
|
||||
uiNote: '不弹 modal,直接进入战斗。',
|
||||
compactDetailText: '战斗决胜负',
|
||||
},
|
||||
};
|
||||
56
src/data/functionCatalog/npc/npcGift.ts
Normal file
56
src/data/functionCatalog/npc/npcGift.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { GiftModalState } from '../../../hooks/rpg-runtime-story/uiTypes';
|
||||
import type { Encounter, GameState } from '../../../types';
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_gift
|
||||
*
|
||||
* 向眼前 NPC 送礼的入口 function。
|
||||
* 这里直接提供 gift modal 的默认构造逻辑。
|
||||
*/
|
||||
export function buildNpcGiftModalIntroText(encounter: Encounter) {
|
||||
return [
|
||||
'你:我想送你一样东西。',
|
||||
`${encounter.npcName}:先让我看看你带了什么,我再决定该怎么收下。`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function buildNpcGiftModalState(
|
||||
state: GameState,
|
||||
encounter: Encounter,
|
||||
actionText: string,
|
||||
selectedItemId: string | null = state.playerInventory[0]?.id ?? null,
|
||||
): GiftModalState {
|
||||
return {
|
||||
encounter,
|
||||
actionText,
|
||||
introText: buildNpcGiftModalIntroText(encounter),
|
||||
selectedItemId,
|
||||
};
|
||||
}
|
||||
|
||||
export const NPC_GIFT_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_gift',
|
||||
domain: 'npc',
|
||||
title: '向该角色送礼',
|
||||
source: 'src/data/functionCatalog/npc/npcGift.ts',
|
||||
summary: '打开送礼面板并根据礼物质量结算 affinity 变化。',
|
||||
detailedDescription:
|
||||
'它会把当前互动引到礼物选择 modal,通过本地规则估算礼物对该 NPC 的吸引力和好感增益,避免送礼结果漂移。',
|
||||
trigger: '玩家背包里存在可送出的物品时出现在 NPC 交互菜单里。',
|
||||
execution:
|
||||
'首次点击只打开 gift modal,确认礼物后再调用 commitGeneratedState 把送礼结果写回主流程。',
|
||||
result: '玩家可立即看到好感变化与送礼反馈,并影响后续交易、聊天和招募阈值。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'modal_then_generate',
|
||||
uiMode: 'gift_modal',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/storyGenerationState.ts + src/hooks/rpg-runtime-story/npcInteraction.ts',
|
||||
animationNote: '第一次点击不驱动额外演出,重点是切到礼物面板。',
|
||||
storyNote:
|
||||
'真正的剧情推进发生在 confirmGift 之后,届时才会写入好感变化与结果文本。',
|
||||
uiNote: '会先打开 gift modal,并默认选中当前最适合作为礼物的物品。',
|
||||
compactDetailText: '送礼提升好感',
|
||||
},
|
||||
};
|
||||
31
src/data/functionCatalog/npc/npcHelp.ts
Normal file
31
src/data/functionCatalog/npc/npcHelp.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_help
|
||||
*
|
||||
* 向眼前 NPC 寻求帮助或支援的 function。
|
||||
*/
|
||||
export const NPC_HELP_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_help',
|
||||
domain: 'npc',
|
||||
title: '向对方寻求帮助',
|
||||
source: 'src/data/functionCatalog/npc/npcHelp.ts',
|
||||
summary: '从 NPC 处申请一次性补给、回复或援助。',
|
||||
detailedDescription:
|
||||
'它把 NPC 互动导向资源支持,奖励内容由本地规则预先计算,避免关键数值完全交给模型临场决定。',
|
||||
trigger: 'NPC 允许帮助且该角色尚未消耗过 helpUsed 时出现。',
|
||||
execution: '点击后直接按本地奖励规则结算,然后继续推进后续剧情。',
|
||||
result:
|
||||
'玩家可能获得生命、灵力、冷却收益或道具补给,并让故事承接“被对方照应了一次”。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'local_effect_then_generate',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts -> handleNpcInteraction',
|
||||
animationNote: '不单独开窗口,直接在当前交互里结算帮助结果。',
|
||||
storyNote: '点击后立即按本地奖励规则结算,并继续生成新的故事状态。',
|
||||
uiNote: '不弹 modal,直接获得帮助反馈。',
|
||||
compactDetailText: '看看能得到什么帮助',
|
||||
},
|
||||
};
|
||||
30
src/data/functionCatalog/npc/npcLeave.ts
Normal file
30
src/data/functionCatalog/npc/npcLeave.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_leave
|
||||
*
|
||||
* 结束当前 NPC 互动、回到探索态的 function。
|
||||
*/
|
||||
export const NPC_LEAVE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_leave',
|
||||
domain: 'npc',
|
||||
title: '不作停留,继续前行',
|
||||
source: 'src/data/functionCatalog/npc/npcLeave.ts',
|
||||
summary: '退出当前 NPC 交互并把注意力拉回前路。',
|
||||
detailedDescription:
|
||||
'它为玩家提供一个明确的“暂时结束这段互动”的出口,避免必须通过交易、聊天或战斗才能离开当前 encounter。',
|
||||
trigger: '绝大多数普通 NPC 菜单的默认退出项。',
|
||||
execution: '点击后清理当前 NPC 交互态,并继续进入下一轮探索或故事推进。',
|
||||
result: '玩家会离开当前角色,恢复到探索导向的故事节奏。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'local_effect_then_generate',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts -> handleNpcInteraction',
|
||||
animationNote: '通常只做轻量离场,不单独打开窗口。',
|
||||
storyNote: '点击后结束当前 NPC 交互,并回到新的探索剧情。',
|
||||
uiNote: '不弹 modal,直接退出互动。',
|
||||
compactDetailText: '离开并继续探索',
|
||||
},
|
||||
};
|
||||
71
src/data/functionCatalog/npc/npcPreviewTalk.ts
Normal file
71
src/data/functionCatalog/npc/npcPreviewTalk.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
AnimationState,
|
||||
type Encounter,
|
||||
type StoryOption,
|
||||
} from '../../../types';
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_preview_talk
|
||||
*
|
||||
* 眼前出现 NPC 预览后,把玩家从“远处观察”切换到“正式交互”的入口 function。
|
||||
* 这里直接收口了这个选项的视觉和构造 helper。
|
||||
*/
|
||||
export const NPC_PREVIEW_TALK_OPTION_VISUALS: StoryOption['visuals'] = {
|
||||
playerAnimation: AnimationState.IDLE,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterChanges: [],
|
||||
};
|
||||
|
||||
export function buildNpcPreviewTalkOption(encounter: Encounter): StoryOption {
|
||||
const actionText = `与 ${encounter.npcName} 交谈`;
|
||||
return {
|
||||
functionId: NPC_PREVIEW_TALK_FUNCTION.id,
|
||||
actionText,
|
||||
text: actionText,
|
||||
detailText: '先专注于眼前的人,再决定如何回应。',
|
||||
priority: 3,
|
||||
visuals: NPC_PREVIEW_TALK_OPTION_VISUALS,
|
||||
};
|
||||
}
|
||||
|
||||
export function isNpcPreviewTalkFunctionId(functionId: string) {
|
||||
return functionId === NPC_PREVIEW_TALK_FUNCTION.id;
|
||||
}
|
||||
|
||||
export function isNpcPreviewTalkOption(option: StoryOption) {
|
||||
return isNpcPreviewTalkFunctionId(option.functionId);
|
||||
}
|
||||
|
||||
export const NPC_PREVIEW_TALK_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_preview_talk',
|
||||
domain: 'npc',
|
||||
title: '转向眼前角色',
|
||||
source: 'src/data/functionCatalog/npc/npcPreviewTalk.ts',
|
||||
summary: '把当前遭遇从前探预览切入正式 NPC 交互层。',
|
||||
detailedDescription:
|
||||
'它不直接完成一轮聊天,而是把镜头、当前 encounter 和可选项池真正切换到角色互动上下文,为后续 trade / chat / recruit 等动作铺路。',
|
||||
trigger:
|
||||
'通常在探索过程中已经锁定眼前 NPC,并且玩家准备正式和对方互动时出现。',
|
||||
execution:
|
||||
'第一次点击后主要进入 NPC interaction 流程,而不是直接结算完整剧情回合。',
|
||||
result:
|
||||
'玩家会进入针对该 NPC 的专属交互菜单,并开始看到交易、聊天、切磋等本地规则项。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'enter_interaction',
|
||||
uiMode: 'npc_interaction_entry',
|
||||
visuals: NPC_PREVIEW_TALK_OPTION_VISUALS,
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/choiceActions.ts + src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts',
|
||||
animationNote:
|
||||
'保持轻量 idle 站场,不做额外位移,重点是把交互焦点切到 NPC 身上。',
|
||||
storyNote:
|
||||
'普通 NPC 直接进入 enterNpcInteraction;初始同伴会改走 opening adventure 特殊序列。',
|
||||
uiNote: '不弹 modal,而是切换到 NPC interaction 选项面板。',
|
||||
compactDetailText: '先专注于眼前的人',
|
||||
},
|
||||
};
|
||||
20
src/data/functionCatalog/npc/npcQuestAccept.ts
Normal file
20
src/data/functionCatalog/npc/npcQuestAccept.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_quest_accept
|
||||
*
|
||||
* 接下眼前 NPC 委托的 function。
|
||||
*/
|
||||
export const NPC_QUEST_ACCEPT_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_quest_accept',
|
||||
domain: 'npc',
|
||||
title: '接下委托',
|
||||
source: 'src/data/functionCatalog/npc/npcQuestAccept.ts',
|
||||
summary: '把 NPC 提供的任务写入 quest log。',
|
||||
detailedDescription:
|
||||
'它用于把当前交互中的委托正式变成可追踪任务,并让故事明确承接“玩家已经答应了这件事”。',
|
||||
trigger: 'NPC 当前没有活跃任务且本地规则成功为其生成了一个可接任务时出现。',
|
||||
execution: '点击后会在本地 questFlow 中创建 active quest,并继续推进剧情。',
|
||||
result: '玩家获得新的任务目标、任务文本与后续交付条件。',
|
||||
active: true,
|
||||
};
|
||||
21
src/data/functionCatalog/npc/npcQuestTurnIn.ts
Normal file
21
src/data/functionCatalog/npc/npcQuestTurnIn.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_quest_turn_in
|
||||
*
|
||||
* 向眼前 NPC 交付已完成委托的 function。
|
||||
*/
|
||||
export const NPC_QUEST_TURN_IN_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_quest_turn_in',
|
||||
domain: 'npc',
|
||||
title: '交付委托',
|
||||
source: 'src/data/functionCatalog/npc/npcQuestTurnIn.ts',
|
||||
summary: '完成任务回报与任务状态收尾的 NPC function。',
|
||||
detailedDescription:
|
||||
'当相关任务已经完成,它负责把“领奖、交付、兑现承诺”从普通聊天里独立出来,确保 quest log 与剧情反馈同步更新。',
|
||||
trigger: '玩家在该 NPC 名下拥有 status=completed 的任务时出现。',
|
||||
execution:
|
||||
'点击后走本地 questFlow 的 turn-in 逻辑,结算奖励并推进 story history。',
|
||||
result: '任务状态会被正式收尾,玩家获得奖励与交付文本。',
|
||||
active: true,
|
||||
};
|
||||
65
src/data/functionCatalog/npc/npcRecruit.ts
Normal file
65
src/data/functionCatalog/npc/npcRecruit.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { RecruitModalState } from '../../../hooks/rpg-runtime-story/uiTypes';
|
||||
import type { Encounter, GameState } from '../../../types';
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_recruit
|
||||
*
|
||||
* 邀请眼前 NPC 加入队伍的 function。
|
||||
* 这里直接收口了“队伍已满时弹窗,否则立即进入招募序列”的分流逻辑。
|
||||
*/
|
||||
export function buildNpcRecruitModalIntroText(encounter: Encounter) {
|
||||
return [
|
||||
'你:我想认真谈谈同行的事。',
|
||||
`${encounter.npcName}:先把你队伍里的位置理顺,再给我一个明确答复。`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function buildNpcRecruitModalState(
|
||||
state: GameState,
|
||||
encounter: Encounter,
|
||||
actionText: string,
|
||||
): RecruitModalState {
|
||||
return {
|
||||
encounter,
|
||||
actionText,
|
||||
introText: buildNpcRecruitModalIntroText(encounter),
|
||||
selectedReleaseNpcId: state.companions[0]?.npcId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldNpcRecruitOpenModal(
|
||||
companionCount: number,
|
||||
maxCompanions: number,
|
||||
) {
|
||||
return companionCount >= maxCompanions;
|
||||
}
|
||||
|
||||
export const NPC_RECRUIT_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_recruit',
|
||||
domain: 'npc',
|
||||
title: '邀请该角色加入队伍',
|
||||
source: 'src/data/functionCatalog/npc/npcRecruit.ts',
|
||||
summary: '把 NPC 转化为同伴的招募入口。',
|
||||
detailedDescription:
|
||||
'它负责承接好感达标或开局同行的特殊情境,让 NPC 进入 recruitment 流程。若当前队伍已满,会先弹出替换同伴的确认流程。',
|
||||
trigger: 'NPC 可招募、尚未 recruited,且满足 affinity 或特殊开局条件时出现。',
|
||||
execution:
|
||||
'队伍未满时可直接进入招募流程;队伍已满时先打开 recruit modal,再确认替换目标。',
|
||||
result:
|
||||
'成功后 NPC 会加入 companions / roster,并改写后续剧情关系与队伍构成。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'special_sequence',
|
||||
uiMode: 'recruit_modal_or_sequence',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/storyGenerationState.ts + src/hooks/rpg-runtime-story/npcInteraction.ts',
|
||||
animationNote:
|
||||
'若直接进入招募,会先播放招募对话流;若队伍已满,先进入替换弹窗。',
|
||||
storyNote:
|
||||
'队伍未满时第一次点击就会进入招募对白序列;队伍已满时要等 modal 确认后再继续。',
|
||||
uiNote:
|
||||
'会根据队伍人数决定是立刻招募,还是先打开 recruit modal 选择释放对象。',
|
||||
compactDetailText: '谈谈是否愿意入队',
|
||||
},
|
||||
};
|
||||
30
src/data/functionCatalog/npc/npcSpar.ts
Normal file
30
src/data/functionCatalog/npc/npcSpar.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_spar
|
||||
*
|
||||
* 与眼前 NPC 切磋武艺的非致命战斗 function。
|
||||
*/
|
||||
export const NPC_SPAR_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_spar',
|
||||
domain: 'npc',
|
||||
title: '与对方切磋武艺',
|
||||
source: 'src/data/functionCatalog/npc/npcSpar.ts',
|
||||
summary: '把 NPC 互动切到点到为止的 spar 战斗模式。',
|
||||
detailedDescription:
|
||||
'它和 npc_fight 共用战斗骨架,但数值与结算目标不同,重点是以低伤害对局换取关系推进,而不是击杀或掠夺。',
|
||||
trigger: '在可交流的 NPC 菜单里作为友好或试探性过招选项出现。',
|
||||
execution: '点击后进入 spar 模式,本地规则会限制伤害与战后回场逻辑。',
|
||||
result: '切磋结束后通常返回 NPC 场景,并小幅提高 affinity 或推进相关任务。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'special_sequence',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts -> handleNpcInteraction',
|
||||
animationNote: '切到 spar 战斗模式后,由战斗播放链路驱动切磋演出。',
|
||||
storyNote: '不会先弹窗,直接进入点到为止的切磋流程。',
|
||||
uiNote: '不弹 modal,直接切磋。',
|
||||
compactDetailText: '切磋几招看身手',
|
||||
},
|
||||
};
|
||||
64
src/data/functionCatalog/npc/npcTrade.ts
Normal file
64
src/data/functionCatalog/npc/npcTrade.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { TradeModalState } from '../../../hooks/rpg-runtime-story/uiTypes';
|
||||
import type { Encounter, GameState, InventoryItem } from '../../../types';
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* npc_trade
|
||||
*
|
||||
* 与眼前 NPC 发起交易的入口 function。
|
||||
* 这里直接提供 trade modal 的默认构造逻辑,避免窗口初始化散落在别处。
|
||||
*/
|
||||
export function buildNpcTradeModalIntroText(encounter: Encounter) {
|
||||
return [
|
||||
'你:我想先看看你手里有什么能换。',
|
||||
`${encounter.npcName}:先看货吧,买卖和回收的价都写得清楚。`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function buildNpcTradeModalState(
|
||||
state: GameState,
|
||||
encounter: Encounter,
|
||||
actionText: string,
|
||||
npcInventory: InventoryItem[],
|
||||
): TradeModalState {
|
||||
const selectedNpcItemId =
|
||||
npcInventory.find((item) => item.quantity > 0)?.id ?? null;
|
||||
const selectedPlayerItemId =
|
||||
state.playerInventory.find((item) => item.quantity > 0)?.id ?? null;
|
||||
|
||||
return {
|
||||
encounter,
|
||||
actionText,
|
||||
introText: buildNpcTradeModalIntroText(encounter),
|
||||
mode: 'buy',
|
||||
selectedNpcItemId,
|
||||
selectedPlayerItemId,
|
||||
selectedQuantity: 1,
|
||||
};
|
||||
}
|
||||
|
||||
export const NPC_TRADE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'npc_trade',
|
||||
domain: 'npc',
|
||||
title: '与对方交易',
|
||||
source: 'src/data/functionCatalog/npc/npcTrade.ts',
|
||||
summary: '打开 NPC 交易流程并结算买卖或交换。',
|
||||
detailedDescription:
|
||||
'它负责把当前交互引到交易面板,展示 NPC 库存、折扣和可交换物。第一次点击通常只打开 modal,真正确认后才继续推进剧情。',
|
||||
trigger: '当 NPC 允许交易且自身库存非空时出现在 NPC 交互菜单里。',
|
||||
execution:
|
||||
'首次点击进入 trade modal,确认后再通过 commitGeneratedState 把结果写回主流程。',
|
||||
result: '玩家可以买入、以物易物,或在失败时得到明确的价值差提示。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'modal_then_generate',
|
||||
uiMode: 'trade_modal',
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/storyGenerationState.ts + src/hooks/rpg-runtime-story/npcInteraction.ts',
|
||||
animationNote: '第一次点击不播额外战斗或位移动画,重点是切到交易窗口。',
|
||||
storyNote:
|
||||
'真正的剧情推进发生在 confirmTrade 之后,而不是打开 modal 的瞬间。',
|
||||
uiNote: '会先打开交易 modal,并预选 NPC 第一件商品与玩家第一件可卖物品。',
|
||||
compactDetailText: '查看库存与价格',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user