Resolving spells and variables in spell texts

The DataDragon provided by Riot Games contains info on all 158 champions. But when looking at the spell texts of these champions, you often come across variables that are seemingly unresolvable (e.g., dealing <physicalDamage>{{ wdamage }} physical damage</physicalDamage>.). Now, if we look at the rest of champion file, there is no other mention of wdamage. This is why in this post we will be using data from the CommunityDragon and as example champion we will use Aatrox (patch 12.2).

Our main two data sources used in this post are:

Finding the correct spell objects

First, we have to find the correct CharacterRecord that contains the paths to the spell objects.  A champion file typically contains multiple character records for different game modes, like URF. We will use the default character record for normal summoner's rift games, which can be found at JSON key Characters/<championName>/CharacterRecords/Root in the champion file. This character root contains a field called: spellNames  which contains all the champion's spells. If you also want the passive spell, you can find it in the mCharacterPassiveSpell field the only difference between the spell names in the passive spell field and the spell names is that the passive spell contains the full path (Characters/Aatrox/Spells/AatroxPassiveAbility/AatroxPassive) and the spell names field contains the following paths: "spellNames":  ["AatroxQAbility/AatroxQ", "AatroxWAbility/AatroxW", "AatroxEAbility/AatroxE", "AatroxRAbility/AatroxR"]. These spell names can be formatted to a full path as follows: Characters/<championName>/Spells/<spellName>. We need these full paths to be able to find the spell's data, as it's located at the JSON value corresponding to the key which is said path.

Spell Maths

To be able to understand the values and ratios spells are comprised of, we must first understand the mathematics. Although a spell object contains way more fields, we will only need the following fields in this post:

  • mEffectAmount(optional): Contains static data that scales per level point put into the spell.
  • mDataValues: Contains static data that scales per level point put into the spell, but these are mostly used by spell calculations, which we will discuss shortly.
  • mSpellCalculations: Contains dynamic game calculations that use a number of variables to determine its value.

Spell Calculations

Before we can continue resolving spell texts, we first need to understand spell calculations. Spell calculations come in 3 different flavours, namely:

  • GameCalculation: A normal game calculation
  • GameCalculationModified: A GameCalculation but as the name suggests it is modified by a certain modifier or: Modified(gameCalc, multiplier) = gameCalc() * mulitplier
  • GameCalculationConditional: A GameCalculation but it contains 3 other game calculations, 1 for calculating which of the other two it should use as value.

In this post we will only discuss a normal game calculation as this is forms a good base. By the time you fully understand a game calculation, you will be able to figure out the other two.

A game calculation contains the following fields:

  • mFormulaParts: The parts of type IGameCalculationPart that comprise the final equation
  • mDisplayAsPercent: If this is set to true, the final value has to be displayed as (v * 100)%
  • mMultiplier(optional): A IGameCalculationPart. If this field is set, the final value is multiplied by the value from this calculation part.

Calculation Parts

There are many calculation parts, of which a large section is known. As we cannot discuss every part, we will discuss the most frequently occurring ones. The other ones can be found worked out here, every file contains a separate calculation part and a method to parse them from JSON files, format the part as string and get their value based on a context containing stats and other values needed to compute some values.

NumberCalculationPart

NumberCalculationPart is the simplest of all calculation parts, as it returns a static 64-bit floating point contained in the mNumber field.

EffectValueCalculationPart

EffectValueCalculationPart returns a value from the  mEffectAmount table based on the mEffectIndex field and the level of the spell (mEffectAmount[mEffectIndex-1][spellLevel]).

NamedDataValueCalculationPart

NamedDataValueCalculationPart returns a value from the  mDataValues table based on the mDataValue field and the level of the spell (mDataValues[mDataValue][spellLevel]).

StatByNamedDataValueCalculationPart

StatByNamedDataValueCalculationPart is very similar to NamedDataValueCalculationPart also using a named data value, but it has 2 additional fields: mStat and mStatFormula. This part calculates its value according to a champion stat in game. A list of all the stats and their respective integer values can be found here. The mStatFormula field is used to determine whether to use the base stat of a champion, the total stat value, or the bonus amount(0 = Base, 1 = Bonus and 2 = Total).

Resolving Spell Texts

To be able to resolve spell texts, we first have to find the correct tooltip text belonging to the spell. The key for this tooltip text can be found at in the client data object of the spell: mClientData→mTooltipData→keyTooltip. In our case for example Spell_AatroxW_Tooltip because the font config uses only lower case keys we first have to convert our key to lower case, so we get spell_aatroxw_tooltip and if we try this key in the font config we indeed find Aatrox fires a chain... now sometimes a key does not exist in the font config for example Spell_VexQ_Tooltip then we can try to find its corresponding hash instead. The hash algorithm used in the font config is XXH64 truncated to 40 bits(a python function for this can be found in the CDTB). So if we try that again with the lower case key, we get the hash 70ad6d3fc2 we can now format enclose this hash in brackets and look for the key {70ad6d3fc2} in the font config we find Vex launches a wave of mist....

Lets take our example tooltip from Aatrox's W:
Aatrox fires a chain, <status>Slowing</status> the first enemy hit by @WSlowPercentage*-100@% for @WSlowDuration@ seconds and dealing <physicalDamage>@WDamage@ physical damage</physicalDamage>. Champions and large jungle monsters have @WSlowDuration@ seconds to leave the impact area or be <status>Pulled</status> back to the center and damaged again for the same amount.
We see a couple things here namely the variables in between two @ characters. In this case these variables are either named data values or spell calculations. If we look at WSlowPercentage we can find it in the data values table with the following values [-0.25,  -0.25,  -0.25,  -0.25,  -0.25,  -0.25,  -0.25] we can also see a * followed by -100 meaning we have to multiple the value from WSlowPercentage by -100 resulting in 25% accros all levels. As for WDamage we can find this in the spell calculations with two parts a NamedDataValueCalculationPart with WBaseDamage and a StatByNamedDataValueCalculationPart with WTotalADRatio and mStat: 2. The calculation will be as follows: 30/40/50/60/70 + 0.4 * AttackDamage. The first values are the values from WBaseDamage now if we look at that field we will see seven values instead of 5 but as a ability can only be used from level 1 and not level 0 we can ignore the first value then the last value can also be ignored because the spell has a max level of 5. There are also cases where a variable will be in a form conforming to @Effect<number>Amount@(e.g., @Effect2Amount@) this means we have to get the corresponding value from the mEffectAmount table based on that number and the level of the spell (mEffectAmount[<number>-1][spellLevel]).

Sometimes it can occur that we cannot cannot find a certain variable be it a game calculation or a datavalue. This issue is similar to what was described earlier with the config but for this we have to use the FNV-1A32 hashing algorithm (a python function for this can be found in the CDTB). When you get the lower case name of the variable and get its hash you can enclose it in brackets like previously mentioned and then try finding it in the data values or spell callculations.

Useful sources: