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); }
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); }
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); }
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; }
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); }
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(); } }
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]; }
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; }