Example #1
0
 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);
 }
Example #2
0
        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;
        }
Example #3
0
        /* 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;
        }
Example #4
0
        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
        }
Example #5
0
 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];
     }
 }