public ActionDistribution Clone() { ActionDistribution clone = (ActionDistribution)this.MemberwiseClone(); clone.fraction = (double[])clone.fraction.Clone(); clone.eps = (double[])clone.eps.Clone(); clone.mps = (double[])clone.mps.Clone(); clone.cps = (double[])clone.cps.Clone(); clone.tps = (double[])clone.tps.Clone(); clone.passiveEPS = (double[])clone.passiveEPS.Clone(); clone.passiveMPS = (double[])clone.passiveMPS.Clone(); clone.passiveTPS = (double[])clone.passiveTPS.Clone(); return(clone); }
public static ActionDistribution Combine(ActionDistribution[] dists, double[] coeffs) { ActionDistribution dist = new ActionDistribution(dists[0].actions, dists[0].passives); int e = dist.fraction.Length; int p = dist.passives; int n = dists.Length; dist.maxFraction = 0; dist.maxMPS = 0; for (int i = 0; i < n; ++i) { dist.totalMPS += dists[i].totalMPS * coeffs[i]; dist.totalFraction += dists[i].totalFraction * coeffs[i]; dist.maxMPS += dists[i].maxMPS * coeffs[i]; dist.maxFraction += dists[i].maxFraction * coeffs[i]; for (int j = 0; j < e; ++j) { dist.fraction[j] += dists[i].fraction[j] * coeffs[i]; dist.eps[j] += dists[i].eps[j] * coeffs[i]; dist.mps[j] += dists[i].mps[j] * coeffs[i]; dist.tps[j] += dists[i].tps[j] * coeffs[i]; dist.cps[j] += dists[i].cps[j] * coeffs[i]; } for (int j = 0; j < p; ++j) { dist.passiveEPS[j] += dists[i].passiveEPS[j] * coeffs[i]; } for (int j = 0; j < p; ++j) { dist.passiveMPS[j] += dists[i].passiveMPS[j] * coeffs[i]; } for (int j = 0; j < p; ++j) { dist.passiveTPS[j] += dists[i].passiveTPS[j] * coeffs[i]; } } return(dist); }
ActionDistribution[] computeRaidHealing(bool burst, out int[] candidates) { ActionDistribution[] dists = new ActionDistribution[calc.Division.Count]; for (int div = 0; div < calc.Division.Count; ++div) { TreeStats stats = calc.Stats[div]; ComputedSpell[] spells = calc.Spells[div]; TreeComputedData data = DivisionData[div]; bool refreshLBWithDHs = Talents.EmpoweredTouch == 2 && opts.RefreshLifebloomWithDirectHeals; ContinuousAction[] actions = calc.Actions[div]; ActionDistribution dist = new ActionDistribution(actions, (int)TreePassive.Count); if (!burst) dist.MaxMPS = calc.ManaRegen; addPassiveHealing(dist, stats); addSelfHealing(dist, actions, spells, 1); addLifebloomRefresh(dist, actions, spells[(int)TreeSpell.Lifebloom], data, refreshLBWithDHs, opts.RejuvenationTankDuringRaid); double minDHRate = 0; if (refreshLBWithDHs) minDHRate = Math.Max(minDHRate, 1.0f / data.LifebloomRefreshInterval); // note that 1/15 + 1/25 > 1/10, so in practice swiftmends and cchts alone should be enough to keep up Harmony, unless the Swiftmend cast delay has been set high (> 4 seconds) if (opts.Restoration) minDHRate = Math.Max(minDHRate, 1.0f / 10.0f - 1.0 / (15 + opts.SwiftmendCastDelay)); addSpecialDirectHeals(dist, spells, stats, refreshLBWithDHs, !burst, minDHRate, burst); // TODO: add option to choose when to use tranquility (maybe even model it as a division?) dist.AddActionOnCooldown((int)TreeAction.RaidTranquility); if (Restoration) dist.AddActionOnCooldown((int)TreeAction.RaidSwiftmend); if (opts.RejuvenationTankDuringRaid) dist.AddActionOnCooldown((int)TreeAction.TankRejuvenation); if (Talents.WildGrowth > 0) dist.AddActionOnCooldown((int)TreeAction.RaidWildGrowth); dists[div] = dist; } List<int> candidatesList = new List<int>(); if(Talents.NaturesSwiftness > 0) candidatesList.Add((int)TreeAction.RaidSwiftHT); candidatesList.Add((int)TreeAction.RaidRejuvenation); candidatesList.Add((int)TreeAction.RaidHealingTouch); candidatesList.Add((int)TreeAction.RaidNourish); candidatesList.Add((int)TreeAction.RaidRegrowth); if(character.DruidTalents.NaturesBounty > 0) candidatesList.Add(opts.RejuvenationTankDuringRaid ? (int)TreeAction.RaidRj2NourishNB : (int)TreeAction.RaidRj3NourishNB); candidatesList.Add((int)TreeAction.RaidTolLb); candidatesList.Add((int)TreeAction.RaidTolLbCcHt); candidates = candidatesList.ToArray(); return dists; }
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 addPassiveHealing(ActionDistribution dist, TreeStats stats) { dist.AddPassive((int)TreePassive.HealingTrinkets, stats.Healed * stats.DirectHealMultiplier * getCritMultiplier(stats, 0, 0)); if (InsectSwarm) dist.AddActionOnCooldown((int)TreeAction.InsectSwarm); }
void addSpecialDirectHeals(ActionDistribution dist, ComputedSpell[] spells, TreeStats stats, bool onTank, bool useCCs, double minRate, bool htFiller) { // TODO: possibly make configurable, it's disabled because in practice it's unlikely to have a clearcast exactly when Nature's Grace is to be triggered bool procNaturesGraceWithCCs = false; bool naturesGraceHandled = false; if (InsectSwarm) naturesGraceHandled = true; double dhrate = 0; double ccrate = spells[(int)TreeSpell.Lifebloom].TPS * 0.02f * Talents.MalfurionsGift; if (ccrate != 0.0f) { if (procNaturesGraceWithCCs && Talents.NaturesGrace > 0) { naturesGraceHandled = true; dist.AddAction(onTank ? (int)TreeAction.TankClearRegrowth : (int)TreeAction.RaidClearRegrowth, 1.0f / 60.0f * spells[(int)TreeSpell.Regrowth].Action.Time); ccrate -= 1.0f / 60.0f; dhrate += 1.0f / 60.0f; } if (useCCs) { dist.AddAction(onTank ? (int)TreeAction.TankClearHT : (int)TreeAction.RaidClearHT, ccrate * spells[(int)TreeSpell.HealingTouch].Action.Time); dhrate += ccrate; } } if (!naturesGraceHandled && Talents.NaturesGrace > 0) { dist.AddAction(onTank ? (int)TreeAction.TankRegrowth : (int)TreeAction.RaidRegrowth, 1.0f / 60.0f * spells[(int)TreeSpell.Regrowth].Action.Time); dhrate += 1.0f / 60.0f; } if (dhrate < minRate) { if (!htFiller) dist.AddAction(onTank ? (int)TreeAction.TankNourish : (int)TreeAction.RaidNourish, (minRate - dhrate) * spells[(int)TreeSpell.Nourish].Action.Time); else dist.AddAction(onTank ? (int)TreeAction.TankHealingTouch : (int)TreeAction.RaidHealingTouch, (minRate - dhrate) * spells[(int)TreeSpell.HealingTouch].Action.Time); } }
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); }
ActionDistribution[] computeTankHealing(bool burst, out int[] candidates) { ActionDistribution[] dists = new ActionDistribution[calc.Division.Count]; for (int div = 0; div < calc.Division.Count; ++div) { TreeStats stats = calc.Stats[div]; ComputedSpell[] spells = calc.Spells[div]; TreeComputedData data = DivisionData[div]; ContinuousAction[] actions = calc.Actions[div]; ActionDistribution dist = new ActionDistribution(actions, (int)TreePassive.Count); if (!burst) dist.MaxMPS = calc.ManaRegen; addPassiveHealing(dist, stats); addSelfHealing(dist, actions, spells, opts.TankRaidHealingWeight); addLifebloomRefresh(dist, actions, spells[(int)TreeSpell.Lifebloom], data, Talents.EmpoweredTouch != 0, true); // assume we will automatically heal the tank enough to refresh lifebloom if we have Empowered Touch addSpecialDirectHeals(dist, spells, stats, true, !burst, 0, false); if (Restoration && opts.TankSwiftmend) dist.AddActionOnCooldown((int)TreeAction.TankSwiftmend); if (!burst && opts.TankWildGrowth && Talents.WildGrowth > 0) dist.AddActionOnCooldown((int)TreeAction.TankWildGrowth); dists[div] = dist; } List<int> candidatesList = new List<int>(); if (Talents.NaturesSwiftness > 0) candidatesList.Add((int)TreeAction.TankSwiftHT); candidatesList.Add((int)TreeAction.TankRejuvenation); candidatesList.Add((int)TreeAction.TankHealingTouch); candidatesList.Add((int)TreeAction.TankNourish); candidatesList.Add((int)TreeAction.TankRegrowth); if(character.DruidTalents.NaturesBounty > 0) candidatesList.Add((int)TreeAction.TankRj2NourishNB); candidatesList.Add((int)TreeAction.TankTolLbCcHt); candidates = candidatesList.ToArray(); return dists; }
public ActionDistributionsByDivision(FightDivision division, ActionDistribution[] dists) { Division = division; Distributions = dists; Distribution = ActionDistribution.Combine(dists, Division.Fractions); }
public static void AddBestActions(ActionDistribution[] dists, double[] factors, int[] candidates, double unevenPart) { if (unevenPart < 1) { for (int i = 0; i < dists.Length; ++i) { double maxMPS = dists[i].MaxMPS; dists[i].MaxMPS = dists[i].TotalMPS * unevenPart + maxMPS * (1 - unevenPart); dists[i].AddBestActions(candidates); } } if(unevenPart > 0) AddBestActions(dists, factors, candidates); }
public static void AddBestActions(ActionDistribution[] dists, double[] factors, int[] candidates) { KeyValuePair<int, double>[][] selectedActions; FindBestActions(dists, factors, candidates, out selectedActions); for (int i = 0; i < dists.Length; ++i) { double maxMPS = dists[i].MaxMPS; dists[i].MaxMPS = double.PositiveInfinity; foreach(KeyValuePair<int, double> sel in selectedActions[i]) { dists[i].AddAction(sel.Key, sel.Value); } dists[i].MaxMPS = maxMPS; } }
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 ActionDistribution Combine(ActionDistribution[] dists, double[] coeffs) { ActionDistribution dist = new ActionDistribution(dists[0].actions, dists[0].passives); int e = dist.fraction.Length; int p = dist.passives; int n = dists.Length; dist.maxFraction = 0; dist.maxMPS = 0; for(int i = 0; i < n; ++i) { dist.totalMPS += dists[i].totalMPS * coeffs[i]; dist.totalFraction += dists[i].totalFraction * coeffs[i]; dist.maxMPS += dists[i].maxMPS * coeffs[i]; dist.maxFraction += dists[i].maxFraction * coeffs[i]; for (int j = 0; j < e; ++j) { dist.fraction[j] += dists[i].fraction[j] * coeffs[i]; dist.eps[j] += dists[i].eps[j] * coeffs[i]; dist.mps[j] += dists[i].mps[j] * coeffs[i]; dist.tps[j] += dists[i].tps[j] * coeffs[i]; dist.cps[j] += dists[i].cps[j] * coeffs[i]; } for (int j = 0; j < p; ++j) dist.passiveEPS[j] += dists[i].passiveEPS[j] * coeffs[i]; for (int j = 0; j < p; ++j) dist.passiveMPS[j] += dists[i].passiveMPS[j] * coeffs[i]; for (int j = 0; j < p; ++j) dist.passiveTPS[j] += dists[i].passiveTPS[j] * coeffs[i]; } return dist; }