Skip to content

战斗系统

ADVMaker 的战斗系统是一套完整的回合制战斗框架,支持自定义敌方、技能、行动顺序和胜负条件。你可以通过组件 DSL<ABattle>)或 JS APIAdv.startBattle())两种方式来触发战斗。

核心类型

ADVUserEnemy — 敌方配置

定义敌方的基础属性、技能和 AI 行为。

ts
interface ADVUserEnemy {
    name: string; // 名称
    desc?: string; // 描述
    hp: number; // 当前生命值
    maxhp?: number; // 最大生命值(默认 Infinity)
    atk?: number; // 攻击力(默认 0)
    def?: number; // 防御力(默认 0)
    dex?: number; // 敏捷(默认 0,用于先攻排序)
    skill: ADVUserSkill<ADVEnemy>[]; // 技能列表
    /**
     * 敌方回合的 AI 行为
     * @param enemies 所有敌人实例数组
     * 执行完毕后,系统会自动推进到下一个行动者
     */
    move: (enemies: ADVEnemy[]) => Promise<void>;
}

ADVUserSkill — 技能定义

定义攻击、技能或特殊行动。

ts
interface ADVUserSkill<T = ADVEnemy[]> {
    name: string; // 技能名称
    desc: string; // 详细描述
    summary?: string; // 简短摘要
    targetNum?: number; // 目标选择数量(Infinity = 全体)
    /**
     * 技能执行回调。
     *
     * ⚠️ T 的类型取决于触发者:
     * - **玩家**触发(ATKActions / SPActions)
     *   → T = ADVEnemy[],object = 弹窗选中的所有敌人
     * - **敌人 A**触发(enemy.skill)
     *   → T = ADVEnemy,object = 敌人 A 自身(用于读取 atk 等属性计算伤害)
     */
    onUse?: (object: T) => void;
}

ADVUserBattle — 战斗配置

组装一场完整战斗所需的所有配置。

ts
interface ADVUserBattle {
    /** 敌方阵容 */
    enemies: ADVUserEnemy[];
    /** 普通攻击选项(点击"攻击"时弹出) */
    ATKActions: ADVUserSkill[];
    /** 技能选项(点击"技能"时弹出,可选) */
    SPActions?: ADVUserSkill[];
    /** 特殊行动选项(可选) */
    otherActions?: ADVUserSkill[];

    /**
     * 决定先攻序列
     * @param enemies 运行时敌方实例数组
     * @returns 行动顺序数组,元素为敌方在数组中的下标,
     *          -1 代表玩家。越靠前越先行动。
     */
    initiativeOrder: (enemies: ADVEnemy[]) => number[];

    /**
     * 判断战斗是否结束
     * @param enemies 当前所有敌方实例
     * @returns null 继续 | true 胜利 | false 失败
     */
    isFinish: (enemies: ADVEnemy[]) => boolean | null;
}

组件方式:ABattle

<ABattle> 使用 AIf/AElif/AElse 实现三分支(胜利/失败/逃跑)。

基本用法

vue
<template>
    <ADialog>
        <ALine>一只哥布林挡住了去路!</ALine>

        <ABattle :setting="battleConfig">
            <template #success>
                <ALine>🎉 你击败了哥布林!</ALine>
            </template>
            <template #fail>
                <ALine>💀 你被哥布林打倒了...</ALine>
                <AEnding desc="败北" />
            </template>
            <template #flee>
                <ALine>🏃 你成功逃离了战斗。</ALine>
            </template>
        </ABattle>
    </ADialog>
</template>

<script setup lang="ts">
import { ADVUserBattle, ADVUserSkill } from '@advmaker/core';

const battleConfig: ADVUserBattle = {
    enemies: [
        {
            name: '哥布林',
            desc: '森林中常见的小怪物',
            hp: 30,
            maxhp: 30,
            atk: 8,
            def: 4,
            dex: 10,
            skill: [],
            move: async (enemies) => {
                Adv.print(`${enemies[0].name} 向你发动攻击!`);
            },
        },
    ],
    ATKActions: [
        {
            name: '斩击',
            desc: '用武器劈向敌人',
            summary: '基础攻击',
            targetNum: 1,
            onUse: (targets) => {
                targets[0].hp -= 10;
                Adv.print(`对 ${targets[0].name} 造成 10 点伤害!`);
            },
        },
    ],
    initiativeOrder: () => [-1, 0],
    isFinish: (enemies) => (enemies[0].hp <= 0 ? true : null),
};
</script>

Props

属性类型说明
settingADVUserBattle战斗配置对象

插槽

插槽条件说明
#successisFinish() 返回 true战斗胜利后展示
#failisFinish() 返回 false战斗失败后展示
#flee玩家逃跑逃离战斗后展示

执行流程

  1. ABattle 挂载 → ABattleBase 向父级 ADialog 注册异步脚本项。
  2. 脚本执行到该项 → 调用 Adv.startBattle(setting)
  3. battleStore.isBattle = trueMessage 隐藏,Battle.vue 显示。
  4. initiativeOrder() 生成先攻序列,存入 queue
  5. 调用 battleStore.next(true) 启动回合循环:
    • 首次(first=true:直接执行第一个行动者,跳过 isFinish 和队列轮转
    • 后续:先检查 isFinish() → 若结束则 emit('battle-over', res);否则轮转队列 → 执行下一个
    • 玩家回合-1):state = 'player',显示行动按钮
    • 敌方回合>=0):state = 'enemy',调用 enemies[n].move(),执行完后递归 next()
  6. 战斗结束:
    • isFinish 返回 trueemit('battle-over', true)
    • isFinish 返回 falseemit('battle-over', false)
    • battleStore.flee() 被调用 → state = 'flee'emit('battle-over', 'flee')
  7. Promise 兑现,ABattle 渲染对应插槽。

JS API 方式:Adv.startBattle()

ts
import { ADVUserBattle, ADVUserSkill } from '@advmaker/core/data/model';

Adv.appendDialog('battle_scene')
    .say('一只哥布林挡住了去路!')
    .say(async () => {
        const setting: ADVUserBattle = {
            enemies: [
                {
                    name: '哥布林',
                    hp: 30,
                    maxhp: 30,
                    atk: 8,
                    def: 4,
                    dex: 10,
                    skill: [],
                    move: async () => {},
                },
            ],
            ATKActions: [
                {
                    name: '斩击',
                    desc: '',
                    targetNum: 1,
                    onUse: (targets) => {
                        targets[0].hp -= 10;
                    },
                },
            ],
            initiativeOrder: () => [-1, 0],
            isFinish: (e) => (e[0].hp <= 0 ? true : null),
        };
        const result = await Adv.startBattle(setting);
        if (result === true) Adv.print('🎉 胜利!');
        else if (result === false) Adv.print('💀 失败...');
        else Adv.print('🏃 逃跑了');
    });

函数签名

ts
Adv.startBattle(setting: ADVBattle): Promise<boolean | 'flee'>
返回值含义
true玩家胜利
false玩家失败
'flee'玩家逃跑

该方法会阻塞直到战斗结束。


逃跑机制

敌方 AI 中或按钮处理中调用 battleStore.flee() 即可触发逃跑:

ts
// Battle.vue 中逃跑按钮
battleStore.flee();

逃跑后流程:

  1. battleStore.state 设为 'flee'
  2. 下次 next() 检测到 state === 'flee' → 立即 emit('battle-over', 'flee')
  3. startBattle 的 Promise 以 'flee' 兑现
  4. ABattle 渲染 #flee 插槽

战斗 UI 组件

Battle.vue — 主战斗界面

位于 components/battle/Battle.vuebattleStore.isBattle = true 时替换 Message

┌──────────────────────────────────────┐
│  [敌人1卡片]  [敌人2卡片]             │  ← 点击查看详情
├──────────────────────────────────────┤
│  📜 战斗日志区域(battleStore.log[])  │
├──────────────────────────────────────┤
│  [攻击]  [技能]  [特殊行动]  [逃跑]    │
└──────────────────────────────────────┘

Enemy.vue — 敌人详情弹窗

点击敌人卡片弹出,显示完整属性(名称、描述、HP、攻防、技能列表)。

Action.vue — 行动选择弹窗

点击行动按钮后弹出可用技能列表:

  1. targetNum > 0,触发 choose-object 信号让玩家选目标
  2. 选中后执行 skill.onUse(targets) → 调用 battleStore.next()