void addLifebloomRefresh(ActionDistribution dist, ContinuousAction[] actions, ComputedSpell spell, TreeComputedData data, bool automatic, bool rejuvenationUp) { if (!automatic) dist.AddActionOnCooldown((int)TreeAction.ReLifebloom); dist.AddPassive((int)TreePassive.RollingLifebloom, (rejuvenationUp ? spell.TankAction.Periodic : spell.RaidAction.Periodic) * 3 * opts.TankLifebloomEH / spell.Duration, -data.LifebloomMPSGain); dist.AddPassiveTPS((int)TreePassive.RollingLifebloom, spell.TPS); }
ContinuousAction[] computeDivisionActions(TreeStats stats, TreeComputedData data, ComputedSpell[] spells) { double healingTouchNSReduction = 0; bool swiftmendEatsRejuvenation = true; double swiftmendExtraTargets = 0; double lifebloomManaPerTick = 0; if (T12Count >= 2) lifebloomManaPerTick += CalculationsTree.BaseMana * 0.01 * 0.4; if (T12Count >= 4) swiftmendExtraTargets += opts.SwiftmendExtraHealEH; if (Talents.GlyphOfSwiftmend) swiftmendEatsRejuvenation = false; if (Talents.GlyphOfHealingTouch) healingTouchNSReduction = 10; data.LifebloomMPSGain = lifebloomManaPerTick * spells[(int)TreeSpell.Lifebloom].TPS; DiscreteAction[] actions = new DiscreteAction[(int)TreeAction.Count]; for (int i = 0; i < spells.Length; ++i) { actions[i] = spells[i].RaidAction; actions[spells.Length + i] = spells[i].TankAction; } #region Effective healing actions[(int)TreeAction.RaidTolLb].Direct *= opts.ToLLifebloomEH; actions[(int)TreeAction.RaidTolLb].Periodic *= opts.ToLLifebloomEH; actions[(int)TreeAction.RaidRejuvenation].Periodic *= opts.RejuvenationEH; actions[(int)TreeAction.RaidHealingTouch].Direct *= opts.HealingTouchEH; actions[(int)TreeAction.RaidNourish].Direct *= opts.NourishEH; actions[(int)TreeAction.RaidRegrowth].Direct *= opts.NourishEH; actions[(int)TreeAction.RaidRegrowth].Periodic *= opts.NourishEH; actions[(int)TreeAction.RaidWildGrowth].Periodic *= opts.WildGrowthEH; actions[(int)TreeAction.RaidSwiftmend].Periodic *= opts.SwiftmendEH; actions[(int)TreeAction.RaidTranquility].Direct *= opts.TranquilityEH; actions[(int)TreeAction.RaidTranquility].Periodic *= opts.TranquilityEH; #endregion #region Targets double defaultCritMultiplier = getCritMultiplier(stats, 0, 0); double wgTargets = (Talents.GlyphOfWildGrowth ? 6 : 5) + stats.TreeOfLifeUptime * 2; actions[(int)TreeAction.TankWildGrowth].Periodic += (wgTargets - 1) * actions[(int)TreeAction.RaidWildGrowth].Periodic * opts.TankRaidHealingWeight; actions[(int)TreeAction.RaidWildGrowth].Periodic *= wgTargets; actions[(int)TreeAction.TankWildGrowth].Ticks *= wgTargets; actions[(int)TreeAction.RaidWildGrowth].Ticks *= wgTargets; actions[(int)TreeAction.TankSwiftmend].Direct *= (1 + swiftmendExtraTargets * defaultCritMultiplier * stats.DirectHealMultiplier * opts.TankRaidHealingWeight); actions[(int)TreeAction.RaidSwiftmend].Direct *= (1 + swiftmendExtraTargets * defaultCritMultiplier * stats.DirectHealMultiplier); actions[(int)TreeAction.TankSwiftmend].Casts *= (1 + swiftmendExtraTargets); actions[(int)TreeAction.RaidSwiftmend].Casts *= (1 + swiftmendExtraTargets); actions[(int)TreeAction.TankSwiftmend].Periodic *= 1 + opts.TankRaidHealingWeight * Math.Max(3 * opts.EfflorescenceEH - 1.0, 0); actions[(int)TreeAction.RaidSwiftmend].Periodic *= 3 * opts.EfflorescenceEH; actions[(int)TreeAction.TankSwiftmend].Ticks *= Math.Max(3 * opts.EfflorescenceEH, 1); actions[(int)TreeAction.RaidSwiftmend].Ticks *= 3 * opts.EfflorescenceEH; #endregion #region Swiftmend if (swiftmendEatsRejuvenation) actions[(int)TreeAction.RaidSwiftmend].Direct -= actions[(int)TreeAction.RaidRejuvenation].Periodic * 0.5f; #endregion #region Lifebloom if (stats.TreeOfLifeUptime > 0) { // TODO: figure out how 2T12 works in ToL and adjust the code as needed //actions[(int)TreeAction.RaidTolLb].Mana -= lifebloomManaPerTick * spells[(int)TreeSpell.Lifebloom].Ticks; //actions[(int)TreeAction.TankTolLb].Mana -= lifebloomManaPerTick * spells[(int)TreeSpell.Lifebloom].Ticks; } else { actions[(int)TreeAction.RaidTolLb].Direct = 0; actions[(int)TreeAction.RaidTolLb].Periodic = 0; actions[(int)TreeAction.TankTolLb].Direct = 0; actions[(int)TreeAction.TankTolLb].Periodic = 0; } #endregion #region Rejuvenation actions[(int)TreeAction.TankRejuvenation].Cooldown = spells[(int)TreeSpell.Rejuvenation].Duration; #endregion #region Nature's Swiftness if (Talents.NaturesSwiftness > 0) { // Nature's Swiftness is actually additive double nshtMultiplier = 1 + 0.5 / (1 + stats.PassiveDirectHealBonus + spells[(int)TreeSpell.HealingTouch].ExtraDirectBonus); actions[(int)TreeAction.RaidSwiftHT] = actions[(int)TreeAction.RaidHealingTouch]; actions[(int)TreeAction.TankSwiftHT] = actions[(int)TreeAction.TankHealingTouch]; actions[(int)TreeAction.RaidSwiftHT].Time = stats.Haste.HastedGCD; actions[(int)TreeAction.TankSwiftHT].Time = stats.Haste.HastedGCD; actions[(int)TreeAction.RaidSwiftHT].Cooldown = 180 + opts.NaturesSwiftnessCastDelay; actions[(int)TreeAction.TankSwiftHT].Cooldown = 180 + opts.NaturesSwiftnessCastDelay; actions[(int)TreeAction.RaidSwiftHT].Direct *= nshtMultiplier; actions[(int)TreeAction.TankSwiftHT].Direct *= nshtMultiplier; if (healingTouchNSReduction > 0) { // add the NS effect as an amortized extra heal to each HT double swiftHtFraction = (healingTouchNSReduction / (180.0 + opts.NaturesSwiftnessCastDelay)); double htAddTime = swiftHtFraction * stats.Haste.HastedGCD; double htMulDirect = 1 + swiftHtFraction * nshtMultiplier; double htMulMana = 1 + swiftHtFraction; actions[(int)TreeAction.TankHealingTouch].Time += htAddTime; actions[(int)TreeAction.TankHealingTouch].Mana *= htMulMana; actions[(int)TreeAction.TankHealingTouch].Direct *= htMulDirect; actions[(int)TreeAction.RaidHealingTouch].Time += htAddTime; actions[(int)TreeAction.RaidHealingTouch].Mana *= htMulMana; actions[(int)TreeAction.RaidHealingTouch].Direct *= htMulDirect; } } #endregion #region Clearcasting actions[(int)TreeAction.RaidClearHT] = applyCC(actions[(int)TreeAction.RaidHealingTouch]); actions[(int)TreeAction.TankClearHT] = applyCC(actions[(int)TreeAction.TankHealingTouch]); actions[(int)TreeAction.RaidClearRegrowth] = applyCC(actions[(int)TreeAction.RaidRegrowth]); actions[(int)TreeAction.TankClearRegrowth] = applyCC(actions[(int)TreeAction.TankRegrowth]); if (stats.TreeOfLifeUptime > 0) { actions[(int)TreeAction.RaidTolLbCcHt] = buildTolLbCcHt(stats, spells[(int)TreeSpell.Lifebloom].Ticks, actions[(int)TreeAction.RaidTolLb], 1, actions[(int)TreeAction.RaidClearHT]); actions[(int)TreeAction.TankTolLbCcHt] = buildTolLbCcHt(stats, spells[(int)TreeSpell.Lifebloom].Ticks, actions[(int)TreeAction.RaidTolLb], opts.TankRaidHealingWeight, actions[(int)TreeAction.TankClearHT]); } #endregion #region Nature's Bounty actions[(int)TreeAction.RaidRj2NourishNB] = buildNourishNB(stats, actions[(int)TreeAction.RaidRejuvenation], spells[(int)TreeSpell.Rejuvenation].Duration, 2, 1, actions[(int)TreeAction.RaidNourish]); actions[(int)TreeAction.RaidRj3NourishNB] = buildNourishNB(stats, actions[(int)TreeAction.RaidRejuvenation], spells[(int)TreeSpell.Rejuvenation].Duration, 3, 1, actions[(int)TreeAction.RaidNourish]); actions[(int)TreeAction.TankRj2NourishNB] = buildNourishNB(stats, actions[(int)TreeAction.RaidRejuvenation], spells[(int)TreeSpell.Rejuvenation].Duration, 2, opts.TankRaidHealingWeight, actions[(int)TreeAction.TankNourish]); #endregion #region Additional actions data.LifebloomRefreshInterval = spells[(int)TreeSpell.Lifebloom].Duration - opts.LifebloomWastedDuration * stats.Haste.HastedSecond; if (data.LifebloomRefreshInterval < stats.Haste.HastedSecond * 3) data.LifebloomRefreshInterval = stats.Haste.HastedSecond * 3; actions[(int)TreeAction.ReLifebloom].Time = spells[(int)TreeSpell.Lifebloom].Action.Time; actions[(int)TreeAction.ReLifebloom].Mana = spells[(int)TreeSpell.Lifebloom].Action.Mana; actions[(int)TreeAction.ReLifebloom].Direct = 0; actions[(int)TreeAction.ReLifebloom].Periodic = 0; actions[(int)TreeAction.ReLifebloom].Cooldown = data.LifebloomRefreshInterval; // TODO: actually compute hit instead of assuming we have 0 hit; also maybe have an option/BossHandler value for the target level (there might a permanent add with lower level than the boss) actions[(int)TreeAction.InsectSwarm].Time = stats.Haste.HastedGCD / (1 - StatConversion.GetSpellMiss(character.Level - character.BossOptions.Level, false)); actions[(int)TreeAction.InsectSwarm].Mana = ((int)Math.Floor(CalculationsTree.BaseMana * 8 / 100f) - stats.SpellsManaCostReduction) * stats.SpellsManaCostMultiplier; actions[(int)TreeAction.InsectSwarm].Cooldown = 12 + Talents.Genesis * 2; #endregion ContinuousAction[] factions = new ContinuousAction[(int)TreeAction.Count]; for (int i = 0; i < (int)TreeAction.Count; ++i) factions[i] = new ContinuousAction(actions[i]); return factions; }
/* Spell data is computed in the following stages: * 1. SpellData with constants * 2. ComputedSpell containing computed data for a generic target (thus excluding crit, mastery, targets, effective healing) * 3. ComputedSpell with RaidAction and TankAction filled with information except effective healing and multiplication by number of targets * 4. SpellAction adding effective healing, targets and strange options, plus special SpellActions containing combination of spells * 5. Actions containing the SpellActions converted to EPS/MPS/time format * * Note that Tank mostly means "target always with hots, hit frequently" while Raid means "target which might have hots (from wild growth), hit rarely" */ ComputedSpell[] computeDivisionSpells(TreeStats stats, TreeComputedData data) { ComputedSpell[] spells = new ComputedSpell[(int)TreeSpell.Count]; for (int i = 0; i < (int)TreeSpell.Count; ++i) { SpellData spdata = CalculationsTree.SpellData[i]; if(i == (int)TreeSpell.WildGrowth && opts.WildGrowthNerf) spdata = CalculationsTree.NerfedWildGrowthSpellData; spells[i] = new ComputedSpell(spdata, stats); } spells[(int)TreeSpell.Tranquility].DirectMultiplier *= 4 * 5; spells[(int)TreeSpell.Tranquility].TickMultiplier *= 4 * 5; #region Talents double rejuvenationInstantTicks = 0; spells[(int)TreeSpell.Swiftmend].ExtraDirectBonus += 0.02f * Talents.Genesis; spells[(int)TreeSpell.Rejuvenation].ExtraTickBonus += Talents.BlessingOfTheGrove * 0.02f; spells[(int)TreeSpell.Nourish].TimeReductionMS += 250 * Talents.Naturalist; spells[(int)TreeSpell.HealingTouch].TimeReductionMS += 250 * Talents.Naturalist; spells[(int)TreeSpell.Rejuvenation].ExtraTickBonus += 0.05f * Talents.ImprovedRejuvenation; spells[(int)TreeSpell.Swiftmend].ExtraTickBonus += 0.05f * Talents.ImprovedRejuvenation; spells[(int)TreeSpell.Swiftmend].ExtraDirectBonus += 0.05f * Talents.ImprovedRejuvenation; // according to Paragon's Anaram posting on ElitistJerks, Efflorescence double-dips Master Shapeshifter and Harmony spells[(int)TreeSpell.Swiftmend].TickMultiplier *= 0.04f * Talents.Efflorescence * (1.0f + Talents.MasterShapeshifter * 0.04f) * (1.0f + stats.Harmony); spells[(int)TreeSpell.Nourish].ExtraDirectBonus += 0.05f * Talents.EmpoweredTouch; spells[(int)TreeSpell.HealingTouch].ExtraDirectBonus += 0.05f * Talents.EmpoweredTouch; spells[(int)TreeSpell.Regrowth].ExtraDirectBonus += 0.05f * Talents.EmpoweredTouch; // formula from TreeCalcs // TODO: test this in-game rejuvenationInstantTicks = Talents.GiftOfTheEarthmother * 0.05f * Math.Floor(4.0f * (1 + StatConversion.GetSpellHasteFromRating((float)stats.Haste.HasteRating)) + 0.5f) * 1.0135f; spells[(int)TreeSpell.Lifebloom].DirectMultiplier *= 1 + Talents.GiftOfTheEarthmother * 0.05f; if (Talents.SwiftRejuvenation > 0) spells[(int)TreeSpell.Rejuvenation].TimeReductionMS = 500; #endregion #region Glyphs if (Talents.GlyphOfRejuvination) spells[(int)TreeSpell.Rejuvenation].ExtraTickBonus += 0.1f; if (Talents.GlyphOfRegrowth) spells[(int)TreeSpell.Regrowth].ExtraDurationMS += (Talents.GlyphOfRegrowth ? (int)(Math.Min(opts.GlyphOfRegrowthExtraDuration, calc.FightLength) * 1000.0) : 0); #endregion for (int i = 0; i < (int)TreeSpell.Count; ++i) { if (i != (int)TreeSpell.HealingTouch && i != (int)TreeSpell.WildGrowth) spells[i].ComputeTiming(); } if (T13Count >= 4) { // here we assume that the tick structure is computed as if the spell just had double duration // this is NOT the same as doubling the amount of ticks, due to rounding // also we assume that overhealing ratio doesn't depend on duration in our model // if the model is changed to do that, then this averaging will need to be done much later for (int i = 0; i < CalculationsTree.T13Spells.Length; ++i) { ComputedSpell extspell = new ComputedSpell(CalculationsTree.T13SpellData[i], stats); extspell.ComputeTiming(); ComputedSpell spell = spells[(int)CalculationsTree.T13Spells[i]]; spell.Ticks = 0.9 * spell.Ticks + 0.1 * extspell.Ticks; spell.TPS = 0.9 * spell.TPS + 0.1 * extspell.TPS; spell.Duration = 0.9 * spell.Duration + 0.1 * extspell.Duration; } } // optimization to avoid duplicating computations spells[(int)TreeSpell.HealingTouch].Action.Time = spells[(int)TreeSpell.Nourish].Action.Time; spells[(int)TreeSpell.WildGrowth].Action.Time = spells[(int)TreeSpell.Swiftmend].Action.Time; spells[(int)TreeSpell.WildGrowth].Ticks = spells[(int)TreeSpell.Swiftmend].Ticks; spells[(int)TreeSpell.WildGrowth].Duration = spells[(int)TreeSpell.Swiftmend].Duration; spells[(int)TreeSpell.WildGrowth].TPS = spells[(int)TreeSpell.Swiftmend].TPS; for (int i = 0; i < (int)TreeSpell.Count; ++i) spells[i].ComputeRest(); if (rejuvenationInstantTicks > 0) { spells[(int)TreeSpell.Rejuvenation].Action.Direct += rejuvenationInstantTicks * spells[(int)TreeSpell.Rejuvenation].Tick; ++spells[(int)TreeSpell.Rejuvenation].Action.Ticks; } #region Cooldowns spells[(int)TreeSpell.WildGrowth].Action.Cooldown = 8 + opts.WildGrowthCastDelay; spells[(int)TreeSpell.Tranquility].Action.Cooldown = 8 * 60 - (150 * Talents.MalfurionsGift) + opts.TranquilityCastDelay; spells[(int)TreeSpell.Swiftmend].Action.Cooldown = 15 + opts.SwiftmendCastDelay; if (opts.GlyphOfWildGrowthCDIncrease && character.DruidTalents.GlyphOfWildGrowth) spells[(int)TreeSpell.WildGrowth].Action.Cooldown += 2; #endregion return spells; }
void computeDivisionSpellActions(TreeStats stats, TreeComputedData data, ComputedSpell[] spells) { for (int i = 0; i < (int)TreeSpell.Count; ++i) { spells[i].RaidAction = spells[i].Action; spells[i].TankAction = spells[i].Action; } double lifebloomExtraDirectCrit = 0; double lifebloomExtraTickCrit = 0; double regrowthExtraCrit = 0; double tankLivingSeed, raidLivingSeed; #region Talents if (T11Count >= 2) lifebloomExtraTickCrit += 0.05f; tankLivingSeed = raidLivingSeed = 0.1f * Talents.LivingSeed; raidLivingSeed *= opts.LivingSeedEH; regrowthExtraCrit += 0.2f * Talents.NaturesBounty; if (Talents.GlyphOfLifebloom) { lifebloomExtraDirectCrit += 0.1f; lifebloomExtraTickCrit += 0.1f; } #endregion #region Crit double defaultCritMultiplier = getCritMultiplier(stats, 0, 0); double raidLivingSeedCritMultiplier = getCritMultiplier(stats, 0, raidLivingSeed); double tankLivingSeedCritMultiplier = getCritMultiplier(stats, 0, tankLivingSeed); spells[(int)TreeSpell.Lifebloom].MultiplyDirect(getCritMultiplier(stats, lifebloomExtraDirectCrit, 0)); spells[(int)TreeSpell.Lifebloom].MultiplyPeriodic(getCritMultiplier(stats, lifebloomExtraTickCrit, 0)); spells[(int)TreeSpell.WildGrowth].MultiplyPeriodic(defaultCritMultiplier); spells[(int)TreeSpell.Tranquility].Multiply(defaultCritMultiplier); // gotem doesn't crit, but is affected by Symbiosis, according to TreeCalcs spells[(int)TreeSpell.Rejuvenation].MultiplyPeriodic(defaultCritMultiplier); spells[(int)TreeSpell.Swiftmend].RaidAction.Direct *= raidLivingSeedCritMultiplier; spells[(int)TreeSpell.Swiftmend].TankAction.Direct *= tankLivingSeedCritMultiplier; spells[(int)TreeSpell.Swiftmend].MultiplyPeriodic(defaultCritMultiplier); spells[(int)TreeSpell.HealingTouch].RaidAction.Direct *= raidLivingSeedCritMultiplier; spells[(int)TreeSpell.HealingTouch].TankAction.Direct *= tankLivingSeedCritMultiplier; spells[(int)TreeSpell.Nourish].RaidAction.Direct *= raidLivingSeedCritMultiplier; spells[(int)TreeSpell.Nourish].TankAction.Direct *= tankLivingSeedCritMultiplier; spells[(int)TreeSpell.Regrowth].RaidAction.Direct *= getCritMultiplier(stats, regrowthExtraCrit, raidLivingSeed); spells[(int)TreeSpell.Regrowth].TankAction.Direct *= getCritMultiplier(stats, regrowthExtraCrit, tankLivingSeed); spells[(int)TreeSpell.Regrowth].MultiplyPeriodic(getCritMultiplier(stats, regrowthExtraCrit, 0)); #endregion #region Nourish HoTs spells[(int)TreeSpell.Nourish].RaidAction.Direct *= 1.0 + opts.NourishHoTRate * 0.2; spells[(int)TreeSpell.Nourish].TankAction.Direct *= 1.2; #endregion }
void computeActions() { calc.Spells = new ComputedSpell[calc.Division.Count][]; DivisionData = new TreeComputedData[calc.Division.Count]; calc.Actions = new ContinuousAction[calc.Division.Count][]; for (int div = 0; div < calc.Division.Fractions.Length; ++div) { DivisionData[div] = new TreeComputedData(); calc.Spells[div] = computeDivisionSpells(calc.Stats[div], DivisionData[div]); computeDivisionSpellActions(calc.Stats[div], DivisionData[div], calc.Spells[div]); if (opts.ActivityRate == 1.0f || (div + 1) != calc.Division.Fractions.Length) calc.Actions[div] = computeDivisionActions(calc.Stats[div], DivisionData[div], calc.Spells[div]); else calc.Actions[div] = new ContinuousAction[(int)TreeAction.Count]; } }