编写 Bot 技能施法 Spec

SkillFiles & storage

Write bot AI casting rules (AbilitySpec) for a given Dota ability, so the bot automatically casts it at the right moment. Whether it's a vanilla ability, a custom ability, or one drawn from the lottery, register a file under src/vscripts/ai/ability/specs/. Read the ability's KV in docs/reference to

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the 编写 Bot 技能施法 Spec skill

What this skill tells your AI

The instructions your AI receives, as published by windy10v10ai/game in .claude/skills/bot-ability-usage/SKILL.md and read by ahel’s review.

把"何时何处施放该技能"以数据形式登记到 AbilityRegistry,由 AbilityDispatcher 在每个 bot tick 自动遍历并执行。

架构背景:AbilityDispatcher(按 spec 注册表)是 bot 精细主动施法的唯一目标架构。存量 UseAbilityXxx 手写逻辑属于迁移债务,修改涉及这些技能时应将规则迁入 spec 并删除重复入口。新技能一律走 spec,不要再往英雄文件加。

关键路径:


第一步:解析技能输入

按 CLAUDE.md「技能系统名查找」规则处理(支持系统名 / 中文名 / 英雄名-技能名),最终得到 abilityName(如 omniknight_purification)。


第二步:检查是否已有 spec

Glob pattern: src/vscripts/ai/ability/specs/<abilityName>.ts
情况处理
已存在操作模式 = 修正现有 spec(读取并按用户需求编辑 SPECS 数组)
不存在操作模式 = 新建 spec 文件

同一技能在不同目标场景下条件不同(例如对英雄/对小兵),通过同一文件内 SPECS 数组多条 entry 表达,不要建多个文件


第三步:读取技能 KV,提取关键字段

按 CLAUDE.md「Dota 2 参考文件速查」找到该技能的 KV 块,提取:

KV 字段用途取值映射
AbilityBehavior决定 cast 调用方式dispatcher 自动按 UNIT_TARGET / POINT / AOE / NO_TARGET 派发,spec 不用关心
AbilityUnitTargetTeam决定 TargetSideENEMYEnemyHero/EnemyCreep/EnemyBuildingFRIENDLYFriendlyHero/FriendlyBuilding;技能仅作用施法者 → Self
AbilityUnitTargetType区分英雄/小兵/建筑HERO*Hero;仅 BASIC/CREEPEnemyCreep;含 BUILDING*Building;同一技能多种合法目标且语义合理时注册多条 spec(如冰霜魔盾对友方英雄 + 友方建筑)
AbilityCastRange施法距离dispatcher 会自动按 cast range + 施法距离加成过滤目标,spec 通常不要手写 range.lte

如该技能是 PASSIVE 或纯 NO_TARGET 自身 buff 不需要选目标 → TargetSide.Self


第四步:与用户确认施法条件

AskUserQuestion 与用户确认以下几项中需要的项目(不需要的项直接省略,spec 越简单越好):

  1. 目标血量条件(最常见):例如"残血斩杀"target.unitCondition.healthPercent.lte: 25;"低血量队友"lte: 70;"避开满血"lte: 95
  2. 目标数量条件:群体技能要求"施法范围内至少 N 个敌人才出手"target.count.gte: 3计数范围 = 生效的 range.lte(spec 显式写的优先,未写时按 cast range / rangeFromAbilityValue 自动补齐),不是固定的 1800 预搜半径。计数只按存活与距离收窄,不受 target.unitCondition 影响。若判据需要比施法距离更大的观察范围,注意 range 同时决定目标筛选,放大会让 bot 追着远处目标跑。
  3. 施法者条件:例如"蓝量够才用"self.unitCondition.manaPercent.gte: 50,或"血量低才用某保命技能"。
  4. 技能等级 / 充能条件ability.level.gte: 3ability.charges.gte: 1
  5. 避免重复施法target.unitCondition.noModifier: ['modifier_xxx'],常用于持续 debuff/buff。Modifier 名查 DOTA_Tooltip_modifier_<name><name>优先查项目 game/resource/addon_schinese.txt(自定义/克隆/override 技能以项目本地化为准);项目搜不到再查 reference 最新版本 docs/reference/<version>/abilities_schinese.txt(原版技能兜底)。例:寒霜魔盾 = modifier_lich_frost_shieldlich_frost_armor 是项目把奥术法师寒冰盔甲克隆给巫妖,原版 lich 无此技能,modifier 名 = modifier_lich_frost_armor,仅在项目本地化有定义。
  6. 跳过已被控目标target.unitCondition.notActionable: true,目标处于眩晕/变羊/噩梦/虚空大等硬控状态则跳过,对已被控的目标使用控制技能通常是浪费。
  7. 附近无敌方英雄才施法self.noEnemyHeroInRange: 900(距离可自定义),常用于对小兵或建筑施法前确认安全。此字段在 dispatcher tryCast 层检查,不是 self.unitCondition 的子字段,直接挂在 self 下。
  8. 附近需要足够友方小兵self.friendlyCreepNearby: { count: { gte: 3 } },常用于推塔场景(对 EnemyBuilding 施法时确认有推线波)。range 不填默认 900。此字段也直接挂在 self 下,dispatcher inline FindUnitsInRadius 检查。
  9. 排除施法者自己target.excludeSelf: true。友方候选天然包含施法者且距离 0 排在首位,以自身生命为代价的技能(如亚巴顿迷雾缠绕)必须排掉;纯增益给自己用通常合理,不要随手加。
  10. 目标相对朝向target.facing: 'front' | 'back',只保留位于施法者正面 / 背面半区的目标(水平面点积取符号,正侧方两者都不满足)。用于带位移的技能区分追击(朝目标跳)与撤退(背对目标跳),如宙斯神圣一跳。
  11. 同名多条 spec:若英雄/小兵/建筑 不同目标场景条件不同(如群蛇守卫对英雄/对塔),写多条 AbilitySpec entry,按"重要的写前面"排序。

是否补一条对小兵的清兵规则

对英雄的规则确认完之后,再判断这个技能要不要顺带清兵。不要自行决定,用 AskUserQuestion 问用户,并在选项说明里带上该技能的冷却与法力消耗,让用户有判断依据。

三个条件全部满足才提问:

  1. 范围伤害AbilityBehaviorAOE,或是 POINT 类技能,或 UNIT_TARGET 同时带 AOE
  2. 能作用于普通单位AbilityUnitTargetTypeBASICCREEPPOINTNO_TARGET 类天然满足。仅 HERO 的单位指定技能选不中小兵,直接排除。
  3. 拿去清兵不亏。冷却与法力属于关键技能级别的不要提问,直接排除。经验线是冷却 45 秒以上或法力 200 以上;同时看这个技能在英雄战里的地位,核心机动与保命技能即使便宜也不清兵。

以下类型任何情况都不提问:单体伤害与单体控制、增益 / 护盾 / 治疗、纯位移、被动。

用户同意后,在同一文件的 SPECS 数组里再加一条 targetSide: TargetSide.EnemyCreep 的 entry,排在对英雄的规则之后。默认门槛由 dispatcher 自动套用,通常不需要再写任何条件。

EnemyCreep 默认条件CREEP_DEFAULT_CONDITION,由 dispatcher 自动套用,无需在 spec 中重复写):

  • self.unitCondition.manaPercent.gte: 40
  • self.unitCondition.healthPercent.gte: 40
  • ability.level.gte: 3
  • self.noEnemyHeroInRange: 900

spec 中显式指定的同路径值会通过 DeepMerge 覆盖默认值(NumberRange 整体替换,非 key 级合并)。例如想在自身蓝量低时才吸蓝:self.unitCondition.manaPercent: { lte: 40 } 会替换默认的 gte: 40

现有条件结构见 cast-condition.tsUnitCondition / AbilityCoindition / NumberRange

不要发明 cast-condition.ts 没有的字段;若用户的诉求超出现有条件能力(例如"距离敌方塔太近不施放"),告知用户当前框架不支持,需要扩展 dispatcher,不要自行加 spec 字段。


第五步:写 spec 文件

文件名 = <abilityName>.ts,路径 src/vscripts/ai/ability/specs/

模板:

import { AbilitySpec, TargetSide } from '../ability-spec';

/**
 * <技能中文名>:<原版 behavior / target team 摘录,例如 UNIT_TARGET / ENEMY / HERO>。
 *
 * <一句话说明何时施放、为什么这样限定。>
 */
export const SPECS: AbilitySpec[] = [
  {
    abilityName: '<abilityName>',
    targetSide: TargetSide.<EnemyHero | EnemyCreep | EnemyBuilding | FriendlyHero | Self>,
    condition: {
      target: {
        unitCondition: { healthPercent: { lte: 25 } },
      },
    },
  },
];

可省略的部分尽量省:

  • 无 condition → 直接 targetSide: TargetSide.Self, 后不写 condition
  • 没有 target / self / ability 任一分支 → 别写空对象。

第六步:在 index.ts 中注册

修改 src/vscripts/ai/ability/specs/index.ts

  1. 顶部加 import { SPECS as <camelName> } from './<abilityName>';(按字母序)
  2. registerAbilitySpecs() 内对应的分组段落调用 AbilityRegistry.registerAll(<camelName>);
    • 治疗 / 护盾类(friendly target) → 友方组
    • 高伤大招(enemy hero) → 敌方组
    • 其他根据技能性质判断;段落不够时新加注释段落

dispatcher 按 hero.GetAbilityByIndex 槽位顺序遍历,所以多个技能间的优先级由"技能挂在英雄第几槽"决定;同名多条 spec 的优先级才由 SPECS 数组顺序决定。


第七步:验证

检查命令 / 动作
类型 / 编译npm run lint && npm run build:vscripts
单元测试npm test(无需新增 spec 测试,框架本身已有测试覆盖)
游戏内npm run start 进 tools,让一个 bot 学到 / 抽到该技能并构造触发条件,观察控制台 [AI] CastByBehavior <abilityName> 日志

常见陷阱

  • 不要在 spec 里手写 range.lte:dispatcher 会用技能 KV 中的 AbilityCastRange + GetCastRangeBonus 自动填入。手写反而会覆盖默认值,导致超出施法距离也尝试施放。例外:spec 想要更小的搜索半径才显式覆盖。
  • 不要为 spec 加新的字段类型:spec 字段只能是 ability-spec.ts 中已定义的;新需求先扩展 cast-condition.ts 与 dispatcher,再消费。
  • 不要往英雄文件 UseAbilityXxx 加新技能:新技能一律走 spec。遇到已有手写规则时,将有效条件迁入 spec,并在确认行为等价后删除对应英雄覆盖,不能把英雄专属施法保留为长期第二执行层。
  • toggle / autoCast 类技能:通过 condition.action.toggleOn / toggleOff / autoCastOn 表达。dispatcher 命中 action 条件后只切换到目标状态,不走正常施法派发;已经处于目标状态时返回 false,继续尝试后续规则。
  • TSTL 对象 spread 陷阱:见 CLAUDE.md「常见陷阱」末条;spec 文件本身用不到 spread,但若需要扩展 dispatcher / cast-condition,绝对不能写 { ...maybeUndefined }
  • KV 数值字段术语:Dota 2 现行 KV 中数值字段块名为 AbilityValues(旧版 AbilitySpecial 已废弃)。在注释、字段命名、文档中统一使用 AbilityValue 表述;引擎 API GetSpecialValueFor(key) 仍可调用,但变量名和注释应写 abilityValue / rangeFromAbilityValue,不用 specialValue
  • spec 文件头部注释不要复述 condition 里的字段/数值:注释只写意图("范围内有敌人即用"),不要带上 range.lte 等字段的具体值("900 范围内")。同一个数值出现两处,后续只改其中一处就会自相矛盾,且无法判断哪个是真相源。此规则同样适用于 ItemSpec(ai/item/specs/)文件。发现注释数值与代码不一致时,不要默认注释代表设计意图、代码是笔误就去改代码——应先查 git blame / 实机测试确认谁是真相源,再决定改代码还是改注释。

Signals

GitHub stars
73
Forks
40
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
bot-ability-usage
Source
github.com/windy10v10ai/game