PathOfBuilding Mod 语法完全指南:从mod()函数到 Extra Tags 的源码级解析
【免费下载链接】PathOfBuildingOffline build planner for Path of Exile.项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding
本文基于 docs/modSyntax.md 展开,系统讲解 Path of Building(PoB)中贯穿全代码库的 Mod 修饰符语法。该语法同时存在于物品、天赋树、珠宝、技能等多个数据源中,理解它即可看懂、编写和调试 PoB 中的任何属性修饰。读完本文,你将掌握
mod()的七个参数、六大 ModType 的语义、位运算标志(ModFlag / KeywordFlag)的组合规则,以及全部 Extra Tag 类型的参数用法,并能结合源码定位具体实现。
语法总览:一段 Mod 的完整生命周期
PoB 的核心工作是把"游戏内的一条词缀文本"翻译成"内部可计算的修饰符结构"。整个代码库中大量使用同一种 Mod 语法,但绝大多数 Mod 集中在两个位置:
- 物品与通用修饰:解析逻辑在 src/Modules/ModParser.lua;
- 技能数据:映射表在 src/Data/SkillStatMap.lua。
在 ModParser 中,一个 Mod 的标准格式是:
mod(ModName, ModType, Value, source, modFlags, keywordFlags, extraTags)对应的函数声明位于 src/Modules/ModTools.lua#L20-L46,即modLib.createMod。从源码的@overload注解可以看到,source、modFlags、keywordFlags三个参数会根据传入值的类型被自动识别:第一个字符串参数被当作source,后续的数字参数依次被当作flags和keywordFlags,剩余参数全部并入 Extra Tags(见 ModTools.lua#L33-L67 的实现)。这意味着你完全可以省略其中的若干参数,让 Lua 的类型判断替你填充。
对于技能而言,格式被简化了。makeSkillMod(src/Modules/Data.lua#L52-L67)去掉了source参数,因为在processMod(src/Modules/Data.lua#L68-L72)中会自动以mod.source = grantedEffect.modSource的方式从宝石数据补上来源。
ModName:全局索引键
ModName是整个 Mod 的"键",用于在 PoB 内部引用这条 Mod。它的取值可以是任意字符串,但实践中应当复用代码库中已有的名称,例如:
"Damage"—— 伤害"Life"—— 生命"PhysicalDamageGainAsLightning"—— 物理伤害转化为闪电伤害
编写新 Mod 前,先搜索代码库中是否已存在你要用的名字,避免重复造轮子。
ModType:六种计算语义
ModType决定数值如何参与结算,是 Mod 语法中最关键的部分:
| 类型 | 语义 | 说明 |
|---|---|---|
"BASE" | 基础值 | 与其他基础值相加(如附加伤害、平坦生命、平坦闪避) |
"INC" | 增/减 | 加法叠加;负值表示"reduced"(减少) |
"MORE" | 更多/更少 | 乘法叠加;负值表示"less"(更少) |
"OVERRIDE" | 覆盖 | 忽略其他任何计算,直接采用该值(例如 Loreweave 的"你的抗性为 78%") |
"FLAG" | 条件开关 | 值为true/false,用于表达条件是否成立 |
"MAX"/"MIN" | 取最大/最小 | 多个来源同时存在时,只取最高值或最低值生效 |
"MAX"/"MIN"的典型例子是"ImprovedMinionDamageAppliesToPlayer"("召唤物伤害的增/减按 X% 作用于你")以及"PoisonStackLimit"("无法对已叠加至少 X 层中毒的敌人施毒")。
有趣的是,ModTools 的@alias NumericModTypes(ModTools.lua#L23)还额外列出了"CHANCE"、"DUMMY"等内部类型,说明数值类型家族比文档列举的更广,但对外作者通常只用上述几种核心语义。
关于 FLAG 的便捷函数:当你要使用"FLAG"类型时,官方建议优先使用flag()函数,它缩短代码并让意图更清晰。例如:
-- 写法一:完整 mod() 调用 mod("ZealotsOath", "FLAG", true, { type = "Condition", var = "UsingFlask" }) -- 写法二:flag() 便捷函数,效果完全相同 flag("ZealotsOath", { type = "Condition", var = "UsingFlask" })在技能映射表 SkillStatMap.lua 中,flag()被大量使用,例如flag("CannotMultiplePoison")、flag("BloodMagicReserved")、flag("Condition:NonPoisonedOnly")等。
Value:数值(或 nil)
Value是 Mod 的原始数值。在技能映射表中,这个值通常是nil,因为实际数值会根据宝石等级从 gem 数据中动态提取。例如 SkillStatMap.lua#L491-L497 中的吸血类词缀全部写成mod("Damage", "INC", nil, 0, 0, { type = "Condition", var = "LeechingLife" }),数值留空,交给引擎在运行时计算。
Source:Mod 的来源
Source记录这条 Mod 从何而来。绝大多数情况下它会由系统自动填充——来自天赋节点、宝石或物品。如果你确实需要手动指定,它是一个字符串,且可以使用特殊值"Tree:[nodeId]",这样在悬停时会在工具提示中显示天赋树的局部插图。
ModFlags:位运算标志("能作用于什么")
ModFlags是位标志(bitwise flags),描述这条 Mod 可以作用于哪些对象。完整列表见 src/Data/Global.lua#L102-L133 中的ModFlag表,其组织方式非常清晰:
- 伤害模式:
Attack(0x1)、Spell(0x2)、Hit(0x4)、Dot(0x8)、Cast(0x10); - 伤害来源:
Melee(0x100)、Area(0x200)、Projectile(0x400)、Ailment(0x800); - 武器类型:
Axe(0x10000)、Bow、Claw、Dagger、Mace、Staff、Sword、Wand、Unarmed、Fishing; - 武器类别:
WeaponMelee、WeaponRanged、Weapon1H、Weapon2H及掩码WeaponMask(0x2FFF0000)。
组合多个标志时使用bit.bor(ModParser.lua 中直接别名成了bor)。当多个 ModFlag 组合时,要求全部匹配(AND 逻辑);如果只需要其中一个匹配,应改用 Extra Tag 中的"ModFlagOr"。
KeywordFlags:关键字位标志("与技能关键字匹配")
KeywordFlags与 ModFlag 类似,但使用的是 Global.lua#L135-L171 中的KeywordFlag组,通常基于宝石自身的技能关键字(Aura、Curse、Warcry、Movement、Physical、Fire、Cold、Lightning、Chaos、Vaal、Bow、Trap、Mine、Totem、Minion、Attack、Spell、Hit、Brand、Poison、Bleed、Ignite 以及各类 DoT 标志)。
关键差异在于匹配逻辑相反:组合多个 KeywordFlag 时,只要其中一个匹配即可(OR 逻辑);如果需要全部匹配,使用 Extra Tag 中的"KeywordFlagAnd"。
实际上,Global.lua 在源码层面还内置了KeywordFlag.MatchAll(0x40000000) 标志与MatchKeywordFlags缓存函数(Global.lua#L189-L214),配合两级数字缓存对高频比较做了性能优化——这印证了关键字匹配是 PoB 结算热路径上的重要操作。
Extra Tags:条件与高级行为
Extra Tags 往往决定"这条 Mod 在什么条件下才生效""要不要按某个属性重复计算"等。语法高度依赖第一个参数type,数量不限,可以多个同时叠加。部分参数(如actor、neg)可用于所有类型。以下是全部类型详解:
Condition —— 玩家条件
要求玩家满足特定条件后 Mod 才生效(例如 CritRecently、Shocked)。
var:条件名称;neg(默认false):布尔值,取反条件。
要设置条件本身,请用"Condition:[name]"作为 FLAG Mod。例如:
mod("Damage", "INC", 10, { type = "Condition", var = "LeechingLife" })ActorCondition —— 敌人或召唤物条件
作用于敌人或召唤物上的条件,比 Condition 多了actor参数。
var:条件名称;neg(默认false):布尔取反;actor:取值"enemy"或"parent"。"parent"用于给召唤物赋予基于玩家(其控制者)条件的 Mod。
官方示例——当玩家拥有物理魔像时,魔像获得伤害增幅:
mod("MinionModifier", "LIST", { mod = mod("Damage", "INC", num, { type = "ActorCondition", actor = "parent", var = "HavePhysicalGolem" }) }, { type = "SkillType", skillType = SkillType.Golem })Multiplier —— 按变量成倍放大
将 Mod 乘以某个变量(通常来自充能球、尸体数等)。
var:要乘的 Mod 名;limit:Mod 能达到的最大值;limitTotal(默认false):布尔值,改变limit的行为使其在乘法之后再应用;globalLimit:即使多个来源叠加,Mod 全局能达到的最大值,适合"最多不超过……"的词缀;globalLimitKey:全局上限的字符串标识,相同 key 的 Mod 无法突破 globalLimit。
SkillStatMap.lua#L638 的示例:
mod("Damage", "INC", nil, 0, 0, { type = "Multiplier", var = "EnduranceCharge" })MultiplierThreshold —— 阈值型条件
与条件类似,但只有变量超过指定阈值后才生效。
var:Mod 名;threshold:需要达到的数值。
PerStat —— 按角色属性计算
与 Multiplier 类似,但乘的是角色属性(力量、敏捷、智慧等),而非任意计数。
stat:要乘的属性名;div(默认 1):计算后除以该数并向下取整,适合"每 5 点力量"这类词缀。
示例(SkillStatMap.lua#L644-L647):
mod("Damage", "INC", nil, ModFlag.Attack, 0, { type = "PerStat", stat = "Armour", div = 450 }) mod("Damage", "INC", nil, ModFlag.Attack, 0, { type = "PerStat", stat = "Evasion", div = 450 })StatThreshold —— 属性阈值
与 MultiplierThreshold 类似,但基于属性值。
stat:属性名;threshold:生效前需达到的数值。
PercentStat —— 基于其他属性的百分比
用于"以某属性的百分比计算"的 Mod(如 Agnostic)。
stat:属性名;percent:百分比数值。
SkillType —— 按技能类型生效
作用于所有指定类型的技能。
skillType:Global.lua 中的枚举值(如SkillType.Golem)。
SkillName —— 按技能名生效
与 SkillType 类似,但指定技能名称,通常用于附魔(enchantments)。
skillName:技能的英文名,例如"Decoy Totem"。
GlobalEffect —— 全局效果(增益/诅咒)
主要用于即使不是主技能也能影响其他角色的 Buff 和 Curse。
effectType:取值"Guard"、"Buff"、"Debuff"、"Aura"、"AuraDebuff"、"Curse",分别作用于:你 / 你 / 敌人 / 你+召唤物 / 敌人 / 敌人;effectName:字符串,标明全局效果来自哪里;effectEnemyCond:指定条件,满足时 Mod 作用于敌人;effectStackVar:将该 Mod 乘以一个变量(通常是另一个 Mod);modCond:当角色具有该条件时应用 Mod;unscalable:布尔值,决定该增益是否可被"增益效果(buff effect)"缩放。
在processMod中,凡是带GlobalEffect标签的 Mod 会被标记grantedEffect.hasGlobalEffect = true(Data.lua#L74-L79),这决定了该技能是否需要在全局效果池中被追踪。
DistanceRamp —— 距离渐变(稀有类型)
用于随角色距离不同而行为变化的技能/效果。
ramp:不同距离对应的乘数列表。例如ramp = {{35,0},{70,1}}表示 35 单位距离时无效果,70 单位时达到满值,中间线性过渡。
ModFlagOr —— ModFlag 或匹配
当只需要一个 ModFlag 匹配时使用。官方示例:
["with axes or swords"] = { flags = ModFlag.Hit, tag = { type = "ModFlagOr", modFlags = bor(ModFlag.Axe, ModFlag.Sword) } }上例中要求必须有Hit,但武器可以是斧或剑中的任意一个。
modFlags:像正常添加 ModFlag 一样使用bor。
KeywordFlagAnd —— KeywordFlag 与匹配
当需要全部 KeywordFlag 都匹配时使用。
keywordFlags:像正常添加 KeywordFlag 一样使用bor。
从文本到结构:ModParser 如何解析游戏词缀
理解了内部结构后,再回头看 ModParser 的前端解析逻辑会更有全局感。formList(ModParser.lua#L72-L158)用一组正则把游戏原文词缀翻译成内部类型:
["^(%d+)%% increased"] = "INC", ["^(%d+)%% reduced"] = "RED", ["^(%d+)%% more"] = "MORE", ["^(%d+)%% less"] = "LESS", ["^([%+%-][%d%.]+)%%?"] = "BASE", ["^you have "] = "FLAG", ["is (%-?%d+)%%? "] = "OVERRIDE",也就是说,"12% increased Damage" 会先被识别为INC类,再查表映射到"Damage"这个 ModName,最终拼装成mod("Damage", "INC", 12, ...)结构。解析入口modParser.parseMod(ModParser.lua#L7000-L7019)带有内存缓存,并对无法识别的词缀形态进行去重统计,便于开发者发现未知词缀。
实战:如何正确书写一条 Mod
结合以上全部内容,一条合格的 Mod 应当依次确认:
- ModName:先在代码库中检索是否已有同名 Mod,优先复用;
- ModType:根据计算语义选择 BASE / INC / MORE / OVERRIDE / FLAG / MAX / MIN;
- Value:固定数值直接填;技能映射场景填
nil; - Source:通常省略,交给系统;需要手动指定时用字符串,天赋节点用
"Tree:[nodeId]"; - ModFlags:用
bor()组合,全部匹配;只需其一则改ModFlagOr; - KeywordFlags:同样用
bor()组合,任一匹配;需全部匹配则改KeywordFlagAnd; - Extra Tags:按需叠加,
type决定语义,其余参数随类型而定。
例如一条"每 450 点护甲使攻击伤害提高 1%"的 Mod 写作:
mod("Damage", "INC", nil, ModFlag.Attack, 0, { type = "PerStat", stat = "Armour", div = 450 })它同时展示了三个要点:技能场景数值为nil、用ModFlag.Attack限定仅作用于攻击、用PerStat表达按护甲缩放。对照 src/Data/SkillStatMap.lua、src/Data/Global.lua 与 src/Modules/ModParser.lua 即可逐条验证每部分语义,这套语法也是理解 PoB 全部数据文件(物品、天赋、珠宝、技能)的通用钥匙。
【免费下载链接】PathOfBuildingOffline build planner for Path of Exile.项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考