接着上次 DBC 文件格式那篇,这次钻进一张具体又特别实用的表:SpellItemEnchantment.dbc。魔兽里凡是叫得上名字的「物品附魔」——磨刀石、武器油、毒药、萨满武器附魔、工程附魔、珠宝宝石属性、附魔专业配方、装备自带的随机词条——全在这张表里。改私服加附魔、写随机附魔脚本、排查宝石激活条件,最后都得回到这张表读记录。
这张表管什么
SpellItemEnchantment.dbc 是所有物品附魔的唯一权威定义表。一条记录 = 一种附魔效果,2656 条记录覆盖了整个 3.3.5a 的附魔体系。
它被三处引用:
- 物品模板
item_template的随机属性RandomProperty/RandomSuffix,以及装备自带的enchantedLocation,都通过附魔 ID 指向这里。 - 法术效果
SPELL_EFFECT_ENCHANT_ITEM/SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC,附魔专业配方卷轴施法时按 ID 施加附魔。 - 服务端运行时
sSpellItemEnchantmentStore.LookupEntry(ID),装备穿上/脱下时查这张表算属性。
所以理解附魔 = 理解这张表的 38 个字段怎么读。
文件头与整体结构
文件格式就是标准的 WDBC(上一篇讲过),三段式:
1 | +----------------------+ |
本表的关键头部数值:
| 字段 | 值 | 说明 |
|---|---|---|
| magic | WDBC |
校验用 |
| RecordCount | 2656 | 记录总数 |
| FieldCount | 38 | 每条记录 38 个字段 |
| RecordSize | 152 | = 38 × 4,纯 uint32 表 |
| StringBlockSize | 99756 | 16 种语言的名称都在这里 |
RecordSize == FieldCount × 4,说明这张表没有 1 字节字段,每列都是 4 字节,解析器可以放心按 uint32 切。
读头沿用老套路:
1 | import struct |
38 字段总览
按记录内的下标(uint32 序号)排:
| 下标 | CSV 列名 | 类型 | 说明 |
|---|---|---|---|
| 0 | ID | uint32 | 附魔 ID(主键) |
| 1 | Charges | uint32 | 使用次数(0 = 永久/无限) |
| 2-4 | Effect_1/_2/_3 | uint32 | 3 个效果的类型 |
| 5-7 | EffectPointsMin_1/_2/_3 | uint32 | 3 个效果的最小数值 |
| 8-10 | EffectPointsMax_1/_2/_3 | uint32 | 3 个效果的最大数值(服务端不加载) |
| 11-13 | EffectArg_1/_2/_3 | uint32 | 3 个效果的参数 |
| 14-29 | Name_Lang_xx | string×16 | 16 个语言的名称偏移 |
| 30 | Name_Lang_Mask | uint32 | 名称语言掩码(服务端不加载) |
| 31 | ItemVisual | uint32 | 物品视觉效果 ID |
| 32 | Flags | uint32 | 附魔标志位 |
| 33 | Src_ItemID | uint32 | 源物品 ID(宝石附魔用) |
| 34 | Condition_Id | uint32 | 附魔生效条件 |
| 35 | RequiredSkillID | uint32 | 所需专业/技能 |
| 36 | RequiredSkillRank | uint32 | 所需专业技能等级 |
| 37 | MinLevel | uint32 | 最低角色等级 |
一条记录里真正承载效果语义的只有 4 组:Effect / EffectPointsMin / EffectArg(各 3 个槽),加上一个 Charges。其余字段要么是显示/本地化,要么是使用门槛。把这 4 组读懂,附魔就懂了八成。
服务端怎么读它:format 串
AzerothCore 用 DBCfmt.h 里的 format 字符串描述每列怎么读:
1 | char constexpr SpellItemEnchantmentfmt[] = "niiiiiiixxxiiissssssssssssssssxiiiiiii"; |
逐位拆:
| 位 | 字符 | 对应字段 | 含义 |
|---|---|---|---|
| 0 | n |
ID | 主键,建索引 |
| 1 | i |
charges | Charges |
| 2-4 | iii |
type[3] | Effect 类型 |
| 5-7 | iii |
amount[3] | EffectPointsMin |
| 8-10 | xxx |
— | 跳过 EffectPointsMax |
| 11-13 | iii |
spellid[3] | EffectArg |
| 14-29 | s×16 |
description[16] | 名称(偏移→char*) |
| 30 | x |
— | 跳过 Name_Lang_Mask |
| 31 | i |
aura_id | ItemVisual |
| 32 | i |
slot | Flags |
| 33 | i |
GemID | Src_ItemID |
| 34 | i |
EnchantmentCondition | Condition_Id |
| 35 | i |
requiredSkill | RequiredSkillID |
| 36 | i |
requiredSkillValue | RequiredSkillRank |
| 37 | i |
requiredLevel | MinLevel |
对应 DBCStructure.h 里的结构体:
1 |
|
两个被注释掉的字段是关键坑(见后文)。
字段逐一详解
总览表只给了字段名,真正改附魔、排查问题时要的是每个字段的含义、用途、取值、示例。下面按字段下标逐个拆。
字段 0:ID —— 附魔主键
全表唯一标识符,业务主键。几乎所有附魔逻辑的入口都靠它:
- 物品模板
item_template的随机属性RandomProperty/RandomSuffix、装备自带enchantedLocation通过它引用附魔。 - 法术效果
SPELL_EFFECT_ENCHANT_ITEM/SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC按它施加附魔。 - 服务端
sSpellItemEnchantmentStore.LookupEntry(ID)全局查询。
示例:1 = Rockbiter 3(石化武器 3 级)、7 = Deadly Poison(致命毒药)、2686 = +8 Strength(+8 力量宝石)。ID 不连续,索引表大小通常是 max(id)+1,不是行号。
字段 1:Charges —— 使用次数
「使用型」附魔效果的可触发次数:
0= 永久 / 被动 / 无限(绝大多数附魔)。>0= 触发 N 次后消失(工程附魔、毒药等消耗型)。
示例:1003 Venomhide Poison Charges=15(击中 15 次后消失)。仅对 COMBAT_SPELL / USE_SPELL 类型有意义,属性类附魔恒为 0。
字段 2-4:Effect_1 / _2 / _3 —— 效果类型
3 个独立效果槽的类型,决定同槽 EffectPointsMin 与 EffectArg 的语义。完整 9 种类型枚举与 2656 条记录的分布见后文「Effect 类型」一节。
这里只要记住:一条附魔可以同时挂多种效果,各占一个槽。比如一件装备可以同时「+8 力量」+「击中概率触发暗影箭」,就是 Effect_1=STAT、Effect_2=COMBAT_SPELL 各填一槽。
字段 5-7:EffectPointsMin_1 / _2 / _3 —— 效果数值
对应效果的数值(最小值),语义随 Effect 类型变:
| Effect 类型 | PointsMin 含义 |
|---|---|
| STAT | 属性加成点数(+8 力量 → 8) |
| DAMAGE | 武器伤害增加量(+3 Damage → 3) |
| RESISTANCE | 抗性点数(+8 护甲 → 8) |
| COMBAT_SPELL | 通常 0(由 SpellID 决定) |
| EQUIP_SPELL | 通常 0(法术自身决定) |
| TOTEM | 每秒伤害基数(实际值 = amount × 武器速度) |
可为负数:如 27 Sundered 破甲 PointsMin = -10(减少护甲)。DBC 里存的是 uint32,服务端按 int32 解释,所以自己解析时要用 <i 而不是 <I,否则破甲类附魔会读成一个巨大的正数。
字段 8-10:EffectPointsMax_1 / _2 / _3 —— 效果最大值(服务端不加载)
理论上用于范围随机附魔(min~max),但 AzerothCore 完全不读这三个字段:format 串里是 xxx,结构体里对应成员 amount2 被整段注释掉。服务端实际数值恒等于 EffectPointsMin。
也就是说:想靠改 DBC 实现「随机范围数值附魔」(比如 +5~+10 力量),在 3.3.5 服务端不会生效,得自己写代码支持。这个字段是纯客户端/理论保留。
字段 11-13:EffectArg_1 / _2 / _3 —— 效果参数
效果的具体指向,是整张表最容易踩坑的字段:语义随 Effect 类型变。STAT 时是 ItemModType,RESISTANCE 时是抗性学校,COMBAT_SPELL / EQUIP_SPELL / USE_SPELL 时才是 SpellID。完整对照表与 ItemModType 取值见后文「EffectArg 的多义性」一节。
源码成员名叫 spellid,但只有法术类 Effect 它才是 SpellID——这个名字本身就是个坑。
字段 14-29:Name_Lang_xx —— 16 个语言名称
附魔的显示名称,按 16 个语言槽位存储。记录里存的是字符串块偏移(uint32),0 表示该语言未提供名称,客户端会回退到 enUS。
| 下标 | 语言 | 下标 | 语言 | |
|---|---|---|---|---|
| 14 | enUS | 22 | zhTW | |
| 15 | enGB | 23 | esES | |
| 16 | koKR | 24 | esMX | |
| 17 | frFR | 25 | ruRU | |
| 18 | deDE | 26 | ptPT | |
| 19 | enCN | 27 | ptBR | |
| 20 | zhCN | 28 | itIT | |
| 21 | enTW | 29 | Unk(保留) |
中文端特殊处理(重要):国服 3.3.5 汉化端常把中文字符串实际写进 deDE 槽位(下标 18)而非标准 zhCN(下标 20)。做中英 DBC 合并时(如 merge_spellitemenchantment_dbc.py),脚本只把英文 enUS 名称追加到字符串块末尾、更新 enUS 槽位(下标 14),保留 deDE 槽位的中文字符串不动。处理本地化前务必先确认中文到底落在哪个槽,否则会读到空名或英文名。
字段 30:Name_Lang_Mask —— 名称语言掩码(服务端不加载)
位掩码,标记哪些语言槽位有效(非空)。本表几乎所有记录都是 16712190。服务端不读取(format 里是 x),纯客户端字段,用于决定某语言未提供时回退到哪种语言。改它对服务端逻辑毫无影响。
字段 31:ItemVisual —— 物品视觉效果
CSV 列名 ItemVisual,源码成员 aura_id。指向 ItemVisual.dbc 的 ID,决定附魔后在角色身上的视觉特效(火焰、冰霜、闪电光晕)。
服务端只在构建物品更新数据包 SMSG_UPDATE_OBJECT 时透传给客户端,自身不解释其值:
1 | *data << uint32(enchant ? enchant->aura_id : 0); |
所以改这个字段只影响外观光效,不影响任何数值。示例:Rockbiter=61、Flametongue 3=32(火焰光效)、Reinforced=0(无特效)。
字段 32:Flags —— 附魔标志位
CSV 列名 Flags,源码成员 slot(历史遗留命名,实际语义是 flags 位掩码)。位定义(Item.h):
1 | enum EnchantmentSlotMask |
服务端检查(Item.cpp):
1 | if (enchantEntry->slot & ENCHANTMENT_CAN_SOULBOUND) |
常见取值:0 = 普通附魔无标志;1 = 施加后灵魂绑定(多数消耗型附魔:磨刀石、武器油、毒药)。字段名 slot 极易和装备上的附魔槽位枚举 EnchantmentSlot 混淆,看注释别看名字。
字段 33:Src_ItemID —— 源物品 ID
CSV 列名 Src_ItemID,源码成员 GemID。对宝石附魔(珠宝加工镶嵌)指向宝石的 item_template.entry,服务端用它数「装备上有几颗同类宝石」来判断 meta 宝石激活条件:
1 | uint32 gemid = enchantEntry->GemID; |
非宝石附魔此字段为 0。示例:
| ID | 名称 | Src_ItemID | 含义 |
|---|---|---|---|
| 2686 | +8 Strength | 23233 | 宝石物品 23233 的属性 |
| 2687 | +8 Agility | 23234 | 宝石物品 23234 |
| 2688 | +8 Stamina | 23235 | 宝石物品 23235 |
字段 34:Condition_Id —— 附魔条件
指向 SpellItemEnchantmentCondition.dbc 的 ID,定义该附魔生效所需的复杂条件(如「装备至少 4 颗蓝色宝石」)。条件表支持 5 组比较:颜色、比较运算符、比较颜色、数值、逻辑连接符。0 = 无条件,附魔总是生效。
| ID | 名称 | Condition_Id |
|---|---|---|
| 2689 | +8 mana every 5 sec. | 28 |
| 2704 | +12 Strength if 4 blue gems equipped | 3 |
| 2827 | +14 Crit and 1% Spell Reflect | 64 |
| 2829 | +24 AP and Minor Run Speed Increase | 63 |
meta 宝石「为什么没激活」类问题,最终都要回到这个条件表查。
字段 35:RequiredSkillID —— 所需专业
源码成员 requiredSkill,指向 SkillLine.dbc 的专业技能 ID。服务端校验专业门槛(Item.cpp):
1 | if (enchantEntry->requiredSkill && |
常见值:0 = 无专业要求(普通附魔、宝石属性);164 = 锻造;333 = 附魔;755 = 珠宝加工。高级珠宝专属宝石(如 BoP 橙色宝石)会限 RequiredSkillID=755。
字段 36:RequiredSkillRank —— 所需专业等级
源码成员 requiredSkillValue,与 RequiredSkillID 配合,指定专业的等级下限。取值 1~450(WLK 上限 450)。示例:RequiredSkillID=755, RequiredSkillRank=350 → 需珠宝加工 350 才生效。
字段 37:MinLevel —— 最低角色等级
源码成员 requiredLevel,使用此附魔的物品要求的最低角色等级。附魔可能抬高物品需求等级(Item.cpp):
1 | if (enchantEntry->requiredLevel > level) |
多数记录为 0(无等级加成),高级配方(TBC/WLK 顶级)可能设为 60/70/80。这也是为什么同一件装备打了高级附魔后,小号反而戴不上去。
Effect 类型:附魔效果的分发器
3 个 Effect 槽位的取值来自 DBCEnums.h 的 ItemEnchantmentType 枚举,它决定 EffectPointsMin 和 EffectArg 怎么解释:
1 | enum ItemEnchantmentType |
2656 条记录里 Effect_1 的分布,能直观看出这张表的主体是什么:
1 | 5 STAT 1566 条 ← 绝对主体,属性加成类(含绝大多数宝石) |
属性加成类(STAT)占了近 60%,因为珠宝加工每一颗宝石的属性都是一条独立记录。
EffectArg 的多义性(最关键的坑)
EffectArg(结构体里叫 spellid)名字像法术 ID,但它的语义随 Effect 类型变。脱离 Effect 单看 EffectArg 一定会读错:
| Effect 类型 | EffectArg 含义 | 引用 |
|---|---|---|
COMBAT_SPELL(1) |
触发法术的 SpellID | Spell.dbc |
EQUIP_SPELL(3) |
装备时施加的光环 SpellID | Spell.dbc |
USE_SPELL(7) |
右键使用时施放的 SpellID | Spell.dbc |
RESISTANCE(4) |
抗性学校索引(0=物理护甲…) | SpellSchools 枚举 |
STAT(5) |
属性类型(ItemModType) | ItemModType 枚举 |
DAMAGE(2) |
通常为 0 | — |
TOTEM(6) |
通常为 0 | — |
PRISMATIC_SOCKET(8) |
通常为 0 | — |
STAT 时 EffectArg = ItemModType
最常见的分支,EffectArg 是属性类型枚举(ItemTemplate.h)。节选实战中最常遇到的值:
1 | ITEM_MOD_MANA = 0, // 法力值 |
所以一条「+8 力量宝石」的记录就是:Effect=5(STAT),EffectPointsMin=8,EffectArg=4(ITEM_MOD_STRENGTH)。
一组真实记录对照
从 CSV 抽几条典型记录,把 Effect / PointsMin / EffectArg 串起来看:
| ID | 名称 | Effect | PointsMin | EffectArg | 解读 |
|---|---|---|---|---|---|
| 1 | Rockbiter 3 | 6 (TOTEM) | 6 | 0 | 萨满石化武器,每秒 6 点伤害基数 |
| 2 | Frostbrand 1 | 1 (COMBAT_SPELL) | 0 | 8034 | 击中时施放 Spell 8034 |
| 7 | Deadly Poison | 1 (COMBAT_SPELL) | 30 | 2818 | 30 PPM 触发 Spell 2818 |
| 13 | Sharpened (+3 Dmg) | 2 (DAMAGE) | 3 | 0 | 磨刀石 +3 武器伤害 |
| 15 | Reinforced (+8 Arm) | 4 (RESISTANCE) | 8 | 0 | +8 物理护甲 |
| 24 | +5 Mana | 5 (STAT) | 5 | 0 | +5 法力值(ITEM_MOD_MANA=0) |
| 28 | +4 All Resistances | 3 (EQUIP_SPELL) | 0 | 7438 | 装备时施放 Spell 7438(全抗光环) |
| 2686 | +8 Strength | 5 (STAT) | 8 | 4 | +8 力量(宝石属性) |
| 3319 | (Prismatic Socket) | 8 (PRISMATIC_SOCKET) | 0 | 0 | 添加一个棱彩孔 |
注意 EffectPointsMin 可以是负数——比如破甲类附魔存 -10(DBC 里是 uint32,服务端按 int32 解释)。
服务端怎么应用效果
装备穿上时,PlayerStorage.cpp 的 _ApplyItemMods 遍历 3 个效果槽,按 type 分发:
1 | for (uint8 s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s) |
这就是为什么附魔表用「3 槽 + type 分发」而不是定长字段——一条附魔可以同时给「+8 力量 + 概率触发法术」两种效果,各占一个槽。
常见坑
字段详解里已分散提过,这里收成一张速查表,排查时按图索骥:
- 字段 8-10 EffectPointsMax 服务端用
xxx跳过:改 DBC 想做随机范围附魔不生效,数值恒等于 EffectPointsMin。 - 字段 30 Name_Lang_Mask 服务端用
x跳过:纯客户端字段,决定回退语言。 - 中文端 zhCN 常落在 deDE 槽(下标 18):合并本地化前先确认中文槽位,合并脚本常把
LOCALE_ZHCN硬编码成 4。 - 字段 11-13 EffectArg 必须配合 Effect 解释:Effect=5 是 ItemModType,=4 是抗性学校,=1/3/7 才是 SpellID。
- 字段 31 ItemVisual(aura_id)是纯视觉:改它只影响光效,不影响数值。
- 字段 32 源码名
slot实为 Flags:跟装备EnchantmentSlot枚举是两回事,看注释别看名字。 - EffectPointsMin 可为负:破甲类存负数,解析要用
<i不能用<I,否则读到巨大正数。
Python 解析示例
接上一篇的 parse_dbc 思路,写一个针对附魔表的解析器,把每条记录解成可读的三效果描述:
1 | #!/usr/bin/env python3 |
跑出来大致是:
1 | [ 1] Rockbiter 3 charges=0 | TOTEM +6 arg=0 |
排查「为什么这颗宝石没生效」「这条随机词条给了什么属性」时,比起翻 SQL 表,直接把 .dbc 拉出来跑一遍这个脚本更快。
和随机附魔脚本的关系
仓库里的 Bin/lua_scripts/RandomEnchants.lua(Eluna 脚本)实现随机附魔系统,脚本里那些附魔 ID——比如 {343, 0, 0, "+8 敏捷", "敏捷"} 里的 343——全部指向这张表的 ID 字段。item:SetEnchantment(enchId, slot) 最终也是回到 sSpellItemEnchantmentStore 查记录。
也就是说:Lua 脚本只负责分配和过滤 ID,效果的真实数值/类型永远由 DBC 里的 Effect / EffectPointsMin / EffectArg 决定。加新附魔词条时,先确认对应 ID 在 DBC 里确实存在且语义正确,否则脚本配了也触发不出效果。
小结
- 一条附魔 = 3 个效果槽,每槽由
Effect(类型)+EffectPointsMin(数值)+EffectArg(参数)三字段决定。 EffectArg是不是 SpellID 取决于Effect:STAT 时是 ItemModType,RESISTANCE 时是抗性学校,只有 1/3/7 时才是 SpellID。EffectPointsMax(8-10)和Name_Lang_Mask(30)服务端用xxx/x跳过,纯客户端字段。- 中文端 zhCN 常实际存在 deDE 槽位,做本地化合并前先确认。
- 源码字段名
slot实为 Flags、spellid实为 EffectArg,都是历史遗留,看注释别看名字。
排查顺序:解析 .dbc 确认 ID/Effect/Arg → 对照 ItemModType 翻译属性 → 对照 Spell.dbc 翻译法术 → 最后才去看运行时日志。