init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

View 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,
];

View 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: '聊聊并试探口风',
},
};

View 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: '暂时不接',
},
};

View 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: '战斗决胜负',
},
};

View 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: '送礼提升好感',
},
};

View 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: '看看能得到什么帮助',
},
};

View 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: '离开并继续探索',
},
};

View 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: '先专注于眼前的人',
},
};

View 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,
};

View 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,
};

View 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: '谈谈是否愿意入队',
},
};

View 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: '切磋几招看身手',
},
};

View 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: '查看库存与价格',
},
};