Monday, May 12, 2014

Theorycrafting, part 1: Spell mechanics


From WoWWiki:

"Theorycraft is the attempt to mathematically analyze game mechanics in order to gain a better understanding of the inner workings of the game."

As it applies to World of Warcraft, theorycrafting usually involves analyzing stats and spell rotations to optimize damage output.  In this article, I'll go over the basics of WoW's spell mechanics in order to lay the foundations for more substantial theorycrafting.

Note: Although I'm starting with the basics, this series of articles will culminate with the creation of a simulation program.  Some knowledge of Java might be helpful, but will not be necessary.


Stats

Hit Chance - Pick a number between 0 and 1.  If that number is greater than hit chance, the spell misses (mana is still deducted).

Spell Power - Spell damage is increased by Spell Power, multiplied by a spell-specific Spell Power Coefficient.  Spells have a damage range (eg. 1374 to 1748 damage).  Because Spell Power is added to damage, rather than multiplied, the range does not increase with Spell Power.
Haste - Haste rating decreases the global cooldown and the cast time of all spells.  Cast time is divided by 1 + haste, so 50% haste equates to 33.3% cast time reduction.  Haste also increases mana regeneration rate.
Critical Strike Chance - After successfully casting a spell, pick a number between 0 and 1.  If that number is lower than critical strike chance, the spell deals double damage.

Stacking effects


It seems obvious that adding 8800 haste rating to 7700 haste rating should give a total of 16500.  Similarly, doubling a critical strike chance of 30% gives 60%.  But what happens when both additive and multiplicative effects are in play?  And what about percent-based increases?  Let's look at an example with haste rating.


I have a haste rating of 8426.  At level 90, 425 points of haste gives 1% bonus haste, so I have 19.83% haste unbuffed.  I keep Frost Armor up, which increases haste by 7%.  Interestingly, this does not bring my haste to 26.83%.  Instead,  there is a multiplicative increase.  My base haste increases cast speed by a factor of 1.1983, and Frost armor increases that by another 1.07, giving 1.2821, or 28.21% bonus haste.  I also have a trinket which has a chance to increase my haste rating by 8800 (20.7%).  This additive effect is factored in before the multiplicative effect, regardless of when it procs.  So my cast speed is increased by (1.1983 + .207)*1.07 = 1.5037, or 50.37%.


From what I've found, additive effects are always considered before multiplicative effects, and percent-based increases can be either additive or multiplicative, depending on the spell.  For example, Frost Armor (+7% haste) is multiplicative, while Molten Armor (+5% critical strike) is additive.


Buffs, and when to apply them


Buffs can be applied immediately after spellcast or upon hitting the target.  While this has a negligible effect on the buff's relative duration, it is important to consider whether a buff is applied to the current spell.  I'll give an example:  my Frostbolt casts can proc Fingers of Frost and my trinket.  Fingers of Frost triggers immediately after the cast time has completed, while the trinket procs only on successful hits (after the flight time).  The pattern here seems to be that buffs that can affect the current spell proc on hit.  Fingers of Frost affects Ice Lance and Deep Freeze, but not Frostbolt, so it can proc immediately on cast.


Simple spellcast

During a spell's lifetime, the following events occur:

1. Incur the GCD
...cast time...
2. Deduct mana
3. Apply initial buffs
...flight time...
4. Hit check
5. Damage target
6&7. Apply final buffs (to self) and debuffs (to target)

Translating to code


Here's a simple spellcast, in pseudo-Java syntax:


//calculate damage

if(Random() > hitChance){
     damage = 0;
}

else{
     apply initialBuffs;
     damage = spCoef*spellPower + minDmg + (maxDmg - minDmg)*Random();
     if(Random() < critChance){
          damage *= 2;
     }
     apply finalBuffs;
     apply debuffs;
}

//calculate time

GCD = max(1, 1.5 / (1 + haste));
time = max(GCD, castTime / (1 + haste));

//calculate mana expenditure

mana -= manaCost;
mana += time*baseCombatRegen*(1 + haste);

Buffs make things complicated (read: possible, but less easy).  Next time, in a more code-heavy manner, we'll look at ways to implement buffs and intelligent prioritizing into a simulation.