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,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。',
},
};

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

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

View 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直接进入对白流。',
},
};

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

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

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

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

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

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

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

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

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

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

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

View 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:
'前端透传 functionIdRust 后端经 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: '直接攻击眼前敌人',
},
};

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

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

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

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

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

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

View 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.skillIdRust 后端经 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: '释放一个指定技能',
},
};

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

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

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

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

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

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

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

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

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

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

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

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