Skip to content

运行与检定

除了控制故事流程,你还需要在关键时刻执行自定义逻辑(修改属性、播放音效)和进行属性检定(通过掷骰决定成败)。本章介绍 ARunACheck 两个组件,它们让你的故事具备真正的交互逻辑和随机性。


运行时执行:ARun

ARun 组件让你能够在对话脚本的执行流程中嵌入任意自定义代码,非常适合执行状态变更、播放音效、触发动画等副作用。

xml
<ADialog>
    <ALine>你从宝箱中拿出了一把闪闪发光的宝剑。</ALine>
    <ARun :run="() => { Adv.bag.legendarySword += 1; Adv.status.attack += 10; }" />
    <ALine>攻击力提升了 10 点!</ALine>
</ADialog>

属性

属性类型必需说明
run() => void | Promise<void>需要执行的函数,支持同步和异步操作

异步操作

ARun 支持 async 函数,引擎会等待 Promise 完成后再继续执行后续脚本:

xml
<ARun
    :run="async () => {
  Adv.audio.playBgm('battle.mp3');
  await sleep(500);
  Adv.bag.gold -= 10;
}"
/>

API 等价用法

在 API 中,直接将函数作为参数传给 .say(),引擎会自动执行它:

ts
Adv.appendDialog('sword_chest')
    .say('你从宝箱中拿出了一把闪闪发光的宝剑。')
    .say(() => {
        Adv.bag.legendarySword += 1;
        Adv.status.attack += 10;
    })
    .say('攻击力提升了 10 点!');

// 异步版本
Adv.appendDialog('battle_start').say(async () => {
    Adv.audio.playBgm('battle.mp3');
    await new Promise((r) => setTimeout(r, 500));
    Adv.bag.gold -= 10;
});

.say() 接受任意函数 () => void | Promise<void>,其行为与 ARun 完全一致。

典型用途

  • 修改玩家属性:增减金币、生命值、道具数量。
  • 调用音频 API:播放背景音乐或音效。
  • 触发外部动画或特效:如屏幕震动、粒子效果。
  • 记录日志或统计:将玩家的关键决策写入存档。

AOptiononChoose 对比AOption.onChoose 仅在玩家选择该选项时触发,且发生在选项反馈台词播放之前。而 ARun 可以放在对话或选项反馈的任意位置,在指定的时刻执行。在 API 中两者都表现为函数,区别在于函数在脚本数组中的位置。


属性检定:ACheck

ACheck 是 ADVMaker 中最强大的组件之一,它封装了完整的 TRPG 式属性检定流程:掷骰 → 计算修正 → 比较目标值 → 分流成功/失败分支。

基本用法

ACheck 使用两个具名插槽 #success#fail 来分别定义检定成功和失败后展示的内容:

xml
<ADialog>
    <ALine>你试图撬开这把古老的锁。</ALine>
    <ACheck :target="10" :modifier="[{ name: '灵巧', value: () => Adv.status.dexterity }]">
        <template #success>
            <ALine>锁芯发出一声清脆的"咔嗒",锁开了!</ALine>
        </template>
        <template #fail>
            <ALine>锁纹丝不动,你的手指有些酸痛。</ALine>
        </template>
    </ACheck>
</ADialog>

属性详解

属性类型必需说明
targetnumber | (() => number)检定目标值(难度等级 DC),默认为 0
targetDescstring目标描述文本,会在检定提示中显示(如"开锁难度")
diceADVDice | DiceExpression骰子表达式,如 "d20""2d6""3d8+2"
modifier{ name: string; value: () => number }[]修正值列表,每个修正项包含名称和取值函数
onSuccess() => VlAndAsync<void>检定成功时的回调函数
onFail() => VlAndAsync<void>检定失败时的回调函数

骰子表达式

dice 属性支持以下格式的骰子表达式:

表达式含义示例
d201 个 20 面骰范围 1~20
2d62 个 6 面骰范围 2~12
3d8+23 个 8 面骰+2范围 5~26
1d100-51 个 100 面骰-5范围 -4~95

如果未指定 dice,则使用游戏配置中的默认骰子(通常为 d20)。

自定义骰子

你可以传入一个 ADVDice 对象来实现完全自定义的掷骰逻辑:

xml
<script setup>
    const customDice = new ADVDice('命运骰', () => {
        // 自定义:六面骰,但结果总是奇数
        return Math.floor(Math.random() * 3) * 2 + 1;
    });
</script>

<ACheck :target="3" :dice="customDice">
    <template #success>命运眷顾了你!</template>
    <template #fail>命运与你擦肩而过。</template>
</ACheck>

API 等价用法

ACheck 组件本质上是 AIf + AElse + Adv.check 的语法糖。在 API 中,有两种等效方式:

方式一:使用 Adv.check 结合条件分支(推荐)

Adv.check() 返回 Promise<boolean>,可直接作为 AIf 的条件源:

ts
Adv.appendDialog('pick_lock')
    .say('你试图撬开这把古老的锁。')
    .say(
        Adv.if(
            async () =>
                await Adv.check({
                    target: 10,
                    modifier: [{ name: '灵巧', value: () => Adv.status.dexterity }],
                }),
        ),
    )
    .say('锁芯发出一声清脆的"咔嗒",锁开了!')
    .say(Adv.else())
    .say('锁纹丝不动,你的手指有些酸痛。')
    .say(Adv.end());

Adv.check() 本身会:

  1. 掷骰并计算修正。
  2. 在屏幕上显示详细的检定提示信息(骰子类型、原始点数、修正项、最终结果)。
  3. 执行 onSuccess / onFail 回调。
  4. 返回 true(成功)或 false(失败)。

方式二:使用 Dialog 级别的 check 属性

你也可以将 check 直接配置在 ADialogAChoice 上,这样引擎会在对话脚本执行完毕后自动进行检定:

ts
Adv.appendDialog('pick_lock', {
    check: {
        target: 10,
        modifier: [{ name: '灵巧', value: () => Adv.status.dexterity }],
        success: 'after_lock_success', // 检定成功后的跳转
        fail: 'after_lock_fail', // 检定失败后的跳转
        onSuccess: () => {
            /* 成功回调 */
        },
        onFail: () => {
            /* 失败回调 */
        },
    },
})
    .say('你试图撬开这把古老的锁。')
    .say('你专注地摆弄着锁芯……');

完整示例

下面是一个完整的场景检定的例子,综合展示了 ACheck 的各个特性:

xml
<ADialog>
    <ALine>你需要在众目睽睽之下潜入贵族宅邸。</ALine>

    <ACheck
        target="15"
        targetDesc="潜行难度"
        dice="d20"
        :modifier="[
      { name: '敏捷', value: () => Adv.status.agility },
      { name: '潜行技能', value: () => Adv.status.stealth },
      { name: '夜色掩护', value: () => (Adv.bag.nightCloak > 0 ? 5 : 0) },
    ]"
        :on-success="() => Adv.bag.missionProgress = 'infiltrated'"
        :on-fail="() => Adv.bag.gold -= 50"
    >
        <template #success>
            <ALine>你身轻如燕,悄无声息地翻过了围墙。</ALine>
            <ALine>任务日志已更新:潜入成功。</ALine>
        </template>
        <template #fail>
            <ALine>你不小心踢到了一只铁桶!</ALine>
            <ALine>守卫被惊动,你被迫交了 50 金币罚款。</ALine>
        </template>
    </ACheck>

    <ALine>你整理了一下衣襟,继续前进。</ALine>
</ADialog>

API 等价

ts
Adv.appendDialog('stealth_check')
    .say('你需要在众目睽睽之下潜入贵族宅邸。')
    .say(
        Adv.if(
            async () =>
                await Adv.check({
                    target: 15,
                    targetDesc: '潜行难度',
                    dice: 'd20',
                    modifier: [
                        { name: '敏捷', value: () => Adv.status.agility },
                        { name: '潜行技能', value: () => Adv.status.stealth },
                        { name: '夜色掩护', value: () => (Adv.bag.nightCloak > 0 ? 5 : 0) },
                    ],
                    onSuccess: () => (Adv.bag.missionProgress = 'infiltrated'),
                    onFail: () => (Adv.bag.gold -= 50),
                }),
        ),
    )
    .say('你身轻如燕,悄无声息地翻过了围墙。')
    .say('任务日志已更新:潜入成功。')
    .say(Adv.else())
    .say('你不小心踢到了一只铁桶!')
    .say('守卫被惊动,你被迫交了 50 金币罚款。')
    .say(Adv.end())
    .say('你整理了一下衣襟,继续前进。');

工作原理

ACheck 本质上是对 AIfAElseAdv.check 的封装。它在内部:

  1. 调用 Adv.check({ ... }) 执行完整的检定流程:
    • 掷骰得到原始点数。
    • 遍历 modifier 列表,累加所有修正值。
    • 将最终点数与目标值比较(根据游戏配置的判定模式)。
    • 在屏幕上显示详细的检定提示信息,包括骰子类型、原始点数、各项修正和最终结果。
  2. 根据检定结果:
    • 成功 → 渲染 #success 插槽的内容,执行 onSuccess 回调。
    • 失败 → 渲染 #fail 插槽的内容,执行 onFail 回调。

判定模式

游戏的判定模式通过 game.config.ts 中的 judgmentMode 配置:

  • 'd20'(默认):点数 ≥ 目标值 为成功。适用于 D&D 等 d20 系统。
  • 'coC'点数 ≤ 目标值 为成功。适用于克苏鲁的呼唤等百面骰系统。

引擎会输出类似下面的检定信息:

text
检定成功!潜行难度,目标 15 点,【投掷 d20】13 | 敏捷 +2 | 潜行技能 +3 =18 点。