public ContinuousAction(DiscreteAction pa) { Time = pa.Time; MPS = pa.Mana; EPS = pa.Direct + pa.Periodic; CPS = pa.Casts; TPS = pa.Ticks; Limit = Time / pa.Cooldown; if (!(Limit >= 0 && Limit <= 1)) { Limit = 1; } if (Time != 0) { MPS /= Time; EPS /= Time; CPS /= Time; TPS /= Time; } }
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; }
DiscreteAction applyCC(DiscreteAction action) { action.Mana = 0; return action; }
// cast Lifebloom on raid during ToL, then an amortized Healing Touch using the average number of clearcasts from the Lifebloom DiscreteAction buildTolLbCcHt(TreeStats stats, double lifebloomTicks, DiscreteAction tollb, double tollbWeight, DiscreteAction ccht) { double cchtsPerLifebloom = lifebloomTicks * 0.02f * Talents.MalfurionsGift; DiscreteAction action = new DiscreteAction(); action.Time = tollb.Time + cchtsPerLifebloom * ccht.Time; action.Direct = tollb.Direct * tollbWeight + cchtsPerLifebloom * ccht.Direct; action.Periodic = tollb.Periodic * tollbWeight; action.Mana = tollb.Mana; action.Casts = cchtsPerLifebloom; action.Ticks = lifebloomTicks + cchtsPerLifebloom; return action; }
// cast Rejuvenation rjn times to get Nature's Bounty, then cast Nourish until it drops off // TODO: do we have some extra time before it drops off? DiscreteAction buildNourishNB(TreeStats stats, DiscreteAction rj, double rjduration, int rjn, double rjWeight, DiscreteAction nourish) { DiscreteAction action = new DiscreteAction(); double nourishCasts = (rjduration - rj.Time * rjn) / ((1.0f - 0.1f * Talents.NaturesBounty) * nourish.Time); action.Time = rjduration; action.Periodic = rj.Periodic * rjn * rjWeight; action.Direct = rj.Direct * rjn * rjWeight + nourishCasts * nourish.Direct; action.Mana = rj.Mana * rjn + nourishCasts * nourish.Mana; action.Casts = rj.Casts * rjn + nourishCasts; action.Ticks = rj.Ticks * rjn; return action; }
public ContinuousAction(DiscreteAction pa) { Time = pa.Time; MPS = pa.Mana; EPS = pa.Direct + pa.Periodic; CPS = pa.Casts; TPS = pa.Ticks; Limit = Time / pa.Cooldown; if(!(Limit >= 0 && Limit <= 1)) Limit = 1; if (Time != 0) { MPS /= Time; EPS /= Time; CPS /= Time; TPS /= Time; } }