Esempio n. 1
0
        public static ContinuousAction[] AverageActionSets(ContinuousAction[][] actionSets, double[] weights)
        {
            int n = actionSets[0].Length;

            ContinuousAction[] actions     = new ContinuousAction[n];
            double[]           totalWeight = new double[n];
            for (int i = 0; i < weights.Length; ++i)
            {
                for (int j = 0; j < n; ++j)
                {
                    if (actionSets[i][j].Time > 0)
                    {
                        totalWeight[j]  += weights[i];
                        actions[j].EPS  += weights[i] * actionSets[i][j].EPS;
                        actions[j].MPS  += weights[i] * actionSets[i][j].MPS;
                        actions[j].Time += weights[i] * actionSets[i][j].Time;
                    }
                }
            }

            for (int j = 0; j < n; ++j)
            {
                if (totalWeight[j] > 0)
                {
                    actions[j].EPS  /= totalWeight[j];
                    actions[j].MPS  /= totalWeight[j];
                    actions[j].Time /= totalWeight[j];
                }
            }
            return(actions);
        }
Esempio n. 2
0
        public static void FindBestActions(ActionDistribution[] dists, double[] factors, int[] candidates, out KeyValuePair <int, double>[][] selectedActions)
        {
            int n = dists.Length;

            ContinuousAction[][] actionSets = new ContinuousAction[n][];
            double mpsLeft = 0.0;

            double[] timeLeft = new double[n];
            for (int i = 0; i < n; ++i)
            {
                actionSets[i] = dists[i].Actions;
                timeLeft[i]   = dists[i].MaxFraction - dists[i].TotalFraction;
                mpsLeft      += (dists[i].MaxMPS - dists[i].TotalMPS) * factors[i];
            }

            FindBestActions(actionSets, factors, candidates, timeLeft, mpsLeft, out selectedActions);
        }
Esempio n. 3
0
 void addSelfHealing(ActionDistribution dist, ContinuousAction[] actions, ComputedSpell[] spells, double weight)
 {
     dist.AddPassive((int)TreePassive.Perserverance, PerseveranceHPS * weight);
     dist.AddPassive((int)TreePassive.NaturesWard, actions[(int)TreeAction.RaidRejuvenation].EPS * actions[(int)TreeAction.RaidRejuvenation].Time / spells[(int)TreeSpell.Rejuvenation].Duration * NaturesWardUptime * weight);
 }
Esempio n. 4
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);
 }
Esempio n. 5
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;
        }
Esempio n. 6
0
        public static void FindBestActions(ActionDistribution[] dists, double[] factors, int[] candidates, out KeyValuePair<int, double>[][] selectedActions)
        {
            int n = dists.Length;
            ContinuousAction[][] actionSets = new ContinuousAction[n][];
            double mpsLeft = 0.0;
            double[] timeLeft = new double[n];
            for (int i = 0; i < n; ++i)
            {
                actionSets[i] = dists[i].Actions;
                timeLeft[i] = dists[i].MaxFraction - dists[i].TotalFraction;
                mpsLeft += (dists[i].MaxMPS - dists[i].TotalMPS) * factors[i];
            }

            FindBestActions(actionSets, factors, candidates, timeLeft, mpsLeft, out selectedActions);
        }
Esempio n. 7
0
        public static void FindBestActions(ContinuousAction[][] actionSets, double[] factors, int[] candidates, double[] timeLeft, double mpsLeft, out KeyValuePair<int, double>[][] selectedActions)
        {
            List<int> cooldownsList = new List<int>(candidates.Length);
            List<int> fillersList = new List<int>(candidates.Length);
            for (int j = 0; j < candidates.Length; ++j)
            {
                bool isCooldown = false;
                bool isUseful = false;
                for (int i = 0; i < actionSets.Length; ++i)
                {
                    if (actionSets[i][candidates[j]].EPS > 0)
                    {
                        isUseful = true;
                        if (actionSets[i][candidates[j]].Limit < 1)
                            isCooldown = true;
                    }
                }

                if (isUseful)
                {
                    if (isCooldown)
                        cooldownsList.Add(candidates[j]);
                    else
                        fillersList.Add(candidates[j]);
                }
            }
            int[] cooldowns = cooldownsList.ToArray();
            int[] fillers = fillersList.ToArray();

            int nsubdivs = cooldowns.Length + 1;
            int nactions = fillers.Length + 2;
            int ndivdivs = actionSets.Length * nsubdivs;
            double[] mps = new double[ndivdivs * nactions];
            double[] eps = new double[ndivdivs * nactions];
            for (int i = 0; i < actionSets.Length; ++i)
            {
                double nonCooldownTime = timeLeft[i];
                for (int j = 0; j < cooldowns.Length; ++j)
                {
                    nonCooldownTime -= actionSets[i][cooldowns[j]].Limit;
                }
                if (nonCooldownTime < 0)
                    throw new NotSupportedException("There must be enough time to fully use all cooldowns at once");
            
                for (int j = 0; j <= cooldowns.Length; ++j)
                {
                    double factor = factors[i] * ((j < cooldowns.Length) ? actionSets[i][cooldowns[j]].Limit : nonCooldownTime);
                    for (int k = 0; k < fillers.Length; ++k)
                    {
                        mps[(i * nsubdivs + j) * nactions + k] = actionSets[i][fillers[k]].MPS * factor;
                        eps[(i * nsubdivs + j) * nactions + k] = actionSets[i][fillers[k]].EPS * factor;
                    }
                    if (j < cooldowns.Length)
                    {
                        mps[(i * nsubdivs + j + 1) * nactions - 2] = actionSets[i][cooldowns[j]].MPS * factor;
                        eps[(i * nsubdivs + j + 1) * nactions - 2] = actionSets[i][cooldowns[j]].EPS * factor;
                    }
                    //mps[(i * nsubdivs + j + 1) * nactions - 1] = 0;
                    //eps[(i * nsubdivs + j + 1) * nactions - 1] = 0;
                }                
            }
            int[] selections;
            int interpSet;
            int interpTarget;
            double interpT;

            FindBestBudgetSplit(mps, eps, nactions, ndivdivs, mpsLeft, out selections, out interpSet, out interpT, out interpTarget);

            List<KeyValuePair<int, double>> selectionList = new List<KeyValuePair<int, double>>();

            selectedActions = new KeyValuePair<int, double>[ndivdivs][];
            for (int i = 0; i < actionSets.Length; ++i)
            {
                double nonCooldownTime = timeLeft[i];
                for (int j = 0; j < cooldowns.Length; ++j)
                {
                    nonCooldownTime -= actionSets[i][cooldowns[j]].Limit;
                }

                selectionList.Clear();
                for (int j = 0; j <= cooldowns.Length; ++j)
                {
                    int p = i * nsubdivs + j;
                    double factor = (j < cooldowns.Length) ? actionSets[i][cooldowns[j]].Limit : nonCooldownTime;
                    int limit = nactions - (j == cooldowns.Length ? 2 : 1);
                    if (selections[p] < limit)
                    {
                        int actionNum = (selections[p] < fillers.Length) ? fillers[selections[p]] : cooldowns[j];

                        if (actionSets[i][actionNum].EPS > 0)
                            selectionList.Add(new KeyValuePair<int,double>(actionNum, factor * ((p == interpSet) ? (1 - interpT) : 1)));
                    }
                    if (p == interpSet && interpTarget < limit)
                    {
                        int actionNum = (interpTarget < fillers.Length) ? fillers[interpTarget] : cooldowns[j];
                        if (actionSets[i][actionNum].EPS > 0)
                            selectionList.Add(new KeyValuePair<int, double>(actionNum, factor * interpT));
                    }
                }
                selectedActions[i] = selectionList.ToArray();
            }
        }
Esempio n. 8
0
 public ActionDistribution(ContinuousAction[] actions, int passives)
 {
     this.passives = passives;
     this.actions = actions;
     fraction = new double[actions.Length];
     mps = new double[actions.Length];
     eps = new double[actions.Length];
     cps = new double[actions.Length];
     tps = new double[actions.Length];
     passiveEPS = new double[passives];
     passiveMPS = new double[passives];
     passiveTPS = new double[passives];
 }
Esempio n. 9
0
        public static ContinuousAction[] AverageActionSets(ContinuousAction[][] actionSets, double[] weights)
        {
            int n = actionSets[0].Length;
            ContinuousAction[] actions = new ContinuousAction[n];
            double[] totalWeight = new double[n];
            for (int i = 0; i < weights.Length; ++i)
            {
                for (int j = 0; j < n; ++j)
                {
                    if (actionSets[i][j].Time > 0)
                    {
                        totalWeight[j] += weights[i];
                        actions[j].EPS += weights[i] * actionSets[i][j].EPS;
                        actions[j].MPS += weights[i] * actionSets[i][j].MPS;
                        actions[j].Time += weights[i] * actionSets[i][j].Time;
                    }
                }
            }

            for (int j = 0; j < n; ++j)
            {
                if (totalWeight[j] > 0)
                {
                    actions[j].EPS /= totalWeight[j];
                    actions[j].MPS /= totalWeight[j];
                    actions[j].Time /= totalWeight[j];
                }
            }
            return actions;
        }