news 2026/10/15 20:20:55

Path of Building 修改符计算引擎拆解:Mod 模型、ModDB 求值与缓存如何协作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Path of Building 修改符计算引擎拆解:Mod 模型、ModDB 求值与缓存如何协作

Path of Building 修改符计算引擎拆解:Mod 模型、ModDB 求值与缓存如何协作

【免费下载链接】PathOfBuildingOffline build planner for Path of Exile.项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding

Path of Building 是《流放之路》的离线构建规划器,它的核心不是一张树图,而是一台计算引擎:被动树节点、装备词条、技能宝石、光环与诅咒全部被归一成同一种 Mod 结构,由 ModDB 按类型求值,最终产出 DPS 与防御面板。生成文件 src/Data/ModCache.lua 有 23215 行,正是这台引擎把"解析"从运行时热路径上挪走的证据。本文带你沿一条词条走完从游戏文本到面板数字的链路,并逐组件拆解 src/Modules/ModParser.lua、src/Classes/ModDB.lua、src/Modules/CalcPerform.lua 各自的红线与协作方式。

为什么不能为每条词条写一个 if?

朴素做法有三条,都走不通。第一条,给每个技能写 if/else 伤害分支——但 PoE 每赛季都引入新机制,3.29 "Allflame" 一个赛季就带来了 Pacts、Vestigial、Intangibility 解析、Abyss 永恒宝石等一整批新词条,分支写法每赛季重写一遍。第二条,每条新词条写一个解析函数——同一措辞会出现在装备、宝石、天赋三处,三份实现必然漂移。第三条,每次启动都重新解析全库——PoB 每次打开都要算满屏技能,逐条跑正则会成为冷启动瓶颈。

PoB 的答案是把问题拆成三段:

统一数据模型——无论词条来自哪里,都归一成mod(ModName, ModType, Value, source, modFlags, keywordFlags, extraTags)这一个结构,ModType 只有 BASE / INC / MORE / OVERRIDE / FLAG(外加 MAX / MIN)几种,求值逻辑只认类型,不认来源。

解析与求值分离——文本→Mod 的解析是纯函数,产物可以缓存;求值只消费 Mod,永远不回头看文本。

解析产物持久化——解析结果被预生成进 src/Data/ModCache.lua,运行时优先查表,正则解析只作未命中的兜底。

组件位置(文件/模块)一句话职责红线职责(绝不做的事)
ModParsersrc/Modules/ModParser.lua把词条文本扫成 Mod 结构绝不参与运行时求值,绝不决定数值
ModCachesrc/Data/ModCache.lua存放"文本→Mod"的预解析结果绝不允许手写,只能由 ModParser 生成
ModDBsrc/Classes/ModDB.lua按名分桶、按 ModType 聚合求值绝不做文本解析,绝不算总 DPS
CalcPerform / CalcOffencesrc/Modules/CalcPerform.lua驱动 hit speed / damage / ailments 各 pass绝不解析词条,绝不在 pass 内偷改全局输出

一条词条如何变成 DPS 面板上的数字?

拿 Solidity 天赋的 "Attack skills deal 20% increased damage while holding a Shield" 走一遍,共七步:

  1. 加载构建:查 ModCache,词条文本命中缓存表;
  2. 未命中才落到 ModParser 的五级扫描,产出mod("Damage","INC",20,…);
  3. 带上 ModFlag 与{ type="Condition", var="UsingShield" }标签;
  4. 挂进该构建的 ModDB,按名字入mods["Damage"]桶;
  5. CalcPerform 先mergeKeystones、初始化 minion 的 modDB、对三套 DB 做applyEnemyModifiers;
  6. CalcOffence 在 damage pass 里以1 + Sum("INC")/100 × More(...)聚合求值,条件为真才计入;
  7. 输出写入output,FullDPS 再按技能逐个重算、汇总各源 DPS。

容易忽略的两个协调细节:

  • ModCache 不是手写的,而是开发模式下Ctrl+F5时由 ModParser 全量重跑生成的产物,提交前必须确认它已同步;
  • 同一套 ModDB 类同时实例化给 player、minion、enemy 三方,但桶与桶之间绝不串用——诅咒打在敌人身上,用的是 enemyDB 的求值结果。

ModParser:扫描、标记、分桶——但绝不算数

职责红线:

  • 绝不参与运行时求值,它的唯一产出是 Mod 结构;
  • 绝不在每次启动时对全库重新解析,热路径永远先查缓存;
  • 绝不手写 ModCache 里的任何一行,那是生成文件。

关键流程:

  1. preFlagList先扫attack skills deal,存下KeywordFlag.Attack。先摘标志而不是最后补,因为标志决定后续所有匹配的 ModFlag 语境。
  2. preSkillNameList看行首是否为技能名,主要用于武器附魔;不是就原样继续。
  3. formList命中^(%d+)%% increased,ModType 定成 INC、数值 20 先存进modValue。先定形式再谈内容,形式错了后面全是错的。
  4. modTagList走两遍,摘出while holding a shield转成 Condition 标签。走两遍是为了让一条词条最多挂两个限制条件。
  5. 剩下的damage由modNameList映射为 Mod 名 "Damage",全行消费完毕。

设计细节:文档原话是——"Since mods in most builds contain just the raw mod text, and are not tied to the underlying mod from GGG's data, we need to keep old mod wordings intact so those keep working on older builds." 白话说:构建文件里存的是词条原文而不是数据 ID,老措辞一旦删掉,老构建就废了,所以解析必须向后兼容,而不是跟着游戏文案"清理"旧词条。兜底的specialModList只留给既不标准又不够多的少数词条,文档明确要求"加新词条先想 flag 和 tag,再考虑进 specialModList"。

ModDB:分桶、聚合、覆盖——但绝不碰文本

职责红线:

  • 绝不做词条文本解析,进来就只能是 Mod 结构;
  • 绝不算总 DPS,它是 actor 级账本,汇总是 CalcOffence 的事;
  • 绝不让子库的桶泄漏到父库,parent 只用于"向上查不到时继续找"。

关键流程:

  1. AddMod把 Mod 塞进self.mods[name]列表。按名分桶后,求值只扫一个桶,而不是全表。
  2. EvalMod/Sum("INC")/More按 ModType 聚合:BASE 直接求和,INC 求和后 +100 变倍率,MORE 连乘——这正是 PoE 游戏内"加法堆、乘法乘"的语义。
  3. ReplaceModInternal与ConvertModInternal在同桶找不到匹配项时沿 parent 链上溯。宝石的"替换某节点词条""把 Str 转成 All"这类语义,本质就是跨桶的移动,向上找让子 DB 能改写父 DB 里的条目。
  4. player、minion、enemy 各持一套实例;minion 初始化时直接按怪物常数表填基础属性,例如基础暴伤倍率来自base_critical_strike_multiplier。

设计细节:minion 的 modDB 不是复制玩家的再打补丁,而是独立建库、按怪物等级查monsterConstants填底。这是刻意的——仆从的生命成长曲线、抗性、暴伤与玩家完全是两套数据,共享一个库会让"玩家的 Onslaught 是否传给骷髅"这类条件判定全部失真。

CalcPerform:合并、初始化、跑 pass——但绝不在 pass 里偷改全局输出

职责红线:

  • 绝不做任何文本解析,它拿到的全部是 ModDB 与技能数据;
  • 绝不在 pass 循环内隐式修改全局output,双手的值必须显式合并;
  • 绝不跨技能复用未标记的缓存。

关键流程:

  1. mergeKeystones合并基石词条,并复制出每个主动技能的skillModList。基石会改写其他节点的词条,必须在任何求值前落库。
  2. 为主技能若有 minion,初始化minion.modDB并createMinionSkills。
  3. 对 player / minion / enemy 三套 DB 依次applyEnemyModifiers。
  4. 进入 src/Modules/CalcOffence.lua 的三组 pass:hit speed 与命中、damage、ailments 与 debuff;攻击技能对主手、副手各跑一遍,非攻击只跑一遍。
  5. 每轮 pass 结束后用combineStat把MainHand/OffHand的结果合回全局output,FullDPS 再按技能 UUID 取缓存或重算。

设计细节:docs/calcOffence.md 原句是——"any value added to output inside of a damage pass may not be added onto the main or global output",白话说:pass 内你写的output.X实际落在actor.output.MainHand.X或OffHand.X,不显式合并就会静默丢失。这个坑被写成文档而不是留在代码注释里,因为它的症状是"数值对不上但没有任何报错",只有文档能拦住下一个踩坑的人。

值得借鉴的细节

  • 📦先定数据模型,再加解析器:所有来源归一成同一种 mod 结构后,求值只认 ModType 不认来源,新词条进来几乎不动计算核心。
  • ⚡把最贵的一步从热路径搬走:23215 行的 ModCache 是预解析产物,运行时只剩"查表未命中才跑正则",冷启动与重算都受益。
  • 🔁缓存键必须包含全部影响因子:calcFullDPS按技能 UUID 取缓存并用limitedSkills防无限递归,注释里写明了原因——缓存键漏掉一个因子,比较结果就是"A 和 A 比",差异全为零。

快速上手

git clone -b dev https://gitcode.com/GitHub_Trending/pa/PathOfBuilding.git # 取开发分支 cd PathOfBuilding && ./runtime/Path{space}of{space}Building.exe # 从仓库运行即自动进入 Dev Mode # 修改 ModParser 后:按 Ctrl + F5 重新生成 ModCache,用 Ctrl + ` 打开控制台核对

改了 src/Modules/ModParser.lua 之后必须重新生成 src/Data/ModCache.lua 并随 PR 一起提交——运行时靠它做缓存命中,漏交意味着你的修复在真实环境里根本不会生效。同理,src/Data下所有带 "This file is automatically generated, do not edit!" 头部的文件都不许手改,要动就去改 src/Export 里的导出脚本再重跑。

延伸阅读:Mod 语法总表见 docs/modSyntax.md,解析管线与兼容性注意事项见 docs/addingMods.md,伤害 pass 的 output 陷阱见 docs/calcOffence.md,开发环境搭建见 CONTRIBUTING.md——四份文档读完,这台计算引擎的每一条红线你都能自己指出来。

【免费下载链接】PathOfBuildingOffline build planner for Path of Exile.项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 20:20:54

WeChatMsg 使用指南:把微信聊天记录导出为 HTML、Word 和 CSV

WeChatMsg 使用指南:把微信聊天记录导出为 HTML、Word 和 CSV 【免费下载链接】llm-graph-builder Neo4j graph construction from unstructured data using LLMs 项目地址: https://gitcode.com/GitHub_Trending/ll/llm-graph-builder 准备换新手机&#xf…

作者头像 李华
网站建设 2026/10/15 20:19:54

基于深度学习的智能仓储异常检测系统实践

引言在全球化与电商经济的推动下,物流行业正面临效率与安全的双重挑战。传统仓储管理依赖人工巡检和简单传感器监控,不仅效率低,而且难以应对复杂的动态环境。堆叠倾斜、高度超标等看似微小的异常,可能引发货物倒塌、设备损坏甚至…

作者头像 李华
网站建设 2026/10/15 20:19:49

MySQL数据可视化核心概念与性能优化实战指南

做数据可视化几年下来,我越来越觉得一个现象很有意思:很多人把可视化当成“画图”,觉得选个好看的图表模板、配个舒服的配色就算完成任务。但真正拖垮项目的,往往不是图表不够炫,而是背后的数据没理顺。尤其是当数据源…

作者头像 李华
网站建设 2026/10/15 20:19:11

板式换热器激光焊接技术

板式换热器作为工业领域的关键传热设备,其制造质量直接影响换热效率与设备寿命。传统焊接工艺如氩弧焊、电阻焊等存在热影响区大、变形率高、焊缝一致性差等问题。近年来,随着激光焊接技术的突破性发展,其在板式换热器制造领域展现出显著优势…

作者头像 李华
网站建设 2026/10/15 20:15:47

T+转换U8+工具:账套凭证数据迁移与辅助核算映射实战

简介:这是一份面向用友软件实施与运维人员的T数据向U8迁移转换工具,主要解决两套系统间基础档案与业务数据难以直接对接的问题,适合具备一定用友产品操作经验、需要完成账套迁移或数据整合的技术人员使用。压缩包共13个文件,约426…

作者头像 李华
网站建设 2026/10/15 20:15:36

无线传播路径建模从入门到实战:自由空间损耗与链路预算全解析

做无线覆盖、物联网部署和系统集成的朋友,大概都经历过这样一个场景:现场信号测了几十轮,数据总比仿真预测低一截,设备商说天线没问题,集成商说现场环境太复杂,最后谁也没给出一个能复用的结论。要走出这种…

作者头像 李华