-
Notifications
You must be signed in to change notification settings - Fork 0
dgmExpressions
In this document, I will try my best to explain how effects are handled at the database level for EVE Online. I will be using EOS constants to reference things that are not published by CCP, namely operandID
s and their meaning, as well as the operators. These references can be found as constants in the EOS project: EVE and EOS constants.
Additionally, I may refer to attributes in the form of attrID:attrName
for simplicity.
Before we explain how expressions work, we need to first understand items and effects. Items are the building blocks of EVE - everything in the game is an item. The modules that you put on your ship, the charges you load into your guns, and your ship itself. Items can also be non-physical entities such as system-wide wormhole effects or the tactical destroyer mode. Everything in EVE that has some sort of attribute is an item, and these are defined in the invTypes
table.
But having a bunch of items with attributes is useless without a way for them to affect one another. Thats where effects come in at. Effects are the building blocks of items and are what actually make an item do something. Suppose you fit a Webifier to your ship. When it's active, it applies its decreaseTargetSpeed
effect to the currently locked target. When it's overloaded, it applies its overloadSelfRangeBonus
effect to itself. Effects have a many-to-many relationship with items, where each effect may have be attached to many different items, and each item may have many different effects. Effects are defined in the dgmEffects
table, whereas the item:effects relationship is defined with the dgmTypeEffects
table.
But how do we know what an effect actually does? If you look at an effect entry in the database, it doesn't tell you much about the modifying information 1. To define what an effect does, CCP uses expressions. Each effect contains two references to expressions: preExpression
and postExpression
. The preExpression
is used when an effect is applied (ie: a module is activated), whereas the postExpression
is its mirror and used when the effect is removed. Expressions are stored in the dgmExpressions
table and have a tree data structure, in which each expression can be made out of other expressions. Traversing this tree and extracting the information needed to produce an actionable effect is the goal of this document.
The expression tree is simply a means to an end, to extract the data necessary to make a modifier. It helps to know what data you are looking for before actually looking for it, and this section should help with that before we dive into extracting the data from the expression tree.
Modifiers gives us the information necessary to apply an effect correctly. To apply an effect correctly, we need to know a few things (for simplicity, there are a couple other points I'm omitting):
- The source attribute
- The target
- The target attribute
- The operation / modifier
For example, we have a Stasis Web II and are applying it to our target. The web module itself has an attribute that determines the penalty it deals, and this attribute is 20:speedFactor
with a value of -60.0
. This is our source attribute (the source attribute is always attached to the item that is calling the effect in question, in this case the Web II). The target attribute is 37:maxVelocity
with the target being CurrentTarget
(which is CCP's definition of the currently locked and selected target). The modifier is PostPercent
, which basically tells us apply our source attribute as a percentage to the target attribute.
We have an enemy Retribution currently locked and selected and it has with a max velocity of 348m/s. We convert our source attribute value, -60.0
, to a percentage modifier (defined with PostPercent = val / 100 + 1
)2 and we get 0.4
. This is then multiplied with the target attribute to get the final value of 348 * 0.4 = 139.2m/s
. And that's how effects work.
We will be revisiting the Stasis Webifier II in our traversal examples section.
Here is a typical entry in 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>
The important things to note here are arg1
, arg2
, and operandID
. The operandID
is the value that defines exactly what arg1
and arg2
is supposed to be or do. Usually, they are references to other expressions (like in this example), but they could also be used in comparison constructs (eg: operandID = 38
means that it uses the argument values in the following comparison: arg1 > arg2
). The operandID
is also useful in determining if you have workable data. For example, operandID = 22
defines this expression as pointing to an attribute, and to find the attribute ID in the expressionAttributeID
field. As another example, operandID = 24
tells us that current expression defines the target which is found in the expressionValue
field.
Unfortunately, CCP does not publish the information on what operand does what. This is when you would reference player-made documentation, such as the EOS EVE constants that I referred at the beginning of the document.
So now that you know what the three main fields do, what about the other ones? All the expression*ID
fields have information that can be used in the final modifier, whether it be an attribute, target, or even a filter (for example, some effects are defined as being applied to only items under a certain group).
expressionName
is basically an overview of what you could expect to find when you traverse the expression tree. It's a compilation of all the other expressions that are attached to the current one.
Let's start back at our decreaseTargetSpeed
effect, which has a preExpression
of 3489
and a postExpression
of 3491
. As they are both practically the same, we'll focus on the preExpression
for now. We will call this our root tree. Under the root tree of any expression, the arguments will always reference other expressions. Additionally, arg2
is always going be an expression that defines the source attribute, while arg1
will eventually define everything else.
We start by loading both arguments and look at the data we obtain:
We can see that root.arg2.operandID = 22
, which designates that this expression is defining an attribute. You can see the attribute ID is 20
and the name is speedFactor
. Again, arg2
of the root tree always defines the source attribute, so already we have 1 piece of critical information.
arg1
is left to define everything else. root.arg1.operandID = 31
designates this expression as one that joins the operator and target definitions. Being a joiner expression, we can safely load more expressions into arg1
and arg2
, using their values as the expressionID
. Loading that data up shows:
In the above image, I collapsed root.arg2
and have expanded both arguments in root.arg1
. Be very careful - it's easy to get lost in the tree. Looking at root.arg1.arg1.operandID = 21
, which defines the operator of the effect in the expressionValue
field; PostPercent
in this case. So now we have both the source attribute and the operator of the effect.
root.arg1.arg2.operandID = 12
, which defines the expression as joining the target and the target item - our last two data points needed for this effect. Again, load the next two expressions using the values in the arguments as the expressionID
Again, I've collapsed the arguments that have already been used, and expanded the two new ones. Immediately you can see that the tree ends here, as there are no more arguments.
root.arg1.arg2.arg1.operandID = 24
which defines the expression as carrying the target information in expressionValue
, in this case Target
. This is CCP's definition of the currently locked and selected target. The only thing that is left is the target attribute that we are modifying...
root.arg1.arg2.arg2.operandID = 22
defines the expression as an attribute, the ID of which is in expressionAttributeID
, in this case 37:maxVelocity
. You may be wondering how we know this is the target attribute and not another attribute that might be used in the expression. The answer to this would be the parent expression, whose operand defines it as joining the target and target attribute.
And that's it! We have traversed the preExpression
tree and compiled a list of the needed information:
- The source attribute:
20:speedFactor
- The target:
Target
- The target attribute:
37:maxVelocity
- The operation / modifier:
PostPercent
One you extract the modifier information from the expression tree, you can use it in your dogma calculations. The difference in preExpression
and postExpression
does not seem to affect any calculations once you have the modifier information, and is probably only used by CCP. From what I've seen, the only difference is the root.operandID
of the postExpression
is defines as the reverse of the root.operandID
of the preExpression
.
EOS, for example, only used the compiled modifier information in its calculations. Once it's made its modifier database, it never touches the expression trees again. Ultimately it's up to the developer of the application to decide what they wish to do with the informaton.
This was a simple example. There are more complicated effects than this; ones that are filtered based on skills, one that are applied to certain groups of items, effects that have different scope (eg: ganglinks). It's important to remember that the operandID
is the key to determining what exactly an expression is trying to portray.
-
EOS - EVE constants Has a lot of information on
operandID
s as well as effect categories - EOS - EOS constants EOS-specific constants, however they provide a little more context for things like domains (the target of the effect).
- EOS - Cache Generator logic This is a great resource for figuring out how this information may be applied programatically. It's complex, but fairly easy to follow.
1 This is not completely the case. With the release of Tactical Destroyers, CCP has begun to implement a new system in which modifier information is stored in the modifierInfo
column of the effect row. This makes it much easier to figure out what an effect does. However, at the time of this writing, this is only used for a very limited selection of effects, and the vast majority of effects still use the legacy Expression method.
2 Arithmetic for the various operations can be found here