This commit is contained in:
2026-04-28 19:36:39 +08:00
parent a9febe7678
commit f0471a4f8d
206 changed files with 18456 additions and 10133 deletions

View File

@@ -17,6 +17,7 @@ import {
NPC_PREVIEW_TALK_FUNCTION,
shouldNpcRecruitOpenModal,
} from './index';
import { RPG_FUNCTION_RUNTIME_OVERVIEW } from './runtimeIndex';
import type { Encounter, GameState, InventoryItem } from '../../types';
function createEncounter(overrides: Partial<Encounter> = {}): Encounter {
@@ -87,6 +88,12 @@ describe('functionCatalog', () => {
});
});
it('keeps runtime overview aligned with the main function documentation list', () => {
expect(RPG_FUNCTION_RUNTIME_OVERVIEW.allDocumentation).toEqual(
ALL_FUNCTION_DOCUMENTATION,
);
});
it('builds flow helper options with the expected function ids', () => {
const continueOption = buildContinueAdventureOption();
const campTravelOption = buildCampTravelHomeOption('竹林古道');
@@ -110,16 +117,12 @@ describe('functionCatalog', () => {
const state = createModalState();
const encounter = createEncounter();
const tradeModal = buildNpcTradeModalState(
state,
encounter,
'先看看货',
[
createInventoryItem('npc-herb', '止血草'),
createInventoryItem('npc-ore', '陨铁碎片'),
],
'npc-herb',
'player-potion',
);
const giftModal = buildNpcGiftModalState(
state,
encounter,
'送你一样东西',
'player-charm',
@@ -138,18 +141,13 @@ describe('functionCatalog', () => {
expect(shouldNpcRecruitOpenModal(1, 2)).toBe(false);
});
it('prefers the first tradable player item when zero-quantity items exist', () => {
it('keeps server-selected trade item ids when opening the trade modal', () => {
const encounter = createEncounter();
const tradeModal = buildNpcTradeModalState(
createModalState({
playerInventory: [
createInventoryItem('empty-slot', '空槽位', { quantity: 0 }),
createInventoryItem('usable-item', '可售草药', { quantity: 2 }),
],
}),
encounter,
'交易',
[createInventoryItem('npc-herb', '止血草')],
'npc-herb',
'usable-item',
);
expect(tradeModal.selectedPlayerItemId).toBe('usable-item');

View File

@@ -31,6 +31,7 @@ export * from './panel/forgeCraft';
export * from './panel/forgeDismantle';
export * from './panel/forgeReforge';
export * from './panel/inventoryUse';
export * from './runtimeIndex';
export * from './state';
export * from './treasure/treasureInspect';
export * from './treasure/treasureLeave';

View File

@@ -1,5 +1,5 @@
import type { GiftModalState } from '../../../hooks/rpg-runtime-story/uiTypes';
import type { Encounter, GameState } from '../../../types';
import type { Encounter } from '../../../types';
import type { FunctionDocumentationEntry } from '../types';
/**
@@ -16,10 +16,9 @@ export function buildNpcGiftModalIntroText(encounter: Encounter) {
}
export function buildNpcGiftModalState(
state: GameState,
encounter: Encounter,
actionText: string,
selectedItemId: string | null = state.playerInventory[0]?.id ?? null,
selectedItemId: string | null,
): GiftModalState {
return {
encounter,
@@ -34,13 +33,13 @@ export const NPC_GIFT_FUNCTION: FunctionDocumentationEntry = {
domain: 'npc',
title: '向该角色送礼',
source: 'src/data/functionCatalog/npc/npcGift.ts',
summary: '打开送礼面板并根据礼物质量结算 affinity 变化。',
summary: '打开送礼面板并由后端结算 affinity 变化。',
detailedDescription:
'它会把当前互动引到礼物选择 modal通过本地规则估算礼物对该 NPC 的吸引力和好感增益,避免送礼结果漂移。',
trigger: '玩家背包里存在可送出的物品时出现在 NPC 交互菜单里。',
'它会把当前互动引到礼物选择 modal礼物列表、好感增益和不可选原因都读取后端 runtimeNpcInteraction view。',
trigger: '后端判断当前 NPC 可接收礼物时出现在 NPC 交互菜单里。',
execution:
'首次点击只打开 gift modal确认礼物后再调用 commitGeneratedState 把送礼结果写回主流程。',
result: '玩家可立即看到好感变化与送礼反馈,并影响后续交易、聊天和招募阈值。',
'首次点击只打开 gift modal确认礼物后只提交 itemId 给后端结算。',
result: '玩家可立即看到后端结算后的好感变化与送礼反馈,并影响后续交易、聊天和招募阈值。',
active: true,
runtime: {
storyMode: 'modal_then_generate',
@@ -50,7 +49,7 @@ export const NPC_GIFT_FUNCTION: FunctionDocumentationEntry = {
animationNote: '第一次点击不驱动额外演出,重点是切到礼物面板。',
storyNote:
'真正的剧情推进发生在 confirmGift 之后,届时才会写入好感变化与结果文本。',
uiNote: '会先打开 gift modal并默认选中当前最适合作为礼物的物品。',
uiNote: '会先打开 gift modal并默认选中后端 view 中第一件可提交的礼物。',
compactDetailText: '送礼提升好感',
},
};

View File

@@ -1,5 +1,5 @@
import type { TradeModalState } from '../../../hooks/rpg-runtime-story/uiTypes';
import type { Encounter, GameState, InventoryItem } from '../../../types';
import type { Encounter } from '../../../types';
import type { FunctionDocumentationEntry } from '../types';
/**
@@ -16,21 +16,17 @@ export function buildNpcTradeModalIntroText(encounter: Encounter) {
}
export function buildNpcTradeModalState(
state: GameState,
encounter: Encounter,
actionText: string,
npcInventory: InventoryItem[],
selectedNpcItemId: string | null,
selectedPlayerItemId: string | null,
mode: 'buy' | 'sell' = selectedNpcItemId ? 'buy' : 'sell',
): 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',
mode,
selectedNpcItemId,
selectedPlayerItemId,
selectedQuantity: 1,
@@ -44,11 +40,11 @@ export const NPC_TRADE_FUNCTION: FunctionDocumentationEntry = {
source: 'src/data/functionCatalog/npc/npcTrade.ts',
summary: '打开 NPC 交易流程并结算买卖或交换。',
detailedDescription:
'它负责把当前交互引到交易面板,展示 NPC 库存、折扣和可交换物。第一次点击通常只打开 modal真正确认后才继续推进剧情。',
'它负责把当前交互引到交易面板,库存、价格、折扣和不可选原因都读取后端 runtimeNpcInteraction view。第一次点击通常只打开 modal真正确认后才继续推进剧情。',
trigger: '当 NPC 允许交易且自身库存非空时出现在 NPC 交互菜单里。',
execution:
'首次点击进入 trade modal确认后再通过 commitGeneratedState 把结果写回主流程。',
result: '玩家可以买入、以物易物,或在失败时得到明确的价值差提示。',
'首次点击进入 trade modal确认后只提交 mode、itemId、quantity 给后端结算。',
result: '玩家可以买入、出售物品,或在后端拒绝时得到明确的失败原因。',
active: true,
runtime: {
storyMode: 'modal_then_generate',
@@ -58,7 +54,7 @@ export const NPC_TRADE_FUNCTION: FunctionDocumentationEntry = {
animationNote: '第一次点击不播额外战斗或位移动画,重点是切到交易窗口。',
storyNote:
'真正的剧情推进发生在 confirmTrade 之后,而不是打开 modal 的瞬间。',
uiNote: '会先打开交易 modal并预选 NPC 第一件商品与玩家第一件可卖物品。',
uiNote: '会先打开交易 modal并预选后端 view 中第一件可提交的买入 / 卖出物品。',
compactDetailText: '查看库存与价格',
},
};

View File

@@ -0,0 +1,35 @@
import { FLOW_FUNCTION_DOCUMENTATION } from './flow';
import { NPC_FUNCTION_DOCUMENTATION } from './npc';
import { PANEL_FUNCTION_DOCUMENTATION } from './panel';
import {
STATE_FUNCTION_DOCUMENTATION,
STATE_FUNCTION_RUNTIME_SOURCES,
} from './state';
import { TREASURE_FUNCTION_DOCUMENTATION } from './treasure';
import type { FunctionDocumentationEntry } from './types';
export const RPG_FUNCTION_RUNTIME_ALL_DOCUMENTATION: FunctionDocumentationEntry[] =
[
...STATE_FUNCTION_DOCUMENTATION,
...NPC_FUNCTION_DOCUMENTATION,
...TREASURE_FUNCTION_DOCUMENTATION,
...FLOW_FUNCTION_DOCUMENTATION,
...PANEL_FUNCTION_DOCUMENTATION,
];
/**
* RPG function 运行时总览入口。
*
* 目的:
* 1. 在同一个脚本里集中看到当前所有 function 的注册入口。
* 2. 先看总表,再跳到各自独立文件维护实现,避免重新回到巨型 switch。
*/
export const RPG_FUNCTION_RUNTIME_OVERVIEW = {
allDocumentation: RPG_FUNCTION_RUNTIME_ALL_DOCUMENTATION,
stateDocumentation: STATE_FUNCTION_DOCUMENTATION,
npcDocumentation: NPC_FUNCTION_DOCUMENTATION,
treasureDocumentation: TREASURE_FUNCTION_DOCUMENTATION,
flowDocumentation: FLOW_FUNCTION_DOCUMENTATION,
panelDocumentation: PANEL_FUNCTION_DOCUMENTATION,
stateRuntimeSources: STATE_FUNCTION_RUNTIME_SOURCES,
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* battle_all_in_crush
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 战斗中的正面爆发动作。它要求主角不绕、不拖,直接把当前回合的叙事、
* 技能权重和视觉表现都推向“强压正面敌人”的方向。
*/
export const BATTLE_ALL_IN_CRUSH_FUNCTION_SOURCE: StateFunctionSource = {
export const BATTLE_ALL_IN_CRUSH_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'battle_all_in_crush',
state: 'battle',
@@ -56,5 +56,23 @@ export const BATTLE_ALL_IN_CRUSH_FUNCTION_SOURCE: StateFunctionSource = {
category: 'battle',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics, environment }) {
if (metrics.monsterHpRatio <= 0.25) {
return `压上去收掉${environment.monsterName}最后一口气`;
}
if (metrics.playerHpRatio <= 0.35) {
return `顶着伤势强压${environment.monsterName}赌一波强杀`;
}
return `正面强压${environment.monsterName}不给喘息`;
},
getPriority({ metrics }) {
return metrics.monsterHpRatio <= 0.25
? 8
: metrics.playerHpRatio <= 0.35
? 2
: 4;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* battle_escape_breakout
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 战斗中的脱离动作。它不是继续换血,而是明确让主角放弃当前缠斗,
* 把叙事重心切到“拉开距离、甩开追击、离开战场”。
*/
export const BATTLE_ESCAPE_BREAKOUT_FUNCTION_SOURCE: StateFunctionSource = {
export const BATTLE_ESCAPE_BREAKOUT_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'battle_escape_breakout',
state: 'battle',
@@ -51,5 +51,20 @@ export const BATTLE_ESCAPE_BREAKOUT_FUNCTION_SOURCE: StateFunctionSource = {
category: 'escape',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics, environment }) {
if (metrics.playerHpRatio <= 0.35) {
return `撑着伤势先脱离${environment.monsterName}的追杀`;
}
return `转身拉开距离,甩开${environment.monsterName}`;
},
getPriority({ metrics }) {
return metrics.playerHpRatio <= 0.2
? 9
: metrics.playerHpRatio <= 0.35
? 5
: 1;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* battle_feint_step
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 战斗中的机动切入动作。它把重点放在虚晃、变线与抢身位,
* 让战斗叙事更偏向灵活切入而不是硬扛伤害。
*/
export const BATTLE_FEINT_STEP_FUNCTION_SOURCE: StateFunctionSource = {
export const BATTLE_FEINT_STEP_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'battle_feint_step',
state: 'battle',
@@ -56,5 +56,16 @@ export const BATTLE_FEINT_STEP_FUNCTION_SOURCE: StateFunctionSource = {
category: 'battle',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics, environment }) {
if (metrics.monsterHpRatio <= 0.35) {
return `虚晃切进去收掉${environment.monsterName}`;
}
return `借假动作切进${environment.monsterName}身前`;
},
getPriority({ metrics }) {
return metrics.monsterHpRatio <= 0.5 ? 5 : 3;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* battle_finisher_window
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 战斗中的终结窗口动作。它要求系统把这一回合理解为“敌人已经露出空档”,
* 因而优先演出收割、补刀和终结技。
*/
export const BATTLE_FINISHER_WINDOW_FUNCTION_SOURCE: StateFunctionSource = {
export const BATTLE_FINISHER_WINDOW_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'battle_finisher_window',
state: 'battle',
@@ -55,5 +55,23 @@ export const BATTLE_FINISHER_WINDOW_FUNCTION_SOURCE: StateFunctionSource = {
category: 'battle',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics, environment }) {
if (metrics.monsterHpRatio <= 0.25) {
return `完成对${environment.monsterName}的残血收割`;
}
if (metrics.monsterHpRatio <= 0.45) {
return `抓住${environment.monsterName}露出的破绽补上重击`;
}
return `盯住${environment.monsterName}的空当准备终结一击`;
},
getPriority({ metrics }) {
return metrics.monsterHpRatio <= 0.25
? 10
: metrics.monsterHpRatio <= 0.45
? 6
: 1;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* battle_guard_break
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 战斗中的破架重击动作。它强调“针对敌人当前动作强拆架势”,
* 比纯换血更讲究把敌人的节奏打断。
*/
export const BATTLE_GUARD_BREAK_FUNCTION_SOURCE: StateFunctionSource = {
export const BATTLE_GUARD_BREAK_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'battle_guard_break',
state: 'battle',
@@ -54,5 +54,16 @@ export const BATTLE_GUARD_BREAK_FUNCTION_SOURCE: StateFunctionSource = {
category: 'battle',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics, environment }) {
if (metrics.monsterHpRatio <= 0.35) {
return `砸开${environment.monsterName}的架势直接斩落`;
}
return `重击破开${environment.monsterName}的招架`;
},
getPriority({ metrics }) {
return metrics.monsterHpRatio <= 0.4 ? 6 : 3;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* battle_probe_pressure
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 战斗中的稳扎试探动作。适合在局势未明、资源需要保留时,
* 先用安全且持续的压制把信息和节奏摸出来。
*/
export const BATTLE_PROBE_PRESSURE_FUNCTION_SOURCE: StateFunctionSource = {
export const BATTLE_PROBE_PRESSURE_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'battle_probe_pressure',
state: 'battle',
@@ -54,5 +54,19 @@ export const BATTLE_PROBE_PRESSURE_FUNCTION_SOURCE: StateFunctionSource = {
category: 'battle',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics, environment }) {
if (metrics.playerManaRatio <= 0.3) {
return `稳住节奏试探${environment.monsterName},先省下灵力`;
}
if (metrics.monsterHpRatio <= 0.3) {
return `稳步逼近,补掉${environment.monsterName}残余血量`;
}
return `稳扎稳打继续试探${environment.monsterName}`;
},
getPriority({ metrics }) {
return metrics.playerManaRatio <= 0.3 ? 8 : 4;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* battle_recover_breath
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 战斗中的恢复动作。它会把当前回合塑造成“先稳住伤势与灵力”,
* 让数值、冷却和叙事都朝回气与整顿节奏的方向靠拢。
*/
export const BATTLE_RECOVER_BREATH_FUNCTION_SOURCE: StateFunctionSource = {
export const BATTLE_RECOVER_BREATH_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'battle_recover_breath',
state: 'battle',
@@ -57,5 +57,22 @@ export const BATTLE_RECOVER_BREATH_FUNCTION_SOURCE: StateFunctionSource = {
category: 'recovery',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics }) {
if (metrics.playerHpRatio <= 0.35) {
return '原地打坐恢复血量';
}
if (metrics.playerManaRatio <= 0.3) {
return '收势调息回一口灵力';
}
return '边守边调息稳住节奏';
},
getPriority({ metrics }) {
return (
(metrics.playerHpRatio <= 0.35 ? 10 : 0) +
(metrics.playerManaRatio <= 0.3 ? 6 : 0)
);
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* idle_call_out
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 空闲状态下的主动喊话动作。它会把探索从“静悄悄地摸过去”
* 转成“先出声试探,看谁先回应”的节奏。
*/
export const IDLE_CALL_OUT_FUNCTION_SOURCE: StateFunctionSource = {
export const IDLE_CALL_OUT_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'idle_call_out',
state: 'idle',
@@ -44,5 +44,24 @@ export const IDLE_CALL_OUT_FUNCTION_SOURCE: StateFunctionSource = {
category: 'idle',
active: true,
},
runtime: {
applyDefinitionAdjustments(definition) {
return {
...definition,
text: '主动出声试探',
description:
'主动朝前方喊话试探,可能把附近潜着的角色或怪物直接从远处引出来。',
};
},
buildSuggestedActionText({ environment }) {
return `冲着${environment.sceneName}前方扬声试探,看是谁先被逼出来`;
},
buildDetailText() {
return '主动打破寂静,把附近潜着的角色或怪物从屏幕外直接引到眼前。';
},
getPriority() {
return 5;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* idle_explore_forward
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 空闲状态下最核心的推进动作。它负责把“继续往前探”从一句泛化文案,
* 落成真正会引出下一幕遭遇的运行时 function。
*/
export const IDLE_EXPLORE_FORWARD_FUNCTION_SOURCE: StateFunctionSource = {
export const IDLE_EXPLORE_FORWARD_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'idle_explore_forward',
state: 'idle',
@@ -44,5 +44,32 @@ export const IDLE_EXPLORE_FORWARD_FUNCTION_SOURCE: StateFunctionSource = {
category: 'idle',
active: true,
},
runtime: {
applyDefinitionAdjustments(definition) {
return {
...definition,
text: '继续向前探索',
description:
'沿着当前场景继续深入,把前路真正探出来,下一刻就可能撞上新的危险或际遇。',
};
},
buildSuggestedActionText({ metrics, environment }) {
if (metrics.playerHpRatio <= 0.35) {
return `按着伤口,沿着${environment.sceneName}继续往深处摸去`;
}
if (environment.hasForwardScene) {
return `顺着${environment.sceneName}的路势,继续朝前方深处探去`;
}
return `拨开${environment.sceneName}前的遮挡,继续朝更深处探去`;
},
buildDetailText({ environment }) {
return environment.hasForwardScene
? `沿着${environment.sceneName}继续往前压过去,真正把前方会遇到的人影、怪物或宝藏探出来。`
: `继续深入${environment.sceneName}前方未探明的地带,下一刻就可能撞见新的动静。`;
},
getPriority({ metrics }) {
return metrics.playerHpRatio > 0.45 ? 6 : 2;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* idle_follow_clue
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 空闲状态下的循线推进动作。它在源码定义层仍然存在,
* 但当前运行时会在聚合阶段被过滤,因此属于保留中的停用 function。
*/
export const IDLE_FOLLOW_CLUE_FUNCTION_SOURCE: StateFunctionSource = {
export const IDLE_FOLLOW_CLUE_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'idle_follow_clue',
state: 'idle',
@@ -44,5 +44,16 @@ export const IDLE_FOLLOW_CLUE_FUNCTION_SOURCE: StateFunctionSource = {
category: 'idle',
active: false,
},
runtime: {
buildSuggestedActionText() {
return '顺着可疑痕迹继续靠近';
},
buildDetailText() {
return '沿着声音、脚印或灵气痕迹继续摸过去,可能更快接近前方目标。';
},
getPriority() {
return 5;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* idle_observe_signs
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 空闲状态下的侦察动作。它把当前回合定义成“停下来观察”,
* 重点不是立刻推进,而是为后续选择生成可引用的观察结果。
*/
export const IDLE_OBSERVE_SIGNS_FUNCTION_SOURCE: StateFunctionSource = {
export const IDLE_OBSERVE_SIGNS_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'idle_observe_signs',
state: 'idle',
@@ -44,5 +44,16 @@ export const IDLE_OBSERVE_SIGNS_FUNCTION_SOURCE: StateFunctionSource = {
category: 'idle',
active: true,
},
runtime: {
buildSuggestedActionText() {
return '停步观察附近的风吹草动';
},
buildDetailText() {
return '先确认附近是否潜伏着人影、怪物或其他值得靠近的东西。';
},
getPriority() {
return 4;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* idle_rest_focus
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 空闲状态下的原地恢复动作。它不会推进遭遇,而是给玩家一个
* 在非战斗场景里回收少量血蓝的缓冲回合。
*/
export const IDLE_REST_FOCUS_FUNCTION_SOURCE: StateFunctionSource = {
export const IDLE_REST_FOCUS_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'idle_rest_focus',
state: 'idle',
@@ -46,5 +46,21 @@ export const IDLE_REST_FOCUS_FUNCTION_SOURCE: StateFunctionSource = {
category: 'recovery',
active: true,
},
runtime: {
buildSuggestedActionText({ metrics }) {
if (metrics.playerHpRatio <= 0.35) {
return '原地打坐恢复气血';
}
if (metrics.playerManaRatio <= 0.35) {
return '盘坐调息恢复灵力';
}
return '原地调息整理状态';
},
getPriority({ metrics }) {
return metrics.playerHpRatio <= 0.35 || metrics.playerManaRatio <= 0.35
? 8
: 2;
},
},
};

View File

@@ -1,5 +1,5 @@
import { AnimationState } from '../../../types';
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } from '../types';
/**
* idle_travel_next_scene
@@ -7,7 +7,7 @@ import type { StateFunctionSource } from '../types';
* 空闲状态下的切场景动作。它代表玩家主动离开当前地点,
* 进入相邻场景重新开启新的遭遇周期。
*/
export const IDLE_TRAVEL_NEXT_SCENE_FUNCTION_SOURCE: StateFunctionSource = {
export const IDLE_TRAVEL_NEXT_SCENE_FUNCTION_SOURCE: StateFunctionRuntimeSource = {
definition: {
id: 'idle_travel_next_scene',
state: 'idle',
@@ -44,5 +44,21 @@ export const IDLE_TRAVEL_NEXT_SCENE_FUNCTION_SOURCE: StateFunctionSource = {
category: 'idle',
active: true,
},
runtime: {
buildSuggestedActionText({ environment }) {
return environment.travelSceneName
? `前往${environment.travelSceneName}`
: '前往其他场景';
},
buildDetailText({ environment }) {
return (
environment.travelSceneDescription ??
'离开当前区域,前往相邻场景继续冒险。'
);
},
getPriority({ metrics }) {
return metrics.playerHpRatio > 0.45 ? 5 : 3;
},
},
};

View File

@@ -1,4 +1,4 @@
import type { StateFunctionSource } from '../types';
import type { StateFunctionRuntimeSource } 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';
@@ -15,7 +15,7 @@ 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[] = [
export const STATE_FUNCTION_SOURCES: StateFunctionRuntimeSource[] = [
BATTLE_ALL_IN_CRUSH_FUNCTION_SOURCE,
BATTLE_GUARD_BREAK_FUNCTION_SOURCE,
BATTLE_PROBE_PRESSURE_FUNCTION_SOURCE,
@@ -31,6 +31,10 @@ export const STATE_FUNCTION_SOURCES: StateFunctionSource[] = [
IDLE_CALL_OUT_FUNCTION_SOURCE,
];
export const STATE_FUNCTION_RUNTIME_SOURCES = STATE_FUNCTION_SOURCES.filter(
(source) => source.runtime,
);
export const STATE_FUNCTION_DEFINITIONS = STATE_FUNCTION_SOURCES.map(
(source) => source.definition,
);
@@ -47,4 +51,3 @@ export const STATE_FUNCTION_DOCUMENTATION = [
BATTLE_USE_SKILL_FUNCTION,
...STATE_FUNCTION_SOURCES.map((source) => source.documentation),
];

View File

@@ -56,3 +56,40 @@ export interface StateFunctionSource {
documentation: FunctionDocumentationEntry;
promptDescription: string;
}
export interface StateFunctionRuntimeMetrics {
playerHpRatio: number;
playerManaRatio: number;
monsterHpRatio: number;
}
export interface StateFunctionRuntimeEnvironment {
sceneName: string;
monsterName: string;
hasForwardScene: boolean;
travelSceneName?: string | null;
travelSceneDescription?: string | null;
}
export interface StateFunctionRuntimeHandler {
applyDefinitionAdjustments?: (
definition: StateFunctionDefinition,
) => StateFunctionDefinition;
buildSuggestedActionText?: (params: {
definition: StateFunctionDefinition;
metrics: StateFunctionRuntimeMetrics;
environment: StateFunctionRuntimeEnvironment;
}) => string;
buildDetailText?: (params: {
definition: StateFunctionDefinition;
environment: StateFunctionRuntimeEnvironment;
}) => string | undefined;
getPriority?: (params: {
definition: StateFunctionDefinition;
metrics: StateFunctionRuntimeMetrics;
}) => number;
}
export interface StateFunctionRuntimeSource extends StateFunctionSource {
runtime?: StateFunctionRuntimeHandler;
}