AzerothCore 法术系统开发指南:从 Spell.dbc 到 SpellScript 与 Aura 的完整链路 原创
法术系统是 WoW 服务端规则的核心载体
在 AzerothCore 里,几乎一切战斗行为最终都被抽象成”法术”:一次普通攻击、一个治疗、一个增益光环、一次冲锋打断,背后都是一个带 Spell ID 的法术,经过同一套法术施放管线。理解法术系统,等于拿到了修改战斗规则的总开关。本文沿着”DBC 定义 → 施放校验 → 效果执行 → Aura 维持 → 脚本挂钩”这条真实链路讲清自定义法术的完整开发过程。
第一层:法术的静态定义在 Spell.dbc
法术的基础数据不在 SQL,而在客户端与服务端共享的 Spell.dbc。服务端启动时加载为 SpellInfo。一个法术最关键的字段:
| 字段 | 作用 |
|---|---|
| ID / SpellName | 法术唯一标识与名称 |
| SchoolMask | 伤害学派(物理/火/冰/奥术等),决定抗性与驱散归类 |
| Effects 1-3 | Effect 类型决定行为:伤害、治疗、施加 Aura、传送、召唤等 |
| RangeIndex / CastTime / ManaCost | 射程、施法时间、消耗 |
| Attributes 系列 | 位掩码开关:能否被打断、是否被动、是否正面增益等 |
| SpellVisual / Icon | 客户端表现资源 |
自定义法术通常复制一个近似的原版法术再改,而不是从零填几十个字段。社区常用 spell editor 生成 DBC,服务端与客户端必须使用一致的 DBC,否则表现与判定会错位。纯服务端的技能也可借助 Spell.dbc 覆盖模块,避免直接改客户端文件。
第二层:施放管线与校验
玩家施放法术并不是”按下按钮立即生效”,请求进入世界服务器后走一条严格管线:
CMSG_CAST_SPELL
→ Spell::prepare 检查能否施放
→ Spell::cast 真正进入施法
→ 各 Effect 的 DoEffect 执行
→ 若是持续效果,创建 Aura 挂到目标身上
prepare 阶段的校验决定了大量规则:法力是否足够、是否在射程内、是否被沉默/眩晕、目标是否合法、冷却与 GCD 是否就绪。任何一条不满足都向客户端回 SMSG_CAST_FAILED。自定义”沉默时不能放””只对特定生物有效”这类限制,应在这一层通过条件或脚本拦截,而不是在伤害结算阶段补救。
第三层:SpellScript 挂载服务端逻辑
DBC 只能描述静态效果,动态规则要用 C++ 脚本。AzerothCore 提供 SpellScript(即时法术)与 AuraScript(持续光环)两套钩子。注册通过宏把脚本绑到 Spell ID:
class spell_my_heal : public SpellScript
{
PrepareSpellScript(spell_my_heal);
void HandleHeal(SpellEffIndex /*effIndex*/)
{
Unit* caster = GetCaster();
Unit* target = GetHitUnit();
if (!caster || !target) return;
int32 heal = GetEffectValue();
// 低血量目标获得额外 50% 治疗
if (target->HealthBelowPct(30))
heal = int32(heal * 1.5f);
SetEffectValue(heal);
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_my_heal::HandleHeal,
EFFECT_0, SPELL_EFFECT_HEAL);
}
};
void AddSC_custom_spells()
{
RegisterSpellScript(spell_my_heal);
}
常用钩子覆盖整个生命周期:OnCast、OnCheckCast、OnEffectHitTarget、OnHit、AfterCast。选择钩子的原则是:能在最贴近判定点的位置改数据,就不要在更外层包逻辑。改治疗量用 OnEffectHitTarget + SetEffectValue,改能否施放用 OnCheckCast。
第四层:AuraScript 管理持续效果
增益、减益、周期伤害(DoT/HoT)由 Aura 系统管理。Aura 挂在目标的 Unit::m_AuraApplication 上,按 tick 触发,到期或被驱散时移除。
class aura_my_shield : public AuraScript
{
PrepareAuraScript(aura_my_shield);
// 每个周期 tick 时触发
void HandlePeriodic(AuraEffect const* /*aurEff*/)
{
Unit* target = GetTarget();
if (target->GetHealthPct() < 20)
target->CastSpell(target, 80001, true); // 濒危时触发吸收盾
}
// 计算吸收量时修改数值
void CalcAbsorb(AuraEffect* /*aurEff*/, int32& amount, Unit* /*attacker*/)
{
amount += GetCaster()->GetStat(STAT_STAMINA) * 2;
}
void Register() override
{
OnEffectPeriodic += AuraEffectPeriodicFn(aura_my_shield::HandlePeriodic,
EFFECT_0, SPELL_AURA_PERIODIC_DUMMY);
OnEffectAbsorb += AuraEffectAbsorbFn(aura_my_shield::CalcAbsorb, EFFECT_0);
}
};
开发 Aura 要分清三件事:施加(OnApply,适合初始化)、tick(OnEffectPeriodic,周期行为)、移除(OnRemove,清理与还原)。把本应在 OnRemove 里还原的属性漏掉,是自定义光环最常见的 bug——光环消失后加成仍在。
proc、伤害修改与脚本协作
很多自定义装备或套装效果是”受到/造成伤害时有几率触发”,这走 proc 系统。除了在法术/光环脚本里挂钩,Unit 层还提供伤害修改入口,可在最终伤害落地前统一调整。注意 proc 频率必须受内置概率(PPM)或冷却约束,否则一个高攻速职业会把触发率放大到失控——这是平衡性问题,也是服务端负载问题。
调试与验证方法
# 游戏内 GM 命令
.cast <spellid> # 强制施放
.aura <spellid> # 直接给自己挂光环
.unaura <spellid> # 移除光环
# 观察法术是否被校验拒绝:开启 Spell 日志后复现,
# 日志会打印 cast 失败原因(射程/冷却/免疫等)
排查顺序建议:先确认 DBC 被正确加载(ID、Effect 类型对不对),再用 .cast 排除网络与客户端因素验证服务端效果,最后进入脚本钩子检查是否真的被调用、GetHitUnit 是否为空。很多”脚本没生效”其实是注册宏没加进 AddSC 汇总函数,或 Spell ID 绑错。
小结
AzerothCore 法术开发的完整链路是:Spell.dbc 定义静态数据 → prepare/cast 管线做施放校验 → SpellScript 在效果点改写即时行为 → AuraScript 管理施加/tick/移除的生命周期 → proc 与伤害钩子和装备系统协作。掌握”在最贴近判定的钩子上改数据”这一原则,自定义技能就能既符合引擎设计,又保持可维护性,而不是在外层堆叠补丁。