This commit is contained in:
62
src/data/functionCatalog/flow/campTravelHomeScene.ts
Normal file
62
src/data/functionCatalog/flow/campTravelHomeScene.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { AnimationState, type StoryOption } from '../../../types';
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* camp_travel_home_scene
|
||||
*
|
||||
* 从营地与同伴对话结束后,正式前往角色主线场景的控制 function。
|
||||
* 这里除了元信息,也直接收口了它的按钮构造与判定 helper。
|
||||
*/
|
||||
export const CAMP_TRAVEL_HOME_OPTION_VISUALS: StoryOption['visuals'] = {
|
||||
playerAnimation: AnimationState.RUN,
|
||||
playerMoveMeters: 1.1,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterChanges: [],
|
||||
};
|
||||
|
||||
export function buildCampTravelHomeOption(sceneName: string): StoryOption {
|
||||
return {
|
||||
functionId: CAMP_TRAVEL_HOME_FUNCTION.id,
|
||||
actionText: `前往 ${sceneName}`,
|
||||
text: `前往 ${sceneName}`,
|
||||
detailText: `离开营地,前往 ${sceneName}。`,
|
||||
visuals: CAMP_TRAVEL_HOME_OPTION_VISUALS,
|
||||
};
|
||||
}
|
||||
|
||||
export function isCampTravelHomeFunctionId(functionId: string) {
|
||||
return functionId === CAMP_TRAVEL_HOME_FUNCTION.id;
|
||||
}
|
||||
|
||||
export function isCampTravelHomeOption(option: StoryOption) {
|
||||
return isCampTravelHomeFunctionId(option.functionId);
|
||||
}
|
||||
|
||||
export const CAMP_TRAVEL_HOME_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'camp_travel_home_scene',
|
||||
domain: 'flow',
|
||||
title: '前往角色主场景',
|
||||
source: 'src/data/functionCatalog/flow/campTravelHomeScene.ts',
|
||||
summary: '营地开场后的专用旅行控制项。',
|
||||
detailedDescription:
|
||||
'它负责把开局同伴营地流程平稳切到角色真正的起始场景,并清理当前营地 encounter、战斗态和镜头残留状态。',
|
||||
trigger: '常见于开局同伴营地对话后的跟进选项。',
|
||||
execution:
|
||||
'点击后不会走普通 state function 结算,而是执行一次定制的场景迁移和历史写入。',
|
||||
result: '玩家会离开营地进入角色主场景,正式开始该角色的冒险线。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'special_travel',
|
||||
uiMode: 'none',
|
||||
visuals: CAMP_TRAVEL_HOME_OPTION_VISUALS,
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/choiceActions.ts -> handleCampTravelHome',
|
||||
animationNote:
|
||||
'先播放营地离场的 run 演出,再切到正式场景并生成 encounter preview。',
|
||||
storyNote:
|
||||
'通过 commitGeneratedStateWithEncounterEntry 写入离营结果,并在新场景继续后续剧情。',
|
||||
uiNote: '这是专用旅行流程,不会打开 modal。',
|
||||
},
|
||||
};
|
||||
10
src/data/functionCatalog/flow/index.ts
Normal file
10
src/data/functionCatalog/flow/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
import { CAMP_TRAVEL_HOME_FUNCTION } from './campTravelHomeScene';
|
||||
import { CONTINUE_ADVENTURE_FUNCTION } from './storyContinueAdventure';
|
||||
import { STORY_OPENING_CAMP_DIALOGUE_FUNCTION } from './storyOpeningCampDialogue';
|
||||
|
||||
export const FLOW_FUNCTION_DOCUMENTATION: FunctionDocumentationEntry[] = [
|
||||
CONTINUE_ADVENTURE_FUNCTION,
|
||||
CAMP_TRAVEL_HOME_FUNCTION,
|
||||
STORY_OPENING_CAMP_DIALOGUE_FUNCTION,
|
||||
];
|
||||
61
src/data/functionCatalog/flow/storyContinueAdventure.ts
Normal file
61
src/data/functionCatalog/flow/storyContinueAdventure.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { AnimationState, type StoryOption } from '../../../types';
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* story_continue_adventure
|
||||
*
|
||||
* 聊天或特殊流程已经提前完成推理后,用于“把延后展示的 options 放出来”的控制 function。
|
||||
* 这里除了说明文本外,也直接收口了这个 function 的按钮视觉和判定 helper。
|
||||
*/
|
||||
export const CONTINUE_ADVENTURE_OPTION_VISUALS: StoryOption['visuals'] = {
|
||||
playerAnimation: AnimationState.RUN,
|
||||
playerMoveMeters: 1.1,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterChanges: [],
|
||||
};
|
||||
|
||||
export function buildContinueAdventureOption(): StoryOption {
|
||||
return {
|
||||
functionId: CONTINUE_ADVENTURE_FUNCTION.id,
|
||||
actionText: CONTINUE_ADVENTURE_FUNCTION.title,
|
||||
text: CONTINUE_ADVENTURE_FUNCTION.title,
|
||||
priority: 99,
|
||||
visuals: CONTINUE_ADVENTURE_OPTION_VISUALS,
|
||||
};
|
||||
}
|
||||
|
||||
export function isContinueAdventureFunctionId(functionId: string) {
|
||||
return functionId === CONTINUE_ADVENTURE_FUNCTION.id;
|
||||
}
|
||||
|
||||
export function isContinueAdventureOption(option: StoryOption) {
|
||||
return isContinueAdventureFunctionId(option.functionId);
|
||||
}
|
||||
|
||||
export const CONTINUE_ADVENTURE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'story_continue_adventure',
|
||||
domain: 'flow',
|
||||
title: '继续冒险',
|
||||
source: 'src/data/functionCatalog/flow/storyContinueAdventure.ts',
|
||||
summary: '承接 deferredOptions 的延迟展示控制项。',
|
||||
detailedDescription:
|
||||
'它不是重新推理剧情,而是在某些流程已经先算好后续 options 时,给玩家一个清晰的继续按钮,再把 deferredOptions 真正放回界面。',
|
||||
trigger: '常见于 npc_chat 等先生成正文、后延迟显示选项的链路。',
|
||||
execution:
|
||||
'点击后主要走本地 UI / state 还原逻辑,而不是再请求一次新的故事推理。',
|
||||
result:
|
||||
'玩家会看到之前已经准备好的后续冒险选项,误以为“没继续生成”的风险也会降低。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'reveal_deferred_options',
|
||||
uiMode: 'none',
|
||||
visuals: CONTINUE_ADVENTURE_OPTION_VISUALS,
|
||||
executor: 'src/hooks/rpg-runtime-story/choiceActions.ts -> handleChoice',
|
||||
animationNote: '按钮本身沿用轻量前进动画,但不驱动新的战斗或场景演出。',
|
||||
storyNote:
|
||||
'点击时直接把 deferredOptions 放回 currentStory.options,不再请求新的 generateNextStep。',
|
||||
uiNote: '这是一个流程确认按钮,不会弹 modal。',
|
||||
},
|
||||
};
|
||||
38
src/data/functionCatalog/flow/storyOpeningCampDialogue.ts
Normal file
38
src/data/functionCatalog/flow/storyOpeningCampDialogue.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* story_opening_camp_dialogue
|
||||
*
|
||||
* 开局营地场景的特殊对话控制 function。
|
||||
* 这里同时提供判定 helper,供 prompt 和故事流程判断是否进入营地开场对白模式。
|
||||
*/
|
||||
export function isOpeningCampDialogueFunctionId(
|
||||
functionId: string | null | undefined,
|
||||
) {
|
||||
return functionId === STORY_OPENING_CAMP_DIALOGUE_FUNCTION.id;
|
||||
}
|
||||
|
||||
export const STORY_OPENING_CAMP_DIALOGUE_FUNCTION: FunctionDocumentationEntry =
|
||||
{
|
||||
id: 'story_opening_camp_dialogue',
|
||||
domain: 'flow',
|
||||
title: '营地开场对话',
|
||||
source: 'src/data/functionCatalog/flow/storyOpeningCampDialogue.ts',
|
||||
summary: '驱动开局营地 4 到 6 行开场对白的流程项。',
|
||||
detailedDescription:
|
||||
'它告诉 prompt 与运行时:当前不是普通探索推进,而是要围绕营地背景、初始同伴态度和刚进入世界的紧张感生成一段结构化开场对白。',
|
||||
trigger: '开局同伴营地场景进入正式对话时出现。',
|
||||
execution:
|
||||
'点击后会进入 opening adventure 的特殊对话生成链,而不是普通 function option 链路。',
|
||||
result: '玩家会先看到一段营地对白,再衔接后续 npc_chat 或离营流程。',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'special_sequence',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'server-rs/crates/api-server/src/runtime_story/compat.rs -> resolve_runtime_story_choice_action',
|
||||
animationNote: '重点在对白本身,不额外驱动独立战斗/位移动画。',
|
||||
storyNote: '会把 prompt 切到营地开场对白模式,并要求输出结构化对话行。',
|
||||
uiNote: '不弹 modal,直接进入对白流。',
|
||||
},
|
||||
};
|
||||
157
src/data/functionCatalog/functionCatalog.test.ts
Normal file
157
src/data/functionCatalog/functionCatalog.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
|
||||
import { SERVER_RUNTIME_FUNCTION_IDS } from '../../../packages/shared/src/contracts/rpgRuntimeStoryAction';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
ALL_FUNCTION_DOCUMENTATION,
|
||||
buildCampTravelHomeOption,
|
||||
buildContinueAdventureOption,
|
||||
buildNpcGiftModalState,
|
||||
buildNpcPreviewTalkOption,
|
||||
buildNpcRecruitModalState,
|
||||
buildNpcTradeModalState,
|
||||
CONTINUE_ADVENTURE_FUNCTION,
|
||||
getFunctionDocumentationById,
|
||||
isNpcPreviewTalkOption,
|
||||
NPC_PREVIEW_TALK_FUNCTION,
|
||||
shouldNpcRecruitOpenModal,
|
||||
} from './index';
|
||||
import type { Encounter, GameState, InventoryItem } from '../../types';
|
||||
|
||||
function createEncounter(overrides: Partial<Encounter> = {}): Encounter {
|
||||
return {
|
||||
id: 'npc-trader',
|
||||
kind: 'npc',
|
||||
npcName: '梁伯',
|
||||
npcDescription: '沿路摆摊的商人。',
|
||||
npcAvatar: '梁',
|
||||
context: '商贩',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createInventoryItem(
|
||||
id: string,
|
||||
name: string,
|
||||
overrides: Partial<InventoryItem> = {},
|
||||
): InventoryItem {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
description: `${name} 的测试描述`,
|
||||
quantity: 1,
|
||||
category: 'misc',
|
||||
rarity: 'common',
|
||||
tags: [],
|
||||
value: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createModalState(overrides: Partial<GameState> = {}): GameState {
|
||||
return {
|
||||
playerInventory: [
|
||||
createInventoryItem('player-potion', '疗伤药'),
|
||||
createInventoryItem('player-charm', '护符'),
|
||||
],
|
||||
companions: [
|
||||
{
|
||||
npcId: 'npc-ally-1',
|
||||
characterId: 'ally-1',
|
||||
name: '阿青',
|
||||
role: '同伴',
|
||||
joinedAtAffinity: 12,
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
} as GameState;
|
||||
}
|
||||
|
||||
describe('functionCatalog', () => {
|
||||
it('keeps function documentation ids unique and source files resolvable', () => {
|
||||
const documentationIds = ALL_FUNCTION_DOCUMENTATION.map((entry) => entry.id);
|
||||
|
||||
expect(new Set(documentationIds).size).toBe(documentationIds.length);
|
||||
ALL_FUNCTION_DOCUMENTATION.forEach((entry) => {
|
||||
expect(existsSync(entry.source), `${entry.id} -> ${entry.source}`).toBe(
|
||||
true,
|
||||
);
|
||||
expect(getFunctionDocumentationById(entry.id)).toEqual(entry);
|
||||
});
|
||||
});
|
||||
|
||||
it('covers every server runtime function id with documentation metadata', () => {
|
||||
SERVER_RUNTIME_FUNCTION_IDS.forEach((functionId) => {
|
||||
expect(getFunctionDocumentationById(functionId)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('builds flow helper options with the expected function ids', () => {
|
||||
const continueOption = buildContinueAdventureOption();
|
||||
const campTravelOption = buildCampTravelHomeOption('竹林古道');
|
||||
|
||||
expect(continueOption.functionId).toBe(CONTINUE_ADVENTURE_FUNCTION.id);
|
||||
expect(continueOption.priority).toBe(99);
|
||||
expect(campTravelOption.functionId).toBe('camp_travel_home_scene');
|
||||
expect(campTravelOption.actionText).toBe('前往 竹林古道');
|
||||
expect(campTravelOption.detailText).toBe('离开营地,前往 竹林古道。');
|
||||
});
|
||||
|
||||
it('builds npc preview talk options from the current encounter', () => {
|
||||
const option = buildNpcPreviewTalkOption(createEncounter());
|
||||
|
||||
expect(option.functionId).toBe(NPC_PREVIEW_TALK_FUNCTION.id);
|
||||
expect(option.actionText).toBe('与 梁伯 交谈');
|
||||
expect(isNpcPreviewTalkOption(option)).toBe(true);
|
||||
});
|
||||
|
||||
it('builds modal helper state for trade, gift and recruit flows', () => {
|
||||
const state = createModalState();
|
||||
const encounter = createEncounter();
|
||||
const tradeModal = buildNpcTradeModalState(
|
||||
state,
|
||||
encounter,
|
||||
'先看看货',
|
||||
[
|
||||
createInventoryItem('npc-herb', '止血草'),
|
||||
createInventoryItem('npc-ore', '陨铁碎片'),
|
||||
],
|
||||
);
|
||||
const giftModal = buildNpcGiftModalState(
|
||||
state,
|
||||
encounter,
|
||||
'送你一样东西',
|
||||
'player-charm',
|
||||
);
|
||||
const recruitModal = buildNpcRecruitModalState(
|
||||
state,
|
||||
encounter,
|
||||
'谈谈同行的事',
|
||||
);
|
||||
|
||||
expect(tradeModal.selectedNpcItemId).toBe('npc-herb');
|
||||
expect(tradeModal.selectedPlayerItemId).toBe('player-potion');
|
||||
expect(giftModal.selectedItemId).toBe('player-charm');
|
||||
expect(recruitModal.selectedReleaseNpcId).toBe('npc-ally-1');
|
||||
expect(shouldNpcRecruitOpenModal(2, 2)).toBe(true);
|
||||
expect(shouldNpcRecruitOpenModal(1, 2)).toBe(false);
|
||||
});
|
||||
|
||||
it('prefers the first tradable player item when zero-quantity items exist', () => {
|
||||
const encounter = createEncounter();
|
||||
const tradeModal = buildNpcTradeModalState(
|
||||
createModalState({
|
||||
playerInventory: [
|
||||
createInventoryItem('empty-slot', '空槽位', { quantity: 0 }),
|
||||
createInventoryItem('usable-item', '可售草药', { quantity: 2 }),
|
||||
],
|
||||
}),
|
||||
encounter,
|
||||
'交易',
|
||||
[createInventoryItem('npc-herb', '止血草')],
|
||||
);
|
||||
|
||||
expect(tradeModal.selectedPlayerItemId).toBe('usable-item');
|
||||
});
|
||||
});
|
||||
61
src/data/functionCatalog/index.ts
Normal file
61
src/data/functionCatalog/index.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { FLOW_FUNCTION_DOCUMENTATION } from './flow';
|
||||
import { NPC_FUNCTION_DOCUMENTATION } from './npc';
|
||||
import { PANEL_FUNCTION_DOCUMENTATION } from './panel';
|
||||
import {
|
||||
STATE_FUNCTION_DEFINITIONS,
|
||||
STATE_FUNCTION_DOCUMENTATION,
|
||||
STATE_FUNCTION_PROMPT_DESCRIPTIONS,
|
||||
STATE_FUNCTION_SOURCES,
|
||||
} from './state';
|
||||
import { TREASURE_FUNCTION_DOCUMENTATION } from './treasure';
|
||||
import type { FunctionDocumentationEntry } from './types';
|
||||
|
||||
export * from './flow/campTravelHomeScene';
|
||||
export * from './flow/storyContinueAdventure';
|
||||
export * from './flow/storyOpeningCampDialogue';
|
||||
export * from './npc/npcChat';
|
||||
export * from './npc/npcChatQuestOffer';
|
||||
export * from './npc/npcFight';
|
||||
export * from './npc/npcGift';
|
||||
export * from './npc/npcHelp';
|
||||
export * from './npc/npcLeave';
|
||||
export * from './npc/npcPreviewTalk';
|
||||
export * from './npc/npcQuestAccept';
|
||||
export * from './npc/npcQuestTurnIn';
|
||||
export * from './npc/npcRecruit';
|
||||
export * from './npc/npcSpar';
|
||||
export * from './npc/npcTrade';
|
||||
export * from './panel/equipmentEquip';
|
||||
export * from './panel/equipmentUnequip';
|
||||
export * from './panel/forgeCraft';
|
||||
export * from './panel/forgeDismantle';
|
||||
export * from './panel/forgeReforge';
|
||||
export * from './panel/inventoryUse';
|
||||
export * from './state';
|
||||
export * from './treasure/treasureInspect';
|
||||
export * from './treasure/treasureLeave';
|
||||
export * from './treasure/treasureSecure';
|
||||
export * from './types';
|
||||
|
||||
export const ALL_FUNCTION_DOCUMENTATION: FunctionDocumentationEntry[] = [
|
||||
...STATE_FUNCTION_DOCUMENTATION,
|
||||
...NPC_FUNCTION_DOCUMENTATION,
|
||||
...TREASURE_FUNCTION_DOCUMENTATION,
|
||||
...FLOW_FUNCTION_DOCUMENTATION,
|
||||
...PANEL_FUNCTION_DOCUMENTATION,
|
||||
];
|
||||
|
||||
export const ALL_FUNCTION_DOCUMENTATION_MAP = new Map(
|
||||
ALL_FUNCTION_DOCUMENTATION.map((entry) => [entry.id, entry]),
|
||||
);
|
||||
|
||||
export function getFunctionDocumentationById(functionId: string) {
|
||||
return ALL_FUNCTION_DOCUMENTATION_MAP.get(functionId) ?? null;
|
||||
}
|
||||
|
||||
export {
|
||||
STATE_FUNCTION_DEFINITIONS,
|
||||
STATE_FUNCTION_DOCUMENTATION,
|
||||
STATE_FUNCTION_PROMPT_DESCRIPTIONS,
|
||||
STATE_FUNCTION_SOURCES,
|
||||
};
|
||||
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: '查看库存与价格',
|
||||
},
|
||||
};
|
||||
21
src/data/functionCatalog/panel/equipmentEquip.ts
Normal file
21
src/data/functionCatalog/panel/equipmentEquip.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* equipment_equip
|
||||
*
|
||||
* 在装备面板中把背包物品穿戴到对应槽位的 function。
|
||||
*/
|
||||
export const EQUIPMENT_EQUIP_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'equipment_equip',
|
||||
domain: 'panel',
|
||||
title: '装备物品',
|
||||
source: 'src/data/functionCatalog/panel/equipmentEquip.ts',
|
||||
summary: '负责角色装备替换、背包回收和属性重算。',
|
||||
detailedDescription:
|
||||
'它是装备面板触发的局部动作,不通过自由叙事来决定结果,而是由本地规则严格处理槽位、被替换装备和属性改写。',
|
||||
trigger: '玩家在非战斗状态下从背包点击一件可装备物品时触发。',
|
||||
execution:
|
||||
'先更新 equipment loadout 与背包,再通过 commitGeneratedState 把装备结果写进故事历史。',
|
||||
result: '角色装备变更生效,必要时旧装备回到背包,故事中会留下装备变动说明。',
|
||||
active: true,
|
||||
};
|
||||
20
src/data/functionCatalog/panel/equipmentUnequip.ts
Normal file
20
src/data/functionCatalog/panel/equipmentUnequip.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* equipment_unequip
|
||||
*
|
||||
* 从装备槽位卸下一件装备并放回背包的 function。
|
||||
*/
|
||||
export const EQUIPMENT_UNEQUIP_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'equipment_unequip',
|
||||
domain: 'panel',
|
||||
title: '卸下装备',
|
||||
source: 'src/data/functionCatalog/panel/equipmentUnequip.ts',
|
||||
summary: '处理卸装、背包回收和属性回退。',
|
||||
detailedDescription:
|
||||
'它是装备系统的反向动作,确保角色在非战斗状态下可以安全地卸装,而不会破坏背包数量或 loadout 一致性。',
|
||||
trigger: '玩家在非战斗状态下从装备面板点击某个已装备槽位时触发。',
|
||||
execution: '先把装备放回背包,再重算应用到 GameState 的角色装备效果。',
|
||||
result: '装备槽位清空、背包新增对应物品,并留下卸装结果文本。',
|
||||
active: true,
|
||||
};
|
||||
21
src/data/functionCatalog/panel/forgeCraft.ts
Normal file
21
src/data/functionCatalog/panel/forgeCraft.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* forge_craft
|
||||
*
|
||||
* 在锻造面板中制作配方产物的 function。
|
||||
*/
|
||||
export const FORGE_CRAFT_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'forge_craft',
|
||||
domain: 'panel',
|
||||
title: '制作配方',
|
||||
source: 'src/data/functionCatalog/panel/forgeCraft.ts',
|
||||
summary: '执行锻造配方、扣除材料和货币并产出新物品。',
|
||||
detailedDescription:
|
||||
'它由锻造系统本地校验配方合法性与资源足额情况,再把产物、消耗和结果文本统一写回游戏状态。',
|
||||
trigger: '玩家在非战斗状态下从锻造面板选择一条可制作配方时触发。',
|
||||
execution:
|
||||
'先执行 executeForgeRecipe,再通过 commitGeneratedState 写回制作结果。',
|
||||
result: '玩家消耗材料和钱币,获得新物品,同时故事历史记录一次制作行为。',
|
||||
active: true,
|
||||
};
|
||||
21
src/data/functionCatalog/panel/forgeDismantle.ts
Normal file
21
src/data/functionCatalog/panel/forgeDismantle.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* forge_dismantle
|
||||
*
|
||||
* 在锻造面板中拆解物品回收材料的 function。
|
||||
*/
|
||||
export const FORGE_DISMANTLE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'forge_dismantle',
|
||||
domain: 'panel',
|
||||
title: '拆解物品',
|
||||
source: 'src/data/functionCatalog/panel/forgeDismantle.ts',
|
||||
summary: '执行拆解并返还材料收益的锻造 function。',
|
||||
detailedDescription:
|
||||
'它允许玩家把现有物品重新拆回材料,由本地锻造规则决定返还内容,避免拆解产出与物品设计脱节。',
|
||||
trigger: '玩家在非战斗状态下于锻造面板选择可拆解物品时触发。',
|
||||
execution:
|
||||
'先执行 executeDismantleItem,再通过 commitGeneratedState 记录拆解结果。',
|
||||
result: '原物品被移除,背包增加拆解产物,并留下拆解说明文本。',
|
||||
active: true,
|
||||
};
|
||||
22
src/data/functionCatalog/panel/forgeReforge.ts
Normal file
22
src/data/functionCatalog/panel/forgeReforge.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* forge_reforge
|
||||
*
|
||||
* 在锻造面板中重铸现有物品的 function。
|
||||
*/
|
||||
export const FORGE_REFORGE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'forge_reforge',
|
||||
domain: 'panel',
|
||||
title: '重铸物品',
|
||||
source: 'src/data/functionCatalog/panel/forgeReforge.ts',
|
||||
summary: '支付货币后重构物品结果的锻造 function。',
|
||||
detailedDescription:
|
||||
'它用于把已有物品重新洗练成新的结果,由本地规则负责消耗、生成与可视化说明,避免重铸结果脱离装备系统。',
|
||||
trigger: '玩家在非战斗状态下于锻造面板选择可重铸物品时触发。',
|
||||
execution:
|
||||
'先执行 executeReforgeItem 和花费计算,再通过 commitGeneratedState 写回重铸结果。',
|
||||
result:
|
||||
'玩家消耗货币、失去旧版本物品并获得重铸后的新物品,同时剧情历史记录本次重铸。',
|
||||
active: true,
|
||||
};
|
||||
16
src/data/functionCatalog/panel/index.ts
Normal file
16
src/data/functionCatalog/panel/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
import { EQUIPMENT_EQUIP_FUNCTION } from './equipmentEquip';
|
||||
import { EQUIPMENT_UNEQUIP_FUNCTION } from './equipmentUnequip';
|
||||
import { FORGE_CRAFT_FUNCTION } from './forgeCraft';
|
||||
import { FORGE_DISMANTLE_FUNCTION } from './forgeDismantle';
|
||||
import { FORGE_REFORGE_FUNCTION } from './forgeReforge';
|
||||
import { INVENTORY_USE_FUNCTION } from './inventoryUse';
|
||||
|
||||
export const PANEL_FUNCTION_DOCUMENTATION: FunctionDocumentationEntry[] = [
|
||||
INVENTORY_USE_FUNCTION,
|
||||
EQUIPMENT_EQUIP_FUNCTION,
|
||||
EQUIPMENT_UNEQUIP_FUNCTION,
|
||||
FORGE_CRAFT_FUNCTION,
|
||||
FORGE_DISMANTLE_FUNCTION,
|
||||
FORGE_REFORGE_FUNCTION,
|
||||
];
|
||||
21
src/data/functionCatalog/panel/inventoryUse.ts
Normal file
21
src/data/functionCatalog/panel/inventoryUse.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* inventory_use
|
||||
*
|
||||
* 在背包中消耗一个可用物品的面板动作 function。
|
||||
*/
|
||||
export const INVENTORY_USE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'inventory_use',
|
||||
domain: 'panel',
|
||||
title: '使用背包物品',
|
||||
source: 'src/data/functionCatalog/panel/inventoryUse.ts',
|
||||
summary: '从背包面板结算药品、灵力物或 build buff 道具。',
|
||||
detailedDescription:
|
||||
'它不属于场景探索目录,而是 UI 面板动作。点击具体物品后,本地规则会先结算回复与 buff,再把结果写回剧情历史。',
|
||||
trigger: '玩家在 InventoryPanel 中点击可使用物品时触发。',
|
||||
execution:
|
||||
'本地结算 hp / mana / cooldown / buildBuffs,然后通过 commitGeneratedState 把结果挂回主故事。',
|
||||
result: '物品数量减少,角色资源更新,故事文本会明确记录“使用了什么”。',
|
||||
active: true,
|
||||
};
|
||||
60
src/data/functionCatalog/state/battleAllInCrush.ts
Normal file
60
src/data/functionCatalog/state/battleAllInCrush.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* battle_all_in_crush
|
||||
*
|
||||
* 战斗中的正面爆发动作。它要求主角不绕、不拖,直接把当前回合的叙事、
|
||||
* 技能权重和视觉表现都推向“强压正面敌人”的方向。
|
||||
*/
|
||||
export const BATTLE_ALL_IN_CRUSH_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'battle_all_in_crush',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
text: '战斗:全力压上',
|
||||
description:
|
||||
'正面强攻,优先触发高爆发和终结类技能,伤害更高,但承受的反击也更重。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.SKILL3,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterActionTemplate: '{monster}被正面强攻逼得节节后退',
|
||||
monsterAnimation: 'attack',
|
||||
monsterMoveMeters: 0,
|
||||
},
|
||||
effect: {
|
||||
damageMultiplier: 1.3,
|
||||
incomingDamageMultiplier: 1.15,
|
||||
turnTimeMultiplier: 1,
|
||||
skillWeights: {
|
||||
finisher: 5,
|
||||
burst: 4,
|
||||
mobility: 2,
|
||||
steady: 1.5,
|
||||
projectile: 1.2,
|
||||
},
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'对面前敌人全力猛攻,文案可以自然改写,但仍要保持这是正面强攻而不是别的行为。',
|
||||
documentation: {
|
||||
id: 'battle_all_in_crush',
|
||||
domain: 'state',
|
||||
title: '战斗:全力压上',
|
||||
source: 'src/data/functionCatalog/state/battleAllInCrush.ts',
|
||||
summary: '战斗阶段的高风险高收益强攻 function。',
|
||||
detailedDescription:
|
||||
'这个 function 用于把当前回合塑造成硬碰硬的压制回合,让主角优先打出爆发和终结倾向更强的技能或叙事动作。',
|
||||
trigger: '仅在 battle 状态且场上仍有存活敌人时参与候选。',
|
||||
execution:
|
||||
'提高 damageMultiplier,并抬高 finisher / burst 权重,同时略微提高 incomingDamageMultiplier,突出换血压力。',
|
||||
result: '适合在残局抢收头、需要快速压血,或者希望把敌人直接压回去时使用。',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
35
src/data/functionCatalog/state/battleAttackBasic.ts
Normal file
35
src/data/functionCatalog/state/battleAttackBasic.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* battle_attack_basic
|
||||
*
|
||||
* 后端单行为战斗模型的普通攻击入口。该 function 只登记文档和契约,
|
||||
* 不进入前端本地 state function 候选池。
|
||||
*/
|
||||
export const BATTLE_ATTACK_BASIC_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'battle_attack_basic',
|
||||
domain: 'state',
|
||||
title: '普通攻击',
|
||||
source: 'src/data/functionCatalog/state/battleAttackBasic.ts',
|
||||
summary: '后端单行为战斗模型中的基础攻击 function。',
|
||||
detailedDescription:
|
||||
'这个 function 代表一次明确的普通攻击点击,后端直接结算伤害、敌方反击和下一轮战斗选项,不再请求 AI 续写整段战斗剧情。',
|
||||
trigger: '仅在 battle 状态且场上仍有存活敌人时,由后端战斗 option 池下发。',
|
||||
execution:
|
||||
'前端透传 functionId,Rust 后端经 story battle facade 调用 module-combat 按普通攻击规则结算本回合。',
|
||||
result: '刷新 HP、战斗日志和下一轮战斗 options;若敌人被击败,再进入脱战剧情推理。',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'local_only',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'server-rs/crates/module-combat/src/lib.rs -> resolve_combat_action',
|
||||
animationNote: '播放一次基础攻击和受击反馈,不扩展成连续多段连击。',
|
||||
storyNote:
|
||||
'战斗未结束时只展示本次结算文本;战斗结束后才请求脱战剧情。',
|
||||
uiNote: '由后端战斗 option 池生成,不进入前端本地 state function 候选。',
|
||||
compactDetailText: '直接攻击眼前敌人',
|
||||
},
|
||||
};
|
||||
55
src/data/functionCatalog/state/battleEscapeBreakout.ts
Normal file
55
src/data/functionCatalog/state/battleEscapeBreakout.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* battle_escape_breakout
|
||||
*
|
||||
* 战斗中的脱离动作。它不是继续换血,而是明确让主角放弃当前缠斗,
|
||||
* 把叙事重心切到“拉开距离、甩开追击、离开战场”。
|
||||
*/
|
||||
export const BATTLE_ESCAPE_BREAKOUT_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'battle_escape_breakout',
|
||||
state: 'battle',
|
||||
category: 'escape',
|
||||
text: '逃跑:转身甩开',
|
||||
description: '立刻放弃缠斗,转身拉开距离,冲向下一片区域。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.RUN,
|
||||
playerMoveMeters: -0.6,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'left',
|
||||
scrollWorld: true,
|
||||
monsterActionTemplate: '{monster}在后方死死咬住不放',
|
||||
monsterAnimation: 'idle',
|
||||
monsterMoveMeters: 0,
|
||||
},
|
||||
effect: {
|
||||
escapeDurationMs: 5000,
|
||||
escapeDistance: 5,
|
||||
monsterLagStart: 0.52,
|
||||
monsterLagEnd: 0.34,
|
||||
sceneShift: 0,
|
||||
enterBattle: false,
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'从当前战斗中脱身、拉开距离或撤离,文案可以自然改写,但仍要保持这是逃脱而不是继续交战。',
|
||||
documentation: {
|
||||
id: 'battle_escape_breakout',
|
||||
domain: 'state',
|
||||
title: '逃跑:转身甩开',
|
||||
source: 'src/data/functionCatalog/state/battleEscapeBreakout.ts',
|
||||
summary: '用于脱战、逃离和切离镜头压力的战斗 function。',
|
||||
detailedDescription:
|
||||
'它让回合从“继续打”切换到“先活下来”,并带出逃跑镜头、距离拉开和怪物追击落后的演出逻辑。',
|
||||
trigger: '仅在 battle 状态下参与候选,并会在低血时提升优先级。',
|
||||
execution:
|
||||
'不追求伤害,而是提供 escapeDurationMs、escapeDistance 与 monsterLag 参数,驱动逃跑流程与镜头表现。',
|
||||
result: '适合生命见底、资源不够,或玩家主动决定放弃当前战斗时使用。',
|
||||
state: 'battle',
|
||||
category: 'escape',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
60
src/data/functionCatalog/state/battleFeintStep.ts
Normal file
60
src/data/functionCatalog/state/battleFeintStep.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* battle_feint_step
|
||||
*
|
||||
* 战斗中的机动切入动作。它把重点放在虚晃、变线与抢身位,
|
||||
* 让战斗叙事更偏向灵活切入而不是硬扛伤害。
|
||||
*/
|
||||
export const BATTLE_FEINT_STEP_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'battle_feint_step',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
text: '战斗:虚晃切入',
|
||||
description:
|
||||
'通过假动作和变线切入制造破绽,偏向机动技能,伤害适中但更安全。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.SKILL1_JUMP,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterActionTemplate: '{monster}被晃得节奏发虚,动作明显迟疑',
|
||||
monsterAnimation: 'attack',
|
||||
monsterMoveMeters: 0,
|
||||
},
|
||||
effect: {
|
||||
damageMultiplier: 1.05,
|
||||
incomingDamageMultiplier: 0.8,
|
||||
skillWeights: {
|
||||
mobility: 5,
|
||||
burst: 2.4,
|
||||
steady: 2,
|
||||
finisher: 1.4,
|
||||
projectile: 1.2,
|
||||
},
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'通过虚晃、变线或切步逼近面前敌人,文案可以自然改写,但仍要保持这是机动切入。',
|
||||
documentation: {
|
||||
id: 'battle_feint_step',
|
||||
domain: 'state',
|
||||
title: '战斗:虚晃切入',
|
||||
source: 'src/data/functionCatalog/state/battleFeintStep.ts',
|
||||
summary: '通过身位变化制造安全破绽的机动战斗 function。',
|
||||
detailedDescription:
|
||||
'它适合把回合讲成“先骗、再切、再进”的过程,让主角依靠节奏误导和位移逼出敌人的迟疑。',
|
||||
trigger: '仅在 battle 状态下参与候选。',
|
||||
execution:
|
||||
'小幅提高输出,显著降低 incomingDamageMultiplier,并把技能权重偏到 mobility,让系统更愿意选机动型动作。',
|
||||
result:
|
||||
'适合想稳一点地逼近敌人、减少硬吃反击,或需要围绕灵巧角色塑造打法时使用。',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
59
src/data/functionCatalog/state/battleFinisherWindow.ts
Normal file
59
src/data/functionCatalog/state/battleFinisherWindow.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* battle_finisher_window
|
||||
*
|
||||
* 战斗中的终结窗口动作。它要求系统把这一回合理解为“敌人已经露出空档”,
|
||||
* 因而优先演出收割、补刀和终结技。
|
||||
*/
|
||||
export const BATTLE_FINISHER_WINDOW_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'battle_finisher_window',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
text: '战斗:抓住破绽',
|
||||
description: '专门针对敌人露出的空档压上终结技,节奏更短、更猛。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.SKILL3,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterActionTemplate: '{monster}来不及调整姿态,只能仓促迎击',
|
||||
monsterAnimation: 'attack',
|
||||
monsterMoveMeters: 0,
|
||||
},
|
||||
effect: {
|
||||
damageMultiplier: 1.4,
|
||||
incomingDamageMultiplier: 1.05,
|
||||
turnTimeMultiplier: 0.92,
|
||||
skillWeights: {
|
||||
finisher: 6,
|
||||
burst: 3.5,
|
||||
mobility: 1.5,
|
||||
steady: 0.8,
|
||||
projectile: 0.6,
|
||||
},
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'抓住面前敌人的破绽收割或终结,文案可以自然改写,但仍要保持这是终结窗口。',
|
||||
documentation: {
|
||||
id: 'battle_finisher_window',
|
||||
domain: 'state',
|
||||
title: '战斗:抓住破绽',
|
||||
source: 'src/data/functionCatalog/state/battleFinisherWindow.ts',
|
||||
summary: '围绕残局补刀与爆发终结的战斗 function。',
|
||||
detailedDescription:
|
||||
'它会强烈推动“敌人已经失衡、主角准备收口”的叙事判断,让 actionText 与技能选择都更像最后一击。',
|
||||
trigger: '仅在 battle 状态下参与候选,并会在敌方血量偏低时明显升优先级。',
|
||||
execution:
|
||||
'提供最高档位之一的 damageMultiplier,缩短 turnTimeMultiplier,并极度偏向 finisher / burst 技能。',
|
||||
result: '适合在敌方残血、明显露出空档,或需要一口气结束战斗时使用。',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
58
src/data/functionCatalog/state/battleGuardBreak.ts
Normal file
58
src/data/functionCatalog/state/battleGuardBreak.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* battle_guard_break
|
||||
*
|
||||
* 战斗中的破架重击动作。它强调“针对敌人当前动作强拆架势”,
|
||||
* 比纯换血更讲究把敌人的节奏打断。
|
||||
*/
|
||||
export const BATTLE_GUARD_BREAK_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'battle_guard_break',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
text: '战斗:重击破架',
|
||||
description: '针对怪物当前动作强拆架势,伤害偏高,反击压力略低。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.SKILL2,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterActionTemplate: '{monster}被重击震得动作一滞',
|
||||
monsterAnimation: 'attack',
|
||||
monsterMoveMeters: 0,
|
||||
},
|
||||
effect: {
|
||||
damageMultiplier: 1.2,
|
||||
incomingDamageMultiplier: 0.9,
|
||||
skillWeights: {
|
||||
burst: 4.5,
|
||||
finisher: 3,
|
||||
steady: 2.2,
|
||||
mobility: 1.5,
|
||||
projectile: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'针对面前敌人的架势或破绽重击破防,文案可以自然改写,但仍要保持这是破架强攻。',
|
||||
documentation: {
|
||||
id: 'battle_guard_break',
|
||||
domain: 'state',
|
||||
title: '战斗:重击破架',
|
||||
source: 'src/data/functionCatalog/state/battleGuardBreak.ts',
|
||||
summary: '围绕破架、震停和打断敌人节奏的战斗 function。',
|
||||
detailedDescription:
|
||||
'它不是单纯地压血,而是把回合重心放在“砸开架势、制造空档”,让后续剧情更容易承接敌人失衡的结果。',
|
||||
trigger: '仅在 battle 状态且敌人仍在场时参与候选。',
|
||||
execution:
|
||||
'维持较高 damageMultiplier,同时降低 incomingDamageMultiplier,并偏向 burst / steady 组合,突出稳中带狠的重击感。',
|
||||
result: '适合对付正在招架、准备反扑或看起来露出结构性破绽的敌人。',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
58
src/data/functionCatalog/state/battleProbePressure.ts
Normal file
58
src/data/functionCatalog/state/battleProbePressure.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* battle_probe_pressure
|
||||
*
|
||||
* 战斗中的稳扎试探动作。适合在局势未明、资源需要保留时,
|
||||
* 先用安全且持续的压制把信息和节奏摸出来。
|
||||
*/
|
||||
export const BATTLE_PROBE_PRESSURE_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'battle_probe_pressure',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
text: '战斗:稳扎试探',
|
||||
description: '以持续压制和稳健试探为主,更容易发动常规连段和中段技能。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.SKILL1,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterActionTemplate: '{monster}一边招架一边寻找反扑的缝隙',
|
||||
monsterAnimation: 'attack',
|
||||
monsterMoveMeters: 0,
|
||||
},
|
||||
effect: {
|
||||
damageMultiplier: 1,
|
||||
incomingDamageMultiplier: 0.95,
|
||||
skillWeights: {
|
||||
steady: 5,
|
||||
burst: 2.2,
|
||||
mobility: 2,
|
||||
projectile: 2,
|
||||
finisher: 1.2,
|
||||
},
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'对面前敌人稳扎稳打地试探施压,文案可以自然改写,但仍要保持这是试探压制。',
|
||||
documentation: {
|
||||
id: 'battle_probe_pressure',
|
||||
domain: 'state',
|
||||
title: '战斗:稳扎试探',
|
||||
source: 'src/data/functionCatalog/state/battleProbePressure.ts',
|
||||
summary: '强调观察、压迫和中段连段的稳健战斗 function。',
|
||||
detailedDescription:
|
||||
'这个 function 让战斗回合更像“边压边看”,适合在想控制资源消耗、等待更好机会,或需要防止自己出手过重时使用。',
|
||||
trigger: '仅在 battle 状态下参与候选。',
|
||||
execution:
|
||||
'保持标准 damageMultiplier,轻微降低 incomingDamageMultiplier,并把 skillWeights 明显偏向 steady,兼顾少量 mobility / projectile。',
|
||||
result: '适合拉平节奏、压住敌人反扑窗口,或在低蓝时维持安全输出。',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
61
src/data/functionCatalog/state/battleRecoverBreath.ts
Normal file
61
src/data/functionCatalog/state/battleRecoverBreath.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* battle_recover_breath
|
||||
*
|
||||
* 战斗中的恢复动作。它会把当前回合塑造成“先稳住伤势与灵力”,
|
||||
* 让数值、冷却和叙事都朝回气与整顿节奏的方向靠拢。
|
||||
*/
|
||||
export const BATTLE_RECOVER_BREATH_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'battle_recover_breath',
|
||||
state: 'battle',
|
||||
category: 'recovery',
|
||||
text: '战斗:收势调息',
|
||||
description: '边守边调息,恢复少量生命和灵力,并让技能冷却更快转动。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.IDLE,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
monsterActionTemplate: '{monster}仍在逼近,不给你轻松喘息的空当',
|
||||
monsterAnimation: 'move',
|
||||
monsterMoveMeters: -0.1,
|
||||
},
|
||||
effect: {
|
||||
damageMultiplier: 0.72,
|
||||
incomingDamageMultiplier: 0.7,
|
||||
healAmount: 12,
|
||||
manaRestore: 18,
|
||||
cooldownTickBonus: 1,
|
||||
skillWeights: {
|
||||
steady: 3,
|
||||
mobility: 2,
|
||||
projectile: 1.8,
|
||||
burst: 1,
|
||||
finisher: 0.4,
|
||||
},
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'在战斗中收势调息、稳住伤势或回气,文案可以自然改写,但仍要保持这是恢复而不是继续猛攻。',
|
||||
documentation: {
|
||||
id: 'battle_recover_breath',
|
||||
domain: 'state',
|
||||
title: '战斗:收势调息',
|
||||
source: 'src/data/functionCatalog/state/battleRecoverBreath.ts',
|
||||
summary: '战斗中的保命与回气 function。',
|
||||
detailedDescription:
|
||||
'这个 function 专门为低血、低蓝或技能轮转吃紧时准备,让一回合承担“止血、回蓝、缓冷却”的综合恢复职责。',
|
||||
trigger: '仅在 battle 状态下参与候选,且通常会在低血或低蓝时被提权。',
|
||||
execution:
|
||||
'显著降低 damageMultiplier 和 incomingDamageMultiplier,同时提供 healAmount、manaRestore 与 cooldownTickBonus。',
|
||||
result: '适合临时止损、等关键技能转好,或把高压战斗拉回可控节奏。',
|
||||
state: 'battle',
|
||||
category: 'recovery',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
36
src/data/functionCatalog/state/battleUseSkill.ts
Normal file
36
src/data/functionCatalog/state/battleUseSkill.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* battle_use_skill
|
||||
*
|
||||
* 后端单行为战斗模型的技能释放入口。每个技能 option 复用同一个
|
||||
* functionId,具体技能必须由 runtimePayload.skillId 指定。
|
||||
*/
|
||||
export const BATTLE_USE_SKILL_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'battle_use_skill',
|
||||
domain: 'state',
|
||||
title: '释放技能',
|
||||
source: 'src/data/functionCatalog/state/battleUseSkill.ts',
|
||||
summary: '后端单行为战斗模型中的指定技能释放 function。',
|
||||
detailedDescription:
|
||||
'这个 functionId 可以对应多个技能 option 实例。前端只展示技能名和不可用原因,后端根据 runtimePayload.skillId 校验蓝量、冷却并结算本次技能效果。',
|
||||
trigger: '仅在 battle 状态下由后端按角色技能列表生成,可能携带 disabled 状态。',
|
||||
execution:
|
||||
'前端透传 runtimePayload.skillId,Rust 后端经 story battle facade 调用 module-combat 校验技能并完成一次技能动作结算。',
|
||||
result:
|
||||
'更新 MP、技能冷却、敌我 HP 和下一轮战斗 options;若战斗结束,再触发脱战剧情推理。',
|
||||
state: 'battle',
|
||||
category: 'battle',
|
||||
active: true,
|
||||
runtime: {
|
||||
storyMode: 'local_only',
|
||||
uiMode: 'none',
|
||||
executor:
|
||||
'server-rs/crates/module-combat/src/lib.rs -> resolve_combat_action',
|
||||
animationNote: '根据技能 option 播放一次技能演出,不在本 function 内追加多回合动作。',
|
||||
storyNote:
|
||||
'战斗未结束时使用本次技能结算文本;只有战斗结束才请求新剧情。',
|
||||
uiNote: '每个技能是一个后端下发的独立 option,必须携带 skillId。',
|
||||
compactDetailText: '释放一个指定技能',
|
||||
},
|
||||
};
|
||||
48
src/data/functionCatalog/state/idleCallOut.ts
Normal file
48
src/data/functionCatalog/state/idleCallOut.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* idle_call_out
|
||||
*
|
||||
* 空闲状态下的主动喊话动作。它会把探索从“静悄悄地摸过去”
|
||||
* 转成“先出声试探,看谁先回应”的节奏。
|
||||
*/
|
||||
export const IDLE_CALL_OUT_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'idle_call_out',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
text: '主动出声试探',
|
||||
description: '朝前方主动喊话试探,可能把藏着的角色、怪物或其他动静逼出来。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.IDLE,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
},
|
||||
effect: {
|
||||
sceneShift: 0,
|
||||
enterBattle: false,
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'主动朝前方出声试探,文案可以自然改写,但仍要保持这是出声试探。',
|
||||
documentation: {
|
||||
id: 'idle_call_out',
|
||||
domain: 'state',
|
||||
title: '主动出声试探',
|
||||
source: 'src/data/functionCatalog/state/idleCallOut.ts',
|
||||
summary: '通过主动发声引出附近实体反应的空闲 function。',
|
||||
detailedDescription:
|
||||
'它把探索节奏从被动观察切成主动打破寂静,更适合在玩家想逼出暗处角色、敌人或潜伏动静时使用。',
|
||||
trigger: '仅在 idle 状态下参与候选,且在当前设计里优先级较高。',
|
||||
execution:
|
||||
'维持站立演出,不推动场景位移;上层会把它理解为向前方喊话,从而让实体更容易被直接引到眼前。',
|
||||
result: '适合探草、叫阵、主动试探潜伏目标,或把隐藏互动直接拉到台前。',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
48
src/data/functionCatalog/state/idleExploreForward.ts
Normal file
48
src/data/functionCatalog/state/idleExploreForward.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* idle_explore_forward
|
||||
*
|
||||
* 空闲状态下最核心的推进动作。它负责把“继续往前探”从一句泛化文案,
|
||||
* 落成真正会引出下一幕遭遇的运行时 function。
|
||||
*/
|
||||
export const IDLE_EXPLORE_FORWARD_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'idle_explore_forward',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
text: '继续向前探索',
|
||||
description: '沿当前场景继续深入,很可能立刻撞上新的敌人或危险。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.RUN,
|
||||
playerMoveMeters: 0.9,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
},
|
||||
effect: {
|
||||
sceneShift: 0,
|
||||
enterBattle: true,
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'继续向前推进当前场景,文案可以自然改写,但仍要保持这是往前探索。',
|
||||
documentation: {
|
||||
id: 'idle_explore_forward',
|
||||
domain: 'state',
|
||||
title: '继续向前探索',
|
||||
source: 'src/data/functionCatalog/state/idleExploreForward.ts',
|
||||
summary: '空闲阶段默认的主推进 function。',
|
||||
detailedDescription:
|
||||
'它负责把玩家继续深入当前场景的意图交给运行时,让场景实体池、前探预览和下一幕遭遇有机会真正落地。',
|
||||
trigger: '仅在 idle 状态下参与候选;营地场景会在运行时被额外过滤。',
|
||||
execution:
|
||||
'保持空闲探索态,视觉上推动角色向前,effect 中通过 enterBattle / sceneShift 让下一步遭遇有机会进入前探或战斗链路。',
|
||||
result: '适合在当前场景继续摸深、主动触发新的角色、怪物、宝藏或危险。',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
48
src/data/functionCatalog/state/idleFollowClue.ts
Normal file
48
src/data/functionCatalog/state/idleFollowClue.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* idle_follow_clue
|
||||
*
|
||||
* 空闲状态下的循线推进动作。它在源码定义层仍然存在,
|
||||
* 但当前运行时会在聚合阶段被过滤,因此属于保留中的停用 function。
|
||||
*/
|
||||
export const IDLE_FOLLOW_CLUE_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'idle_follow_clue',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
text: '顺着线索靠近',
|
||||
description: '沿着眼前痕迹、小道或声音来源继续靠近,可能更快撞上新的目标。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.RUN,
|
||||
playerMoveMeters: 0.6,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
},
|
||||
effect: {
|
||||
sceneShift: 0,
|
||||
enterBattle: false,
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'顺着眼前线索继续靠近目标,文案可以自然改写,但仍要保持这是循线逼近。',
|
||||
documentation: {
|
||||
id: 'idle_follow_clue',
|
||||
domain: 'state',
|
||||
title: '顺着线索靠近',
|
||||
source: 'src/data/functionCatalog/state/idleFollowClue.ts',
|
||||
summary: '保留在源码中的循线 function,目前默认不进入运行时候选池。',
|
||||
detailedDescription:
|
||||
'它原本用于把观察结果进一步收束成“沿线索追过去”的动作,但当前项目在 applyRuntimeFunctionAdjustments 中会将其过滤。',
|
||||
trigger: '定义上属于 idle 状态,但默认运行时不会提供给玩家。',
|
||||
execution:
|
||||
'保留了向前靠近的视觉与 effect 配置,方便后续重新启用或用于编辑器审计。',
|
||||
result: '当前主要用于保留设计意图和支持后续恢复,不是默认可点选项。',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
active: false,
|
||||
},
|
||||
};
|
||||
|
||||
48
src/data/functionCatalog/state/idleObserveSigns.ts
Normal file
48
src/data/functionCatalog/state/idleObserveSigns.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* idle_observe_signs
|
||||
*
|
||||
* 空闲状态下的侦察动作。它把当前回合定义成“停下来观察”,
|
||||
* 重点不是立刻推进,而是为后续选择生成可引用的观察结果。
|
||||
*/
|
||||
export const IDLE_OBSERVE_SIGNS_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'idle_observe_signs',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
text: '停步观察动静',
|
||||
description: '先收住脚步观察附近痕迹与风吹草动,判断前方是否有异样。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.IDLE,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
},
|
||||
effect: {
|
||||
sceneShift: 0,
|
||||
enterBattle: false,
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'停步观察周围动静、痕迹或征兆,文案可以自然改写,但仍要保持这是观察判断。',
|
||||
documentation: {
|
||||
id: 'idle_observe_signs',
|
||||
domain: 'state',
|
||||
title: '停步观察动静',
|
||||
source: 'src/data/functionCatalog/state/idleObserveSigns.ts',
|
||||
summary: '围绕侦察、判断和前情确认的空闲 function。',
|
||||
detailedDescription:
|
||||
'这个 function 不要求马上遇敌或推进,而是把回合用在收集线索、确认附近实体池与下一步风险上。',
|
||||
trigger: '仅在 idle 状态下参与候选。',
|
||||
execution:
|
||||
'保持原地观察姿态,不推进 sceneShift;上层 prompt 会把这一回合视为观察请求,要求模型输出可延续的侦察结论。',
|
||||
result: '适合进入未知区域前先看风向、脚印、气息或异响,减少盲走。',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
50
src/data/functionCatalog/state/idleRestFocus.ts
Normal file
50
src/data/functionCatalog/state/idleRestFocus.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* idle_rest_focus
|
||||
*
|
||||
* 空闲状态下的原地恢复动作。它不会推进遭遇,而是给玩家一个
|
||||
* 在非战斗场景里回收少量血蓝的缓冲回合。
|
||||
*/
|
||||
export const IDLE_REST_FOCUS_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'idle_rest_focus',
|
||||
state: 'idle',
|
||||
category: 'recovery',
|
||||
text: '原地调息恢复',
|
||||
description: '暂时停步整理呼吸,恢复少量生命与灵力,继续保持空闲状态。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.IDLE,
|
||||
playerMoveMeters: 0,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
},
|
||||
effect: {
|
||||
healAmount: 10,
|
||||
manaRestore: 15,
|
||||
sceneShift: 0,
|
||||
enterBattle: false,
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'原地调息、休整或恢复状态,文案可以自然改写,但仍要保持这是恢复。',
|
||||
documentation: {
|
||||
id: 'idle_rest_focus',
|
||||
domain: 'state',
|
||||
title: '原地调息恢复',
|
||||
source: 'src/data/functionCatalog/state/idleRestFocus.ts',
|
||||
summary: '空闲阶段用于回血回蓝的恢复 function。',
|
||||
detailedDescription:
|
||||
'它承担非战斗状态下的短暂停步与内息整理,让玩家不推进主线遭遇,也能通过一回合修整状态。',
|
||||
trigger: '仅在 idle 状态下参与候选,并会在生命或灵力偏低时提升优先级。',
|
||||
execution:
|
||||
'不推动场景前进,effect 直接提供 healAmount 与 manaRestore,确保结果稳定由本地规则控制。',
|
||||
result: '适合战后休整、资源吃紧,或玩家想稳一下再继续探路时使用。',
|
||||
state: 'idle',
|
||||
category: 'recovery',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
48
src/data/functionCatalog/state/idleTravelNextScene.ts
Normal file
48
src/data/functionCatalog/state/idleTravelNextScene.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { AnimationState } from '../../../types';
|
||||
import type { StateFunctionSource } from '../types';
|
||||
|
||||
/**
|
||||
* idle_travel_next_scene
|
||||
*
|
||||
* 空闲状态下的切场景动作。它代表玩家主动离开当前地点,
|
||||
* 进入相邻场景重新开启新的遭遇周期。
|
||||
*/
|
||||
export const IDLE_TRAVEL_NEXT_SCENE_FUNCTION_SOURCE: StateFunctionSource = {
|
||||
definition: {
|
||||
id: 'idle_travel_next_scene',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
text: '前往其他场景',
|
||||
description: '离开当前场景,进入下一处地点,并在那里重新遭遇新的威胁。',
|
||||
visual: {
|
||||
playerAnimation: AnimationState.RUN,
|
||||
playerMoveMeters: 1.1,
|
||||
playerOffsetY: 0,
|
||||
playerFacing: 'right',
|
||||
scrollWorld: false,
|
||||
},
|
||||
effect: {
|
||||
sceneShift: 1,
|
||||
enterBattle: true,
|
||||
},
|
||||
},
|
||||
promptDescription:
|
||||
'离开当前场景并前往下一个场景,文案可以自然改写,但仍要保持这是切换场景。',
|
||||
documentation: {
|
||||
id: 'idle_travel_next_scene',
|
||||
domain: 'state',
|
||||
title: '前往其他场景',
|
||||
source: 'src/data/functionCatalog/state/idleTravelNextScene.ts',
|
||||
summary: '空闲阶段的地图流转 function。',
|
||||
detailedDescription:
|
||||
'当玩家不再想在当前场景深挖,而是希望切换地形、敌人池和环境压力时,这个 function 负责驱动合法的场景迁移。',
|
||||
trigger: '仅在 idle 状态下参与候选。',
|
||||
execution:
|
||||
'视觉上继续向前奔跑,effect 里通过 sceneShift: 1 把结果导向相邻场景,并允许新的遭遇在新场景刷新。',
|
||||
result: '适合主动换图、回避当前区域、或推进地图探索节奏时使用。',
|
||||
state: 'idle',
|
||||
category: 'idle',
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
||||
50
src/data/functionCatalog/state/index.ts
Normal file
50
src/data/functionCatalog/state/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { StateFunctionSource } from '../types';
|
||||
import { BATTLE_ALL_IN_CRUSH_FUNCTION_SOURCE } from './battleAllInCrush';
|
||||
import { BATTLE_ATTACK_BASIC_FUNCTION } from './battleAttackBasic';
|
||||
import { BATTLE_ESCAPE_BREAKOUT_FUNCTION_SOURCE } from './battleEscapeBreakout';
|
||||
import { BATTLE_FEINT_STEP_FUNCTION_SOURCE } from './battleFeintStep';
|
||||
import { BATTLE_FINISHER_WINDOW_FUNCTION_SOURCE } from './battleFinisherWindow';
|
||||
import { BATTLE_GUARD_BREAK_FUNCTION_SOURCE } from './battleGuardBreak';
|
||||
import { BATTLE_PROBE_PRESSURE_FUNCTION_SOURCE } from './battleProbePressure';
|
||||
import { BATTLE_RECOVER_BREATH_FUNCTION_SOURCE } from './battleRecoverBreath';
|
||||
import { BATTLE_USE_SKILL_FUNCTION } from './battleUseSkill';
|
||||
import { IDLE_CALL_OUT_FUNCTION_SOURCE } from './idleCallOut';
|
||||
import { IDLE_EXPLORE_FORWARD_FUNCTION_SOURCE } from './idleExploreForward';
|
||||
import { IDLE_FOLLOW_CLUE_FUNCTION_SOURCE } from './idleFollowClue';
|
||||
import { IDLE_OBSERVE_SIGNS_FUNCTION_SOURCE } from './idleObserveSigns';
|
||||
import { IDLE_REST_FOCUS_FUNCTION_SOURCE } from './idleRestFocus';
|
||||
import { IDLE_TRAVEL_NEXT_SCENE_FUNCTION_SOURCE } from './idleTravelNextScene';
|
||||
|
||||
export const STATE_FUNCTION_SOURCES: StateFunctionSource[] = [
|
||||
BATTLE_ALL_IN_CRUSH_FUNCTION_SOURCE,
|
||||
BATTLE_GUARD_BREAK_FUNCTION_SOURCE,
|
||||
BATTLE_PROBE_PRESSURE_FUNCTION_SOURCE,
|
||||
BATTLE_FEINT_STEP_FUNCTION_SOURCE,
|
||||
BATTLE_RECOVER_BREATH_FUNCTION_SOURCE,
|
||||
BATTLE_FINISHER_WINDOW_FUNCTION_SOURCE,
|
||||
BATTLE_ESCAPE_BREAKOUT_FUNCTION_SOURCE,
|
||||
IDLE_EXPLORE_FORWARD_FUNCTION_SOURCE,
|
||||
IDLE_TRAVEL_NEXT_SCENE_FUNCTION_SOURCE,
|
||||
IDLE_REST_FOCUS_FUNCTION_SOURCE,
|
||||
IDLE_OBSERVE_SIGNS_FUNCTION_SOURCE,
|
||||
IDLE_FOLLOW_CLUE_FUNCTION_SOURCE,
|
||||
IDLE_CALL_OUT_FUNCTION_SOURCE,
|
||||
];
|
||||
|
||||
export const STATE_FUNCTION_DEFINITIONS = STATE_FUNCTION_SOURCES.map(
|
||||
(source) => source.definition,
|
||||
);
|
||||
|
||||
export const STATE_FUNCTION_PROMPT_DESCRIPTIONS = Object.fromEntries(
|
||||
STATE_FUNCTION_SOURCES.map((source) => [
|
||||
source.definition.id,
|
||||
source.promptDescription,
|
||||
]),
|
||||
) as Record<string, string>;
|
||||
|
||||
export const STATE_FUNCTION_DOCUMENTATION = [
|
||||
BATTLE_ATTACK_BASIC_FUNCTION,
|
||||
BATTLE_USE_SKILL_FUNCTION,
|
||||
...STATE_FUNCTION_SOURCES.map((source) => source.documentation),
|
||||
];
|
||||
|
||||
10
src/data/functionCatalog/treasure/index.ts
Normal file
10
src/data/functionCatalog/treasure/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
import { TREASURE_INSPECT_FUNCTION } from './treasureInspect';
|
||||
import { TREASURE_LEAVE_FUNCTION } from './treasureLeave';
|
||||
import { TREASURE_SECURE_FUNCTION } from './treasureSecure';
|
||||
|
||||
export const TREASURE_FUNCTION_DOCUMENTATION: FunctionDocumentationEntry[] = [
|
||||
TREASURE_SECURE_FUNCTION,
|
||||
TREASURE_INSPECT_FUNCTION,
|
||||
TREASURE_LEAVE_FUNCTION,
|
||||
];
|
||||
20
src/data/functionCatalog/treasure/treasureInspect.ts
Normal file
20
src/data/functionCatalog/treasure/treasureInspect.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* treasure_inspect
|
||||
*
|
||||
* 先调查机关、线索与伪装,再收取宝藏的 function。
|
||||
*/
|
||||
export const TREASURE_INSPECT_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'treasure_inspect',
|
||||
domain: 'treasure',
|
||||
title: '先排查机关与线索',
|
||||
source: 'src/data/functionCatalog/treasure/treasureInspect.ts',
|
||||
summary: '以更谨慎的方式获取宝藏收益的调查项。',
|
||||
detailedDescription:
|
||||
'它强调先看清环境与机关,再拆开伪装拿收益,因此通常会附带额外恢复、线索或更丰富的战利品组合。',
|
||||
trigger: '遭遇宝藏交互时的标准选项之一。',
|
||||
execution: '点击后生成 inspect 变体奖励,并用更细致的结果文本描述排查过程。',
|
||||
result: '玩家通常获得更完整的道具、金钱与恢复收益,但叙事上会多花一步检查。',
|
||||
active: true,
|
||||
};
|
||||
21
src/data/functionCatalog/treasure/treasureLeave.ts
Normal file
21
src/data/functionCatalog/treasure/treasureLeave.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* treasure_leave
|
||||
*
|
||||
* 暂时放过眼前宝藏、回到主流程的 function。
|
||||
*/
|
||||
export const TREASURE_LEAVE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'treasure_leave',
|
||||
domain: 'treasure',
|
||||
title: '先记下位置离开',
|
||||
source: 'src/data/functionCatalog/treasure/treasureLeave.ts',
|
||||
summary: '放弃本次收取、只保留线索记忆的宝藏退出项。',
|
||||
detailedDescription:
|
||||
'它允许玩家在风险、资源或节奏不合适时先不碰宝藏,把互动结果定格为“记住位置和异常”,而不是强行收取。',
|
||||
trigger: '遭遇宝藏交互时的标准退出项。',
|
||||
execution:
|
||||
'点击后不发放宝藏奖励,而是直接写入 leave 结果文本并回到后续剧情。',
|
||||
result: '玩家不会获得物品,但故事会保留“这里有异常宝藏”的记忆。',
|
||||
active: true,
|
||||
};
|
||||
20
src/data/functionCatalog/treasure/treasureSecure.ts
Normal file
20
src/data/functionCatalog/treasure/treasureSecure.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { FunctionDocumentationEntry } from '../types';
|
||||
|
||||
/**
|
||||
* treasure_secure
|
||||
*
|
||||
* 直接收取眼前宝藏的 function。
|
||||
*/
|
||||
export const TREASURE_SECURE_FUNCTION: FunctionDocumentationEntry = {
|
||||
id: 'treasure_secure',
|
||||
domain: 'treasure',
|
||||
title: '先把宝藏收下',
|
||||
source: 'src/data/functionCatalog/treasure/treasureSecure.ts',
|
||||
summary: '快速结算宝藏收益的直接收取项。',
|
||||
detailedDescription:
|
||||
'它代表玩家不再额外排查机关,而是优先把主要收获拿到手。奖励仍由本地规则根据当前 encounter 和构筑上下文生成。',
|
||||
trigger: '遭遇宝藏交互时的标准选项之一。',
|
||||
execution: '点击后直接生成 secure 变体奖励,并继续推进主故事。',
|
||||
result: '玩家立刻拿到主要物品和钱币,但放弃 inspect 路线的额外侦查收益。',
|
||||
active: true,
|
||||
};
|
||||
58
src/data/functionCatalog/types.ts
Normal file
58
src/data/functionCatalog/types.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type {
|
||||
FunctionCategory,
|
||||
PlayerStateMode,
|
||||
StoryOption,
|
||||
} from '../../types';
|
||||
import type { StateFunctionDefinition } from '../stateFunctions';
|
||||
|
||||
export type FunctionDomain = 'state' | 'npc' | 'treasure' | 'flow' | 'panel';
|
||||
|
||||
export type FunctionStoryMode =
|
||||
| 'local_effect_then_generate'
|
||||
| 'modal_then_generate'
|
||||
| 'stream_then_defer'
|
||||
| 'reveal_deferred_options'
|
||||
| 'enter_interaction'
|
||||
| 'special_sequence'
|
||||
| 'special_travel'
|
||||
| 'local_only';
|
||||
|
||||
export type FunctionUiMode =
|
||||
| 'none'
|
||||
| 'npc_interaction_entry'
|
||||
| 'trade_modal'
|
||||
| 'gift_modal'
|
||||
| 'recruit_modal_or_sequence';
|
||||
|
||||
export interface FunctionRuntimeGuide {
|
||||
storyMode: FunctionStoryMode;
|
||||
uiMode: FunctionUiMode;
|
||||
visuals?: StoryOption['visuals'];
|
||||
executor: string;
|
||||
animationNote: string;
|
||||
storyNote: string;
|
||||
uiNote: string;
|
||||
compactDetailText?: string;
|
||||
}
|
||||
|
||||
export interface FunctionDocumentationEntry {
|
||||
id: string;
|
||||
domain: FunctionDomain;
|
||||
title: string;
|
||||
source: string;
|
||||
summary: string;
|
||||
detailedDescription: string;
|
||||
trigger: string;
|
||||
execution: string;
|
||||
result: string;
|
||||
state?: PlayerStateMode;
|
||||
category?: FunctionCategory;
|
||||
active?: boolean;
|
||||
runtime?: FunctionRuntimeGuide;
|
||||
}
|
||||
|
||||
export interface StateFunctionSource {
|
||||
definition: StateFunctionDefinition;
|
||||
documentation: FunctionDocumentationEntry;
|
||||
promptDescription: string;
|
||||
}
|
||||
Reference in New Issue
Block a user