简介
在此文档中,我将会(尝试)解释 Dogma 系统是如何在 EVE Online 的装配计算中工作的。我会使用装配计算引擎 EOS 来引用 CCP 没有发布的东西,也就是 operandID
和它的含义以及运算符。这些引用可以在 EOS 项目中作为常量找到: EVE 和 EOS 常量。这些常量可能会也可能不会包含在以后的文档中。
另外,为简单起见,我可能会以 attrID:attrName
的形式来引用属性。
最后,我制作了一个简单的GUI,在了解 Dogma 表达式时可能会有所帮助。 屏幕快照将在本文档中使用,以在视觉上帮助理解表达式树。要找到此程序的发布,请参阅 exp-gui。
什么是 Dogma ?
Dogma 是管理 EVE 中各种项目的所有属性和修改效果的核心系统。在解释 Dogma 的工作原理之前,我们需要首先了解项目和效果。项目是 EVE 的基础——游戏中的任何东西都是一个项目。你装在船上的装备,载入到你武器里的弹药,还有你的舰船本身。项目也可以不是一个实际的实体,比如星系范围的虫洞天象效果或者是战术驱逐舰的模式。EVE 中的任何东西都是有某种类型的属性附加于其上的项目,而它被定义在 invTypes
表中。
但是拥有一堆具有属性的项目是没有用的,它们无法相互影响。这时,效果出现了。效果是项目的基础,它使得项目能够实际上做某些事情。假设你给你的舰船安装了一个停滞缠绕光束。当它激活的时候,它会将它的 decreaseTargetSpeed
效果附加到当前锁定的目标。当它被超载的时候,它会将它的 overloadSelfRangeBonus
效果附加给自身。效果与项目之间有多对多的关系,每个效果可能附着在许多不同的项目上,每个项目可能有许多不同的效果。 效果都定义在 dgmEffects
表中,它和项目的关系被定义在 dgmTypeEffects
表中。
每个效果(通常)都有一个或多个修改器,最终用于计算。
那么我们现在知道了什么是项目和效果,我们如何将一个项目的效果应用到另一个项目上呢?我们必须创造一个修改器,一般每个效果都会有一个或多个。要做到这一点,我们需要知道一些东西(为了简单起见,有几点被省略了):
- 源属性
- 目标(或者位置)
- 目标属性
- 操作 / 修改器
- 过滤器
举个例子,我们有一个停滞缠绕光束 II,现在将它应用到我们的目标上。这个网子装备它自己有一个属性决定了它的惩罚方式,而这个属性是 20:speedFactor
,它的数值是 -60.0
。这就是我们的源属性(源属性总是附加在调用相关效果的项目上,在本例中是停滞缠绕光束 II)。目标属性是目标 CurrentTarget
(这是CCP对当前锁定和选择的目标的定义) 的 37:maxVelocity
。这里最好注意的是,如果我们修改的属性不是在舰船本身,而是在另一个项目上,这将指的是该项目可能所在的位置(例如:当前舰船、当前目标、帮派成员等)。修改器是 PostPercent
,它基本上告诉我们将源属性按百分比应用于目标属性。
最后一点,过滤器,并不适用于我们的具体例子,但在很多其他效果中都会用到。用于根据项目的组别或项目所需的技能,将效果限制在特定的项目组别。举个例子, durationBonusForGroupAfterburner
效果被用于降低加力燃烧器和微型跃迁推进器的持续时间(被引擎热能防护装置改装件使用)。它并不会被应用于我们所有的装备(这不合常理),我们需要过滤出我们的装备中属于“加力燃烧器”组的装备,也就是所有的加力燃烧器和微型跃迁推进器。
我们目前锁定并选择了一个敌方 Retribution,它的最大速度为348米/秒。我们转换我们的源属性 -60.0
到一个百分比修改器(定义于 PostPercent = val / 100 + 1
)[1](javascript:void(0)) 然后我们得到了 0.4
。这时我们将它乘到目标属性,得到的最终数值是 348 * 0.4 = 139.2m/s
。这就是效果的工作机制。
所以现在你知道你应该找什么了。你怎么得到它?
简单的办法:modifierInfo
注意:需要编写更多关于各种运算符和功能的文档。
CCP 最近开始向新的方向修改他们的效果,把修改器数据存储在效果的 modifierInfo
字段中,而不是放在表达式树中.这是一个 YAML 格式的字段,一个典型的看起来就像这样(来自 shipModeMaxTargetRangePostDiv
效果):
- domain: shipID
func: ItemModifier
modifiedAttributeID: 76
modifyingAttributeID: 1991
operator: 5
从中你可以很容易的看出涉及到的属性。operator
的值是 5,这被定义为是 PostDiv 运算符。 func
决定了我们进行的修改的类型——这是一个简单的从属性到属性的直接修改(就像 ItemModifier
),或者这有一个过滤器需要被应用(就像 LocationGroupModifier
)。在这种情况下,没有过滤器被应用。 domain
告诉了我们什么是我们的目标,在这个情况下它是我们自己的舰船。请注意, modifierInfo
相对来说是比较新的,而且种类还不多。举个例子, 据我所知,没有任何投射效果(那些投射到其他舰船和角色上的效果)使用 modifierInfo
格式,因此将来可能会改变。
传统的办法:dgmExpressions
CCP最初定义效果的方式是使用表达式。每个效果包含对表达式的两个引用: preExpression
和 postExpression
。preExpression
被用于一个效果被应用的时候(例如:一个装备被激活时),而 postExpression
一般是它的镜像,被用于那个效果被移除的时候。表达式存储在 dgmExpressions
表中,而且有一个树状数据结构,每个表达式都可以由其他表达式组成;最终目标是遍历此树并提取产生可操作的修改器所需的信息。
这是一个典型的 dgmExpressions 条目。
"expressionGroupID": null,
"expressionAttributeID": null,
"description": null,
"expressionValue": null,
"arg1": 3487,
"arg2": 105,
"expressionName": "((CurrentTarget->maxVelocity).(PostPercent)).AddItemModifier (speedFactor)",
"operandID": 6,
"expressionID": 3489,
"expressionTypeID": null
这里需要注意的重要内容是 arg1
, arg2
,和 operandID
。operandID
是精确定义 arg1
和 arg2
实际上做什么的值。通常,它们是对其他表达式的引用(如本例所示),但它们也可以用于比较结构(例如: operandID = 38
表示它在以下比较中使用参数值: arg1 > arg2
)或将多个表达式拼接在一起以形成多个修改器 (operandID = 17
)。 operandID
在确定您是否有可用数据时也很有用。举个例子,operandID = 22
将该表达式定义为指向属性,并且将会在 expressionAttributeID
字段中找到属性的ID。另一个例子,operandID = 24
告诉我们当前表达式定义了在 expressionValue
字段中找到的目标。
不幸的是,CCP不会发布有关什么操作数做什么的信息,但是您可以从客户端缓存中获取此数据。您还可以参考玩家制作的文档,例如我在本文开头引用的EOS EVE常量,可能会更有用/更有意义。
因此,既然您知道三个主要字段在做什么,其他字段又如何呢? 所有的 expression*ID
字段都具有可在最终修改器中使用的信息,无论它是一个属性,目标,还是过滤器(例如,某些效果被定义为仅应用于属于特定组的项目)。
expressionName
基本上是遍历表达式树时可能会发现的内容的概述。它是附加到当前表达式的所有其他表达式的汇编。
遍历表达式树:停滞缠绕光束 II
让我们回到我们的 decreaseTargetSpeed
效果,其 preExpression
为 3489
, postExpression
为 3491
。因为它们实际上是相同的,现在我们将注意力集中到 preExpression
。我们将其称为我们的根树。在大多数表达式的根树下,参数将始终引用其他表达式——同样,您将需要检查 operandID
来确定它。如果这是一个可操作的操作数(一个开始定义修改器而不是逻辑构造的操作数),则 arg2
始终将是一个定义源属性的表达式,而 arg1
最终将定义其他所有内容。
我们首先加载两个参数,然后查看获得的数据:
我们可以看到 root.arg2.operandID = 22
,它表示此表达式定义了一个属性。你可以看到属性的ID是 20
而且它的名字是 speedFactor
。重复,一个可操作的根树中的 arg2
总是定义了源属性,因此我们已经有了一条关键属性。
剩下的 arg1
定义了其他所有内容。 root.arg1.operandID = 31
将该表达式指定为连接运算符和目标定义的表达式。作为联接表达式,我们可以将它们的值用作expressionID
,以将更多表达式安全地加载到 arg1
和 arg2
中。加载这些数据显示:
在上图中,我折叠了 root.arg2
并且同时展开了 root.arg1
的两个参数。要非常小心——很容易迷失在树上。注意 root.arg1.arg1.operandID = 21
,它在 expressionValue
字段中定义效果的运算符;在这个例子中是 PostPercent
。因此,现在我们既有源属性又有效果的运算符。
root.arg1.arg2.operandID = 12
,它将表达式定义为连接目标和目标项目——我们需要的最后两个数据点。同样,使用参数中的值作为 expressionID
来加载接下来的两个表达式。
同样,我折叠了已经使用的参数,并展开了两个新参数。 随即您会看到树到此结束,因为没有更多参数了。
root.arg1.arg2.arg1.operandID = 24
,它将表达式定义为在 expressionValue
中承载目标信息,在本例中为 Target
。这是 CCP 对当前锁定和选定目标的定义。现在剩下的唯一的就是我们在修改的目标属性了…
root.arg1.arg2.arg2.operandID = 22
将表达式定义为一个属性,它的ID在 expressionAttributeID
中定义,在这个例子中是 37:maxVelocity
。您可能想知道我们如何知道这是目标属性,而不是表达式中可能使用的另一个属性。 答案是父表达式,其操作数将其定义为连接目标和目标属性。
就是这样! 我们遍历了 preExpression
树并编译了所需信息的列表:
- 源属性:
20:speedFactor
- 目标:
Target
- 目标属性:
37:maxVelocity
- 操作 / 修改器:
PostPercent
遍历表达式树:护盾回充增量放大器
这个示例将利用 shieldBoostAmplifier
效果来演示过滤器,并希望阐明“可操作的”表达式的含义,因为前面的示例有点模糊。
从这里我们可以看到 root.operandID = 17
,它被定义为一个拼接。一个拼接将多个修改器简单的链接在一起;一种使用一个效果表达不同的修改器的方式。当您想为一个效果附加多个修改器时,请考虑一种情况。损伤控制装备进入了我的思绪。它有11个拼接表达式,总共12个修改器(每个伤害类型每个血量类型都有一个修改器)。这也不是一个“可操作的”表达式,因为我们尚未收集创建修改器所需的数据。因此, arg2
并不指向源属性,而是指向一个全新的表达式树。arg1
也是如此。尽管参数的使用取决于所使用的操作数,但是在这种特定情况下, arg1
和 arg2
都应当视为根表达式,并以此进行评估。如果我们展开 arg1
,我们会看到 root.arg1.operandIS = 9
,它被定义为使用技能过滤器将某些属性应用于某个项目。
从这里开始,我们与前面的示例相同。 因此,我仅作总结: root.arg1.arg2
将定义源属性。 root.arg1.arg1
展开以定义操作(root.arg1.arg1.arg1
)和目标(root.arg1.arg1.arg2
)。
在目标表达式中,root.arg1.arg1.arg2.arg2
定义目标属性,而 root.arg1.arg1.arg2.arg1
定义目标本身。 这是我们获得一些新信息的地方。
您可以看到我们有一个新的操作: root.arg1.arg1.arg2.arg1.operandID = 49
将此表达式定义为加入目标(arg1
)和技能要求定义(arg2
)。这两个定义告诉我们要查看我们自己船上的装备(目标: CurrentShip
),并将效果应用到需要 Capital Shield Operation
技能的装备。
但是,我们都知道 护盾回充增量放大器 II 不仅适用于旗舰级护盾回充增量器。这就是为什么有一个拼接的原因: root.arg2
使用不同的技能定义了相同的修饰符:护盾操作,它将效果应用于游戏中的所有其他护盾回充增量器。
- 修改器1
- 源属性:
548:shieldBoostMultiplier
- 目标:
Ship
- 目标属性:
68:shieldBonus
- 操作 / 修改器:
PostPercent
- 过滤器: 技能 –
21802:Capital Shield Operation
- 源属性:
- 修改器2.
- 源属性:
548:shieldBoostMultiplier
- 目标:
Ship
- 目标属性:
68:shieldBonus
- 操作 / 修改器:
PostPercent
- 过滤器: 技能 –
3416:Shield Operation
- 源属性:
使用修改器数据
您可以从表达式树中提取修饰符信息,然后将其用于 dogma 计算中。 例如,EOS仅在计算中使用编译的修改器信息。 建立修改器数据库后,就再也不会触及表达式树。 最终,由应用程序的开发人员来决定他们希望如何处理信息。
这里只是简单的例子。还有很多比这里讨论的要复杂的效果。重要的是要记住, operandID
是确定表达式要描述的东西的关键。
警告
在构建表达式树时有一些“陷阱”。有一些组过滤器似乎被歪曲了。还有, preExpressions
和postExpressions
并不总是互为镜像,特别是对于有状态装备(修改游戏中舰船状态而不是属性的装备,例如修改你的装甲损坏量而不是你拥有的装甲量的装甲维修器)。
但是,这些示例应该足以启动您的开发,始终欢迎您扩展本文。
资源
- EOS – EVE 常量 很多信息关于
operandID
和效果类别。 - EOS – EOS 常量 EOS 特定的常量,但是,它们为域(效果的目标)之类的事物提供了更多的上下文。
- EOS – 缓存生成器逻辑 这是用于弄清楚如何以编程方式应用此信息的重要资源。 它很复杂,但是很容易理解。
- dgmOperands: 从 EVE 客户端缓存获取的 JSON 文件,定义了
dgmOperands
表。