编写 Bot 技能施法 Spec
SkillFiles & storageWrite 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.
No other account needed.
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 | 决定 TargetSide | ENEMY → EnemyHero/EnemyCreep/EnemyBuilding;FRIENDLY → FriendlyHero/FriendlyBuilding;技能仅作用施法者 → Self |
AbilityUnitTargetType | 区分英雄/小兵/建筑 | 含 HERO 用 *Hero;仅 BASIC/CREEP 用 EnemyCreep;含 BUILDING 用 *Building;同一技能多种合法目标且语义合理时注册多条 spec(如冰霜魔盾对友方英雄 + 友方建筑) |
AbilityCastRange | 施法距离 | dispatcher 会自动按 cast range + 施法距离加成过滤目标,spec 通常不要手写 range.lte |
如该技能是 PASSIVE 或纯 NO_TARGET 自身 buff 不需要选目标 → TargetSide.Self。
第四步:与用户确认施法条件
用 AskUserQuestion 与用户确认以下几项中需要的项目(不需要的项直接省略,spec 越简单越好):
- 目标血量条件(最常见):例如"残血斩杀"
target.unitCondition.healthPercent.lte: 25;"低血量队友"lte: 70;"避开满血"lte: 95。 - 目标数量条件:群体技能要求"施法范围内至少 N 个敌人才出手"
target.count.gte: 3。计数范围 = 生效的range.lte(spec 显式写的优先,未写时按 cast range /rangeFromAbilityValue自动补齐),不是固定的 1800 预搜半径。计数只按存活与距离收窄,不受target.unitCondition影响。若判据需要比施法距离更大的观察范围,注意range同时决定目标筛选,放大会让 bot 追着远处目标跑。 - 施法者条件:例如"蓝量够才用"
self.unitCondition.manaPercent.gte: 50,或"血量低才用某保命技能"。 - 技能等级 / 充能条件:
ability.level.gte: 3、ability.charges.gte: 1。 - 避免重复施法:
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_shield;lich_frost_armor是项目把奥术法师寒冰盔甲克隆给巫妖,原版 lich 无此技能,modifier 名 =modifier_lich_frost_armor,仅在项目本地化有定义。 - 跳过已被控目标:
target.unitCondition.notActionable: true,目标处于眩晕/变羊/噩梦/虚空大等硬控状态则跳过,对已被控的目标使用控制技能通常是浪费。 - 附近无敌方英雄才施法:
self.noEnemyHeroInRange: 900(距离可自定义),常用于对小兵或建筑施法前确认安全。此字段在 dispatchertryCast层检查,不是self.unitCondition的子字段,直接挂在self下。 - 附近需要足够友方小兵:
self.friendlyCreepNearby: { count: { gte: 3 } },常用于推塔场景(对EnemyBuilding施法时确认有推线波)。range不填默认 900。此字段也直接挂在self下,dispatcher inlineFindUnitsInRadius检查。 - 排除施法者自己:
target.excludeSelf: true。友方候选天然包含施法者且距离 0 排在首位,以自身生命为代价的技能(如亚巴顿迷雾缠绕)必须排掉;纯增益给自己用通常合理,不要随手加。 - 目标相对朝向:
target.facing: 'front' | 'back',只保留位于施法者正面 / 背面半区的目标(水平面点积取符号,正侧方两者都不满足)。用于带位移的技能区分追击(朝目标跳)与撤退(背对目标跳),如宙斯神圣一跳。 - 同名多条 spec:若英雄/小兵/建筑 不同目标场景条件不同(如群蛇守卫对英雄/对塔),写多条
AbilitySpecentry,按"重要的写前面"排序。
是否补一条对小兵的清兵规则
对英雄的规则确认完之后,再判断这个技能要不要顺带清兵。不要自行决定,用 AskUserQuestion 问用户,并在选项说明里带上该技能的冷却与法力消耗,让用户有判断依据。
三个条件全部满足才提问:
- 范围伤害。
AbilityBehavior含AOE,或是POINT类技能,或UNIT_TARGET同时带AOE。 - 能作用于普通单位。
AbilityUnitTargetType含BASIC或CREEP;POINT与NO_TARGET类天然满足。仅HERO的单位指定技能选不中小兵,直接排除。 - 拿去清兵不亏。冷却与法力属于关键技能级别的不要提问,直接排除。经验线是冷却 45 秒以上或法力 200 以上;同时看这个技能在英雄战里的地位,核心机动与保命技能即使便宜也不清兵。
以下类型任何情况都不提问:单体伤害与单体控制、增益 / 护盾 / 治疗、纯位移、被动。
用户同意后,在同一文件的 SPECS 数组里再加一条 targetSide: TargetSide.EnemyCreep 的 entry,排在对英雄的规则之后。默认门槛由 dispatcher 自动套用,通常不需要再写任何条件。
EnemyCreep 默认条件(
CREEP_DEFAULT_CONDITION,由 dispatcher 自动套用,无需在 spec 中重复写):
self.unitCondition.manaPercent.gte: 40self.unitCondition.healthPercent.gte: 40ability.level.gte: 3self.noEnemyHeroInRange: 900spec 中显式指定的同路径值会通过
DeepMerge覆盖默认值(NumberRange 整体替换,非 key 级合并)。例如想在自身蓝量低时才吸蓝:self.unitCondition.manaPercent: { lte: 40 }会替换默认的gte: 40。
现有条件结构见 cast-condition.ts 的
UnitCondition / 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:
- 顶部加
import { SPECS as <camelName> } from './<abilityName>';(按字母序) - 在
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表述;引擎 APIGetSpecialValueFor(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