6

In World of Warcraft, the damage or healing done by a spell varies with your level.

For example, Greater Healing Wave for a level 84 Shaman does 8778-10028 healing:

enter image description here

What i cannot figure out is where Wowhead is getting these numbers from.

There's a javascript slider, that you can adjust and see the base range for all the available levels:

Level  Minimum  Maximum
=====  =======  =======
68     4085     4667
69     4357     4977
70     4639     5299
71     4931     5633
72     5233     5977
73     5546     6334
74     5869     6703
75     6202     7084
76     6546     7478
77     6902     7884
78     7269     8303
79     7647     8735
80     8037     9181
81     8219     9389
82     8404     9600
83     8590     9812
84     8778    10028
85     8969    10245

Where is Wowhead getting these numbers from?

The values defy logic, as you can see when they're plotted:

enter image description here

i'm trying to maintain the HealPoints addon; and i cannot find these values documented anywhere.


Edit: The values do not come from any API. e.g.:

GetSpellInfo("Greater Healing Wave")
  • name: Greater Healing Wave
  • rank:
  • icon: Interface\Icons\Spell_Nature_HealingWaveLesser
  • powerCost: 5201
  • isFunnel: false
  • powerType: 0
  • castingTime: 2500
  • minRange: 0
  • maxRange: 40

Update: Graph of Paladin's Holy Light base healed amounts, by level:

enter image description here

Perhaps the numbers are a function of base mana, which is non-linear? Or intelligence?

4
  • 2
    As far as I know, these numbers are data mined directly from Blizzards spell ID's, and reflect the numbers a player with zero gear and stats would see. How exactly do you feel that they defy logic? Jul 19, 2012 at 1:32
  • The numbers do reflect what a player with zero spell power and bonuses would see. "Defy logic" was a poor choice of words. i should have said "defy logical explanation" i.e. the curve is not linear, exponential, or power. When you begin to plot some healing spells over a range of 70 levels you see they exhibit dramatic non-linearities (typically around level 70 and level 80). And no wow api gives spell damage values.
    – Ian Boyd
    Jul 19, 2012 at 1:40
  • 8
    Those 'dramatic non-linearities' you're talking about coincide with expansions that made substantial changes to gear scaling and base health values. Keep in mind also that these numbers are essentially an arbitrary curve that was applied over what used to be a spell-rank system in which spells incremented dramatically every few levels and not at all between them. There likely is no direct formula, or, if there is one, it's likely heavily meddled with by various hardcoded exceptions to keep up with the scaling changes in each expansions content. Jul 19, 2012 at 1:46
  • 1
    Blizzard's heavy handed approach to balance to results in quite a few oddities for healers at the points where expansions occur. Expect another weird one in a few months once Pandaland hits.
    – kalina
    Jul 19, 2012 at 8:50

3 Answers 3

6

The numbers are likely based on tiered formulas (i.e. from level 1-60, one formula, from level 60-70, another, etc). Considering you are managing an addon, I'd recommend the approach that Wowhead takes. Wowhead doesn't calculate these values on the fly. They save the values on the server-side and reuse them.

Considering Wowhead is a reliable source for in-game data, I'd recommend simply scraping your values from Wowhead and using those.


UPDATED

To expand on the tables in the link, I dug into the Wowhead code just deep enough to determine how some of these values are used for Greater Healing Wave. Note that all of the data and functions used are visible in the $WH JavaScript object.

The link has 2 JSON objects:

  1. The $WH.g_convertScalingSpell.SD object lists scaling values for different distribution types. Each element is an array with 15 values.
  2. The $WH.g_convertScalingSpell.SV object lists scaling values for different levels. Each element is an array with 12 values.

The function where the magic happens is the $WH.g_convertScalingSpell function. Basically, it accepts a level and a scaling type, then uses the tables to match up the two and calculate the final value. A quick glance at the $WH object shows there are a couple more g_convert functions for different values.

While I can't speak on behalf of what all of the values of the scaling tables represent, I can expose the formula used in the $WH.g_convertScalingSpell function.

a = $WH.g_convertScalingSpell.SD[ distribution_type ]
b = $WH.g_convertScalingSpell.SV[ level ]
c = b[a[3] - 1] * (min(level, a[14]) + (a[13] * max(0, level - a[14]))) / level

Then, using c, an object is built out with multiple min, max, and avg values. Only the first min and max values are visible in Greater Healing Wave's tooltip. The value i displayed below is evaluated at 0, 1, and 2. If you are only interested in the first object that is built, you can omit the i entirely, as it's value is 0.

avg = a[4 + i] * c * (a[1] > 0 ? cast / a[1] : 1)
min = round(avg) - floor(avg * a[7 + i] / 2)
max = round(avg) + floor(avg * a[7 + i] / 2)

And if you are curious, the cast value (casting time in milliseconds) comes from this formula, and is only rounded after calculations, prior to displaying the value to the user:

cast = min(a[1], a[1] > 0 && level > 1 ? a[0] + (((level - 1) * (a[1] - a[0])) / (a[2] - 1)) : a[0])

When applying this, you have to know the desired distribution type. This is found in an HTML comment in the tooltip's markup, starting with a question mark. So, to show the example of Greater Healing Wave, the HTML comment is <!--?77472:68:85:85:355:0:1000-->. While I do not know what all of the values are, I know the first few are spellId, minLevel, maxLevel, defaultLevel, and distScale.

Let's do the math:

a = $WH.g_convertScalingSpell.SD[355]
  = [3000, 3000, 20, 7, 9.564, 0, 0, 0.133, 0, 0, 0, 0, 0, 1, 0]

b = $WH.g_convertScalingSpell.SV[85]
  = [1125.23, 1029.49, 1125.23, 1125.23, 945.189, 1125.23, 1004.49, 937.33, 962.336, 0, 986.626, 443]

c = b[a[3] - 1] * (min(level, a[14]) + (a[13] * max(0, level - a[14]))) / level
  = b[7    - 1] * (min(85,    0    ) + (1     * max(0, 85    - 0    ))) / 85
  = b[6]        * (min(85,    0    ) + (1     * max(0, 85           ))) / 85
  = 1004.49 * (min(85, 0) + (1 * max(0, 85))) / 85
  = 1004.49 * (0 + (1 * 85)) / 85
  = 1004.49 * 85 / 85
  = 1004.49

And then the spell values:

cast = min(a[1], a[1] > 0 && level > 1 ? a[0] + (((level - 1) * (a[1] - a[0])) / (a[2] - 1)) : a[0])
     = min(3000, 3000 > 0 && 85    > 1 ? 3000 + (((85    - 1) * (3000 - 3000)) / (20   - 1)) : 3000)
     = min(3000, true && true ? 3000 + ((84 * 0) / (19)) : 3000)
     = min(3000, true ? 3000 + (0 / 19) : 3000)
     = min(3000, 3000 + 0)
     = 3000

avg = a[4 + i] * c * (a[1] > 0 ? cast / a[1] : 1)
    = a[4 + 0] * c * (a[1] > 0 ? 3000 / a[1] : 1)
    = 9.564    * c * (3000 > 0 ? 3000 / 3000 : 1)
    = 9.564    * c * (true ? 1 : 1)
    = 9.564 * 1004.49
    = 9606.942360000001

min = round(avg)        - floor(avg               * a[7 + i] / 2)
    = round(8964.62412) - floor(9606.942360000001 * a[7 + 0] / 2)
    = 9607              - floor(9606.942360000001 * 0.133    / 2)
    = 9607              - floor(1277.7233338800002           / 2)
    = 9607              - floor(638.8616669400001               )
    = 9607 - 638
    = 8969

max = round(avg)        + floor(avg               * a[7 + i] / 2)
    = 9607 + 638
    = 10245

Hope this helps!

5
  • Where the hell did you find that link to numbers from? More!
    – Ian Boyd
    Jul 20, 2012 at 17:59
  • I hope the question edit will suffice. ;)
    – KOVIKO
    Jul 21, 2012 at 6:12
  • i also dug into the code; but couldn't find how they were referencing any script that contains a table of numbers. How did you find it?
    – Ian Boyd
    Jul 21, 2012 at 13:54
  • Inline JavaScript. All of the external JS files are the framework and libraries. They use inline JavaScript to pass the correct values to the framework, then the framework does the rest.
    – KOVIKO
    Jul 21, 2012 at 14:01
  • @David Good catch. I re-read my answer multiple times because I wrote it at like, 2:00AM. I knew there had to be something wrong, but I couldn't put my finger on it.
    – KOVIKO
    Jul 23, 2012 at 12:33
3

These are hardcoded numbers in the game based on blizzard's complicated history of balance. Spells used to have fixed base damage/healing per rank, but when spell ranks were removed the bases were changed to scale by level.

Each expansion has its own powercurve, so you will find sharp differences in ability damage/healing at levels above the highest rank for the spell in the previous expansion. In your HolyLight example, you can see this at level 58 clearly, where levels 59+ use TBC power curve while 0-58 use the original power curve. This was originally to stop you downranking low level spells to save mana costs, so you had to use the higher mana cost new spells rather than the dirt-cheap old spells.

It doesn't have to make any sense, blizzard only balances for the current expansion, and downstream changes to lower levels are essentially not given a thought as long as they don't completely break levelling.

0

Here's the min values, the differences and the differences of the differences:

Min   Diff 2ndDiff
4639
4931  292
5233  302  10
5546  313  11
5869  323  10
6202  333  10
6546  344  11
6902  356  12
7269  367  11
7647  378  11
8037  390  12
8219  182  --
8404  185  3
8590  186  1
8778  188  2
8969  191  3

Since the difference of the difference are (roughly) two constants. You're looking for two quadratic equations (one from 70-80, one from 81-85): a * level^2 + b * level + c = avg.. The roughness of the constants is explained by the double rounding (avg is rounded, then min is calculated from the rounding avg and itself rounded later).


I went through @Koviko's math hoping to discover these quadratic equations, but level doesn't matter at all it seems. Here's where the level matters:

b = $WH.g_convertScalingSpell.SV[85]

Level is used to form the b array, and b[6] is an input to c. I don't know how the b array is formed, so I cannot produce the level equation from the given code. Which is basically what @Koviko said: the level scaling factors are computed server side.


It is likely that cast means "casting time in milliseconds". Some spells (such as fireball) have casting times that increase as levels go up.


Consider using GetSpellDescription and parsing the result. I'm unsure if this function is affected by the UI setting that controls whether a range or average is displayed.

1
  • It is likely that cast means "casting time in milliseconds". - Yes, that is exactly what it is. I'll clarify that. :)
    – KOVIKO
    Jul 23, 2012 at 12:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .