// Calculate damage and casting time for a single, direct-damage spell. private void DoMainNuke(DruidTalents talents, CharacterCalculationsMoonkin calcs, ref Spell mainNuke, float spellPower, float spellHit, float spellCrit, float spellHaste) { float latency = calcs.Latency; int naturesGrace = talents.NaturesGrace; int starlightWrath = talents.StarlightWrath; float overallDamageModifier = mainNuke.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier); overallDamageModifier *= mainNuke.School == SpellSchool.Arcane ? (1 + calcs.BasicStats.BonusArcaneDamageMultiplier) : (1 + calcs.BasicStats.BonusNatureDamageMultiplier); overallDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 80); float gcd = 1.5f / (1.0f + spellHaste); float instantCast = (float)Math.Max(gcd, 1.0f) + latency; float ngGCD = (float)Math.Max(gcd / 1.2f, 1.0f); float instantCastNG = ngGCD + latency; mainNuke.CastTime = mainNuke.BaseCastTime - 0.1f * starlightWrath; float totalCritChance = spellCrit + mainNuke.CriticalChanceModifier; float normalCastTime = (float)Math.Max(mainNuke.CastTime / (1 + spellHaste), instantCast); mainNuke.NGCastTime = (float)Math.Max(mainNuke.CastTime / (1 + spellHaste) / (1 + 0.2f * naturesGrace / 3.0f), instantCastNG); float NGProcChance = totalCritChance * naturesGrace / 3.0f; float NGUptime = 1.0f - (float)Math.Pow(1.0f - NGProcChance, Math.Floor(3.0f / mainNuke.NGCastTime) + 1.0f); mainNuke.CastTime = (1 - NGUptime) * normalCastTime + NGUptime * mainNuke.NGCastTime; // Damage calculations float damagePerNormalHit = (mainNuke.BaseDamage + mainNuke.SpellDamageModifier * (spellPower + mainNuke.IdolExtraSpellPower)) * overallDamageModifier; float damagePerCrit = damagePerNormalHit * mainNuke.CriticalDamageModifier * (1 + calcs.BasicStats.MoonkinT10CritDot); mainNuke.DamagePerHit = (totalCritChance * damagePerCrit + (1 - totalCritChance) * damagePerNormalHit) * spellHit; }
private static float DoManaRestoreCalcs(CharacterCalculationsMoonkin calcs, SpellRotation rotation, float hitRate) { float manaFromOther = calcs.BasicStats.ManaRestorePerCast * rotation.CastCount; float manaFromJoW = calcs.BasicStats.ManaRestorePerHit * (hitRate * rotation.CastCount); float manaFromTrinket = 0.0f; // Pendant of the Violet Eye - stacking mp5 buff for 20 sec if (calcs.BasicStats.Mp5OnCastFor20SecOnUse2Min > 0) { float currentTime = 0.0f; float currentMp5 = 21.0f; float timeSinceLastCast = 0.0f; while (currentTime < 20.0f) { manaFromTrinket += currentMp5 / 5.0f * 2.0f; currentTime += 2.0f; timeSinceLastCast += 2.0f; if (timeSinceLastCast >= rotation.Duration / rotation.CastCount) { timeSinceLastCast -= rotation.Duration / rotation.CastCount; currentMp5 += 21.0f; } } manaFromTrinket /= 120.0f; manaFromTrinket *= rotation.Duration; } return(manaFromJoW + manaFromOther + manaFromTrinket); }
public float GetSpellHit(CharacterCalculationsMoonkin calcs) { float baseHit = 1.0f; switch (calcs.TargetLevel) { case 80: baseHit -= 0.04f; break; case 81: baseHit -= 0.05f; break; case 82: baseHit -= 0.06f; break; case 83: baseHit -= 0.17f; break; default: baseHit -= 0.17f; break; } baseHit = (float)Math.Min(1.0f, baseHit + calcs.SpellHit); return(baseHit); }
// Calculate damage and casting time for a single, direct-damage spell. public void DoMainNuke(CharacterCalculationsMoonkin calcs, ref Spell mainNuke, float spellPower, float spellHit, float spellCrit, float spellHaste, float naturesGraceUptime, float latency) { float naturesGraceBonusHaste = 0.15f; float overallDamageModifier = mainNuke.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellDamageMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier); // Add a check for the higher of the two spell schools, as Starsurge always chooses the higher one overallDamageModifier *= mainNuke.School == SpellSchool.Arcane ? (1 + calcs.BasicStats.BonusArcaneDamageMultiplier) : (mainNuke.School == SpellSchool.Nature ? (1 + calcs.BasicStats.BonusNatureDamageMultiplier) : (1 + (calcs.BasicStats.BonusArcaneDamageMultiplier > calcs.BasicStats.BonusNatureDamageMultiplier ? calcs.BasicStats.BonusArcaneDamageMultiplier : calcs.BasicStats.BonusNatureDamageMultiplier))); float gcd = 1.5f / (1.0f + spellHaste); float ngGcd = gcd / (1 + naturesGraceBonusHaste); float instantCast = (float)Math.Max(gcd, 1.0f) + latency; float instantCastNG = (float)Math.Max(ngGcd, 1.0f) + latency; float totalCritChance = spellCrit + mainNuke.CriticalChanceModifier; float baseCastTime = (float)Math.Max(mainNuke.BaseCastTime / (1 + spellHaste), instantCast); float ngCastTime = (float)Math.Max(mainNuke.BaseCastTime / (1 + spellHaste) / (1 + naturesGraceBonusHaste), instantCastNG); mainNuke.CastTime = (1 - naturesGraceUptime) * baseCastTime + naturesGraceUptime * ngCastTime; // Damage calculations float damagePerNormalHit = (mainNuke.BaseDamage + mainNuke.SpellDamageModifier * spellPower) * overallDamageModifier; float damagePerCrit = damagePerNormalHit * mainNuke.CriticalDamageModifier; mainNuke.DamagePerHit = (totalCritChance * damagePerCrit + (1 - totalCritChance) * damagePerNormalHit) * spellHit; mainNuke.AverageEnergy = mainNuke.BaseEnergy; }
// Create proc effect calculations for proc-based trinkets. private void BuildProcList(CharacterCalculationsMoonkin calcs) { // Implement a new handler for each special effect in the calculations stats foreach (SpecialEffect effect in calcs.BasicStats.SpecialEffects()) { procEffects.Add(new ProcEffect(effect)); } }
// Calculate damage and casting time for a damage-over-time effect. private void DoDotSpell(DruidTalents talents, CharacterCalculationsMoonkin calcs, ref Spell dotSpell, float spellPower, float spellHit, float spellCrit, float spellHaste) { if (dotSpell.Name == "MF") { DoMoonfire(talents, calcs, ref dotSpell, spellPower, spellHit, spellCrit, spellHaste); } else { DoInsectSwarm(talents, calcs, ref dotSpell, spellPower, spellHit, spellCrit, spellHaste); } }
public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem) { CharacterCalculationsMoonkin calcs = new CharacterCalculationsMoonkin(); Stats stats = GetCharacterStats(character, additionalItem); calcs.BasicStats = stats; float hitRatingMultiplier = 1.0f / CalculationsMoonkin.hitRatingConversionFactor; float critRatingMultiplier = 1.0f / CalculationsMoonkin.critRatingConversionFactor; calcs.SpellCrit = stats.SpellCritRating * critRatingMultiplier; calcs.SpellHit = stats.SpellHitRating * hitRatingMultiplier; CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; // All spells: Damage +((0.08/0.16/0.25) * Int) switch (calcOpts.LunarGuidance) { case 1: stats.SpellDamageFromIntellectPercentage += 0.08f; break; case 2: stats.SpellDamageFromIntellectPercentage += 0.16f; break; case 3: stats.SpellDamageFromIntellectPercentage += 0.25f; break; default: stats.SpellDamageFromIntellectPercentage += 0.0f; break; } calcs.ArcaneDamage = stats.SpellDamageRating + stats.SpellArcaneDamageRating + stats.SpellDamageFromIntellectPercentage * stats.Intellect + stats.SpellDamageFromSpiritPercentage * stats.Spirit; calcs.NatureDamage = stats.SpellDamageRating + stats.SpellNatureDamageRating + stats.SpellDamageFromIntellectPercentage * stats.Intellect + stats.SpellDamageFromSpiritPercentage * stats.Spirit; calcs.Latency = calcOpts.Latency; calcs.FightLength = calcOpts.FightLength; calcs.TargetLevel = calcOpts.TargetLevel; calcs.Scryer = calcOpts.AldorScryer == "Scryer"; // 2.4 spirit regen float baseRegenConstant = 0.00932715221261f; float spiritRegen = 0.001f + baseRegenConstant * (float)Math.Sqrt(calcs.BasicStats.Intellect) * calcs.BasicStats.Spirit; calcs.ManaRegen = spiritRegen + stats.Mp5 / 5f; calcs.ManaRegen5SR = spiritRegen * stats.SpellCombatManaRegeneration + stats.Mp5 / 5f; // Run the solver to do final calculations MoonkinSolver.Solve(character, ref calcs); return(calcs); }
private float DoMushroomCalcs(CharacterCalculationsMoonkin calcs, float effectiveNatureDamage, float spellHit, float spellCrit) { float hitDamageModifier = (1 + calcs.BasicStats.BonusSpellDamageMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusNatureDamageMultiplier); float critDamageModifier = 1.5f * (1 + calcs.BasicStats.BonusCritDamageMultiplier); // 845-1022 damage float baseDamage = (845 + 1022) / 2; float damagePerHit = (baseDamage + effectiveNatureDamage * 0.6032f) * hitDamageModifier; float damagePerCrit = damagePerHit * critDamageModifier; return(spellHit * (damagePerHit * (1 - spellCrit) + damagePerCrit * spellCrit)); }
// Calculate damage and casting time for a damage-over-time effect. public void DoDotSpell(CharacterCalculationsMoonkin calcs, ref Spell dotSpell, float spellPower, float spellHit, float spellCrit, float spellHaste, float naturesGraceUptime, float latency) { float naturesGraceBonusHaste = 0.15f; float schoolMultiplier = dotSpell.School == SpellSchool.Arcane ? calcs.BasicStats.BonusArcaneDamageMultiplier : calcs.BasicStats.BonusNatureDamageMultiplier; float overallDamageModifier = dotSpell.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellDamageMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + schoolMultiplier); float dotEffectDamageModifier = dotSpell.DotEffect.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellDamageMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + schoolMultiplier); float gcd = 1.5f / (1.0f + spellHaste); float ngGcd = gcd / (1 + naturesGraceBonusHaste); float instantCast = (float)Math.Max(gcd, 1.0f) + latency; float instantCastNG = (float)Math.Max(ngGcd, 1.0f) + latency; dotSpell.CastTime = naturesGraceUptime * instantCastNG + (1 - naturesGraceUptime) * instantCast; // Flatly calculated tick rate float baseTickRate = dotSpell.DotEffect.BaseTickLength / (1 + spellHaste); float ngTickRate = baseTickRate / (1 + naturesGraceBonusHaste); // Round the tick rate to the nearest millisecond baseTickRate = ((int)Math.Floor(baseTickRate * 1000 + 0.5f)) / 1000f; ngTickRate = ((int)Math.Floor(ngTickRate * 1000 + 0.5f)) / 1000f; // Round the number of ticks up if > .5, down if <= .5 int baseTicks = (int)Math.Ceiling((dotSpell.DotEffect.BaseDuration / baseTickRate) - 0.5f); int ngTicks = (int)Math.Ceiling((dotSpell.DotEffect.BaseDuration / ngTickRate) - 0.5f); dotSpell.DotEffect.NumberOfTicks = naturesGraceUptime * ngTicks + (1 - naturesGraceUptime) * baseTicks; float baseDuration = baseTickRate * baseTicks; float ngDuration = ngTickRate * ngTicks; dotSpell.DotEffect.Duration = naturesGraceUptime * ngDuration + (1 - naturesGraceUptime) * baseDuration; dotSpell.DotEffect.TickLength = (1 - naturesGraceUptime) * baseTickRate + naturesGraceUptime * ngTickRate; float mfDirectDamage = (dotSpell.BaseDamage + dotSpell.SpellDamageModifier * spellPower) * overallDamageModifier; float mfCritDamage = mfDirectDamage * dotSpell.CriticalDamageModifier; float totalCritChance = spellCrit + dotSpell.CriticalChanceModifier; dotSpell.DamagePerHit = (totalCritChance * mfCritDamage + (1 - totalCritChance) * mfDirectDamage) * spellHit; float normalDamagePerTick = dotSpell.DotEffect.TickDamage + dotSpell.DotEffect.SpellDamageModifierPerTick * spellPower; float critDamagePerTick = normalDamagePerTick * dotSpell.CriticalDamageModifier; float damagePerTick = (totalCritChance * critDamagePerTick + (1 - totalCritChance) * normalDamagePerTick) * dotEffectDamageModifier; dotSpell.DotEffect.DamagePerHit = dotSpell.DotEffect.NumberOfTicks * damagePerTick * spellHit; dotSpell.AverageEnergy = dotSpell.BaseEnergy; }
// Calculate damage and casting time for the Moonfire effect. private void DoMoonfire(DruidTalents talents, CharacterCalculationsMoonkin calcs, ref Spell dotSpell, float spellPower, float spellHit, float spellCrit, float spellHaste) { float latency = calcs.Latency; float naturesGrace = talents.NaturesGrace; float overallDamageModifier = dotSpell.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusArcaneDamageMultiplier); overallDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 80); float dotEffectDamageModifier = dotSpell.DotEffect.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusArcaneDamageMultiplier); dotEffectDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 80); float gcd = 1.5f / (1.0f + spellHaste); float instantCast = (float)Math.Max(gcd, 1.0f) + latency; float ngGCD = (float)Math.Max(gcd / 1.2f, 1.0f); float instantCastNG = ngGCD + latency; float normalCastTime = (float)Math.Max(dotSpell.CastTime / (1 + spellHaste), instantCast); float NGCastTime = (float)Math.Max(dotSpell.CastTime / (1 + spellHaste) / (1 + 0.2f * naturesGrace / 3.0f), instantCastNG); float NGProcChance = spellCrit * naturesGrace / 3.0f; float NGUptime = 1.0f - (float)Math.Pow(1.0f - NGProcChance, Math.Floor(3.0f / normalCastTime) + 1.0f); dotSpell.CastTime = (1 - NGUptime) * normalCastTime + NGUptime * NGCastTime; float mfDirectDamage = (dotSpell.BaseDamage + dotSpell.SpellDamageModifier * (spellPower + dotSpell.IdolExtraSpellPower)) * overallDamageModifier; float mfCritDamage = mfDirectDamage * dotSpell.CriticalDamageModifier; float totalCritChance = spellCrit + dotSpell.CriticalChanceModifier; dotSpell.DamagePerHit = (totalCritChance * mfCritDamage + (1 - totalCritChance) * mfDirectDamage) * spellHit; float normalDamagePerTick = dotSpell.DotEffect.TickDamage + dotSpell.DotEffect.SpellDamageModifierPerTick * spellPower; float damagePerTick = 0.0f; if (dotSpell.DotEffect.CanCrit) { float critDamagePerTick = normalDamagePerTick * dotSpell.CriticalDamageModifier; damagePerTick = (totalCritChance * critDamagePerTick + (1 - totalCritChance) * normalDamagePerTick) * dotEffectDamageModifier; } else { damagePerTick = normalDamagePerTick * dotEffectDamageModifier; } dotSpell.DotEffect.DamagePerHit = dotSpell.DotEffect.NumberOfTicks * damagePerTick * spellHit; }
// Starfall private float DoStarfallCalcs(CharacterCalculationsMoonkin calcs, float effectiveArcaneDamage, float spellHit, float spellCrit) { float hitDamageModifier = (1 + calcs.BasicStats.BonusSpellDamageMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusArcaneDamageMultiplier); // Starfall is affected by Moonfury float critDamageModifier = 1.5f * (1 + calcs.BasicStats.BonusCritDamageMultiplier) + (1.5f * (1 + calcs.BasicStats.BonusCritDamageMultiplier) - 1); float baseDamagePerStar = (370.0f + 428.0f) / 2.0f; float mainStarCoefficient = 0.247f; float damagePerBigStarHit = (baseDamagePerStar + effectiveArcaneDamage * mainStarCoefficient) * hitDamageModifier; float critDamagePerBigStarHit = damagePerBigStarHit * critDamageModifier; float averageDamagePerBigStar = spellCrit * critDamagePerBigStarHit + (1 - spellCrit) * damagePerBigStarHit; float numberOfStarHits = 10f; float avgNumBigStarsHit = spellHit * numberOfStarHits; return(avgNumBigStarsHit * averageDamagePerBigStar); }
// Modified version of above function specifically for use in calculating Moonkin 4T8 proc. public void DoSpecialStarfire(CharacterCalculationsMoonkin calcs, ref Spell mainNuke, float spellPower, float spellHit, float spellCrit, float spellHaste) { float latency = calcs.Latency; float overallDamageModifier = mainNuke.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusArcaneDamageMultiplier); overallDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 80); mainNuke.CastTime = mainNuke.BaseCastTime; float totalCritChance = spellCrit + mainNuke.CriticalChanceModifier; float normalCastTime = (float)Math.Max(1.0f, mainNuke.CastTime / (1 + spellHaste)) + latency; mainNuke.NGCastTime = (float)Math.Max(1.0f, mainNuke.CastTime / (1 + spellHaste) / (1 + 0.2f * Solver.NaturesGrace / 3.0f)) + latency; float NGProcChance = totalCritChance * Solver.NaturesGrace / 3.0f; float NGUptime = 1.0f - (float)Math.Pow(1.0f - NGProcChance, Math.Floor(3.0f / mainNuke.NGCastTime) + 1.0f); mainNuke.CastTime = (1 - NGUptime) * normalCastTime + NGUptime * mainNuke.NGCastTime; // Damage calculations float damagePerNormalHit = (mainNuke.BaseDamage + mainNuke.SpellDamageModifier * (spellPower + mainNuke.IdolExtraSpellPower)) * overallDamageModifier; float damagePerCrit = damagePerNormalHit * mainNuke.CriticalDamageModifier * (1 + calcs.BasicStats.MoonkinT10CritDot); mainNuke.DamagePerHit = (totalCritChance * damagePerCrit + (1 - totalCritChance) * damagePerNormalHit) * spellHit; }
// Now returns damage per cast to allow adjustments for fight length private float DoTreeCalcs(CharacterCalculationsMoonkin calcs, int playerLevel, int bossLevel, float effectiveNatureDamage, float treantLifespan) { float sunderPercent = calcs.BasicStats.TargetArmorReduction; float meleeHit = calcs.SpellHit * (StatConversion.WHITE_MISS_CHANCE_CAP[bossLevel - playerLevel] / StatConversion.GetSpellMiss(playerLevel - bossLevel, false)); float physicalDamageMultiplierBonus = (1f + calcs.BasicStats.BonusDamageMultiplier) * (1f + calcs.BasicStats.BonusPhysicalDamageMultiplier); float physicalDamageMultiplierReduc = (1f - calcs.BasicStats.DamageTakenReductionMultiplier) * (1f - calcs.BasicStats.PhysicalDamageTakenReductionMultiplier); // 932 = base AP, 57% spell power scaling float attackPower = 932.0f + (float)Math.Floor(0.57f * effectiveNatureDamage); // 1.65 s base swing speed float baseAttackSpeed = 1.65f; float attackSpeed = baseAttackSpeed / (1 + calcs.BasicStats.PhysicalHaste); // 580 = base DPS float damagePerHit = (580f + attackPower / 14.0f) * baseAttackSpeed; // 5% base crit rate, inherit crit debuffs // Remove crit depression, as it doesn't appear to have an effect (unless it's base ~10% crit rate) float critRate = 0.05f; // White hit glancing rate float glancingRate = StatConversion.WHITE_GLANCE_CHANCE_CAP[bossLevel - playerLevel]; // Hit rate determined by the amount of melee hit, not by spell hit float missRate = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[bossLevel - playerLevel] - meleeHit); // Since the trees inherit expertise from their hit, scale their hit rate such that when they are hit capped, they are expertise capped float dodgeRate = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[bossLevel - playerLevel] * (missRate / StatConversion.WHITE_MISS_CHANCE_CAP[bossLevel - playerLevel])); // Armor damage reduction, including Sunder float damageReduction = StatConversion.GetArmorDamageReduction(playerLevel, StatConversion.NPC_ARMOR[bossLevel - playerLevel] * (1f - sunderPercent), 0, 0); // Final normal damage per swing damagePerHit *= 1.0f - damageReduction; damagePerHit *= physicalDamageMultiplierReduc; damagePerHit *= physicalDamageMultiplierBonus; // Damage per swing, including crits/glances/misses // This is a cheesy approximation of a true combat table, but because crit/miss/dodge rates will all be fairly low, I don't need to do the whole thing damagePerHit = (critRate * damagePerHit * 2.0f) + (glancingRate * damagePerHit * 0.75f) + ((1 - critRate - glancingRate - missRate - dodgeRate) * damagePerHit); // Total damage done in their estimated lifespan float damagePerTree = (treantLifespan * 30.0f / attackSpeed) * damagePerHit; return(3 * damagePerTree); }
// Calculate damage and casting time for the Insect Swarm effect. private void DoInsectSwarm(DruidTalents talents, CharacterCalculationsMoonkin calcs, ref Spell dotSpell, float spellPower, float spellHit, float spellCrit, float spellHaste) { float latency = calcs.Latency; float naturesGrace = talents.NaturesGrace; float dotEffectDamageModifier = dotSpell.DotEffect.AllDamageModifier * (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusNatureDamageMultiplier); dotEffectDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 80); float gcd = 1.5f / (1.0f + spellHaste); float instantCast = (float)Math.Max(gcd, 1.0f) + latency; float ngGCD = (float)Math.Max(gcd / 1.2f, 1.0f); float instantCastNG = ngGCD + latency; float normalCastTime = (float)Math.Max(dotSpell.CastTime / (1 + spellHaste), instantCast); float NGCastTime = (float)Math.Max(dotSpell.CastTime / (1 + spellHaste) / (1 + 0.2f * naturesGrace / 3.0f), instantCastNG); float NGProcChance = spellCrit * naturesGrace / 3.0f; float NGUptime = 1.0f - (float)Math.Pow(1.0f - NGProcChance, Math.Floor(3.0f / normalCastTime) + 1.0f); dotSpell.CastTime = (1 - NGUptime) * normalCastTime + NGUptime * NGCastTime; float damagePerTick = (dotSpell.DotEffect.TickDamage + dotSpell.DotEffect.SpellDamageModifierPerTick * (spellPower + dotSpell.IdolExtraSpellPower)) * dotEffectDamageModifier; dotSpell.DotEffect.DamagePerHit = dotSpell.DotEffect.NumberOfTicks * damagePerTick * spellHit; }
// Non-rotation-specific mana calculations private float GetEffectiveManaPool(Character character, CalculationOptionsMoonkin calcOpts, CharacterCalculationsMoonkin calcs) { float fightLength = character.BossOptions.BerserkTimer * 60.0f; float innervateCooldown = 180; // Mana/5 calculations float totalManaRegen = calcs.ManaRegen * fightLength; // Mana pot calculations float manaRestoredByPots = 0.0f; foreach (Buff b in character.ActiveBuffs) { if (b.Stats.ManaRestore > 0) { manaRestoredByPots = b.Stats.ManaRestore; break; } } // Innervate calculations float innervateDelay = calcOpts.InnervateDelay * 60.0f; int numInnervates = (calcOpts.Innervate && fightLength - innervateDelay > 0) ? ((int)(fightLength - innervateDelay) / (int)innervateCooldown + 1) : 0; float totalInnervateMana = numInnervates * 0.2f * calcs.BasicStats.Mana; totalInnervateMana *= 1 + 0.15f * character.DruidTalents.Dreamstate; // Replenishment calculations float replenishmentPerTick = calcs.BasicStats.Mana * calcs.BasicStats.ManaRestoreFromMaxManaPerSecond; float replenishmentMana = calcOpts.ReplenishmentUptime * replenishmentPerTick * character.BossOptions.BerserkTimer * 60; return calcs.BasicStats.Mana + totalInnervateMana + totalManaRegen + manaRestoredByPots + replenishmentMana; }
// Perform damage and mana calculations for all spells in the given rotation. Returns damage done over the total duration. public float DamageDone(Character character, CharacterCalculationsMoonkin calcs, float treantLifespan, float spellPower, float spellHit, float spellCrit, float spellHaste, float masteryPoints, float latency) { CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; DruidTalents talents = character.DruidTalents; Spell sf = Solver.Starfire; Spell ss = Solver.Starsurge; Spell w = Solver.Wrath; Spell mf = Solver.Moonfire; Spell iSw = Solver.InsectSwarm; // 4.1: The bug causing the Eclipse buff to be rounded down to the nearest percent has been fixed float eclipseBonus = 1 + MoonkinSolver.ECLIPSE_BASE + masteryPoints * 0.02f; RotationData.NaturesGraceUptime = (float)GetInterpolatedNGUpime(spellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive, talents.GlyphOfStarfire); RotationData.Duration = (float)GetInterpolatedCastTime(calcs.SpellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive, talents.GlyphOfStarfire); double[] castDistribution = GetInterpolatedCastTable(calcs.SpellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive, talents.GlyphOfStarfire); double percentOfMoonfiresExtended = talents.GlyphOfStarfire ? GetPercentOfMoonfiresExtended(calcs.SpellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive) : 0; DoMainNuke(calcs, ref sf, spellPower, spellHit, spellCrit, spellHaste, RotationData.NaturesGraceUptime, latency); DoMainNuke(calcs, ref ss, spellPower, spellHit, spellCrit, spellHaste, RotationData.NaturesGraceUptime, latency); DoMainNuke(calcs, ref w, spellPower, spellHit, spellCrit, spellHaste, RotationData.NaturesGraceUptime, latency); double gcd = Math.Max(1, 1.5 / (1 + spellHaste)) + latency; double ngGcd = Math.Max(1, 1.5 / (1 + spellHaste) / (1 + 0.05 * talents.NaturesGrace)) + latency; // Moonfire related local variables float mfBaseDur, mfMeanDur, mfMaxDur, mfMeanMaxDur, mfTicks, mfMaxTicks; mfBaseDur = mf.DotEffect.BaseDuration; mfMaxDur = mfBaseDur + (talents.GlyphOfStarfire ? 9f : 0f); // Determine Nature's Grace uptime against Moonfire float mfNGUptime = (float)Math.Min(2 * mfMaxDur / RotationData.Duration, 1); DoDotSpell(calcs, ref mf, spellPower, spellHit, spellCrit, spellHaste, mfNGUptime, latency); // Insect Swarm never benefits from Nature's Grace DoDotSpell(calcs, ref iSw, spellPower, spellHit, spellCrit, spellHaste, 0, latency); mfTicks = mf.DotEffect.NumberOfTicks; mfMaxTicks = mfTicks + (talents.GlyphOfStarfire ? 6 : 0); mfMeanDur = mf.DotEffect.Duration; mfMeanMaxDur = mf.DotEffect.Duration + (talents.GlyphOfStarfire ? 6 * mf.DotEffect.TickLength : 0f); RotationData.MoonfireAvgCast = mf.CastTime; RotationData.InsectSwarmAvgCast = iSw.CastTime; // Break the cast distribution down into its component cast counts double wrathCasts = castDistribution[1] * RotationData.Duration / w.CastTime; double eclipseWrathCasts = castDistribution[5] * RotationData.Duration / w.CastTime; double nonEclipsedWrathPercentage = castDistribution[1] / (castDistribution[1] + castDistribution[5]); double eclipsedWrathPercentage = castDistribution[5] / (castDistribution[1] + castDistribution[5]); RotationData.WrathAvgHit = (float)(nonEclipsedWrathPercentage * w.DamagePerHit + eclipsedWrathPercentage * w.DamagePerHit * eclipseBonus); RotationData.WrathAvgEnergy = w.AverageEnergy; RotationData.WrathCount = (float)(wrathCasts + eclipseWrathCasts); double starfireCasts = castDistribution[0] * RotationData.Duration / sf.CastTime; double eclipseStarfireCasts = castDistribution[4] * RotationData.Duration / sf.CastTime; double nonEclipsedStarfirePercentage = castDistribution[0] / (castDistribution[0] + castDistribution[4]); double eclipsedStarfirePercentage = castDistribution[4] / (castDistribution[0] + castDistribution[4]); RotationData.StarfireAvgHit = (float)(nonEclipsedStarfirePercentage * sf.DamagePerHit + eclipsedStarfirePercentage * sf.DamagePerHit * eclipseBonus); RotationData.StarfireAvgEnergy = sf.AverageEnergy; RotationData.StarfireCount = (float)(starfireCasts + eclipseStarfireCasts); double starsurgeCasts = castDistribution[2] * RotationData.Duration / ss.CastTime; double eclipseStarsurgeCasts = castDistribution[6] * RotationData.Duration / ss.CastTime; double shootingStarsProcs = castDistribution[3] * RotationData.Duration / gcd; double eclipseShootingStarsProcs = castDistribution[7] * RotationData.Duration / gcd; double allStarsurgePercentage = castDistribution[2] + castDistribution[6] + castDistribution[3] + castDistribution[7]; double nonEclipsedStarsurgePercentage = (castDistribution[2] + castDistribution[3]) / allStarsurgePercentage; double eclipsedStarsurgePercentage = (castDistribution[6] + castDistribution[7]) / allStarsurgePercentage; double starsurgePercentage = (castDistribution[2] + castDistribution[6]) / allStarsurgePercentage; double shootingStarsPercentage = (castDistribution[3] + castDistribution[7]) / allStarsurgePercentage; RotationData.StarSurgeAvgHit = (float)(nonEclipsedStarsurgePercentage * ss.DamagePerHit + eclipsedStarsurgePercentage * ss.DamagePerHit * eclipseBonus); RotationData.StarSurgeAvgEnergy = ss.AverageEnergy; RotationData.StarSurgeCount = (float)(starsurgeCasts + eclipseStarsurgeCasts + shootingStarsProcs + eclipseShootingStarsProcs); double moonfireCasts = castDistribution[8] * RotationData.Duration / mf.CastTime; double eclipsedMoonfireCasts = castDistribution[10] * RotationData.Duration / mf.CastTime; double nonEclipsedMoonfirePercentage = castDistribution[8] / (castDistribution[8] + castDistribution[10]); double eclipsedMoonfirePercentage = castDistribution[10] / (castDistribution[8] + castDistribution[10]); RotationData.MoonfireCasts = (float)(moonfireCasts + eclipsedMoonfireCasts); double insectSwarmCasts = castDistribution[9] * RotationData.Duration / iSw.CastTime; double eclipsedInsectSwarmCasts = castDistribution[11] * RotationData.Duration / iSw.CastTime; double nonEclipsedInsectSwarmPercentage = castDistribution[9] / (castDistribution[9] + castDistribution[11]); double eclipsedInsectSwarmPercentage = castDistribution[11] / (castDistribution[9] + castDistribution[11]); RotationData.InsectSwarmCasts = (float)(insectSwarmCasts + eclipsedInsectSwarmCasts); double unextendedMoonfireAverage = nonEclipsedMoonfirePercentage * (mf.DamagePerHit + mf.DotEffect.DamagePerHit) + eclipsedMoonfirePercentage * (mf.DamagePerHit + mf.DotEffect.DamagePerHit) * eclipseBonus; double mfExtendedDotDamage = mfMaxTicks * (mf.DotEffect.DamagePerHit / mf.DotEffect.NumberOfTicks); double extendedMoonfireAverage = nonEclipsedMoonfirePercentage * (mf.DamagePerHit + mfExtendedDotDamage) + eclipsedMoonfirePercentage * (mf.DamagePerHit + mfExtendedDotDamage) * eclipseBonus; RotationData.MoonfireTicks = (float)(percentOfMoonfiresExtended * mfMaxTicks + (1 - percentOfMoonfiresExtended) * mfTicks); RotationData.MoonfireDuration = (float)(percentOfMoonfiresExtended * mfMeanDur + (1 - percentOfMoonfiresExtended) * mfMeanMaxDur); RotationData.MoonfireAvgHit = (float)(percentOfMoonfiresExtended * extendedMoonfireAverage + (1 - percentOfMoonfiresExtended) * unextendedMoonfireAverage); RotationData.InsectSwarmTicks = RotationData.InsectSwarmCasts * iSw.DotEffect.NumberOfTicks; RotationData.InsectSwarmDuration = iSw.DotEffect.Duration; RotationData.InsectSwarmAvgHit = (float)(nonEclipsedInsectSwarmPercentage * iSw.DotEffect.DamagePerHit + eclipsedInsectSwarmPercentage * iSw.DotEffect.DamagePerHit * eclipseBonus); RotationData.StarfireAvgCast = sf.CastTime; RotationData.WrathAvgCast = w.CastTime; RotationData.AverageInstantCast = (float)(gcd * (1 - RotationData.NaturesGraceUptime) + ngGcd * RotationData.NaturesGraceUptime); RotationData.StarSurgeAvgCast = (float)(starsurgePercentage * ss.CastTime + shootingStarsPercentage * RotationData.AverageInstantCast); // Modify the rotation duration to simulate the energy bonus from Dragonwrath procs if (calcs.BasicStats.DragonwrathProc > 0) { float baselineNukeDuration = RotationData.StarfireCount * RotationData.StarfireAvgCast + RotationData.WrathCount * RotationData.WrathAvgCast + RotationData.StarSurgeCount * RotationData.StarSurgeAvgCast; float dragonwrathNukeDuration = baselineNukeDuration / (1 + MoonkinSolver.DRAGONWRATH_PROC_RATE); RotationData.Duration -= (baselineNukeDuration - dragonwrathNukeDuration); } RotationData.LunarUptime = (float)(castDistribution[4] + 0.5 * castDistribution[6] + 0.5 * castDistribution[7] + 0.5 * castDistribution[10]); RotationData.SolarUptime = (float)(castDistribution[5] + 0.5 * castDistribution[6] + 0.5 * castDistribution[7] + 0.5 * castDistribution[10] + castDistribution[11]); float starfallReduction = (float)(starsurgeCasts + shootingStarsProcs + eclipseStarsurgeCasts + eclipseShootingStarsProcs) * 5f; float starfallCooldown = (90f - (talents.GlyphOfStarfall ? 30f : 0f)) - (talents.GlyphOfStarsurge ? starfallReduction : 0); float starfallRatio = talents.Starfall == 1 ? (RotationData.StarfallCastMode == StarfallMode.OnCooldown ? RotationData.AverageInstantCast / (starfallCooldown + RotationData.AverageInstantCast) : 0f) : 0f; float starfallTime = RotationData.StarfallCastMode == StarfallMode.LunarOnly ? RotationData.AverageInstantCast : 0f; float treantRatio = talents.ForceOfNature == 1 ? RotationData.AverageInstantCast / (180f + RotationData.AverageInstantCast) : 0; float starfallBaseDamage = (talents.Starfall > 0 && RotationData.StarfallCastMode == StarfallMode.Unused) ? 0 : DoStarfallCalcs(calcs, spellPower, spellHit, spellCrit); starfallBaseDamage *= 1 + (talents.GlyphOfFocus ? 0.1f : 0f); // Dragonwrath starfallBaseDamage *= 1 + (calcs.BasicStats.DragonwrathProc > 0 ? MoonkinSolver.DRAGONWRATH_PROC_RATE : 0f); float starfallEclipseDamage = starfallBaseDamage * eclipseBonus; RotationData.TreantDamage = talents.ForceOfNature == 0 ? 0 : DoTreeCalcs(calcs, character.Level, character.BossOptions.Level, spellPower, treantLifespan); // T12 2-piece: 2-sec cast, 5192-6035 damage, affected by hit, 15-sec duration float T122PieceHitDamage = (5192 + 6035) / 2f * spellHit * (1 + calcs.BasicStats.BonusFireDamageMultiplier); // I'm going to assume a 150% crit modifier on the 2T12 proc until I'm told otherwise float T122PieceCritDamage = T122PieceHitDamage * 1.5f; // Use 2.5% crit rate based on EJ testing // Hard-code 4.5 casts/proc based on EJ testing float T122PieceBaseDamage = (0.975f * T122PieceHitDamage + 0.025f * T122PieceCritDamage) * 4.5f; // Without glyph of Starsurge, you cannot fit a Starfall in every Lunar eclipse. // The actual result will be better than 1/2, because you will be able to cast SFall later in each Eclipse as the fight goes on, // but you will miss a Lunar proc entirely eventually. float starfallCooldownOverlap = starfallCooldown - RotationData.Duration; float rotationsToMiss = starfallCooldownOverlap > 0 ? RotationData.Duration * RotationData.LunarUptime / starfallCooldownOverlap : 0f; float starfallFraction = rotationsToMiss > 0 ? (float)(Math.Ceiling(rotationsToMiss) / (Math.Ceiling(rotationsToMiss) + 1)) : 1f; RotationData.StarfallCasts = RotationData.StarfallCastMode == StarfallMode.OnCooldown ? starfallRatio * RotationData.Duration / RotationData.AverageInstantCast : (RotationData.StarfallCastMode == StarfallMode.LunarOnly ? starfallFraction : 0f); RotationData.TreantCasts = treantRatio * RotationData.Duration / RotationData.AverageInstantCast; RotationData.StarfallStars = 10f; if (RotationData.StarfallCastMode == StarfallMode.LunarOnly) { RotationData.LunarUptime += starfallFraction * RotationData.AverageInstantCast / RotationData.Duration; } else if (RotationData.StarfallCastMode == StarfallMode.OnCooldown) { RotationData.SolarUptime *= 1 + starfallRatio; RotationData.LunarUptime *= 1 + starfallRatio; } RotationData.Duration += RotationData.StarfallCasts * RotationData.AverageInstantCast + RotationData.TreantCasts * RotationData.AverageInstantCast; RotationData.StarfallDamage = RotationData.StarfallCastMode == StarfallMode.OnCooldown ? RotationData.LunarUptime * starfallEclipseDamage + (1 - RotationData.LunarUptime) * starfallBaseDamage : starfallEclipseDamage; float moonfireDamage = RotationData.MoonfireAvgHit * RotationData.MoonfireCasts; float insectSwarmDamage = RotationData.InsectSwarmAvgHit * RotationData.InsectSwarmCasts; // Calculate total damage done for external cooldowns per rotation float starfallDamage = RotationData.StarfallDamage * RotationData.StarfallCasts; float treantDamage = RotationData.TreantDamage * RotationData.TreantCasts; float T122PieceDamage = 0f; if (calcs.BasicStats.ContainsSpecialEffect(se => se.Trigger == Trigger.MageNukeCast)) { foreach (SpecialEffect effect in calcs.BasicStats.SpecialEffects(se => se.Trigger == Trigger.MageNukeCast)) { T122PieceDamage = T122PieceBaseDamage * effect.GetAverageUptime(RotationData.Duration / (RotationData.WrathCount + RotationData.StarfireCount), 1f); } } // Calculate mana cost per cast. // Starfall - 35% of base mana float starfallManaCost = (int)(0.35f * MoonkinSolver.BaseMana) - calcs.BasicStats.SpellsManaCostReduction - calcs.BasicStats.NatureSpellsManaCostReduction; // Force of Nature - 12% of base mana float treantManaCost = (int)(0.12f * MoonkinSolver.BaseMana) - calcs.BasicStats.SpellsManaCostReduction - calcs.BasicStats.NatureSpellsManaCostReduction; RotationData.CastCount = RotationData.WrathCount + RotationData.StarfireCount + RotationData.StarSurgeCount + RotationData.MoonfireCasts + RotationData.InsectSwarmCasts + RotationData.StarfallCasts + RotationData.TreantCasts; RotationData.DotTicks = RotationData.InsectSwarmTicks + RotationData.MoonfireTicks; RotationData.ManaUsed = RotationData.WrathCount * w.BaseManaCost + RotationData.StarfireCount * sf.BaseManaCost + RotationData.StarSurgeCount * ss.BaseManaCost + RotationData.MoonfireCasts * mf.BaseManaCost + RotationData.InsectSwarmCasts * iSw.BaseManaCost + RotationData.StarfallCasts * starfallManaCost + RotationData.TreantCasts * treantManaCost; float manaSavingsFromOOC = MoonkinSolver.OOC_PROC_CHANCE * (RotationData.MoonfireCasts / RotationData.CastCount * mf.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.InsectSwarmCasts / RotationData.CastCount * iSw.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.StarfireCount / RotationData.CastCount * sf.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.WrathCount / RotationData.CastCount * w.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.StarSurgeCount / RotationData.CastCount * ss.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.StarfallCasts / RotationData.CastCount * starfallManaCost); RotationData.ManaUsed -= manaSavingsFromOOC; RotationData.ManaGained = 2 * MoonkinSolver.EUPHORIA_PERCENT * talents.Euphoria * calcs.BasicStats.Mana; return(RotationData.WrathAvgHit * RotationData.WrathCount + RotationData.StarfireAvgHit * RotationData.StarfireCount + RotationData.StarSurgeAvgHit * RotationData.StarSurgeCount + moonfireDamage + insectSwarmDamage + treantDamage + starfallDamage + T122PieceDamage); }
public void Solve(Character character, ref CharacterCalculationsMoonkin calcs) { CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; DruidTalents talents = character.DruidTalents; procEffects = new List <ProcEffect>(); RecreateSpells(talents, ref calcs); cachedResults = new Dictionary <string, RotationData>(); float trinketDPS = 0.0f; float baseSpellPower = calcs.SpellPower; float baseHit = GetSpellHit(calcs); float baseCrit = calcs.SpellCrit; float baseHaste = calcs.SpellHaste; BuildProcList(calcs); float maxDamageDone = 0.0f; float maxBurstDamageDone = 0.0f; SpellRotation maxBurstRotation = rotations[0]; SpellRotation maxRotation = rotations[0]; float manaPool = GetEffectiveManaPool(character, calcOpts, calcs); // Do tree calculations: Calculate damage per cast. float treeDamage = (talents.ForceOfNature == 1) ? DoTreeCalcs(baseSpellPower, calcs.BasicStats.PhysicalHaste, calcs.BasicStats.ArmorPenetration, calcs.BasicStats.PhysicalCrit, calcOpts.TreantLifespan, character.DruidTalents.Brambles) : 0.0f; // Extend that to number of casts per fight. float treeCasts = (float)Math.Floor(calcs.FightLength / 3) + 1.0f; // Partial cast: If the fight lasts 3.x minutes and x is less than 0.5 (30 sec tree duration), calculate a partial cast if ((int)calcs.FightLength % 3 == 0 && calcs.FightLength - (int)calcs.FightLength < 0.5) { treeCasts += (calcs.FightLength - (int)calcs.FightLength) / 0.5f - 1.0f; } treeDamage *= treeCasts; // Multiply by raid-wide damage increases. treeDamage *= (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusPhysicalDamageMultiplier); // Calculate the DPS averaged over the fight length. float treeDPS = treeDamage / (calcs.FightLength * 60.0f); // Calculate mana usage for trees. float treeManaUsage = (float)Math.Ceiling(treeCasts) * CalculationsMoonkin.BaseMana * 0.12f; manaPool -= talents.ForceOfNature == 1 ? treeManaUsage : 0.0f; // Do Starfall calculations. bool starfallGlyph = talents.GlyphOfStarfall; Buff tier102PieceBuff = character.ActiveBuffs.Find(theBuff => theBuff.Name == "Lasherweave Regalia (T10) 2 Piece Bonus"); float numberOfStarHits = 10.0f; float starfallDamage = (talents.Starfall == 1) ? DoStarfallCalcs(baseSpellPower, baseHit, baseCrit, (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusArcaneDamageMultiplier) * (1 + (talents.GlyphOfFocus ? 0.1f : 0.0f)), Wrath.CriticalDamageModifier, out numberOfStarHits) : 0.0f; float starfallCD = 1.5f - (starfallGlyph ? 0.5f : 0.0f); float numStarfallCasts = (float)Math.Floor(calcs.FightLength / starfallCD) + 1.0f; // Partial cast: If the difference between fight length and total starfall CD time is less than 10 seconds (duration), // calculate a partial cast float starfallDiff = calcs.FightLength * 60.0f - (numStarfallCasts - 1) * starfallCD * 60.0f; if (starfallDiff > 0 && starfallDiff < 10) { numStarfallCasts += starfallDiff / 60.0f / (1.0f / 6.0f) - 1.0f; } starfallDamage *= numStarfallCasts; float starfallManaUsage = (float)Math.Ceiling(numStarfallCasts) * CalculationsMoonkin.BaseMana * 0.39f; manaPool -= talents.Starfall == 1 ? starfallManaUsage : 0.0f; // Simple faerie fire mana calc float faerieFireCasts = (float)Math.Floor(calcs.FightLength / 5) + (calcs.FightLength % 5 != 0 ? 1.0f : 0.0f); float faerieFireMana = faerieFireCasts * CalculationsMoonkin.BaseMana * 0.08f; if (talents.ImprovedFaerieFire > 0) { manaPool -= faerieFireMana; } // Calculate effect of casting Starfall/Treants/ImpFF (regular FF is assumed to be provided by a feral) float globalCooldown = 1.5f / (1 + baseHaste) + calcs.Latency; float treantTime = (talents.ForceOfNature == 1) ? globalCooldown * (float)Math.Ceiling(treeCasts) : 0.0f; float starfallTime = (talents.Starfall == 1) ? globalCooldown * (float)Math.Ceiling(numStarfallCasts) : 0.0f; float faerieFireTime = (talents.ImprovedFaerieFire > 0) ? globalCooldown * faerieFireCasts : 0.0f; float totalTimeInRotation = calcs.FightLength * 60.0f - (treantTime + starfallTime + faerieFireTime); float percentTimeInRotation = totalTimeInRotation / (calcs.FightLength * 60.0f); #if RAWR3 BossOptions bossOpts = character.BossOptions; if (bossOpts == null) { bossOpts = new BossOptions(); } float fearShare = (bossOpts.FearingTargsDur / 1000) / bossOpts.FearingTargsFreq; float stunShare = (bossOpts.StunningTargsDur / 1000) / bossOpts.StunningTargsFreq; float invulnerableShare = bossOpts.TimeBossIsInvuln / bossOpts.BerserkTimer; List <Attack> attacks = bossOpts.GetFilteredAttackList(ATTACK_TYPES.AT_AOE); attacks.AddRange(bossOpts.GetFilteredAttackList(ATTACK_TYPES.AT_RANGED)); int movementCount = attacks.Count; float assumedMovementDuration = 2.0f; // Assume 2 seconds per move float accumulatedDurations = 0.0f; foreach (Attack a in attacks) { accumulatedDurations += a.AttackSpeed; } float movementShare = (movementCount == 0 ? 0 : assumedMovementDuration / (accumulatedDurations / movementCount) / (1 + calcs.BasicStats.MovementSpeed)); percentTimeInRotation -= movementShare + fearShare + stunShare + invulnerableShare; #endif float manaGained = manaPool - calcs.BasicStats.Mana; float oldArcaneMultiplier = calcs.BasicStats.BonusArcaneDamageMultiplier; float oldNatureMultiplier = calcs.BasicStats.BonusNatureDamageMultiplier; foreach (SpellRotation rot in rotations) { rot.Solver = this; float accumulatedDamage = 0.0f; float totalUpTime = 0.0f; float[] spellDetails = new float[NUM_SPELL_DETAILS]; List <ProcEffect> activatedEffects = new List <ProcEffect>(); List <ProcEffect> alwaysUpEffects = new List <ProcEffect>(); // Pre-calculate rotational variables with base stats rot.DamageDone(talents, calcs, baseSpellPower, baseHit, baseCrit, baseHaste); // Reset variables modified in the pre-loop to base values float currentSpellPower = baseSpellPower; float currentCrit = baseCrit; float currentHaste = baseHaste; calcs.BasicStats.BonusArcaneDamageMultiplier = oldArcaneMultiplier; calcs.BasicStats.BonusNatureDamageMultiplier = oldNatureMultiplier; // Calculate spell power/spell damage modifying trinkets in a separate pre-loop foreach (ProcEffect proc in procEffects) { if (proc.Effect.Stats.SpellPower > 0 || proc.Effect.Stats.Spirit > 0) { float procSpellPower = proc.Effect.Stats.SpellPower; if (proc.Effect.Stats.Spirit > 0) { procSpellPower += proc.Effect.Stats.Spirit * (0.1f * talents.ImprovedMoonkinForm); } float triggerInterval = 0.0f, triggerChance = 1.0f; switch (proc.Effect.Trigger) { case Trigger.DamageDone: case Trigger.DamageOrHealingDone: triggerInterval = ((rot.Duration / rot.CastCount) + (rot.Duration / (rot.MoonfireTicks + rot.InsectSwarmTicks))) / 2.0f; break; case Trigger.Use: break; case Trigger.SpellHit: case Trigger.DamageSpellHit: triggerInterval = rot.Duration / rot.CastCount; triggerChance = GetSpellHit(calcs); break; case Trigger.SpellCrit: case Trigger.DamageSpellCrit: triggerInterval = rot.Duration / (rot.CastCount - rot.InsectSwarmCasts); triggerChance = baseCrit; break; case Trigger.SpellCast: case Trigger.DamageSpellCast: triggerInterval = rot.Duration / rot.CastCount; break; case Trigger.MoonfireCast: triggerInterval = rot.Duration / rot.MoonfireCasts; break; case Trigger.DoTTick: case Trigger.InsectSwarmOrMoonfireTick: triggerInterval = rot.Duration / (rot.InsectSwarmTicks + rot.MoonfireTicks); break; case Trigger.MoonfireTick: triggerInterval = rot.Duration / rot.MoonfireTicks; break; case Trigger.InsectSwarmTick: triggerInterval = rot.Duration / rot.InsectSwarmTicks; break; default: triggerChance = 0.0f; break; } if (triggerChance > 0) { currentSpellPower += (proc.Effect.MaxStack > 1 ? proc.Effect.GetAverageStackSize(triggerInterval, triggerChance, 3.0f, calcs.FightLength * 60.0f) : 1) * proc.Effect.GetAverageUptime(triggerInterval, triggerChance) * procSpellPower; } } // 2T10 (both if statements, which is why it isn't else-if) if (proc.Effect.Stats.BonusArcaneDamageMultiplier > 0) { calcs.BasicStats.BonusArcaneDamageMultiplier += proc.Effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f) * proc.Effect.Stats.BonusArcaneDamageMultiplier; } if (proc.Effect.Stats.BonusNatureDamageMultiplier > 0) { calcs.BasicStats.BonusNatureDamageMultiplier += proc.Effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f) * proc.Effect.Stats.BonusNatureDamageMultiplier; } if (proc.Effect.Stats._rawSpecialEffectDataSize > 0) { SpecialEffect childEffect = proc.Effect.Stats._rawSpecialEffectData[0]; // Nevermelting Ice Crystal if (childEffect.Stats.CritRating != 0) { float maxStack = proc.Effect.Stats.CritRating; float numNegativeStacks = childEffect.GetAverageStackSize(rot.Duration / (rot.CastCount - rot.InsectSwarmCasts), baseCrit, 3.0f, proc.Effect.Duration); float averageNegativeValue = childEffect.Stats.CritRating * numNegativeStacks; float averageCritRating = maxStack + averageNegativeValue; currentCrit += StatConversion.GetSpellCritFromRating(averageCritRating * proc.Effect.GetAverageUptime(0f, 1f)); } // Fetish of Volatile Power else if (childEffect.Stats.HasteRating != 0) { currentHaste += StatConversion.GetSpellHasteFromRating(childEffect.Stats.HasteRating * childEffect.GetAverageStackSize(rot.Duration / rot.CastCount, 1f, 3.0f, proc.Effect.Duration) * proc.Effect.GetAverageUptime(0f, 1f)); } } } // Calculate damage and mana contributions for non-stat-boosting trinkets // Separate stat-boosting proc trinkets into their own list foreach (ProcEffect proc in procEffects) { if (proc.CalculateDPS != null) { accumulatedDamage += proc.CalculateDPS(rot, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) * rot.Duration; } else if (proc.Activate != null) { float upTime = proc.UpTime(rot, calcs); // Procs with 100% uptime should be activated and not put into the combination loop if (upTime == 1) { alwaysUpEffects.Add(proc); proc.Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } // Procs with uptime 0 < x < 100 should be activated else if (upTime > 0) { activatedEffects.Add(proc); } } else if (proc.CalculateMP5 != null) { manaGained += proc.CalculateMP5(rot, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) / 5.0f * calcs.FightLength * 60.0f; } } // Calculate stat-boosting trinkets, taking into effect interactions with other stat-boosting procs int sign = 1; Dictionary <int, float> cachedDamages = new Dictionary <int, float>(); Dictionary <int, float> cachedUptimes = new Dictionary <int, float>(); Dictionary <int, float[]> cachedDetails = new Dictionary <int, float[]>(); // Iterate over the entire set of trinket combinations (each trinket by itself, 2 at a time, ...) for (int i = 1; i <= activatedEffects.Count; ++i) { // Create a new combination generator for this "level" of trinket interaction CombinationGenerator gen = new CombinationGenerator(activatedEffects.Count, i); // Iterate over all combinations while (gen.HasNext()) { float tempUpTime = 1.0f; int[] vals = gen.GetNext(); int pairs = 0; int lengthCounter = 0; // Activate the trinkets, calculate the damage and uptime, then deactivate them foreach (int idx in vals) { pairs |= 1 << idx; ++lengthCounter; activatedEffects[idx].Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } float tempDPS = rot.DamageDone(talents, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) / rot.Duration; spellDetails[0] = Starfire.DamagePerHit; spellDetails[1] = Wrath.DamagePerHit; spellDetails[2] = Moonfire.DamagePerHit + Moonfire.DotEffect.DamagePerHit; spellDetails[3] = InsectSwarm.DotEffect.DamagePerHit; spellDetails[4] = Starfire.CastTime; spellDetails[5] = Wrath.CastTime; spellDetails[6] = Moonfire.CastTime; spellDetails[7] = InsectSwarm.CastTime; spellDetails[8] = Starfire.NGCastTime; spellDetails[9] = Wrath.NGCastTime; spellDetails[10] = Starfire.ManaCost; spellDetails[11] = Wrath.ManaCost; spellDetails[12] = Moonfire.ManaCost; spellDetails[13] = InsectSwarm.ManaCost; foreach (int idx in vals) { tempUpTime *= activatedEffects[idx].UpTime(rot, calcs); activatedEffects[idx].Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } if (tempUpTime == 0) { continue; } // Adjust previous probability tables by the current factor // At the end of the algorithm, this ensures that the probability table will contain the individual // probabilities of each effect or set of effects. // These adjustments only need to be made for higher levels of the table, and if the current probability is > 0. if (lengthCounter > 1) { List <int> keys = new List <int>(cachedUptimes.Keys); foreach (int subset in keys) { // Truly a subset? if ((pairs & subset) != subset) { continue; } // Calculate the "layer" of the current subset by getting the set bit count. int subsetLength = 0; for (int j = subset; j > 0; ++subsetLength) { j &= --j; } // Set the sign of the operation: Evenly separated layers are added, oddly separated layers are subtracted int newSign = ((lengthCounter - subsetLength) % 2 == 0) ? 1 : -1; // Adjust by current uptime * sign of operation. cachedUptimes[subset] += newSign * tempUpTime; } } // Cache the results to be calculated later cachedUptimes[pairs] = tempUpTime; cachedDamages[pairs] = tempDPS; cachedDetails[pairs] = new float[NUM_SPELL_DETAILS]; Array.Copy(spellDetails, cachedDetails[pairs], NUM_SPELL_DETAILS); totalUpTime += sign * tempUpTime; } sign = -sign; } float accumulatedDPS = 0.0f; Array.Clear(spellDetails, 0, spellDetails.Length); // Apply the above-calculated probabilities to the previously stored damage calculations and add to the result. foreach (KeyValuePair <int, float> kvp in cachedUptimes) { if (kvp.Value == 0) { continue; } accumulatedDPS += kvp.Value * cachedDamages[kvp.Key]; for (int i = 0; i < NUM_SPELL_DETAILS; ++i) { spellDetails[i] += kvp.Value * cachedDetails[kvp.Key][i]; } } float damageDone = rot.DamageDone(talents, calcs, currentSpellPower, baseHit, currentCrit, currentHaste); accumulatedDPS += (1 - totalUpTime) * damageDone / rot.Duration; spellDetails[0] += (1 - totalUpTime) * Starfire.DamagePerHit; spellDetails[1] += (1 - totalUpTime) * Wrath.DamagePerHit; spellDetails[2] += (1 - totalUpTime) * Moonfire.DamagePerHit + Moonfire.DotEffect.DamagePerHit; spellDetails[3] += (1 - totalUpTime) * InsectSwarm.DotEffect.DamagePerHit; spellDetails[4] += (1 - totalUpTime) * Starfire.CastTime; spellDetails[5] += (1 - totalUpTime) * Wrath.CastTime; spellDetails[6] += (1 - totalUpTime) * Moonfire.CastTime; spellDetails[7] += (1 - totalUpTime) * InsectSwarm.CastTime; spellDetails[8] += (1 - totalUpTime) * Starfire.NGCastTime; spellDetails[9] += (1 - totalUpTime) * Wrath.NGCastTime; spellDetails[10] += (1 - totalUpTime) * Starfire.ManaCost; spellDetails[11] += (1 - totalUpTime) * Wrath.ManaCost; spellDetails[12] += (1 - totalUpTime) * Moonfire.ManaCost; spellDetails[13] += (1 - totalUpTime) * InsectSwarm.ManaCost; accumulatedDamage += accumulatedDPS * rot.Duration; float burstDPS = accumulatedDamage / rot.Duration * percentTimeInRotation; float sustainedDPS = burstDPS; float timeToOOM = (manaPool / rot.RotationData.ManaUsed) * rot.Duration; if (timeToOOM <= 0) { timeToOOM = calcs.FightLength * 60.0f; // Happens when ManaUsed is less than 0 } if (timeToOOM < calcs.FightLength * 60.0f) { rot.RotationData.TimeToOOM = new TimeSpan(0, (int)(timeToOOM / 60), (int)(timeToOOM % 60)); sustainedDPS = burstDPS * timeToOOM / (calcs.FightLength * 60.0f); } float t10StarfallDamage = starfallDamage; // Approximate the effect of the 2T10 set bonus if (tier102PieceBuff != null && character.DruidTalents.OmenOfClarity == 1) { Stats.SpecialEffectEnumerator enumerator = tier102PieceBuff.Stats.SpecialEffects(); enumerator.MoveNext(); SpecialEffect effect = enumerator.Current; float upTime = effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f); t10StarfallDamage = upTime * (starfallDamage * (1 + effect.Stats.BonusArcaneDamageMultiplier)) + (1 - upTime) * starfallDamage; } float starfallDPS = t10StarfallDamage / (calcs.FightLength * 60.0f); burstDPS += trinketDPS + treeDPS + starfallDPS; sustainedDPS += trinketDPS + treeDPS + starfallDPS; rot.StarfallDamage = t10StarfallDamage / numStarfallCasts; rot.StarfallStars = numberOfStarHits; rot.RotationData.BurstDPS = burstDPS; rot.RotationData.DPS = sustainedDPS; rot.StarfireAvgHit = spellDetails[0]; rot.WrathAvgHit = spellDetails[1]; rot.MoonfireAvgHit = spellDetails[2]; rot.InsectSwarmAvgHit = spellDetails[3]; rot.StarfireAvgCast = spellDetails[4]; rot.WrathAvgCast = spellDetails[5]; rot.MoonfireCastTime = spellDetails[6]; rot.InsectSwarmCastTime = spellDetails[7]; rot.StarfireNGCastTime = spellDetails[8]; rot.WrathNGCastTime = spellDetails[9]; rot.StarfireManaCost = spellDetails[10]; rot.WrathManaCost = spellDetails[11]; rot.MoonfireManaCost = spellDetails[12]; rot.InsectSwarmManaCost = spellDetails[13]; // Update the sustained DPS rotation if any one of the following three cases is true: // 1) No user rotation is selected and sustained DPS is maximum // 2) A user rotation is selected, Eclipse is not present, and the user rotation matches the current rotation // 3) A user rotation is selected, Eclipse is present, and the user rotation's dot spells matches this rotation's if ((calcOpts.UserRotation == "None" && sustainedDPS > maxDamageDone) || (character.DruidTalents.Eclipse == 0 && calcOpts.UserRotation == rot.Name) || (character.DruidTalents.Eclipse > 0 && (calcOpts.UserRotation == rot.Name.Replace("Filler", "SF") || calcOpts.UserRotation == rot.Name.Replace("Filler", "W")))) { maxDamageDone = sustainedDPS; maxRotation = rot; } if (burstDPS > maxBurstDamageDone) { maxBurstDamageDone = burstDPS; maxBurstRotation = rot; } rot.ManaGained += manaGained / (calcs.FightLength * 60.0f) * rot.Duration; rot.RotationData.ManaGained += manaGained / (calcs.FightLength * 60.0f) * rot.Duration; if (rot.Name.Contains("Filler")) { cachedResults[rot.Name.Replace("Filler", "SF")] = rot.RotationData; cachedResults[rot.Name.Replace("Filler", "W")] = rot.RotationData; } else { cachedResults[rot.Name] = rot.RotationData; } // Deactivate always-up procs foreach (ProcEffect proc in alwaysUpEffects) { proc.Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } } // Present the findings to the user. calcs.TreantDamage = treeDamage / treeCasts; calcs.StarfallMana = starfallManaUsage / numStarfallCasts; calcs.SelectedRotation = maxRotation; calcs.BurstDPSRotation = maxBurstRotation; calcs.SubPoints = new float[] { maxDamageDone, maxBurstDamageDone }; calcs.OverallPoints = calcs.SubPoints[0] + calcs.SubPoints[1]; calcs.Rotations = cachedResults; }
/*private void RecreateRotations() { rotations[0] = new SpellRotation() { RotationData = new RotationData() { Name = "None" } }; for (int mfMode = 0; mfMode < 2; ++mfMode) { for (int isMode = 0; isMode < 2; ++isMode) { for (int sfMode = 0; sfMode < 3; ++sfMode) { for (int wmMode = 0; wmMode < 3; ++wmMode) { int index = 1 + (wmMode + 3 * sfMode + 9 * isMode + 18 * mfMode); DotMode mfModeEnum = (DotMode)mfMode; DotMode isModeEnum = (DotMode)isMode; StarfallMode sfModeEnum = (StarfallMode)sfMode; MushroomMode wmModeEnum = (MushroomMode)wmMode; string name = String.Format("MF {0} IS {1} SF {2} WM {3}", mfModeEnum.ToString(), isModeEnum.ToString(), sfModeEnum.ToString(), wmModeEnum.ToString()); rotations[index] = new SpellRotation() { RotationData = new RotationData() { Name = name, MoonfireRefreshMode = mfModeEnum, InsectSwarmRefreshMode = isModeEnum, StarfallCastMode = sfModeEnum, WildMushroomCastMode = wmModeEnum } }; } } } } }*/ // Add talented effects to the spells private void UpdateSpells(Character character, ref CharacterCalculationsMoonkin calcs) { DruidTalents talents = character.DruidTalents; StatsMoonkin stats = calcs.BasicStats; switch (talents.StarlightWrath) { case 1: Starfire.BaseCastTime -= 0.15f; Wrath.BaseCastTime -= 0.15f; break; case 2: Starfire.BaseCastTime -= 0.25f; Wrath.BaseCastTime -= 0.25f; break; case 3: Starfire.BaseCastTime -= 0.5f; Wrath.BaseCastTime -= 0.5f; break; default: break; } float moonfireDotGlyph = talents.GlyphOfMoonfire ? 0.2f : 0.0f; float insectSwarmGlyph = talents.GlyphOfInsectSwarm ? 0.3f : 0.0f; // Add spell-specific damage // Moonfire, Insect Swarm: glyphs Moonfire.DotEffect.AllDamageModifier += moonfireDotGlyph; InsectSwarm.DotEffect.AllDamageModifier += insectSwarmGlyph; // Moonfire: Direct damage +(0.03 * Blessing of the Grove) Moonfire.AllDamageModifier += 0.03f * talents.BlessingOfTheGrove; // Moonfire, Insect Swarm: +2/4/6 seconds for Genesis Moonfire.DotEffect.BaseDuration += 2f * talents.Genesis; InsectSwarm.DotEffect.BaseDuration += 2f * talents.Genesis; // Wrath: 10% for glyph Wrath.AllDamageModifier += (talents.GlyphOfWrath ? 0.1f : 0f); // Add spell-specific critical strike damage // Burning Shadowspirit Diamond float baseCritMultiplier = 1.5f * (1 + stats.BonusCritDamageMultiplier); float moonfuryMultiplier = baseCritMultiplier + (baseCritMultiplier - 1); Starfire.CriticalDamageModifier = Wrath.CriticalDamageModifier = Moonfire.CriticalDamageModifier = InsectSwarm.CriticalDamageModifier = moonfuryMultiplier; Starsurge.CriticalDamageModifier = moonfuryMultiplier; // Reduce spell-specific mana costs // Shard of Woe (Mana cost -405) Starfire.BaseManaCost -= calcs.BasicStats.SpellsManaCostReduction; Moonfire.BaseManaCost -= calcs.BasicStats.SpellsManaCostReduction; Wrath.BaseManaCost -= calcs.BasicStats.SpellsManaCostReduction + calcs.BasicStats.NatureSpellsManaCostReduction; InsectSwarm.BaseManaCost -= calcs.BasicStats.SpellsManaCostReduction + calcs.BasicStats.NatureSpellsManaCostReduction; Starsurge.BaseManaCost -= calcs.BasicStats.SpellsManaCostReduction; // All spells: Mana cost -(0.03 * Moonglow) Starfire.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); Moonfire.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); Wrath.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); InsectSwarm.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); Starsurge.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); // Add set bonuses Moonfire.CriticalChanceModifier += stats.BonusCritChanceMoonfire; InsectSwarm.CriticalChanceModifier += stats.BonusCritChanceInsectSwarm; Starfire.AllDamageModifier *= 1 + stats.BonusNukeDamageModifier; Wrath.AllDamageModifier *= 1 + stats.BonusNukeDamageModifier; Starsurge.AllDamageModifier *= 1 + stats.BonusNukeDamageModifier; // PTR changes go here /*if (((CalculationOptionsMoonkin)character.CalculationOptions).PTRMode) { MoonkinSolver.DRAGONWRATH_PROC_RATE = 0.0675f; } else { MoonkinSolver.DRAGONWRATH_PROC_RATE = 0.11f; }*/ // Dragonwrath, Tarecgosa's Rest: X% chance on damaging spell cast to proc a duplicate version of the spell. // If it duplicates a DoT tick, it fires Wrath of Tarecgosa for an equivalent amount of damage. // Wrath, Starfire, and Starsurge will duplicate the Eclipse energy gained. if (calcs.BasicStats.DragonwrathProc > 0) { Starfire.AllDamageModifier += MoonkinSolver.DRAGONWRATH_PROC_RATE; Wrath.AllDamageModifier += MoonkinSolver.DRAGONWRATH_PROC_RATE; Starsurge.AllDamageModifier += MoonkinSolver.DRAGONWRATH_PROC_RATE; Moonfire.AllDamageModifier += MoonkinSolver.DRAGONWRATH_PROC_RATE; Moonfire.DotEffect.AllDamageModifier += MoonkinSolver.DRAGONWRATH_PROC_RATE; InsectSwarm.DotEffect.AllDamageModifier += MoonkinSolver.DRAGONWRATH_PROC_RATE; } }
// Redo the spell calculations private void RecreateSpells(DruidTalents talents, ref CharacterCalculationsMoonkin calcs) { ResetSpellList(); if (talents.Eclipse == 0) { rotations = new List <SpellRotation>(new SpellRotation[] { new SpellRotation() { Name = "MF/SF", SpellsUsed = new List <string>(new string[] { "MF", "SF" }) }, new SpellRotation() { Name = "MF/W", SpellsUsed = new List <string>(new string[] { "MF", "W" }) }, new SpellRotation() { Name = "IS/SF", SpellsUsed = new List <string>(new string[] { "IS", "SF" }) }, new SpellRotation() { Name = "IS/W", SpellsUsed = new List <string>(new string[] { "IS", "W" }) }, new SpellRotation() { Name = "IS/MF/SF", SpellsUsed = new List <string>(new string[] { "IS", "MF", "SF" }) }, new SpellRotation() { Name = "IS/MF/W", SpellsUsed = new List <string>(new string[] { "IS", "MF", "W" }) }, new SpellRotation() { Name = "SF Spam", SpellsUsed = new List <string>(new string[] { "SF" }) }, new SpellRotation() { Name = "W Spam", SpellsUsed = new List <string>(new string[] { "W" }) } }); } else { rotations = new List <SpellRotation>(new SpellRotation[] { new SpellRotation() { Name = "W Spam", SpellsUsed = new List <string>(new string[] { "W" }) }, new SpellRotation() { Name = "SF Spam", SpellsUsed = new List <string>(new string[] { "SF" }) }, new SpellRotation() { Name = "MF/Filler", SpellsUsed = new List <string>(new String[] { "MF", "SF" }) }, new SpellRotation() { Name = "IS/Filler", SpellsUsed = new List <string>(new string[] { "IS", "W" }) }, new SpellRotation() { Name = "IS/MF/Filler", SpellsUsed = new List <string>(new string[] { "IS", "MF", "SF" }) } }); } UpdateSpells(talents, ref calcs); }
// Non-rotation-specific mana calculations private float GetEffectiveManaPool(Character character, CalculationOptionsMoonkin calcOpts, CharacterCalculationsMoonkin calcs) { float fightLength = calcs.FightLength * 60.0f; float innervateCooldown = 360 - calcs.BasicStats.InnervateCooldownReduction; // Mana/5 calculations float totalManaRegen = calcs.ManaRegen5SR * fightLength; // Mana pot calculations float manaRestoredByPots = 0.0f; foreach (Buff b in character.ActiveBuffs) { if (b.Stats.ManaRestore > 0) { manaRestoredByPots = b.Stats.ManaRestore; break; } } // Innervate calculations float innervateDelay = calcOpts.InnervateDelay * 60.0f; int numInnervates = (calcOpts.Innervate && fightLength - innervateDelay > 0) ? ((int)(fightLength - innervateDelay) / (int)innervateCooldown + 1) : 0; float totalInnervateMana = numInnervates * CalculationsMoonkin.BaseMana * (4.5f + (character.DruidTalents.GlyphOfInnervate ? 0.9f : 0.0f)); // Replenishment calculations float replenishmentPerTick = calcs.BasicStats.Mana * calcs.BasicStats.ManaRestoreFromMaxManaPerSecond; float replenishmentMana = calcOpts.ReplenishmentUptime * replenishmentPerTick * calcs.FightLength * 60; return(calcs.BasicStats.Mana + totalInnervateMana + totalManaRegen + manaRestoredByPots + replenishmentMana); }
// Add talented effects to the spells private void UpdateSpells(DruidTalents talents, ref CharacterCalculationsMoonkin calcs) { Stats stats = calcs.BasicStats; // Add (possibly talented) +spelldmg // Starfire: Damage +(0.04 * Wrath of Cenarius) // Wrath: Damage +(0.02 * Wrath of Cenarius) Wrath.SpellDamageModifier += 0.02f * talents.WrathOfCenarius; Starfire.SpellDamageModifier += 0.04f * talents.WrathOfCenarius; // Add spell damage from idols //Starfire.IdolExtraSpellPower += stats.StarfireDmg; Starfire.BaseDamage += stats.StarfireDmg; //Moonfire.IdolExtraSpellPower += stats.MoonfireDmg; Moonfire.BaseDamage += stats.MoonfireDmg; Wrath.BaseDamage += stats.WrathDmg; //InsectSwarm.IdolExtraSpellPower += stats.InsectSwarmDmg; InsectSwarm.DotEffect.TickDamage += stats.InsectSwarmDmg / InsectSwarm.DotEffect.NumberOfTicks; float moonfireDDGlyph = talents.GlyphOfMoonfire ? -0.9f : 0.0f; float moonfireDotGlyph = talents.GlyphOfMoonfire ? 0.75f : 0.0f; float insectSwarmGlyph = talents.GlyphOfInsectSwarm ? 0.3f : 0.0f; // Add spell-specific damage // Starfire, Moonfire, Wrath: Damage +(0.03 * Moonfury) (Additive with 4T9?) // Moonfire: Damage +(0.05 * Imp Moonfire) (Additive with Moonfury/Genesis/Glyph) // Moonfire, Insect Swarm: Dot Damage +(0.01 * Genesis) (Additive with Moonfury/Imp. Moonfire/Glyph/Set bonus) Wrath.AllDamageModifier *= 1 + (float)Math.Floor(talents.Moonfury * 10 / 3.0f) / 100.0f + stats.BonusMoonkinNukeDamage; Moonfire.AllDamageModifier *= 1 + (float)Math.Floor(talents.Moonfury * 10 / 3.0f) / 100.0f + 0.05f * talents.ImprovedMoonfire + moonfireDDGlyph; Moonfire.DotEffect.AllDamageModifier *= 1 + (float)Math.Floor(talents.Moonfury * 10 / 3.0f) / 100.0f + 0.01f * talents.Genesis + 0.05f * talents.ImprovedMoonfire + moonfireDotGlyph; Starfire.AllDamageModifier *= 1 + (float)Math.Floor(talents.Moonfury * 10 / 3.0f) / 100.0f + stats.BonusMoonkinNukeDamage; InsectSwarm.DotEffect.AllDamageModifier *= 1 + 0.01f * talents.Genesis + insectSwarmGlyph + stats.BonusInsectSwarmDamage; // Moonfire, Insect Swarm: One extra tick (Nature's Splendor) Moonfire.DotEffect.Duration += 3.0f * talents.NaturesSplendor; InsectSwarm.DotEffect.Duration += 2.0f * talents.NaturesSplendor; // Moonfire: Crit chance +(0.05 * Imp Moonfire) Moonfire.CriticalChanceModifier += 0.05f * talents.ImprovedMoonfire; // Add spell-specific crit chance // Wrath, Starfire: Crit chance +(0.02 * Nature's Majesty) Wrath.CriticalChanceModifier += 0.02f * talents.NaturesMajesty; Starfire.CriticalChanceModifier += 0.02f * talents.NaturesMajesty; // Add spell-specific critical strike damage // Chaotic Skyfire Diamond Starfire.CriticalDamageModifier = stats.BonusCritMultiplier > 0 ? 1.5f * (1 + stats.BonusCritMultiplier) : 1.5f; Wrath.CriticalDamageModifier = stats.BonusCritMultiplier > 0 ? 1.5f * (1 + stats.BonusCritMultiplier) : 1.5f; Moonfire.CriticalDamageModifier = stats.BonusCritMultiplier > 0 ? 1.5f * (1 + stats.BonusCritMultiplier) : 1.5f; // Starfire, Moonfire, Wrath: Crit damage +(0.2 * Vengeance) Starfire.CriticalDamageModifier = (Starfire.CriticalDamageModifier - 1.0f) * (1 + 0.2f * talents.Vengeance) + 1.0f; Wrath.CriticalDamageModifier = (Wrath.CriticalDamageModifier - 1.0f) * (1 + 0.2f * talents.Vengeance) + 1.0f; Moonfire.CriticalDamageModifier = (Moonfire.CriticalDamageModifier - 1.0f) * (1 + 0.2f * talents.Vengeance) + 1.0f; // Reduce spell-specific mana costs // Starfire, Moonfire, Wrath: Mana cost -(0.03 * Moonglow) Starfire.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); Moonfire.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); Wrath.BaseManaCost *= 1.0f - (0.03f * talents.Moonglow); // Add set bonuses // 2T6 Moonfire.DotEffect.Duration += stats.MoonfireExtension; // 4T6 Starfire.CriticalChanceModifier += stats.StarfireCritChance; // 4T7 Starfire.CriticalChanceModifier += stats.BonusNukeCritChance; Wrath.CriticalChanceModifier += stats.BonusNukeCritChance; // 2T9 Moonfire.DotEffect.CanCrit = stats.MoonfireDotCrit == 1; // Nature's Grace NaturesGrace = talents.NaturesGrace; }
private static void DoTrinketCalcs(CharacterCalculationsMoonkin calcs, SpellRotation rotation, float hitRate, ref float effectiveArcaneDamage, ref float effectiveNatureDamage, ref float effectiveSpellCrit, ref float effectiveSpellHaste) { // Unseen Moon proc if (rotation.HasMoonfire && calcs.BasicStats.UnseenMoonDamageBonus > 0) { float numberOfProcs = 0.5f; // 50% proc chance float timeBetweenProcs = rotation.Duration / numberOfProcs; effectiveArcaneDamage += calcs.BasicStats.UnseenMoonDamageBonus * 10.0f / timeBetweenProcs; effectiveNatureDamage += calcs.BasicStats.UnseenMoonDamageBonus * 10.0f / timeBetweenProcs; } // Ashtongue Talisman of Equilibrium (Moonkin version) if (rotation.StarfireCount > 0 && calcs.BasicStats.DruidAshtongueTrinket > 0) { double starfireCastsIn8Sec = 8.0 / starfire.CastTime; double uptime = 1 - Math.Pow((1 - 0.25), starfireCastsIn8Sec); effectiveArcaneDamage += calcs.BasicStats.DruidAshtongueTrinket * (float)uptime; effectiveNatureDamage += calcs.BasicStats.DruidAshtongueTrinket * (float)uptime; } // The Lightning Capacitor if (calcs.BasicStats.LightningCapacitorProc > 0) { float specialDamageModifier = (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusNatureSpellPowerMultiplier); float baseDamage = (694 + 806) / 2.0f; float averageDamage = hitRate * baseDamage * (1 + 0.5f * calcs.SpellCrit) * specialDamageModifier; float timeBetweenProcs = rotation.Duration / (hitRate * (calcs.SpellCrit + rotation.AverageCritChance) * rotation.CastCount); if (timeBetweenProcs < 2.5f) { timeBetweenProcs = timeBetweenProcs * 3.0f + 2.5f; } else { timeBetweenProcs *= 3.0f; } trinketExtraDPS += averageDamage / timeBetweenProcs; } // Shatterered Sun Pendant (45s internal CD) if (calcs.BasicStats.ShatteredSunAcumenProc > 0) { if (calcs.Scryer) { float specialDamageModifier = (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusArcaneSpellPowerMultiplier); float baseDamage = (333 + 367) / 2.0f; float averageDamage = hitRate * baseDamage * (1 + 0.5f * calcs.SpellCrit) * specialDamageModifier; trinketExtraDPS += averageDamage / 45.0f; } else { effectiveArcaneDamage += 120.0f * 10.0f / 45.0f; effectiveNatureDamage += 120.0f * 10.0f / 45.0f; } } // Timbal's Focusing Crystal (10% proc on a DoT tick, 15s internal cooldown) if (calcs.BasicStats.TimbalsProc > 0 && rotation.TotalDotTicks > 0) { float specialDamageModifier = (1 + calcs.BasicStats.BonusShadowSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusSpellPowerMultiplier); float baseDamage = (285 + 475) / 2.0f; float averageDamage = hitRate * baseDamage * (1 + 0.5f * calcs.SpellCrit) * specialDamageModifier; float timeBetweenProcs = 1 / (rotation.TotalDotTicks / rotation.Duration * 0.1f) + 15.0f; trinketExtraDPS += averageDamage / timeBetweenProcs; } // Spell damage for 10 seconds on resist if (calcs.BasicStats.SpellDamageFor10SecOnResist > 0) { float procsPerRotation = (1 - hitRate) * rotation.CastCount; float timeBetweenProcs = rotation.Duration / procsPerRotation; effectiveArcaneDamage += calcs.BasicStats.SpellDamageFor10SecOnResist * 10.0f / timeBetweenProcs; effectiveNatureDamage += calcs.BasicStats.SpellDamageFor10SecOnResist * 10.0f / timeBetweenProcs; } // 5% chance of spell damage on hit, no cooldown. if (calcs.BasicStats.SpellDamageFor10SecOnHit_5 > 0) { float procsPerRotation = 0.05f * hitRate * rotation.CastCount; float timeBetweenProcs = rotation.Duration / procsPerRotation; effectiveArcaneDamage += calcs.BasicStats.SpellDamageFor10SecOnHit_5 * 10.0f / timeBetweenProcs; effectiveNatureDamage += calcs.BasicStats.SpellDamageFor10SecOnHit_5 * 10.0f / timeBetweenProcs; } // 10% chance of spell damage on hit, 45 second cooldown. if (calcs.BasicStats.SpellDamageFor10SecOnHit_10_45 > 0) { float procsPerRotation = 0.1f * hitRate * rotation.CastCount; float timeBetweenProcs = rotation.Duration / procsPerRotation; effectiveArcaneDamage += calcs.BasicStats.SpellDamageFor10SecOnHit_10_45 * 10.0f / (45.0f + timeBetweenProcs); effectiveNatureDamage += calcs.BasicStats.SpellDamageFor10SecOnHit_10_45 * 10.0f / (45.0f + timeBetweenProcs); } // 20% chance of spell damage on crit, 45 second cooldown. if (calcs.BasicStats.SpellDamageFor10SecOnCrit_20_45 > 0) { float procsPerRotation = 0.2f * hitRate * (effectiveSpellCrit / CalculationsMoonkin.critRatingConversionFactor) * rotation.CastCount; float timeBetweenProcs = rotation.Duration / procsPerRotation; effectiveArcaneDamage += calcs.BasicStats.SpellDamageFor10SecOnHit_5 * 10.0f / (45.0f + timeBetweenProcs); effectiveNatureDamage += calcs.BasicStats.SpellDamageFor10SecOnHit_5 * 10.0f / (45.0f + timeBetweenProcs); } // 15% chance of spell haste on cast, 45-second cooldown (Mystical Skyfire Diamond) if (calcs.BasicStats.SpellHasteFor6SecOnCast_15_45 > 0) { float procsPerRotation = 0.15f * rotation.CastCount; float timeBetweenProcs = rotation.Duration / procsPerRotation; effectiveSpellHaste += calcs.BasicStats.SpellHasteFor6SecOnCast_15_45 * 6.0f / (45.0f + timeBetweenProcs); } // 10% chance of spell haste on hit, 45-second cooldown (Quagmirran's Eye) if (calcs.BasicStats.SpellHasteFor6SecOnHit_10_45 > 0) { float procsPerRotation = 0.1f * hitRate * rotation.CastCount; float timeBetweenProcs = rotation.Duration / procsPerRotation; effectiveSpellHaste += calcs.BasicStats.SpellHasteFor6SecOnHit_10_45 * 6.0f / (45.0f + timeBetweenProcs); } // Haste trinkets if (calcs.BasicStats.SpellHasteFor20SecOnUse2Min > 0) { effectiveSpellHaste += calcs.BasicStats.SpellHasteFor20SecOnUse2Min * 20.0f / 120.0f; } // Spell damage trinkets if (calcs.BasicStats.SpellDamageFor15SecOnUse90Sec > 0) { effectiveArcaneDamage += calcs.BasicStats.SpellDamageFor15SecOnUse90Sec * 15.0f / 90.0f; effectiveNatureDamage += calcs.BasicStats.SpellDamageFor15SecOnUse90Sec * 15.0f / 90.0f; } if (calcs.BasicStats.SpellDamageFor20SecOnUse2Min > 0) { effectiveArcaneDamage += calcs.BasicStats.SpellDamageFor20SecOnUse2Min * 20.0f / 120.0f; effectiveNatureDamage += calcs.BasicStats.SpellDamageFor20SecOnUse2Min * 20.0f / 120.0f; } }
private static void UpdateSpells(Character character, ref CharacterCalculationsMoonkin calcs) { Stats stats = calcs.BasicStats; CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; // Add (possibly talented) +spelldmg // Starfire: Damage +(0.04 * Wrath of Cenarius) // Wrath: Damage +(0.02 * Wrath of Cenarius) wrath.SpellDamageModifier += 0.02f * calcOpts.WrathofCenarius; starfire.SpellDamageModifier += 0.04f * calcOpts.WrathofCenarius; // Add spell damage from idols starfire.DamagePerHit += stats.StarfireDmg; moonfire.DamagePerHit += stats.MoonfireDmg; wrath.DamagePerHit += stats.WrathDmg; // Add spell-specific damage // Starfire, Moonfire, Wrath: Damage +(0.02 * Moonfury) wrath.SpecialDamageModifier *= 1.0f + (0.02f * calcOpts.Moonfury); moonfire.SpecialDamageModifier *= 1.0f + (0.02f * calcOpts.Moonfury); starfire.SpecialDamageModifier *= 1.0f + (0.02f * calcOpts.Moonfury); // Wrath, Insect Swarm: Nature spell damage multipliers wrath.SpecialDamageModifier *= ((1 + calcs.BasicStats.BonusNatureSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusSpellPowerMultiplier)); insectSwarm.SpecialDamageModifier *= ((1 + calcs.BasicStats.BonusNatureSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusSpellPowerMultiplier)); // Starfire, Moonfire: Arcane damage multipliers starfire.SpecialDamageModifier *= ((1 + calcs.BasicStats.BonusArcaneSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusSpellPowerMultiplier)); moonfire.SpecialDamageModifier *= ((1 + calcs.BasicStats.BonusArcaneSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusSpellPowerMultiplier)); // Level-based partial resistances wrath.SpecialDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 70); starfire.SpecialDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 70); moonfire.SpecialDamageModifier *= 1 - 0.02f * (calcs.TargetLevel - 70); // Insect Swarm is a binary spell // Add spell-specific crit chance // Wrath, Starfire: Crit chance +(0.02 * Focused Starlight) wrath.SpecialCriticalModifier += 0.02f * calcOpts.FocusedStarlight; starfire.SpecialCriticalModifier += 0.02f * calcOpts.FocusedStarlight; // Moonfire: Damage, Crit chance +(0.05 * Imp Moonfire) moonfire.SpecialDamageModifier *= 1.0f + (0.05f * calcOpts.ImpMoonfire); moonfire.SpecialCriticalModifier += 0.05f * calcOpts.ImpMoonfire; // Add spell-specific critical strike damage // Starfire, Moonfire, Wrath: Crit damage +(0.2 * Vengeance) starfire.CriticalHitMultiplier *= 1 + 0.2f * calcOpts.Vengeance; moonfire.CriticalHitMultiplier *= 1 + 0.2f * calcOpts.Vengeance; wrath.CriticalHitMultiplier *= 1 + 0.2f * calcOpts.Vengeance; // Chaotic Skyfire Diamond starfire.CriticalHitMultiplier *= 1.0f + 1.5f / 0.5f * stats.BonusSpellCritMultiplier; moonfire.CriticalHitMultiplier *= 1.0f + 1.5f / 0.5f * stats.BonusSpellCritMultiplier; wrath.CriticalHitMultiplier *= 1.0f + 1.5f / 0.5f * stats.BonusSpellCritMultiplier; // Reduce spell-specific mana costs // Starfire, Moonfire, Wrath: Mana cost -(0.03 * Moonglow) starfire.ManaCost *= 1.0f - (0.03f * calcOpts.Moonglow); moonfire.ManaCost *= 1.0f - (0.03f * calcOpts.Moonglow); wrath.ManaCost *= 1.0f - (0.03f * calcOpts.Moonglow); // Reduce spell-specific cast times // Wrath, Starfire: Cast time -(0.1 * Starlight Wrath) wrath.CastTime -= 0.1f * calcOpts.StarlightWrath; starfire.CastTime -= 0.1f * calcOpts.StarlightWrath; // Add set bonuses moonfire.DoT.Duration += stats.MoonfireExtension; starfire.SpecialCriticalModifier += stats.StarfireCritChance; }
public static void Solve(Character character, ref CharacterCalculationsMoonkin calcs) { // Try to reset the cached results dictionary on each call cachedResults = new Dictionary <string, RotationData>(); float effectiveSpellHit = calcs.BasicStats.SpellHitRating; CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; bool naturesGrace = calcOpts.NaturesGrace > 0 ? true : false; float fightLength = calcs.FightLength * 60.0f; float baseHitRate = 0.83f; switch (calcs.TargetLevel) { case 70: baseHitRate = 0.96f; break; case 71: baseHitRate = 0.95f; break; case 72: baseHitRate = 0.94f; break; case 73: baseHitRate = 0.83f; break; default: baseHitRate = 0.83f; break; } if (baseHitRate + effectiveSpellHit / CalculationsMoonkin.hitRatingConversionFactor > 0.99f) { effectiveSpellHit = CalculationsMoonkin.hitRatingConversionFactor * (0.99f - baseHitRate); } RecreateSpells(character, ref calcs); float maxDPS = 0.0f; float maxRawDPS = 0.0f; foreach (SpellRotation rotation in SpellRotations) { // Reset all parameters to defaults Spell.GlobalCooldown = 1.5f; float effectiveArcaneDamage = calcs.ArcaneDamage; float effectiveNatureDamage = calcs.NatureDamage; float effectiveSpellCrit = calcs.BasicStats.SpellCritRating; float effectiveSpellHaste = calcs.BasicStats.SpellHasteRating; float effectiveMana = GetEffectiveManaPool(character, calcs); // Trinkets trinketExtraDPS = 0.0f; // Do a pre-emptive call to rotation.DPS to get corrected cast times for spells rotation.DPS(effectiveArcaneDamage, effectiveNatureDamage, baseHitRate + effectiveSpellHit / CalculationsMoonkin.hitRatingConversionFactor, effectiveSpellCrit / CalculationsMoonkin.critRatingConversionFactor, effectiveSpellHaste / CalculationsMoonkin.hasteRatingConversionFactor, effectiveMana, fightLength, naturesGrace, calcs.BasicStats.StarfireBonusWithDot, calcs.Latency); rotation.ResetRotationalVariables(); DoTrinketCalcs(calcs, rotation, baseHitRate + effectiveSpellHit / CalculationsMoonkin.hitRatingConversionFactor, ref effectiveArcaneDamage, ref effectiveNatureDamage, ref effectiveSpellCrit, ref effectiveSpellHaste); // JoW/mana restore procs effectiveMana += DoManaRestoreCalcs(calcs, rotation, baseHitRate + effectiveSpellHit / CalculationsMoonkin.hitRatingConversionFactor) * (fightLength / rotation.Duration); // Calculate average global cooldown based on effective haste rating (includes trinkets) Spell.GlobalCooldown /= 1 + effectiveSpellHaste * (1 / CalculationsMoonkin.hasteRatingConversionFactor); // Reset the cast time on Insect Swarm and Moonfire, since this is affected by haste insectSwarm.CastTime = Spell.GlobalCooldown; moonfire.CastTime = Spell.GlobalCooldown; // Incorporate Nature's Grace with Moonfire into the rotational calculations if (naturesGrace && rotation.HasMoonfire && rotation.StarfireCount > 0) { float critFromGear = effectiveSpellCrit * (1 / CalculationsMoonkin.critRatingConversionFactor); starfire.CastTime -= ((1 - (rotation.AverageCritChance + critFromGear)) * (moonfire.SpecialCriticalModifier + critFromGear) * 0.5f) / rotation.StarfireCount; } float currentDPS = rotation.DPS(effectiveArcaneDamage, effectiveNatureDamage, baseHitRate + effectiveSpellHit / CalculationsMoonkin.hitRatingConversionFactor, effectiveSpellCrit / CalculationsMoonkin.critRatingConversionFactor, effectiveSpellHaste / CalculationsMoonkin.hasteRatingConversionFactor, effectiveMana, fightLength, naturesGrace, calcs.BasicStats.StarfireBonusWithDot, calcs.Latency) + trinketExtraDPS; // Restore Starfire's cast time because the object is reused if (naturesGrace && rotation.HasMoonfire && rotation.StarfireCount > 0) { float critFromGear = effectiveSpellCrit * (1 / CalculationsMoonkin.critRatingConversionFactor); starfire.CastTime += ((1 - (rotation.AverageCritChance + critFromGear)) * (moonfire.SpecialCriticalModifier + critFromGear) * 0.5f) / rotation.StarfireCount; } float currentRawDPS = rotation.RawDPS + trinketExtraDPS; if (currentDPS > maxDPS) { calcs.SelectedRotation = rotation; maxDPS = currentDPS; } if (currentRawDPS > maxRawDPS) { calcs.MaxDPSRotation = rotation; maxRawDPS = currentRawDPS; } cachedResults[rotation.Name] = new RotationData() { RawDPS = currentRawDPS, DPS = currentDPS, DPM = rotation.DPM, TimeToOOM = rotation.TimeToOOM }; } calcs.SubPoints = new float[] { maxDPS, maxRawDPS }; calcs.OverallPoints = calcs.SubPoints[0] + calcs.SubPoints[1]; calcs.Rotations = cachedResults; }
// Perform damage and mana calculations for all spells in the given rotation. Returns damage done over the total duration. public float DamageDone(Character character, CharacterCalculationsMoonkin calcs, float treantLifespan, float spellPower, float spellHit, float spellCrit, float spellHaste, float masteryPoints, float latency) { CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; DruidTalents talents = character.DruidTalents; Spell sf = Solver.Starfire; Spell ss = Solver.Starsurge; Spell w = Solver.Wrath; Spell mf = Solver.Moonfire; Spell iSw = Solver.InsectSwarm; // 4.1: The bug causing the Eclipse buff to be rounded down to the nearest percent has been fixed float eclipseBonus = 1 + MoonkinSolver.ECLIPSE_BASE + masteryPoints * 0.02f; RotationData.NaturesGraceUptime = (float)GetInterpolatedNGUpime(spellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive, talents.GlyphOfStarfire); RotationData.Duration = (float)GetInterpolatedCastTime(calcs.SpellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive, talents.GlyphOfStarfire); double[] castDistribution = GetInterpolatedCastTable(calcs.SpellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive, talents.GlyphOfStarfire); double percentOfMoonfiresExtended = talents.GlyphOfStarfire ? GetPercentOfMoonfiresExtended(calcs.SpellHaste, calcs.BasicStats.BonusWrathEnergy > 0, calcs.BasicStats.T13FourPieceActive) : 0; DoMainNuke(calcs, ref sf, spellPower, spellHit, spellCrit, spellHaste, RotationData.NaturesGraceUptime, latency); DoMainNuke(calcs, ref ss, spellPower, spellHit, spellCrit, spellHaste, RotationData.NaturesGraceUptime, latency); DoMainNuke(calcs, ref w, spellPower, spellHit, spellCrit, spellHaste, RotationData.NaturesGraceUptime, latency); double gcd = Math.Max(1, 1.5 / (1 + spellHaste)) + latency; double ngGcd = Math.Max(1, 1.5 / (1 + spellHaste) / (1 + 0.05 * talents.NaturesGrace)) + latency; // Moonfire related local variables float mfBaseDur, mfMeanDur, mfMaxDur, mfMeanMaxDur, mfTicks, mfMaxTicks; mfBaseDur = mf.DotEffect.BaseDuration; mfMaxDur = mfBaseDur + (talents.GlyphOfStarfire ? 9f : 0f); // Determine Nature's Grace uptime against Moonfire float mfNGUptime = (float)Math.Min(2 * mfMaxDur / RotationData.Duration, 1); DoDotSpell(calcs, ref mf, spellPower, spellHit, spellCrit, spellHaste, mfNGUptime, latency); // Insect Swarm never benefits from Nature's Grace DoDotSpell(calcs, ref iSw, spellPower, spellHit, spellCrit, spellHaste, 0, latency); mfTicks = mf.DotEffect.NumberOfTicks; mfMaxTicks = mfTicks + (talents.GlyphOfStarfire ? 6 : 0); mfMeanDur = mf.DotEffect.Duration; mfMeanMaxDur = mf.DotEffect.Duration + (talents.GlyphOfStarfire ? 6 * mf.DotEffect.TickLength : 0f); RotationData.MoonfireAvgCast = mf.CastTime; RotationData.InsectSwarmAvgCast = iSw.CastTime; // Break the cast distribution down into its component cast counts double wrathCasts = castDistribution[1] * RotationData.Duration / w.CastTime; double eclipseWrathCasts = castDistribution[5] * RotationData.Duration / w.CastTime; double nonEclipsedWrathPercentage = castDistribution[1] / (castDistribution[1] + castDistribution[5]); double eclipsedWrathPercentage = castDistribution[5] / (castDistribution[1] + castDistribution[5]); RotationData.WrathAvgHit = (float)(nonEclipsedWrathPercentage * w.DamagePerHit + eclipsedWrathPercentage * w.DamagePerHit * eclipseBonus); RotationData.WrathAvgEnergy = w.AverageEnergy; RotationData.WrathCount = (float)(wrathCasts + eclipseWrathCasts); double starfireCasts = castDistribution[0] * RotationData.Duration / sf.CastTime; double eclipseStarfireCasts = castDistribution[4] * RotationData.Duration / sf.CastTime; double nonEclipsedStarfirePercentage = castDistribution[0] / (castDistribution[0] + castDistribution[4]); double eclipsedStarfirePercentage = castDistribution[4] / (castDistribution[0] + castDistribution[4]); RotationData.StarfireAvgHit = (float)(nonEclipsedStarfirePercentage * sf.DamagePerHit + eclipsedStarfirePercentage * sf.DamagePerHit * eclipseBonus); RotationData.StarfireAvgEnergy = sf.AverageEnergy; RotationData.StarfireCount = (float)(starfireCasts + eclipseStarfireCasts); double starsurgeCasts = castDistribution[2] * RotationData.Duration / ss.CastTime; double eclipseStarsurgeCasts = castDistribution[6] * RotationData.Duration / ss.CastTime; double shootingStarsProcs = castDistribution[3] * RotationData.Duration / gcd; double eclipseShootingStarsProcs = castDistribution[7] * RotationData.Duration / gcd; double allStarsurgePercentage = castDistribution[2] + castDistribution[6] + castDistribution[3] + castDistribution[7]; double nonEclipsedStarsurgePercentage = (castDistribution[2] + castDistribution[3]) / allStarsurgePercentage; double eclipsedStarsurgePercentage = (castDistribution[6] + castDistribution[7]) / allStarsurgePercentage; double starsurgePercentage = (castDistribution[2] + castDistribution[6]) / allStarsurgePercentage; double shootingStarsPercentage = (castDistribution[3] + castDistribution[7]) / allStarsurgePercentage; RotationData.StarSurgeAvgHit = (float)(nonEclipsedStarsurgePercentage * ss.DamagePerHit + eclipsedStarsurgePercentage * ss.DamagePerHit * eclipseBonus); RotationData.StarSurgeAvgEnergy = ss.AverageEnergy; RotationData.StarSurgeCount = (float)(starsurgeCasts + eclipseStarsurgeCasts + shootingStarsProcs + eclipseShootingStarsProcs); double moonfireCasts = castDistribution[8] * RotationData.Duration / mf.CastTime; double eclipsedMoonfireCasts = castDistribution[10] * RotationData.Duration / mf.CastTime; double nonEclipsedMoonfirePercentage = castDistribution[8] / (castDistribution[8] + castDistribution[10]); double eclipsedMoonfirePercentage = castDistribution[10] / (castDistribution[8] + castDistribution[10]); RotationData.MoonfireCasts = (float)(moonfireCasts + eclipsedMoonfireCasts); double insectSwarmCasts = castDistribution[9] * RotationData.Duration / iSw.CastTime; double eclipsedInsectSwarmCasts = castDistribution[11] * RotationData.Duration / iSw.CastTime; double nonEclipsedInsectSwarmPercentage = castDistribution[9] / (castDistribution[9] + castDistribution[11]); double eclipsedInsectSwarmPercentage = castDistribution[11] / (castDistribution[9] + castDistribution[11]); RotationData.InsectSwarmCasts = (float)(insectSwarmCasts + eclipsedInsectSwarmCasts); double unextendedMoonfireAverage = nonEclipsedMoonfirePercentage * (mf.DamagePerHit + mf.DotEffect.DamagePerHit) + eclipsedMoonfirePercentage * (mf.DamagePerHit + mf.DotEffect.DamagePerHit) * eclipseBonus; double mfExtendedDotDamage = mfMaxTicks * (mf.DotEffect.DamagePerHit / mf.DotEffect.NumberOfTicks); double extendedMoonfireAverage = nonEclipsedMoonfirePercentage * (mf.DamagePerHit + mfExtendedDotDamage) + eclipsedMoonfirePercentage * (mf.DamagePerHit + mfExtendedDotDamage) * eclipseBonus; RotationData.MoonfireTicks = (float)(percentOfMoonfiresExtended * mfMaxTicks + (1 - percentOfMoonfiresExtended) * mfTicks); RotationData.MoonfireDuration = (float)(percentOfMoonfiresExtended * mfMeanDur + (1 - percentOfMoonfiresExtended) * mfMeanMaxDur); RotationData.MoonfireAvgHit = (float)(percentOfMoonfiresExtended * extendedMoonfireAverage + (1 - percentOfMoonfiresExtended) * unextendedMoonfireAverage); RotationData.InsectSwarmTicks = RotationData.InsectSwarmCasts * iSw.DotEffect.NumberOfTicks; RotationData.InsectSwarmDuration = iSw.DotEffect.Duration; RotationData.InsectSwarmAvgHit = (float)(nonEclipsedInsectSwarmPercentage * iSw.DotEffect.DamagePerHit + eclipsedInsectSwarmPercentage * iSw.DotEffect.DamagePerHit * eclipseBonus); RotationData.StarfireAvgCast = sf.CastTime; RotationData.WrathAvgCast = w.CastTime; RotationData.AverageInstantCast = (float)(gcd * (1 - RotationData.NaturesGraceUptime) + ngGcd * RotationData.NaturesGraceUptime); RotationData.StarSurgeAvgCast = (float)(starsurgePercentage * ss.CastTime + shootingStarsPercentage * RotationData.AverageInstantCast); // Modify the rotation duration to simulate the energy bonus from Dragonwrath procs if (calcs.BasicStats.DragonwrathProc > 0) { float baselineNukeDuration = RotationData.StarfireCount * RotationData.StarfireAvgCast + RotationData.WrathCount * RotationData.WrathAvgCast + RotationData.StarSurgeCount * RotationData.StarSurgeAvgCast; float dragonwrathNukeDuration = baselineNukeDuration / (1 + MoonkinSolver.DRAGONWRATH_PROC_RATE); RotationData.Duration -= (baselineNukeDuration - dragonwrathNukeDuration); } RotationData.LunarUptime = (float)(castDistribution[4] + 0.5 * castDistribution[6] + 0.5 * castDistribution[7] + 0.5 * castDistribution[10]); RotationData.SolarUptime = (float)(castDistribution[5] + 0.5 * castDistribution[6] + 0.5 * castDistribution[7] + 0.5 * castDistribution[10] + castDistribution[11]); float starfallReduction = (float)(starsurgeCasts + shootingStarsProcs + eclipseStarsurgeCasts + eclipseShootingStarsProcs) * 5f; float starfallCooldown = (90f - (talents.GlyphOfStarfall ? 30f : 0f)) - (talents.GlyphOfStarsurge ? starfallReduction : 0); float starfallRatio = talents.Starfall == 1 ? (RotationData.StarfallCastMode == StarfallMode.OnCooldown ? RotationData.AverageInstantCast / (starfallCooldown + RotationData.AverageInstantCast) : 0f) : 0f; float starfallTime = RotationData.StarfallCastMode == StarfallMode.LunarOnly ? RotationData.AverageInstantCast : 0f; float treantRatio = talents.ForceOfNature == 1 ? RotationData.AverageInstantCast / (180f + RotationData.AverageInstantCast) : 0; float starfallBaseDamage = (talents.Starfall > 0 && RotationData.StarfallCastMode == StarfallMode.Unused) ? 0 : DoStarfallCalcs(calcs, spellPower, spellHit, spellCrit); starfallBaseDamage *= 1 + (talents.GlyphOfFocus ? 0.1f : 0f); // Dragonwrath starfallBaseDamage *= 1 + (calcs.BasicStats.DragonwrathProc > 0 ? MoonkinSolver.DRAGONWRATH_PROC_RATE : 0f); float starfallEclipseDamage = starfallBaseDamage * eclipseBonus; RotationData.TreantDamage = talents.ForceOfNature == 0 ? 0 : DoTreeCalcs(calcs, character.Level, character.BossOptions.Level, spellPower, treantLifespan); // T12 2-piece: 2-sec cast, 5192-6035 damage, affected by hit, 15-sec duration float T122PieceHitDamage = (5192 + 6035) / 2f * spellHit * (1 + calcs.BasicStats.BonusFireDamageMultiplier); // I'm going to assume a 150% crit modifier on the 2T12 proc until I'm told otherwise float T122PieceCritDamage = T122PieceHitDamage * 1.5f; // Use 2.5% crit rate based on EJ testing // Hard-code 4.5 casts/proc based on EJ testing float T122PieceBaseDamage = (0.975f * T122PieceHitDamage + 0.025f * T122PieceCritDamage) * 4.5f; // Without glyph of Starsurge, you cannot fit a Starfall in every Lunar eclipse. // The actual result will be better than 1/2, because you will be able to cast SFall later in each Eclipse as the fight goes on, // but you will miss a Lunar proc entirely eventually. float starfallCooldownOverlap = starfallCooldown - RotationData.Duration; float rotationsToMiss = starfallCooldownOverlap > 0 ? RotationData.Duration * RotationData.LunarUptime / starfallCooldownOverlap : 0f; float starfallFraction = rotationsToMiss > 0 ? (float)(Math.Ceiling(rotationsToMiss) / (Math.Ceiling(rotationsToMiss) + 1)) : 1f; RotationData.StarfallCasts = RotationData.StarfallCastMode == StarfallMode.OnCooldown ? starfallRatio * RotationData.Duration / RotationData.AverageInstantCast : (RotationData.StarfallCastMode == StarfallMode.LunarOnly ? starfallFraction : 0f); RotationData.TreantCasts = treantRatio * RotationData.Duration / RotationData.AverageInstantCast; RotationData.StarfallStars = 10f; if (RotationData.StarfallCastMode == StarfallMode.LunarOnly) RotationData.LunarUptime += starfallFraction * RotationData.AverageInstantCast / RotationData.Duration; else if (RotationData.StarfallCastMode == StarfallMode.OnCooldown) { RotationData.SolarUptime *= 1 + starfallRatio; RotationData.LunarUptime *= 1 + starfallRatio; } RotationData.Duration += RotationData.StarfallCasts * RotationData.AverageInstantCast + RotationData.TreantCasts * RotationData.AverageInstantCast; RotationData.StarfallDamage = RotationData.StarfallCastMode == StarfallMode.OnCooldown ? RotationData.LunarUptime * starfallEclipseDamage + (1 - RotationData.LunarUptime) * starfallBaseDamage : starfallEclipseDamage; float moonfireDamage = RotationData.MoonfireAvgHit * RotationData.MoonfireCasts; float insectSwarmDamage = RotationData.InsectSwarmAvgHit * RotationData.InsectSwarmCasts; // Calculate total damage done for external cooldowns per rotation float starfallDamage = RotationData.StarfallDamage * RotationData.StarfallCasts; float treantDamage = RotationData.TreantDamage * RotationData.TreantCasts; float T122PieceDamage = 0f; if (calcs.BasicStats.ContainsSpecialEffect(se => se.Trigger == Trigger.MageNukeCast)) { foreach (SpecialEffect effect in calcs.BasicStats.SpecialEffects(se => se.Trigger == Trigger.MageNukeCast)) { T122PieceDamage = T122PieceBaseDamage * effect.GetAverageUptime(RotationData.Duration / (RotationData.WrathCount + RotationData.StarfireCount), 1f); } } // Calculate mana cost per cast. // Starfall - 35% of base mana float starfallManaCost = (int)(0.35f * MoonkinSolver.BaseMana) - calcs.BasicStats.SpellsManaCostReduction - calcs.BasicStats.NatureSpellsManaCostReduction; // Force of Nature - 12% of base mana float treantManaCost = (int)(0.12f * MoonkinSolver.BaseMana) - calcs.BasicStats.SpellsManaCostReduction - calcs.BasicStats.NatureSpellsManaCostReduction; RotationData.CastCount = RotationData.WrathCount + RotationData.StarfireCount + RotationData.StarSurgeCount + RotationData.MoonfireCasts + RotationData.InsectSwarmCasts + RotationData.StarfallCasts + RotationData.TreantCasts; RotationData.DotTicks = RotationData.InsectSwarmTicks + RotationData.MoonfireTicks; RotationData.ManaUsed = RotationData.WrathCount * w.BaseManaCost + RotationData.StarfireCount * sf.BaseManaCost + RotationData.StarSurgeCount * ss.BaseManaCost + RotationData.MoonfireCasts * mf.BaseManaCost + RotationData.InsectSwarmCasts * iSw.BaseManaCost + RotationData.StarfallCasts * starfallManaCost + RotationData.TreantCasts * treantManaCost; float manaSavingsFromOOC = MoonkinSolver.OOC_PROC_CHANCE * (RotationData.MoonfireCasts / RotationData.CastCount * mf.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.InsectSwarmCasts / RotationData.CastCount * iSw.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.StarfireCount / RotationData.CastCount * sf.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.WrathCount / RotationData.CastCount * w.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.StarSurgeCount / RotationData.CastCount * ss.BaseManaCost) + MoonkinSolver.OOC_PROC_CHANCE * (RotationData.StarfallCasts / RotationData.CastCount * starfallManaCost); RotationData.ManaUsed -= manaSavingsFromOOC; RotationData.ManaGained = 2 * MoonkinSolver.EUPHORIA_PERCENT * talents.Euphoria * calcs.BasicStats.Mana; return RotationData.WrathAvgHit * RotationData.WrathCount + RotationData.StarfireAvgHit * RotationData.StarfireCount + RotationData.StarSurgeAvgHit * RotationData.StarSurgeCount + moonfireDamage + insectSwarmDamage + treantDamage + starfallDamage + T122PieceDamage; }
// Now returns damage per cast to allow adjustments for fight length private float DoTreeCalcs(CharacterCalculationsMoonkin calcs, int playerLevel, int bossLevel, float effectiveNatureDamage, float treantLifespan) { float sunderPercent = calcs.BasicStats.TargetArmorReduction; float meleeHit = calcs.SpellHit * (StatConversion.WHITE_MISS_CHANCE_CAP[bossLevel - playerLevel] / StatConversion.GetSpellMiss(playerLevel - bossLevel, false)); float physicalDamageMultiplierBonus = (1f + calcs.BasicStats.BonusDamageMultiplier) * (1f + calcs.BasicStats.BonusPhysicalDamageMultiplier); float physicalDamageMultiplierReduc = (1f - calcs.BasicStats.DamageTakenReductionMultiplier) * (1f - calcs.BasicStats.PhysicalDamageTakenReductionMultiplier); // 932 = base AP, 57% spell power scaling float attackPower = 932.0f + (float)Math.Floor(0.57f * effectiveNatureDamage); // 1.65 s base swing speed float baseAttackSpeed = 1.65f; float attackSpeed = baseAttackSpeed / (1 + calcs.BasicStats.PhysicalHaste); // 580 = base DPS float damagePerHit = (580f + attackPower / 14.0f) * baseAttackSpeed; // 5% base crit rate, inherit crit debuffs // Remove crit depression, as it doesn't appear to have an effect (unless it's base ~10% crit rate) float critRate = 0.05f; // White hit glancing rate float glancingRate = StatConversion.WHITE_GLANCE_CHANCE_CAP[bossLevel - playerLevel]; // Hit rate determined by the amount of melee hit, not by spell hit float missRate = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[bossLevel - playerLevel] - meleeHit); // Since the trees inherit expertise from their hit, scale their hit rate such that when they are hit capped, they are expertise capped float dodgeRate = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[bossLevel - playerLevel] * (missRate / StatConversion.WHITE_MISS_CHANCE_CAP[bossLevel - playerLevel])); // Armor damage reduction, including Sunder float damageReduction = StatConversion.GetArmorDamageReduction(playerLevel, StatConversion.NPC_ARMOR[bossLevel - playerLevel] * (1f - sunderPercent), 0, 0); // Final normal damage per swing damagePerHit *= 1.0f - damageReduction; damagePerHit *= physicalDamageMultiplierReduc; damagePerHit *= physicalDamageMultiplierBonus; // Damage per swing, including crits/glances/misses // This is a cheesy approximation of a true combat table, but because crit/miss/dodge rates will all be fairly low, I don't need to do the whole thing damagePerHit = (critRate * damagePerHit * 2.0f) + (glancingRate * damagePerHit * 0.75f) + ((1 - critRate - glancingRate - missRate - dodgeRate) * damagePerHit); // Total damage done in their estimated lifespan float damagePerTree = (treantLifespan * 30.0f / attackSpeed) * damagePerHit; return 3 * damagePerTree; }
public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, bool referenceCalculation, bool significantChange, bool needsDisplayCalculations) { // First things first, we need to ensure that we aren't using bad data CharacterCalculationsMoonkin calc = new CharacterCalculationsMoonkin(); if (character == null) { return calc; } CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; if (calcOpts == null) { return calc; } // _reforgePriority = calcOpts.ReforgePriority; _enableSpiritToHit = calcOpts.AllowReforgingSpiritToHit; StatsMoonkin stats = (StatsMoonkin)GetCharacterStats(character, additionalItem); calc.BasicStats = stats; calc.SpellPower = (float)Math.Floor((1 + stats.BonusSpellPowerMultiplier) * (stats.SpellPower + stats.Intellect - 10)); calc.SpellCrit = StatConversion.GetSpellCritFromIntellect(stats.Intellect) + StatConversion.GetSpellCritFromRating(stats.CritRating) + stats.SpellCrit + stats.SpellCritOnTarget; calc.SpellHit = StatConversion.GetSpellHitFromRating(stats.HitRating) + stats.SpellHit; calc.SpellHitCap = StatConversion.GetSpellMiss(character.Level - character.BossOptions.Level, false); calc.SpellHaste = (1 + StatConversion.GetSpellHasteFromRating(stats.HasteRating)) * (1 + stats.SpellHaste) - 1; calc.Mastery = 8.0f + StatConversion.GetMasteryFromRating(stats.MasteryRating); calc.ManaRegen = stats.Mp5 / 5f; // Generate the cycles /*if (referenceCalculation) { using (System.IO.StreamWriter writer = System.IO.File.CreateText("C:\\users\\Noah\\Desktop\\CastDistribution.txt")) { //MoonkinCycleGenerator generator = new MoonkinCycleGenerator //{ //EuphoriaChance = 0.24, //Has4T12 = false, //HasteLevel = 0, //ShootingStarsChance = 0.04, //StarlightWrathLevel = 3 //}; MoonkinSimulator generator = new MoonkinSimulator() { HasGlyphOfStarfire = false, Has4T13 = true }; writer.WriteLine("public static double[,] T13CastDistribution = new double[21, 12] {"); double[] baseRotationLengths = new double[21]; double[] baseNGUptimes = new double[21]; double[] baseMFExtended = new double[21]; for (int haste = 0; haste <= 100; haste += 5) { generator.HasteLevel = haste / 100.0; double[] values = generator.GenerateCycle(); writer.Write("{"); for (int i = 0; i < values.Length; ++i) { writer.Write(String.Format(" {0},", values[i])); } writer.WriteLine(" },"); baseRotationLengths[haste / 5] = generator.GetRotationLength(); baseNGUptimes[haste / 5] = generator.GetNGUptime(); //baseMFExtended[haste / 5] = generator.GetPercentMoonfiresExtended(); } writer.WriteLine("};"); generator.HasGlyphOfStarfire = true; writer.WriteLine("public static double[,] T13CastDistributionGoSF = new double[21, 12] {"); double[] T12RotationLengths = new double[21]; double[] T12NGUptimes = new double[21]; double[] T12MFExtended = new double[21]; for (int haste = 0; haste <= 100; haste += 5) { generator.HasteLevel = haste / 100.0; double[] values = generator.GenerateCycle(); writer.Write("{"); for (int i = 0; i < values.Length; ++i) { writer.Write(String.Format(" {0},", values[i])); } writer.WriteLine(" },"); T12RotationLengths[haste / 5] = generator.GetRotationLength(); T12NGUptimes[haste / 5] = generator.GetNGUptime(); T12MFExtended[haste / 5] = generator.GetPercentMoonfiresExtended(); } writer.WriteLine("};"); writer.Write("public static double[] T13RotationDurations = new double[21] {"); for (int i = 0; i < baseRotationLengths.Length; ++i) { writer.Write(String.Format(" {0},", baseRotationLengths[i])); } writer.WriteLine(" };"); writer.Write("public static double[] T13RotationDurationsGoSF = new double[21] {"); for (int i = 0; i < T12RotationLengths.Length; ++i) { writer.Write(String.Format(" {0},", T12RotationLengths[i])); } writer.WriteLine(" };"); writer.Write("public static double[] T13NGUptimes = new double[21] {"); for (int i = 0; i < baseNGUptimes.Length; ++i) { writer.Write(String.Format(" {0},", baseNGUptimes[i])); } writer.WriteLine(" };"); writer.Write("public static double[] T13NGUptimesGoSF = new double[21] {"); for (int i = 0; i < T12NGUptimes.Length; ++i) { writer.Write(String.Format(" {0},", T12NGUptimes[i])); } writer.WriteLine(" };"); //writer.Write("public static double[] BasePercentMoonfiresExtended = new double[21] {"); //for (int i = 0; i < baseMFExtended.Length; ++i) //{ //writer.Write(String.Format(" {0},", baseMFExtended[i])); //} //writer.WriteLine(" };"); writer.Write("public static double[] T13PercentMoonfiresExtended = new double[21] {"); for (int i = 0; i < T12MFExtended.Length; ++i) { writer.Write(String.Format(" {0},", T12MFExtended[i])); } writer.WriteLine(" };"); } System.Windows.Application.Current.Shutdown(); }*/ // Run the solver against the generated cycle new MoonkinSolver().Solve(character, ref calc); return calc; }
private float DoMushroomCalcs(CharacterCalculationsMoonkin calcs, float effectiveNatureDamage, float spellHit, float spellCrit) { float hitDamageModifier = (1 + calcs.BasicStats.BonusSpellDamageMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusNatureDamageMultiplier); float critDamageModifier = 1.5f * (1 + calcs.BasicStats.BonusCritDamageMultiplier); // 845-1022 damage float baseDamage = (845 + 1022) / 2; float damagePerHit = (baseDamage + effectiveNatureDamage * 0.6032f) * hitDamageModifier; float damagePerCrit = damagePerHit * critDamageModifier; return spellHit * (damagePerHit * (1 - spellCrit) + damagePerCrit * spellCrit); }
// Perform damage and mana calculations for all spells in the given rotation. Returns damage done over the total duration. public float DamageDone(DruidTalents talents, CharacterCalculationsMoonkin calcs, float spellPower, float spellHit, float spellCrit, float spellHaste) { if (talents.Eclipse > 0) { return(DoEclipseCalcs(talents, calcs, Solver, spellPower, spellHit, spellCrit, spellHaste)); } float latency = calcs.Latency; float moonkinFormProc = (talents.MoonkinForm == 1) ? 0.02f * calcs.BasicStats.Mana : 0.0f; bool starfireGlyph = talents.GlyphOfStarfire; int impInsectSwarm = talents.ImprovedInsectSwarm; switch (SpellsUsed.Count) { // Nuke only case 1: Spell mainNuke = Solver.FindSpell(SpellsUsed[0]); DoMainNuke(talents, calcs, ref mainNuke, spellPower, spellHit, spellCrit, spellHaste); float omenProcChance = talents.OmenOfClarity == 1 ? 0.06f : 0; mainNuke.ManaCost = mainNuke.BaseManaCost - mainNuke.BaseCastTime / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana * spellHit - (spellCrit + mainNuke.CriticalChanceModifier) * moonkinFormProc - mainNuke.BaseManaCost * omenProcChance * spellHit; Duration = mainNuke.CastTime; RotationData.ManaUsed = ManaUsed = mainNuke.ManaCost; RotationData.ManaGained = ManaGained = mainNuke.BaseManaCost - mainNuke.ManaCost; RotationData.DPM = mainNuke.DamagePerHit / mainNuke.ManaCost; CastCount = 1.0f; DotTicks = 0.0f; WrathCount = mainNuke.Name == "W" ? 1.0f : 0.0f; StarfireCount = mainNuke.Name == "SF" ? 1.0f : 0.0f; return(mainNuke.DamagePerHit); // Nuke + 1 DotEffect case 2: // Find the spells Spell DotEffectSpell = Solver.FindSpell(SpellsUsed[0]); mainNuke = Solver.FindSpell(SpellsUsed[1]); // Do Starfire glyph calculations, if applicable; then do DoT spell calculations if (starfireGlyph && mainNuke.Name == "SF" && DotEffectSpell.Name == "MF") { DotEffectSpell.DotEffect.Duration += 9.0f; } DoDotSpell(talents, calcs, ref DotEffectSpell, spellPower, spellHit, spellCrit, spellHaste); // Do iIS calculations, if applicable if (impInsectSwarm > 0) { if (mainNuke.Name == "SF" && DotEffectSpell.Name == "MF") { mainNuke.CriticalChanceModifier += 0.01f * impInsectSwarm; } else if (mainNuke.Name == "W" && DotEffectSpell.Name == "IS") { mainNuke.AllDamageModifier *= 1 + 0.01f * impInsectSwarm; } } // Calculate main nuke damage DoMainNuke(talents, calcs, ref mainNuke, spellPower, spellHit, spellCrit, spellHaste); // Set rotation duration Duration = DotEffectSpell.DotEffect.Duration; // Calculate mana usage and damage done for this rotation float timeSpentCastingNuke = Duration - DotEffectSpell.CastTime; float nukeDamageDone = mainNuke.DamagePerHit / mainNuke.CastTime * timeSpentCastingNuke; float numNukeCasts = timeSpentCastingNuke / mainNuke.CastTime; float nukeManaSpent = mainNuke.BaseManaCost * numNukeCasts; float totalManaSpent = nukeManaSpent + DotEffectSpell.BaseManaCost; CastCount = numNukeCasts + 1.0f; WrathCount = mainNuke.Name == "W" ? numNukeCasts : 0.0f; StarfireCount = mainNuke.Name == "SF" ? numNukeCasts : 0.0f; DotTicks = DotEffectSpell.DotEffect.NumberOfTicks; if (DotEffectSpell.Name == "IS") { InsectSwarmTicks = DotTicks; } else if (DotEffectSpell.Name == "MF") { MoonfireTicks = DotTicks; MoonfireCasts = 1f; } float manaFromJoW = (mainNuke.ManaCost - mainNuke.BaseCastTime / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana) * numNukeCasts; manaFromJoW += DotEffectSpell.ManaCost - 1.5f / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana; float manaFromOoC = ((0.06f) * mainNuke.BaseManaCost + (numNukeCasts - 1) * (0.06f) * mainNuke.BaseManaCost + (0.06f) * DotEffectSpell.BaseManaCost) * spellHit; float manaFromMoonkin = moonkinFormProc * spellHit * ((spellCrit + mainNuke.CriticalChanceModifier) * numNukeCasts + DotEffectSpell.CriticalChanceModifier); float actualManaSpent = totalManaSpent - manaFromJoW - manaFromMoonkin - (talents.OmenOfClarity == 1 ? manaFromOoC : 0.0f); RotationData.ManaUsed = ManaUsed = actualManaSpent; RotationData.ManaGained = ManaGained = totalManaSpent - actualManaSpent; RotationData.DPM = (nukeDamageDone + DotEffectSpell.DamagePerHit + DotEffectSpell.DotEffect.DamagePerHit) / totalManaSpent; // Undo iIS, if applicable if (impInsectSwarm > 0) { if (mainNuke.Name == "SF" && DotEffectSpell.Name == "MF") { mainNuke.CriticalChanceModifier -= 0.01f * impInsectSwarm; } else if (mainNuke.Name == "W" && DotEffectSpell.Name == "IS") { mainNuke.AllDamageModifier /= 1 + 0.01f * impInsectSwarm; } } // Undo SF glyph, if applicable if (starfireGlyph && mainNuke.Name == "SF" && DotEffectSpell.Name == "MF") { DotEffectSpell.DotEffect.Duration -= 9.0f; } // Return the damage done per rotation return(nukeDamageDone + DotEffectSpell.DamagePerHit + DotEffectSpell.DotEffect.DamagePerHit); // Nuke + both DotEffects case 3: // Find the spells Spell moonFire = Solver.FindSpell(SpellsUsed[0]); Spell insectSwarm = Solver.FindSpell(SpellsUsed[1]); mainNuke = Solver.FindSpell(SpellsUsed[2]); // Do Starfire glyph calculations, if applicable; then do DoT spell calculations if (starfireGlyph && mainNuke.Name == "SF") { moonFire.DotEffect.Duration += 9.0f; } DoDotSpell(talents, calcs, ref moonFire, spellPower, spellHit, spellCrit, spellHaste); DoDotSpell(talents, calcs, ref insectSwarm, spellPower, spellHit, spellCrit, spellHaste); // Do iIS calculations, if applicable if (impInsectSwarm > 0) { if (mainNuke.Name == "SF") { mainNuke.CriticalChanceModifier += 0.01f * impInsectSwarm; } else if (mainNuke.Name == "W") { mainNuke.AllDamageModifier *= 1 + 0.01f * impInsectSwarm; } } // Calculate main nuke damage DoMainNuke(talents, calcs, ref mainNuke, spellPower, spellHit, spellCrit, spellHaste); // Set rotation duration Duration = moonFire.DotEffect.Duration; // Calculate mana usage and damage done for this rotation float timeSpentCastingIS = insectSwarm.CastTime * moonFire.DotEffect.Duration / insectSwarm.DotEffect.Duration; float insectSwarmDamage = insectSwarm.DotEffect.DamagePerHit * moonFire.DotEffect.Duration / insectSwarm.DotEffect.Duration; timeSpentCastingNuke = Duration - timeSpentCastingIS - moonFire.CastTime; nukeDamageDone = mainNuke.DamagePerHit / mainNuke.CastTime * timeSpentCastingNuke; numNukeCasts = timeSpentCastingNuke / mainNuke.CastTime; float numISCasts = timeSpentCastingIS / insectSwarm.CastTime; nukeManaSpent = mainNuke.BaseManaCost * numNukeCasts; totalManaSpent = nukeManaSpent + moonFire.BaseManaCost + numISCasts * insectSwarm.BaseManaCost; CastCount = numNukeCasts + numISCasts + 1.0f; WrathCount = mainNuke.Name == "W" ? numNukeCasts : 0.0f; StarfireCount = mainNuke.Name == "SF" ? numNukeCasts : 0.0f; DotTicks = moonFire.DotEffect.NumberOfTicks + numISCasts * insectSwarm.DotEffect.NumberOfTicks; InsectSwarmTicks = numISCasts * insectSwarm.DotEffect.NumberOfTicks; MoonfireTicks = moonFire.DotEffect.NumberOfTicks; MoonfireCasts = 1.0f; manaFromJoW = numNukeCasts * (mainNuke.ManaCost - mainNuke.BaseCastTime / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana) + (moonFire.ManaCost - 1.5f / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana) + numISCasts * (insectSwarm.ManaCost - 1.5f / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana); manaFromOoC = ((0.06f) * mainNuke.BaseManaCost + (numNukeCasts - 1 - numISCasts) * (0.06f) * mainNuke.BaseManaCost + (0.06f) * moonFire.BaseManaCost + (0.06f) * numISCasts * insectSwarm.BaseManaCost + (0.06f) * numISCasts * mainNuke.BaseManaCost) * spellHit; manaFromMoonkin = moonkinFormProc * spellHit * ((spellCrit + mainNuke.CriticalChanceModifier) * numNukeCasts + moonFire.CriticalChanceModifier); actualManaSpent = totalManaSpent - manaFromJoW - manaFromMoonkin - (talents.OmenOfClarity == 1 ? manaFromOoC : 0.0f); RotationData.ManaUsed = ManaUsed = actualManaSpent; RotationData.ManaGained = ManaGained = totalManaSpent - actualManaSpent; RotationData.DPM = (nukeDamageDone + moonFire.DamagePerHit + moonFire.DotEffect.DamagePerHit + insectSwarm.DotEffect.DamagePerHit) / totalManaSpent; // Undo iIS, if applicable if (impInsectSwarm > 0) { if (mainNuke.Name == "SF") { mainNuke.CriticalChanceModifier -= 0.01f * impInsectSwarm; } else if (mainNuke.Name == "W") { mainNuke.AllDamageModifier /= 1 + 0.01f * impInsectSwarm; } } // Undo SF glyph if (starfireGlyph && mainNuke.Name == "SF") { moonFire.DotEffect.Duration -= 9.0f; } // Return the damage done per rotation return(nukeDamageDone + moonFire.DamagePerHit + moonFire.DotEffect.DamagePerHit + insectSwarmDamage); default: throw new Exception("Invalid rotation specified in rotation solver."); } }
// Starfall private float DoStarfallCalcs(CharacterCalculationsMoonkin calcs, float effectiveArcaneDamage, float spellHit, float spellCrit) { float hitDamageModifier = (1 + calcs.BasicStats.BonusSpellDamageMultiplier) * (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusArcaneDamageMultiplier); // Starfall is affected by Moonfury float critDamageModifier = 1.5f * (1 + calcs.BasicStats.BonusCritDamageMultiplier) + (1.5f * (1 + calcs.BasicStats.BonusCritDamageMultiplier) - 1); float baseDamagePerStar = (370.0f + 428.0f) / 2.0f; float mainStarCoefficient = 0.247f; float damagePerBigStarHit = (baseDamagePerStar + effectiveArcaneDamage * mainStarCoefficient) * hitDamageModifier; float critDamagePerBigStarHit = damagePerBigStarHit * critDamageModifier; float averageDamagePerBigStar = spellCrit * critDamagePerBigStarHit + (1 - spellCrit) * damagePerBigStarHit; float numberOfStarHits = 10f; float avgNumBigStarsHit = spellHit * numberOfStarHits; return avgNumBigStarsHit * averageDamagePerBigStar; }
private float DoEclipseCalcs(DruidTalents talents, CharacterCalculationsMoonkin calcs, MoonkinSolver solver, float spellPower, float spellHit, float spellCrit, float spellHaste) { float latency = calcs.Latency; float omenOfClarityProcChance = talents.OmenOfClarity * 0.06f; float moonkinFormProc = (talents.MoonkinForm == 1) ? 0.02f * calcs.BasicStats.Mana : 0.0f; bool starfireGlyph = talents.GlyphOfStarfire; int impInsectSwarm = talents.ImprovedInsectSwarm; float moonfireCasts = SpellsUsed.Contains("MF") ? 1.0f : 0.0f; float insectSwarmCasts = SpellsUsed.Contains("IS") ? 1.0f : 0.0f; Spell moonfire = moonfireCasts > 0 ? solver.FindSpell("MF") : null; Spell insectSwarm = insectSwarmCasts > 0 ? solver.FindSpell("IS") : null; // Do SF glyph if (starfireGlyph && moonfire != null) { moonfire.DotEffect.Duration += 9.0f; } float eclipseMultiplier = 0.4f + calcs.BasicStats.EclipseBonus; float eclipseDuration = 15.0f; //float eclipseCooldown = 30.0f; Spell preLunarCast = solver.FindSpell("W"); // Do improved Insect Swarm if (insectSwarm != null) { preLunarCast.AllDamageModifier *= 1 + 0.01f * impInsectSwarm; } Spell solarEclipseCast = new Spell(preLunarCast); // Eclipse bonus and improved Insect Swarm // NOTE: Eclipse bonus additive with Moonfury and 4T9; multiplicative with everything else solarEclipseCast.AllDamageModifier = 1 + (float)Math.Floor(talents.Moonfury * 10 / 3.0f) / 100.0f + calcs.BasicStats.BonusMoonkinNukeDamage + eclipseMultiplier; if (insectSwarm != null) { solarEclipseCast.AllDamageModifier *= 1 + 0.01f * impInsectSwarm; } Spell preSolarCast = solver.FindSpell("SF"); if (moonfire != null) { preSolarCast.CriticalChanceModifier += 0.01f * impInsectSwarm; } Spell lunarEclipseCast = new Spell(preSolarCast); lunarEclipseCast.CriticalChanceModifier = (float)Math.Min(1.0f - spellCrit, lunarEclipseCast.CriticalChanceModifier + eclipseMultiplier); DoMainNuke(talents, calcs, ref preSolarCast, spellPower, spellHit, spellCrit, spellHaste); DoMainNuke(talents, calcs, ref solarEclipseCast, spellPower, spellHit, spellCrit, spellHaste); DoMainNuke(talents, calcs, ref preLunarCast, spellPower, spellHit, spellCrit, spellHaste); DoMainNuke(talents, calcs, ref lunarEclipseCast, spellPower, spellHit, spellCrit, spellHaste); float moonfireRatio = 0.0f; float insectSwarmRatio = 0.0f; if (moonfire != null) { DoDotSpell(talents, calcs, ref moonfire, spellPower, spellHit, spellCrit, spellHaste); moonfireRatio = moonfire.CastTime / moonfire.DotEffect.Duration; } if (insectSwarm != null) { DoDotSpell(talents, calcs, ref insectSwarm, spellPower, spellHit, spellCrit, spellHaste); insectSwarmRatio = insectSwarm.CastTime / insectSwarm.DotEffect.Duration; } float castRatio = moonfireRatio + (1 - moonfireRatio) * insectSwarmRatio; float lunarProcChance = (spellCrit + preLunarCast.CriticalChanceModifier) * spellHit * talents.Eclipse / 3.0f * 0.6f; float castsToProcLunar = 1.0f / lunarProcChance; float timeToProcLunar = preLunarCast.CastTime * (castsToProcLunar - 0.5f); float solarProcChance = (spellCrit + preSolarCast.CriticalChanceModifier) * spellHit * talents.Eclipse / 3.0f; float castsToProcSolar = 1.0f / solarProcChance; float timeToProcSolar = preSolarCast.CastTime * (castsToProcSolar - 0.5f); float preLunarTime = timeToProcLunar + (preLunarCast.CastTime * 0.5f) + preLunarCast.NGCastTime * 1.5f; float preLunarDPS = preLunarCast.DamagePerHit / preLunarCast.CastTime; float preLunarManaUsed = preLunarCast.BaseManaCost / preLunarCast.CastTime * preLunarTime; float preLunarManaGained = (preLunarCast.BaseManaCost * omenOfClarityProcChance) + ((spellCrit + preLunarCast.CriticalChanceModifier) * spellHit * moonkinFormProc) + (preLunarCast.BaseCastTime / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana); float lunarTime = (eclipseDuration - (preLunarCast.NGCastTime * 1.5f) - lunarEclipseCast.CastTime * 0.5f) * (1.0f - castRatio); float lunarDPS = lunarEclipseCast.DamagePerHit / lunarEclipseCast.CastTime; float lunarManaUsed = lunarEclipseCast.BaseManaCost / lunarEclipseCast.CastTime * lunarTime; float lunarManaGained = (lunarEclipseCast.BaseManaCost * omenOfClarityProcChance) + ((spellCrit + lunarEclipseCast.CriticalChanceModifier) * spellHit * moonkinFormProc) + (lunarEclipseCast.BaseCastTime / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana); float preSolarTime = timeToProcSolar + (lunarEclipseCast.CastTime * 0.5f) + preSolarCast.NGCastTime; float preSolarDPS = preSolarCast.DamagePerHit / preSolarCast.CastTime; float preSolarManaUsed = preSolarCast.BaseManaCost / preSolarCast.CastTime * preSolarTime; float preSolarManaGained = (preSolarCast.BaseManaCost * omenOfClarityProcChance) + ((spellCrit + preSolarCast.CriticalChanceModifier) * spellHit * moonkinFormProc) + (preSolarCast.BaseCastTime / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana); float solarTime = (eclipseDuration - (preSolarCast.NGCastTime) - (preLunarCast.CastTime * 0.5f)) * (1.0f - castRatio); float solarDPS = solarEclipseCast.DamagePerHit / solarEclipseCast.CastTime; float solarManaUsed = solarEclipseCast.BaseManaCost / solarEclipseCast.CastTime * solarTime; float solarManaGained = (solarEclipseCast.BaseManaCost * omenOfClarityProcChance) + ((spellCrit + solarEclipseCast.CriticalChanceModifier) * spellHit * moonkinFormProc) + (solarEclipseCast.BaseCastTime / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana); float rotationLength = (solarTime + lunarTime + preSolarTime + preLunarTime) / (1 - castRatio); float etSpentOnMFMFPriority = rotationLength * moonfireRatio; float etSpentOnISMFPriority = rotationLength * castRatio - etSpentOnMFMFPriority; float etSpentOnISISPriority = rotationLength * insectSwarmRatio; float etSpentOnMFISPriority = rotationLength * castRatio - etSpentOnISISPriority; float moonfireDamagePerCastTime = moonfireRatio > 0 ? (moonfire.DamagePerHit + moonfire.DotEffect.DamagePerHit) / moonfire.CastTime : 0.0f; float insectSwarmDamagePerCastTime = insectSwarmRatio > 0 ? insectSwarm.DotEffect.DamagePerHit / insectSwarm.CastTime : 0.0f; float dotDamageMoonfirePriority = etSpentOnMFMFPriority * moonfireDamagePerCastTime + etSpentOnISMFPriority * insectSwarmDamagePerCastTime; float dotDamageInsectSwarmPriority = etSpentOnISISPriority * insectSwarmDamagePerCastTime + etSpentOnMFISPriority * moonfireDamagePerCastTime; float totalDotDamage = 0.0f; if (dotDamageMoonfirePriority > dotDamageInsectSwarmPriority) { moonfireCasts = moonfireRatio > 0 ? etSpentOnMFMFPriority / moonfire.CastTime : 0.0f; insectSwarmCasts = insectSwarmRatio > 0 ? etSpentOnISMFPriority / insectSwarm.CastTime : 0.0f; totalDotDamage = dotDamageMoonfirePriority; } else { moonfireCasts = moonfireRatio > 0 ? etSpentOnMFISPriority / moonfire.CastTime : 0.0f; insectSwarmCasts = insectSwarmRatio > 0 ? etSpentOnISISPriority / insectSwarm.CastTime : 0.0f; totalDotDamage = dotDamageInsectSwarmPriority; } float moonfireTicks = moonfire != null ? moonfireCasts * moonfire.DotEffect.NumberOfTicks : 0.0f; float insectSwarmTicks = insectSwarm != null ? insectSwarmCasts * insectSwarm.DotEffect.NumberOfTicks : 0.0f; float moonfireManaUsed = moonfire != null ? moonfireCasts * moonfire.BaseManaCost : 0.0f; float insectSwarmManaUsed = insectSwarm != null ? insectSwarmCasts * insectSwarm.BaseManaCost : 0.0f; float damageDone = preSolarTime * preSolarDPS + solarTime * solarDPS + preLunarTime * preLunarDPS + lunarTime * lunarDPS + totalDotDamage; Duration = rotationLength; DotTicks = moonfireTicks + insectSwarmTicks; InsectSwarmTicks = insectSwarmTicks; MoonfireTicks = moonfireTicks; MoonfireCasts = moonfireCasts; InsectSwarmCasts = insectSwarmCasts; CastCount = castsToProcLunar + (lunarTime / lunarEclipseCast.CastTime) + castsToProcSolar + (solarTime / solarEclipseCast.CastTime) + moonfireCasts + insectSwarmCasts; WrathCount = castsToProcLunar + (solarTime / solarEclipseCast.CastTime); StarfireCount = castsToProcSolar + (lunarTime / lunarEclipseCast.CastTime); ManaUsed = preSolarManaUsed + solarManaUsed + preLunarManaUsed + lunarManaUsed + moonfireManaUsed + insectSwarmManaUsed; ManaGained = castsToProcSolar * preSolarManaGained + (solarTime / solarEclipseCast.CastTime) * solarManaGained + castsToProcLunar * preLunarManaGained + (lunarTime / lunarEclipseCast.CastTime) * lunarManaGained; if (spellCrit + lunarEclipseCast.CriticalChanceModifier > StarfireEclipseCrit) { StarfireEclipseCrit = spellCrit + lunarEclipseCast.CriticalChanceModifier; } if (spellCrit + preSolarCast.CriticalChanceModifier > StarfireNonEclipseCrit) { StarfireNonEclipseCrit = spellCrit + preSolarCast.CriticalChanceModifier; } InsectSwarmDuration = insectSwarm != null ? insectSwarm.DotEffect.Duration : 0.0f; MoonfireDuration = moonfire != null ? moonfire.DotEffect.Duration : 0.0f; float mfSavingsFromOoC = moonfire != null ? (moonfire.BaseManaCost - (moonfire.BaseManaCost * (1 - StarfireCount / WrathCount * 0.06f - (1 - StarfireCount / WrathCount) * 0.06f))) : 0.0f; float isSavingsFromOoC = insectSwarm != null ? (insectSwarm.BaseManaCost - (insectSwarm.BaseManaCost * (1 - StarfireCount / WrathCount * 0.06f - (1 - StarfireCount / WrathCount) * 0.06f))) : 0.0f; ManaGained += moonfire != null ? (moonfireCasts * (mfSavingsFromOoC + ((spellCrit + moonfire.CriticalChanceModifier) * moonkinFormProc * spellHit) + 1.5f / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana)) : 0.0f; ManaGained += insectSwarm != null ? (insectSwarmCasts * (isSavingsFromOoC + 1.5f / 60.0f * calcs.BasicStats.ManaRestoreFromBaseManaPPM * CalculationsMoonkin.BaseMana)) : 0.0f; RotationData.ManaGained = ManaGained; RotationData.DPM = damageDone / ManaUsed; ManaUsed -= ManaGained; RotationData.ManaUsed = ManaUsed; // Undo SF glyph if (starfireGlyph && moonfire != null) { moonfire.DotEffect.Duration -= 9.0f; } // Undo improved Insect Swarm if (insectSwarm != null) { preLunarCast.AllDamageModifier /= 1 + 0.01f * impInsectSwarm; } if (moonfire != null) { preSolarCast.CriticalChanceModifier -= 0.01f * impInsectSwarm; } return(damageDone); }
private static void RecreateSpells(Character character, ref CharacterCalculationsMoonkin calcs) { starfire = new Starfire(); wrath = new Wrath(); insectSwarm = new InsectSwarm(); moonfire = new Moonfire(); SpellRotations = new List <SpellRotation>(new SpellRotation[] { new SpellRotation() { Name = "MF/SFx4", Spells = new List <Spell>(new Spell[] { moonfire, starfire }) }, new SpellRotation() { Name = "MF/Wx8", Spells = new List <Spell>(new Spell[] { moonfire, wrath }) }, new SpellRotation() { Name = "IS/SFx4", Spells = new List <Spell>(new Spell[] { insectSwarm, starfire }) }, new SpellRotation() { Name = "IS/Wx8", Spells = new List <Spell>(new Spell[] { insectSwarm, wrath }) }, new SpellRotation() { Name = "MF/SFx3/W", Spells = new List <Spell>(new Spell[] { moonfire, starfire, wrath }) }, new SpellRotation() { Name = "IS/SFx3/W", Spells = new List <Spell>(new Spell[] { insectSwarm, starfire, wrath }) }, new SpellRotation() { Name = "IS/MF/SFx3", Spells = new List <Spell>(new Spell[] { insectSwarm, moonfire, starfire }) }, new SpellRotation() { Name = "IS/MF/Wx7", Spells = new List <Spell>(new Spell[] { insectSwarm, moonfire, wrath }) }, new SpellRotation() { Name = "SF Spam", Spells = new List <Spell>(new Spell[] { starfire }) }, new SpellRotation() { Name = "W Spam", Spells = new List <Spell>(new Spell[] { wrath }) } }); UpdateSpells(character, ref calcs); }
private static float GetEffectiveManaPool(Character character, CharacterCalculationsMoonkin calcs) { float fightLength = calcs.FightLength * 60.0f; float innervateCooldown = 360 - calcs.BasicStats.InnervateCooldownReduction; // Mana/5 calculations float totalManaRegen = calcs.ManaRegen5SR * fightLength; CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; // Mana pot calculations float manaPotDelay = calcOpts.ManaPotDelay * 60.0f; int numPots = calcOpts.ManaPots && fightLength - manaPotDelay > 0 ? ((int)(fightLength - manaPotDelay) / 120 + 1) : 0; float manaRestoredByPots = 0.0f; if (numPots > 0) { float manaPerPot = 0.0f; if (calcOpts.ManaPotType == "Super Mana Potion") { manaPerPot = 2400.0f; } if (calcOpts.ManaPotType == "Fel Mana Potion") { manaPerPot = 3200.0f; } // Bonus from Alchemist's Stone if (calcs.BasicStats.BonusManaPotion > 0) { manaPerPot *= 1 + calcs.BasicStats.BonusManaPotion; } manaRestoredByPots = numPots * manaPerPot; } // Innervate calculations float innervateDelay = calcOpts.InnervateDelay * 60.0f; int numInnervates = calcOpts.Innervate && fightLength - innervateDelay > 0 ? ((int)(fightLength - innervateDelay) / (int)innervateCooldown + 1) : 0; float totalInnervateMana = 0.0f; if (numInnervates > 0) { // Innervate mana rate increases only spirit-based regen float spiritRegen = (calcs.ManaRegen - calcs.BasicStats.Mp5 / 5f); // Add in calculations for an innervate weapon if (calcOpts.InnervateWeapon) { float baseRegenConstant = 0.00932715221261f; // Calculate the intellect from a weapon swap float userIntellect = calcs.BasicStats.Intellect - (character.MainHand == null ? 0 : character.MainHand.Stats.Intellect) - (character.OffHand == null ? 0 : character.OffHand.Stats.Intellect) + calcOpts.InnervateWeaponInt; // Do the same with spirit float userSpirit = calcs.BasicStats.Spirit - (character.MainHand == null ? 0 : character.MainHand.Stats.Spirit) - (character.OffHand == null ? 0 : character.OffHand.Stats.Spirit) + calcOpts.InnervateWeaponSpi; // The new spirit regen for innervate periods uses the new weapon stats spiritRegen = baseRegenConstant * (float)Math.Sqrt(userIntellect) * userSpirit; } float innervateManaRate = spiritRegen * 4 + calcs.BasicStats.Mp5 / 5f; float innervateTime = numInnervates * 20.0f; totalInnervateMana = innervateManaRate * innervateTime - (numInnervates * calcs.BasicStats.Mana * 0.04f); } // Shadow priest calculations float sPriestMp5 = calcOpts.ShadowPriest; float sPriestMana = sPriestMp5 / 5 * fightLength; return(calcs.BasicStats.Mana + totalInnervateMana + totalManaRegen + manaRestoredByPots + sPriestMana); }
public ProcEffect(SpecialEffect effect) { this.Effect = effect; // Shadow damage procs - most widely varied at the current moment if (effect.Stats.ShadowDamage > 0) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { SpecialEffect e = Effect; float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusShadowDamageMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float triggerInterval = 0.0f; switch (e.Trigger) { case Trigger.DoTTick: // Extract triggerInterval = r.Duration / r.DotTicks; break; case Trigger.SpellHit: // Pendulum triggerInterval = r.Duration / r.CastCount; break; case Trigger.DamageDone: // DMC: Death triggerInterval = r.Duration / (r.CastCount + r.DotTicks); break; case Trigger.DamageOrHealingDone: // DMC: Greatness // Need to add Self-Heals triggerInterval = r.Duration / (r.CastCount + r.DotTicks); break; default: return(0.0f); } float procsPerSecond = e.GetAverageProcsPerSecond(triggerInterval, 1.0f, 3.0f, c.FightLength * 60.0f); return(e.Stats.ShadowDamage * specialDamageModifier * procsPerSecond); }; } // Lightning Capacitor, Thunder Capacitor, Reign of the Unliving/Undead, Nibelung else if (effect.Stats.NatureDamage > 0 || effect.Stats.FireDamage > 0 || effect.Stats.ValkyrDamage > 0) { if (effect.Stats.NatureDamage > 0) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusNatureDamageMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float procsPerSecond = Effect.GetAverageProcsPerSecond(r.Duration / (r.CastCount * sc), 1.0f, 3.0f, c.FightLength * 60.0f); return(Effect.Stats.NatureDamage * specialDamageModifier * procsPerSecond); }; } else if (effect.Stats.FireDamage > 0) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusFireDamageMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float procsPerSecond = Effect.GetAverageProcsPerSecond(r.Duration / (r.CastCount * sc), 1.0f, 3.0f, c.FightLength * 60.0f); return(Effect.Stats.FireDamage * specialDamageModifier * procsPerSecond); }; } else { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float procsPerSecond = Effect.GetAverageProcsPerSecond(r.Duration / (r.CastCount * sc), 1.0f, 3.0f, c.FightLength * 60.0f); return(Effect.Stats.ValkyrDamage * specialDamageModifier * procsPerSecond); }; } } else if (effect.Stats.Mp5 > 0) { CalculateMP5 = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { SpecialEffect e = Effect; float procsPerSecond = e.GetAverageProcsPerSecond(r.Duration / r.CastCount, 1.0f, 3.0f, c.FightLength * 60f); return((e.Stats.Mp5 / 5.0f * e.Duration) * procsPerSecond * 5.0f); }; } // Moonkin 4T8 set bonus (15% chance on IS tick to proc an instant-cast Starfire) else if (effect.Stats.StarfireProc == 1) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { if (r.InsectSwarmTicks == 0) { return(0.0f); } Spell newSF = new Spell() { AllDamageModifier = r.Solver.Starfire.AllDamageModifier, BaseCastTime = 1.5f, BaseDamage = r.Solver.Starfire.BaseDamage, BaseManaCost = r.Solver.Starfire.BaseManaCost, CriticalChanceModifier = r.Solver.Starfire.CriticalChanceModifier, CriticalDamageModifier = r.Solver.Starfire.CriticalDamageModifier, DotEffect = null, IdolExtraSpellPower = r.Solver.Starfire.IdolExtraSpellPower, Name = r.Solver.Starfire.Name, School = r.Solver.Starfire.School, SpellDamageModifier = r.Solver.Starfire.SpellDamageModifier }; r.DoSpecialStarfire(c, ref newSF, sp, sHi, sc, sHa); float timeBetweenProcs = r.Solver.InsectSwarm.DotEffect.TickLength / Effect.Chance; float replaceWrathWithSFDPS = (newSF.DamagePerHit / newSF.CastTime) - (r.Solver.Wrath.DamagePerHit / r.Solver.Wrath.CastTime); float replaceSFWithSFDPS = (newSF.DamagePerHit / newSF.CastTime) - (r.Solver.Starfire.DamagePerHit / r.Solver.Starfire.CastTime); return((replaceWrathWithSFDPS * (r.WrathCount / (r.WrathCount + r.StarfireCount)) + replaceSFWithSFDPS * (r.StarfireCount / (r.WrathCount + r.StarfireCount))) / timeBetweenProcs); }; } else if (Effect.Stats._rawSpecialEffectDataSize == 0 && (Effect.Trigger == Trigger.DamageDone || Effect.Trigger == Trigger.DamageOrHealingDone || Effect.Trigger == Trigger.DamageSpellCast || Effect.Trigger == Trigger.DamageSpellCrit || Effect.Trigger == Trigger.DamageSpellHit || Effect.Trigger == Trigger.SpellCast || Effect.Trigger == Trigger.SpellCrit || Effect.Trigger == Trigger.SpellHit || Effect.Trigger == Trigger.SpellMiss || Effect.Trigger == Trigger.Use || Effect.Trigger == Trigger.MoonfireCast || Effect.Trigger == Trigger.InsectSwarmOrMoonfireTick || Effect.Trigger == Trigger.MoonfireTick || Effect.Trigger == Trigger.InsectSwarmTick || Effect.Trigger == Trigger.DoTTick) && (Effect.Stats.HasteRating > 0 || Effect.Stats.SpellHaste > 0 || Effect.Stats.CritRating > 0 || Effect.Stats.HighestStat > 0)) { Activate = delegate(Character ch, CharacterCalculationsMoonkin c, ref float sp, ref float sHi, ref float sc, ref float sHa) { SpecialEffect e = Effect; int maxStack = e.MaxStack; Stats st = e.Stats; float critRating = st.CritRating; float spellCrit = StatConversion.GetSpellCritFromRating(critRating * maxStack); float hasteRating = st.HasteRating; float spellHaste = StatConversion.GetSpellHasteFromRating(hasteRating * maxStack); spellHaste += st.SpellHaste; float highestStat = st.HighestStat; if (critRating > 0) { sc += spellCrit; } if (spellHaste > 0) { sHa += spellHaste; } if (highestStat > 0) { if (c.BasicStats.Spirit > c.BasicStats.Intellect) { Stats s = c.BasicStats.Clone(); s.Spirit += highestStat; CharacterCalculationsMoonkin cNew = CalculationsMoonkin.GetInnerCharacterCalculations(ch, s, null); storedStats.SpellPower = cNew.SpellPower - c.SpellPower; sp += storedStats.SpellPower; } else { Stats s = c.BasicStats.Clone(); s.Intellect += highestStat; CharacterCalculationsMoonkin cNew = CalculationsMoonkin.GetInnerCharacterCalculations(ch, s, null); storedStats.SpellPower = cNew.SpellPower - c.SpellPower; storedStats.SpellCrit = cNew.SpellCrit - c.SpellCrit; sp += storedStats.SpellPower; sc += storedStats.SpellCrit; } } }; Deactivate = delegate(Character ch, CharacterCalculationsMoonkin c, ref float sp, ref float sHi, ref float sc, ref float sHa) { SpecialEffect e = Effect; int maxStack = e.MaxStack; Stats st = e.Stats; float critRating = st.CritRating; float spellCrit = StatConversion.GetSpellCritFromRating(critRating * maxStack); float hasteRating = st.HasteRating; float spellHaste = StatConversion.GetSpellHasteFromRating(hasteRating * maxStack); spellHaste += st.SpellHaste; float highestStat = st.HighestStat; if (critRating > 0) { sc -= spellCrit; } if (spellHaste > 0) { sHa -= spellHaste; } if (highestStat > 0) { sp -= storedStats.SpellPower; if (c.BasicStats.Intellect >= c.BasicStats.Spirit) { sc -= storedStats.SpellCrit; } } }; UpTime = delegate(SpellRotation r, CharacterCalculationsMoonkin c) { float upTime = 0.0f; switch (Effect.Trigger) { case Trigger.Use: upTime = Effect.GetAverageUptime(0f, 1f); break; case Trigger.SpellHit: case Trigger.DamageSpellHit: upTime = Effect.GetAverageUptime(r.Duration / r.CastCount, r.Solver.GetSpellHit(c)); break; case Trigger.DamageSpellCrit: case Trigger.SpellCrit: upTime = Effect.GetAverageUptime(r.Duration / (r.CastCount - (r.InsectSwarmTicks / r.Solver.InsectSwarm.DotEffect.NumberOfTicks)), c.SpellCrit); break; case Trigger.SpellCast: case Trigger.DamageSpellCast: upTime = Effect.GetAverageUptime(r.Duration / r.CastCount, 1f); break; case Trigger.MoonfireCast: upTime = Effect.GetAverageUptime(r.Duration / r.MoonfireCasts, 1f); break; case Trigger.InsectSwarmOrMoonfireTick: upTime = Effect.GetAverageUptime(r.Duration / (r.InsectSwarmTicks + r.MoonfireTicks), 1f); break; case Trigger.MoonfireTick: upTime = Effect.GetAverageUptime(r.Duration / r.MoonfireTicks, 1f); break; case Trigger.InsectSwarmTick: upTime = Effect.GetAverageUptime(r.Duration / r.InsectSwarmTicks, 1f); break; case Trigger.DoTTick: upTime = Effect.GetAverageUptime(r.Duration / (r.MoonfireTicks + r.InsectSwarmTicks), 1f); break; case Trigger.DamageDone: case Trigger.DamageOrHealingDone: upTime = Effect.GetAverageUptime(((r.Duration / r.CastCount) + (r.Duration / (r.MoonfireTicks + r.InsectSwarmTicks))) / 2.0f, 1f); break; default: break; } return(upTime); }; } }
public void Solve(Character character, ref CharacterCalculationsMoonkin calcs) { CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; DruidTalents talents = character.DruidTalents; procEffects = new List<ProcEffect>(); UpdateSpells(character, ref calcs); float trinketDPS = 0.0f; float baseSpellPower = calcs.SpellPower; float baseHit = 1 - Math.Max(0, calcs.SpellHitCap - calcs.SpellHit); float baseCrit = calcs.SpellCrit; float baseHaste = calcs.SpellHaste; float baseMastery = calcs.Mastery; float sub35PercentTime = (float)(character.BossOptions.Under20Perc + character.BossOptions.Under35Perc); BuildProcList(calcs); float maxDamageDone = 0.0f, maxBurstDamageDone = 0.0f; SpellRotation maxBurstRotation = Rotations[0]; SpellRotation maxRotation = Rotations[0]; float manaPool = GetEffectiveManaPool(character, calcOpts, calcs); float manaGained = manaPool - calcs.BasicStats.Mana; float oldArcaneMultiplier = calcs.BasicStats.BonusArcaneDamageMultiplier; float oldNatureMultiplier = calcs.BasicStats.BonusNatureDamageMultiplier; int rotationIndex = 1; foreach (SpellRotation rot in Rotations) { if (rot.RotationData.Name == "None") continue; rot.Solver = this; // Reset variables modified in the pre-loop to base values float currentSpellPower = baseSpellPower; float currentCrit = baseCrit; float currentHaste = baseHaste; float currentMastery = baseMastery; float currentTrinketDPS = trinketDPS; calcs.BasicStats.BonusArcaneDamageMultiplier = oldArcaneMultiplier; calcs.BasicStats.BonusNatureDamageMultiplier = oldNatureMultiplier; float accumulatedDamage = 0.0f; float totalUpTime = 0.0f; float[] spellDetails = new float[NUM_SPELL_DETAILS]; List<ProcEffect> activatedEffects = new List<ProcEffect>(); List<ProcEffect> alwaysUpEffects = new List<ProcEffect>(); float baselineDPS = rot.DamageDone(character, calcs, calcOpts.TreantLifespan, currentSpellPower, baseHit, currentCrit, currentHaste, currentMastery, calcOpts.Latency); // Calculate spell power/spell damage modifying trinkets in a separate pre-loop // Add spell crit effects here as well, since they no longer affect timing // Add Intellect procs here, since they are a combination of spell power and spell crit foreach (ProcEffect proc in procEffects) { bool handled = false; if (proc.Effect.Stats.SpellPower > 0 || proc.Effect.Stats.CritRating > 0 || proc.Effect.Stats.MasteryRating > 0 || proc.Effect.Stats.Intellect > 0 || proc.Effect.Stats.HighestStat > 0) { handled = true; float procIntellect = (float)Math.Floor((1 + calcs.BasicStats.BonusIntellectMultiplier) * (proc.Effect.Stats.Intellect + proc.Effect.Stats.HighestStat)); float procSpellPower = (float)Math.Floor((1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (proc.Effect.Stats.SpellPower + procIntellect)); float procSpellCrit = StatConversion.GetSpellCritFromRating(proc.Effect.Stats.CritRating) + StatConversion.GetSpellCritFromIntellect(procIntellect); float procMastery = StatConversion.GetMasteryFromRating(proc.Effect.Stats.MasteryRating); float triggerInterval = 0.0f, triggerChance = 1.0f; switch (proc.Effect.Trigger) { case Trigger.DamageDone: case Trigger.DamageOrHealingDone: triggerInterval = ((rot.RotationData.Duration / rot.RotationData.CastCount) + (rot.RotationData.Duration / (rot.RotationData.MoonfireTicks + rot.RotationData.InsectSwarmTicks))) / 2.0f; break; case Trigger.Use: break; case Trigger.SpellHit: case Trigger.DamageSpellHit: triggerInterval = rot.RotationData.Duration / rot.RotationData.CastCount; triggerChance = baseHit; break; case Trigger.SpellCrit: case Trigger.DamageSpellCrit: triggerInterval = rot.RotationData.Duration / (rot.RotationData.CastCount - rot.RotationData.InsectSwarmCasts); triggerChance = baseCrit; break; case Trigger.SpellCast: case Trigger.DamageSpellCast: triggerInterval = rot.RotationData.Duration / rot.RotationData.CastCount; break; case Trigger.MoonfireCast: triggerInterval = rot.RotationData.Duration / rot.RotationData.MoonfireCasts; break; case Trigger.DoTTick: triggerInterval = rot.RotationData.Duration / (rot.RotationData.InsectSwarmTicks + rot.RotationData.MoonfireTicks); break; case Trigger.MoonfireTick: triggerInterval = rot.RotationData.Duration / rot.RotationData.MoonfireTicks; break; case Trigger.InsectSwarmTick: triggerInterval = rot.RotationData.Duration / rot.RotationData.InsectSwarmTicks; break; default: triggerChance = 0.0f; break; } if (triggerChance > 0) { float durationMultiplier = proc.Effect.LimitedToExecutePhase ? sub35PercentTime : 1f; currentSpellPower += (proc.Effect.MaxStack > 1 ? proc.Effect.GetAverageStackSize(triggerInterval, triggerChance, 3.0f, character.BossOptions.BerserkTimer * 60.0f * durationMultiplier) : 1) * proc.Effect.GetAverageUptime(triggerInterval, triggerChance, 3.0f, character.BossOptions.BerserkTimer * 60.0f) * procSpellPower * durationMultiplier; currentCrit += (proc.Effect.MaxStack > 1 ? proc.Effect.GetAverageStackSize(triggerInterval, triggerChance, 3.0f, character.BossOptions.BerserkTimer * 60.0f * durationMultiplier) : 1) * proc.Effect.GetAverageUptime(triggerInterval, triggerChance, 3.0f, character.BossOptions.BerserkTimer * 60.0f) * procSpellCrit * durationMultiplier; currentMastery += (proc.Effect.MaxStack > 1 ? proc.Effect.GetAverageStackSize(triggerInterval, triggerChance, 3.0f, character.BossOptions.BerserkTimer * 60.0f * durationMultiplier) : 1) * proc.Effect.GetAverageUptime(triggerInterval, triggerChance, 3.0f, character.BossOptions.BerserkTimer * 60.0f) * procMastery * durationMultiplier; } } // 2T10 (both if statements, which is why it isn't else-if) if (proc.Effect.Stats.BonusArcaneDamageMultiplier > 0) { handled = true; calcs.BasicStats.BonusArcaneDamageMultiplier += proc.Effect.GetAverageUptime(rot.RotationData.Duration / rot.RotationData.CastCount, 1f, 3.0f, character.BossOptions.BerserkTimer * 60.0f) * proc.Effect.Stats.BonusArcaneDamageMultiplier; } if (proc.Effect.Stats.BonusNatureDamageMultiplier > 0) { handled = true; calcs.BasicStats.BonusNatureDamageMultiplier += proc.Effect.GetAverageUptime(rot.RotationData.Duration / rot.RotationData.CastCount, 1f, 3.0f, character.BossOptions.BerserkTimer * 60.0f) * proc.Effect.Stats.BonusNatureDamageMultiplier; } // Variable Pulse Lightning Capacitor // This might catch some other effects, I probably need a better way to differentiate if (proc.Effect.Trigger == Trigger.DamageSpellCrit && proc.Effect.Stats.NatureDamage > 0) { float procInterval = rot.RotationData.Duration / (rot.RotationData.CastCount - rot.RotationData.InsectSwarmCasts + rot.RotationData.DotTicks); currentTrinketDPS += proc.Effect.GetAverageProcsPerSecond(procInterval, currentCrit, 3.0f, character.BossOptions.BerserkTimer * 60.0f) * proc.Effect.Stats.NatureDamage; } // Nested special effects if (proc.Effect.Stats._rawSpecialEffectDataSize > 0) { handled = true; SpecialEffect childEffect = proc.Effect.Stats._rawSpecialEffectData[0]; // Heart of Ignacious if (childEffect.Stats.SpellPower > 0) { float averageStack = childEffect.GetAverageStackSize(rot.RotationData.Duration / rot.RotationData.CastCount, baseHit, 3.0f, proc.Effect.Duration); currentSpellPower += (float)Math.Floor((1 + calcs.BasicStats.BonusSpellPowerMultiplier) * childEffect.Stats.SpellPower) * averageStack * proc.Effect.GetAverageUptime(rot.RotationData.Duration / rot.RotationData.CastCount, baseHit); } // 4T11 if (childEffect.Stats.SpellCrit != 0) { float maxStack = proc.Effect.Stats.SpellCrit; float numNegativeStacks = childEffect.GetAverageStackSize(rot.RotationData.Duration / (rot.RotationData.CastCount - rot.RotationData.InsectSwarmCasts), Math.Min(1.0f, baseCrit + maxStack), 3.0f, proc.Effect.Duration); float averageNegativeValue = childEffect.Stats.SpellCrit * numNegativeStacks; float averageCrit = maxStack + averageNegativeValue; currentCrit += averageCrit * proc.Effect.GetAverageUptime(rot.RotationData.Duration / 2f, 1f, 3.0f, character.BossOptions.BerserkTimer * 60.0f); } } if (!handled) { if (proc.CalculateDPS != null) { accumulatedDamage += proc.CalculateDPS(rot, calcs, character.BossOptions.BerserkTimer, currentSpellPower, baseHit, currentCrit, currentHaste) * rot.RotationData.Duration; } if (proc.Activate != null) { float upTime = proc.UpTime(rot, calcs, character.BossOptions.BerserkTimer, (float)(character.BossOptions.Under35Perc + character.BossOptions.Under20Perc)); // Procs with 100% uptime should be activated and not put into the combination loop if (upTime == 1) { alwaysUpEffects.Add(proc); proc.Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste, ref currentMastery); } // Procs with uptime 0 < x < 100 should be activated else if (upTime > 0) activatedEffects.Add(proc); } if (proc.CalculateMP5 != null) { manaGained += proc.CalculateMP5(rot, calcs, character.BossOptions.BerserkTimer, currentSpellPower, baseHit, currentCrit, currentHaste) / 5.0f * character.BossOptions.BerserkTimer * 60.0f; } } } // Calculate stat-boosting trinkets, taking into effect interactions with other stat-boosting procs int sign = 1; float[] cachedDamages = new float[1 << activatedEffects.Count]; float[] cachedUptimes = new float[1 << activatedEffects.Count]; float[,] cachedDetails = new float[1 << activatedEffects.Count, NUM_SPELL_DETAILS]; List<int> calculatedPairs = new List<int>(); // Iterate over the entire set of trinket combinations (each trinket by itself, 2 at a time, ...) for (int i = 1; i <= activatedEffects.Count; ++i) { // Create a new combination generator for this "level" of trinket interaction CombinationGenerator gen = new CombinationGenerator(activatedEffects.Count, i); // Iterate over all combinations while (gen.HasNext()) { float tempUpTime = 1.0f; int[] vals = gen.GetNext(); int pairs = 0; int lengthCounter = 0; // Activate the trinkets, calculate the damage and uptime, then deactivate them foreach (int idx in vals) { pairs |= 1 << idx; ++lengthCounter; activatedEffects[idx].Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste, ref currentMastery); } currentCrit = (float)Math.Min(1.0f, currentCrit); float tempDPS = rot.DamageDone(character, calcs, calcOpts.TreantLifespan, currentSpellPower, baseHit, currentCrit, currentHaste, currentMastery, calcOpts.Latency) / rot.RotationData.Duration; spellDetails[0] = rot.RotationData.StarfireAvgHit; spellDetails[1] = rot.RotationData.WrathAvgHit; spellDetails[2] = rot.RotationData.MoonfireAvgHit; spellDetails[3] = rot.RotationData.InsectSwarmAvgHit; spellDetails[4] = rot.RotationData.StarSurgeAvgHit; spellDetails[5] = rot.RotationData.StarfireAvgCast; spellDetails[6] = rot.RotationData.WrathAvgCast; spellDetails[7] = rot.RotationData.MoonfireAvgCast; spellDetails[8] = rot.RotationData.InsectSwarmAvgCast; spellDetails[9] = rot.RotationData.StarSurgeAvgCast; spellDetails[10] = rot.RotationData.AverageInstantCast; spellDetails[11] = rot.RotationData.StarfireAvgEnergy; spellDetails[12] = rot.RotationData.WrathAvgEnergy; spellDetails[13] = rot.RotationData.StarSurgeAvgEnergy; spellDetails[14] = rot.RotationData.TreantDamage; spellDetails[15] = rot.RotationData.StarfallDamage; spellDetails[16] = rot.RotationData.MushroomDamage; foreach (int idx in vals) { tempUpTime *= activatedEffects[idx].UpTime(rot, calcs, character.BossOptions.BerserkTimer, (float)(character.BossOptions.Under35Perc + character.BossOptions.Under20Perc)); activatedEffects[idx].Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste, ref currentMastery); } if (tempUpTime == 0) continue; // Adjust previous probability tables by the current factor // At the end of the algorithm, this ensures that the probability table will contain the individual // probabilities of each effect or set of effects. // These adjustments only need to be made for higher levels of the table, and if the current probability is > 0. if (lengthCounter > 1) { foreach (int subset in calculatedPairs) { // Truly a subset? if ((pairs & subset) != subset) { continue; } // Calculate the "layer" of the current subset by getting the set bit count. int subsetLength = 0; for (int j = subset; j > 0; ++subsetLength) { j &= --j; } // Set the sign of the operation: Evenly separated layers are added, oddly separated layers are subtracted int newSign = ((lengthCounter - subsetLength) % 2 == 0) ? 1 : -1; // Adjust by current uptime * sign of operation. cachedUptimes[subset] += newSign * tempUpTime; } } // Cache the results to be calculated later cachedUptimes[pairs] = tempUpTime; cachedDamages[pairs] = tempDPS; for (int idx = 0; idx < NUM_SPELL_DETAILS; ++idx) { cachedDetails[pairs, idx] = spellDetails[idx]; } calculatedPairs.Add(pairs); totalUpTime += sign * tempUpTime; } sign = -sign; } float accumulatedDPS = 0.0f; Array.Clear(spellDetails, 0, spellDetails.Length); // Apply the above-calculated probabilities to the previously stored damage calculations and add to the result. for (int idx = 0; idx < cachedUptimes.Length; ++idx) { if (cachedUptimes[idx] == 0) continue; accumulatedDPS += cachedUptimes[idx] * cachedDamages[idx]; for (int i = 0; i < NUM_SPELL_DETAILS; ++i) { spellDetails[i] += cachedUptimes[idx] * cachedDetails[idx,i]; } } float damageDone = rot.DamageDone(character, calcs, calcOpts.TreantLifespan, currentSpellPower, baseHit, currentCrit, currentHaste, currentMastery, calcOpts.Latency); accumulatedDPS += (1 - totalUpTime) * damageDone / rot.RotationData.Duration; spellDetails[0] += (1 - totalUpTime) * rot.RotationData.StarfireAvgHit; spellDetails[1] += (1 - totalUpTime) * rot.RotationData.WrathAvgHit; spellDetails[2] += (1 - totalUpTime) * rot.RotationData.MoonfireAvgHit; spellDetails[3] += (1 - totalUpTime) * rot.RotationData.InsectSwarmAvgHit; spellDetails[4] += (1 - totalUpTime) * rot.RotationData.StarSurgeAvgHit; spellDetails[5] += (1 - totalUpTime) * rot.RotationData.StarfireAvgCast; spellDetails[6] += (1 - totalUpTime) * rot.RotationData.WrathAvgCast; spellDetails[7] += (1 - totalUpTime) * rot.RotationData.MoonfireAvgCast; spellDetails[8] += (1 - totalUpTime) * rot.RotationData.InsectSwarmAvgCast; spellDetails[9] += (1 - totalUpTime) * rot.RotationData.StarSurgeAvgCast; spellDetails[10] += (1 - totalUpTime) * rot.RotationData.AverageInstantCast; spellDetails[11] += (1 - totalUpTime) * rot.RotationData.StarfireAvgEnergy; spellDetails[12] += (1 - totalUpTime) * rot.RotationData.WrathAvgEnergy; spellDetails[13] += (1 - totalUpTime) * rot.RotationData.StarSurgeAvgEnergy; spellDetails[14] += (1 - totalUpTime) * rot.RotationData.TreantDamage; spellDetails[15] += (1 - totalUpTime) * rot.RotationData.StarfallDamage; spellDetails[16] += (1 - totalUpTime) * rot.RotationData.MushroomDamage; float burstDPS = accumulatedDPS + accumulatedDamage / rot.RotationData.Duration; float sustainedDPS = burstDPS; // Mana calcs: // Main rotation - all spells // Movement rotation - Lunar Shower MF, IS, Shooting Stars procs, and Starfall only rot.RotationData.ManaGained += manaGained / (character.BossOptions.BerserkTimer * 60.0f) * rot.RotationData.Duration; float timeToOOM = manaPool / ((rot.RotationData.ManaUsed - rot.RotationData.ManaGained) / rot.RotationData.Duration); if (timeToOOM <= 0) timeToOOM = character.BossOptions.BerserkTimer * 60.0f; // Happens when ManaUsed is less than 0 if (timeToOOM < character.BossOptions.BerserkTimer * 60.0f) { rot.RotationData.TimeToOOM = new TimeSpan(0, (int)(timeToOOM / 60), (int)(timeToOOM % 60)); sustainedDPS = burstDPS * timeToOOM / (character.BossOptions.BerserkTimer * 60.0f); } burstDPS += currentTrinketDPS; sustainedDPS += currentTrinketDPS; rot.RotationData.SustainedDPS = sustainedDPS; rot.RotationData.BurstDPS = burstDPS; rot.RotationData.StarfireAvgHit = spellDetails[0]; rot.RotationData.WrathAvgHit = spellDetails[1]; rot.RotationData.MoonfireAvgHit = spellDetails[2]; rot.RotationData.InsectSwarmAvgHit = spellDetails[3]; rot.RotationData.StarSurgeAvgHit = spellDetails[4]; rot.RotationData.StarfireAvgCast = spellDetails[5]; rot.RotationData.WrathAvgCast = spellDetails[6]; rot.RotationData.MoonfireAvgCast = spellDetails[7]; rot.RotationData.InsectSwarmAvgCast = spellDetails[8]; rot.RotationData.StarSurgeAvgCast = spellDetails[9]; rot.RotationData.AverageInstantCast = spellDetails[10]; rot.RotationData.StarfireAvgEnergy = spellDetails[11]; rot.RotationData.WrathAvgEnergy = spellDetails[12]; rot.RotationData.StarSurgeAvgEnergy = spellDetails[13]; rot.RotationData.TreantDamage = spellDetails[14]; rot.RotationData.StarfallDamage = spellDetails[15]; rot.RotationData.MushroomDamage = spellDetails[16]; // Update the sustained DPS rotation if any one of the following three cases is true: // 1) No user rotation is selected and sustained DPS is maximum // 2) A user rotation is selected, Eclipse is not present, and the user rotation matches the current rotation // 3) A user rotation is selected, Eclipse is present, and the user rotation's dot spells matches this rotation's if ((calcOpts.UserRotation == "None" && sustainedDPS > maxDamageDone) || rot.RotationData.Name == calcOpts.UserRotation) { maxDamageDone = sustainedDPS; maxRotation = rot; } if (burstDPS > maxBurstDamageDone) { maxBurstDamageDone = burstDPS; maxBurstRotation = rot; } cachedResults[rotationIndex - 1] = rot.RotationData; // Deactivate always-up procs foreach (ProcEffect proc in alwaysUpEffects) { proc.Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste, ref currentMastery); } ++rotationIndex; } // Present the findings to the user. calcs.SelectedRotation = maxRotation.RotationData; calcs.BurstRotation = maxBurstRotation.RotationData; calcs.SubPoints = new float[] { maxBurstDamageDone, maxDamageDone }; calcs.OverallPoints = calcs.SubPoints[0] + calcs.SubPoints[1]; calcs.Rotations = cachedResults; }