public CombatTable2(Character c, Stats stats, CharacterCalculationsTankDK calcs, CalculationOptionsTankDK calcOpts) { if (calcOpts.bExperimental) { this.m_CState = new CombatState(); if (c != null) { if (c.DeathKnightTalents == null) { c.DeathKnightTalents = new DeathKnightTalents(); } this.m_CState.m_Talents = (DeathKnightTalents)c.DeathKnightTalents.Clone(); } this.m_CState.m_Stats = stats.Clone(); m_Calcs = calcs; m_Opts = calcOpts; this.m_CState.m_NumberOfTargets = (float)m_Opts.uNumberTargets; m_Rotation = calcOpts.m_Rotation; //TODO: Handle Expertise if (c.MainHand != null && c.MainHand.Item.Type != ItemType.None) { m_CState.MH = new Weapon(c.MainHand.Item, m_CState.m_Stats, m_Opts, 0); m_CState.OH = null; if (c.MainHand.Slot != ItemSlot.TwoHand) { if (c.OffHand != null && c.OffHand.Item.Type != ItemType.None) { m_CState.OH = new Weapon(c.OffHand.Item, this.m_CState.m_Stats, m_Opts, 0); } } } else { m_CState.MH = null; m_CState.OH = null; } // Checking the rotation: if (m_Rotation.IcyTouch == 0 && m_Rotation.PlagueStrike == 0 && m_Rotation.BloodStrike == 0) { // Then this is probably a null rotation, and // so let's build one? m_Rotation = new Rotation(this.m_CState.m_Talents); } BuildRotation(); // TODO: move this out of the constructor CompileRotation(m_Rotation); } }
public void TankDK_Rotation() { Rawr.TankDK.CharacterCalculationsTankDK CalcTankDK = new Rawr.TankDK.CharacterCalculationsTankDK(); CalculationOptionsTankDK calcOpts = new CalculationOptionsTankDK(); Rawr.DK.StatsDK TotalStats = new Rawr.DK.StatsDK(); Rawr.DPSDK.CharacterCalculationsDPSDK DPSCalcs = new Rawr.DPSDK.CharacterCalculationsDPSDK(); Rawr.DPSDK.CalculationOptionsDPSDK DPSopts = new Rawr.DPSDK.CalculationOptionsDPSDK(); Rawr.DK.DKCombatTable ct = new Rawr.DK.DKCombatTable(m_char, TotalStats, DPSCalcs, DPSopts, m_char.BossOptions); Rawr.DK.Rotation rot = new Rawr.DK.Rotation(ct, false); rot.PRE_BloodDiseased(); Assert.IsTrue(rot.m_TPS > 0, "rotation BloodDiseased produces 0 DPS"); }
private CharacterCalculationsTankDK GetCharacterCalculations(TankDKChar TDK, StatsDK stats, Rotation rot, bool isBurstCalc, bool needsDisplayCalcs) { CharacterCalculationsTankDK calcs = new CharacterCalculationsTankDK(); // Level differences. int iLevelDiff = Math.Max(TDK.bo.Level - TDK.Char.Level, 0); float fChanceToGetHit = 1f - Math.Min(1f, stats.Miss + stats.Dodge + stats.EffectiveParry); float ArmorDamageReduction = (float)StatConversion.GetArmorDamageReduction(TDK.bo.Level, stats.Armor, 0f, 0f); #region **** Setup Fight parameters **** // Get the values of each type of damage in %. // So first we get each type of damage in the same units: DPS. // Get the total DPS. float[] fCurrentDTPS = new float[3]; fCurrentDTPS[(int)SurvivalSub.Physical] = 0f; fCurrentDTPS[(int)SurvivalSub.Bleed] = 0f; fCurrentDTPS[(int)SurvivalSub.Magic] = 0f; float[] fCurrentDmgBiggestHit = new float[3]; fCurrentDmgBiggestHit[(int)SurvivalSub.Physical] = 0f; fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed] = 0f; fCurrentDmgBiggestHit[(int)SurvivalSub.Magic] = 0f; float[] fCurrentDTPSPerc = new float[3]; fCurrentDTPSPerc[(int)SurvivalSub.Physical] = 1f; fCurrentDTPSPerc[(int)SurvivalSub.Bleed] = 0f; fCurrentDTPSPerc[(int)SurvivalSub.Magic] = 0f; float fTotalDTPS = 0; float fAvoidanceTotal = 1f - fChanceToGetHit; // We want to start getting the Boss Handler stuff going on. // Setup initial Boss data. // How much of what kind of damage does this boss deal with? #region ** Incoming Boss Dam ** // Let's make sure this is even valid float DPHit = 0; float DPTick = 0; switch (TDK.calcOpts.PlayerRole) { case 0: TDK.role = PLAYER_ROLES.MainTank; break; case 1: TDK.role = PLAYER_ROLES.OffTank; break; case 2: TDK.role = PLAYER_ROLES.TertiaryTank; break; default: TDK.role = PLAYER_ROLES.MainTank; break; } TDK.role = PLAYER_ROLES.MainTank; foreach (Attack a in TDK.bo.Attacks) { // PlayerRole on calcOpts is MT=0, OT=1, TT=2, Any Tank = 3 // Any Tank means it should be affected by anything that affects a tanking role if (a.AffectsRole[PLAYER_ROLES.MainTank] && (TDK.calcOpts.PlayerRole == 0 || TDK.calcOpts.PlayerRole == 3) || a.AffectsRole[PLAYER_ROLES.OffTank] && (TDK.calcOpts.PlayerRole == 1 || TDK.calcOpts.PlayerRole == 3) || a.AffectsRole[PLAYER_ROLES.TertiaryTank] && (TDK.calcOpts.PlayerRole == 2 || TDK.calcOpts.PlayerRole == 3)) { // TODO: Figure out a way to get the phase changes handled. DPHit = a.DamagePerHit; DPTick = a.DamagePerTick; if (a.DamageIsPerc) { #if DEBUG if ((a.DamagePerHit >= 1f) || (a.DamagePerTick >= 1f)) throw new Exception("Percentage Damage is >= 100%."); #endif DPHit = a.DamagePerHit * stats.Health; DPTick = a.DamagePerTick * stats.Health; } // Bleeds vs Magic vs Physical if (a.DamageType == ItemDamageType.Physical) { // Bleed or Physical // Need to figure out how to determine bleed vs. physical hits. // Also need to balance out the physical hits and balance the hit rate. // JOTHAY NOTE: Bleeds are DoTs if (a.IsDoT) { fCurrentDTPS[(int)SurvivalSub.Bleed] += GetDPS(DPHit, a.AttackSpeed) + GetDPS(DPTick, a.TickInterval); if (fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed] < DPHit + (DPTick * a.NumTicks)) fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed] = DPHit + (DPTick * a.NumTicks); } else { fCurrentDTPS[(int)SurvivalSub.Physical] += GetDPS(DPHit, a.AttackSpeed); if (fCurrentDmgBiggestHit[(int)SurvivalSub.Physical] < DPHit) fCurrentDmgBiggestHit[(int)SurvivalSub.Physical] = DPHit; } } else { // Magic now covering magical dots. fCurrentDTPS[(int)SurvivalSub.Magic] += GetDPS(DPHit, a.AttackSpeed) + GetDPS(DPTick, a.TickInterval); if (fCurrentDmgBiggestHit[(int)SurvivalSub.Magic] < DPHit + (DPTick * a.NumTicks)) fCurrentDmgBiggestHit[(int)SurvivalSub.Magic] = DPHit + (DPTick * a.NumTicks); } } } fTotalDTPS += fCurrentDTPS[(int)SurvivalSub.Physical]; fTotalDTPS += fCurrentDTPS[(int)SurvivalSub.Bleed]; fTotalDTPS += fCurrentDTPS[(int)SurvivalSub.Magic]; if (fTotalDTPS > 0) { fCurrentDTPSPerc[(int)SurvivalSub.Physical] = fCurrentDTPS[(int)SurvivalSub.Physical] / fTotalDTPS; fCurrentDTPSPerc[(int)SurvivalSub.Bleed] = fCurrentDTPS[(int)SurvivalSub.Bleed] / fTotalDTPS; fCurrentDTPSPerc[(int)SurvivalSub.Magic] = fCurrentDTPS[(int)SurvivalSub.Magic] / fTotalDTPS; } #endregion // Set the Fight Duration to no larger than the Berserk Timer // Question: What is the units for Berserk & Speed Timer? MS/S/M? #endregion #region ***** Survival Rating ***** // Magical damage: // if there is a max resistance, then it's likely they are stacking for that resistance. So factor in that Max resistance. float fMaxResist = Math.Max(stats.ArcaneResistance, stats.FireResistance); fMaxResist = Math.Max(fMaxResist, stats.FrostResistance); fMaxResist = Math.Max(fMaxResist, stats.NatureResistance); fMaxResist = Math.Max(fMaxResist, stats.ShadowResistance); float fMagicDR = StatConversion.GetAverageResistance(TDK.bo.Level, TDK.Char.Level, fMaxResist, 0f); calcs.MagicDamageReduction = fMagicDR; float[] SurvivalResults = new float [EnumHelper.GetCount(typeof(SurvivalSub))]; SurvivalResults = GetSurvival(ref TDK, stats, fCurrentDTPSPerc, ArmorDamageReduction, fMagicDR); calcs.ArmorDamageReduction = ArmorDamageReduction; calcs.PhysicalSurvival = SoftCapSurvival(TDK, fCurrentDmgBiggestHit[(int)SurvivalSub.Physical], SurvivalResults[(int)SurvivalSub.Physical], isBurstCalc); calcs.BleedSurvival = SoftCapSurvival(TDK, fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed], SurvivalResults[(int)SurvivalSub.Bleed], isBurstCalc); calcs.MagicSurvival = SoftCapSurvival(TDK, fCurrentDmgBiggestHit[(int)SurvivalSub.Magic], SurvivalResults[(int)SurvivalSub.Magic], isBurstCalc); calcs.HitsToSurvive = TDK.calcOpts.HitsToSurvive; #endregion #region ***** Threat Rating ***** rot.TotalDamage += (int)(stats.FireDamage * (1 + stats.BonusFireDamageMultiplier) * rot.CurRotationDuration); rot.TotalThreat += (int)(stats.FireDamage * (1 + stats.BonusFireDamageMultiplier) * rot.CurRotationDuration) * 2; calcs.RotationTime = rot.CurRotationDuration; // Display the rot in secs. calcs.Threat = rot.m_TPS; calcs.DPS = rot.m_DPS; calcs.Blood = rot.m_BloodRunes; calcs.Frost = rot.m_FrostRunes; calcs.Unholy = rot.m_UnholyRunes; calcs.Death = rot.m_DeathRunes; calcs.RP = rot.m_RunicPower; calcs.TotalThreat = (int)rot.TotalThreat; calcs.ThreatWeight = TDK.calcOpts.ThreatWeight; if (needsDisplayCalcs) { TDK.calcOpts.szRotReport = rot.ReportRotation(); } #endregion #region ***** Mitigation Rating ***** float[] fCurrentDTPSNoAvoid = new float[3]; fCurrentDTPSNoAvoid = fCurrentDTPS.Clone() as float[]; float[] fCurrentMitigation = GetMitigation(ref TDK, stats, rot, (stats.CritChanceReduction / .06f), ArmorDamageReduction, fCurrentDTPS, fMagicDR); calcs.ArmorMitigation = fCurrentMitigation[(int)MitigationSub.Armor]; calcs.AvoidanceMitigation = fCurrentMitigation[(int)MitigationSub.Avoidance]; calcs.CritMitigation = fCurrentMitigation[(int)MitigationSub.Crit]; calcs.DamageTakenMitigation = fCurrentMitigation[(int)MitigationSub.DamageReduction]; calcs.DamageTakenMitigation += fCurrentMitigation[(int)MitigationSub.Haste]; calcs.HealsMitigation = fCurrentMitigation[(int)MitigationSub.Heals]; calcs.ImpedenceMitigation = fCurrentMitigation[(int)MitigationSub.Impedences]; calcs.MagicDamageReductedByAmount = fCurrentMitigation[(int)MitigationSub.AMS]; calcs.MagicDamageReductedByAmount += fCurrentMitigation[(int)MitigationSub.Magic]; calcs.Crit = (.06f - stats.CritChanceReduction); calcs.DTPS = 0; calcs.DTPSNoAvoidance = 0; foreach (float f in fCurrentDTPS) { // These are sometimes coming back as negative. // Assuming we are just 100% absorbing the attack, no damage if (f > 0) { calcs.DTPS += f; } } if (TDK.calcOpts.b_RecoveryInclAvoidance == false) { GetMitigation(ref TDK, stats, rot, (stats.CritChanceReduction / .06f), ArmorDamageReduction, fCurrentDTPSNoAvoid, fMagicDR, false); foreach (float f in fCurrentDTPSNoAvoid) { // These are sometimes coming back as negative. // Assuming we are just 100% absorbing the attack, no damage if (f > 0) { calcs.DTPSNoAvoidance += f; } } } // Have to ensure we don't divide by 0 calcs.Mitigation = StatConversion.MitigationScaler / (Math.Max(1f, calcs.DTPS) / fTotalDTPS); #endregion return calcs; }
public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, bool referenceCalculation, bool significantChange, bool needsDisplayCalculations) { #region Setup CharacterCalculationsTankDK basecalcs = new CharacterCalculationsTankDK(); CharacterCalculationsTankDK calcs = new CharacterCalculationsTankDK(); TankDKChar TDK = new TankDKChar(); if (character == null) { return calcs; } TDK.calcOpts = character.CalculationOptions as CalculationOptionsTankDK; if (TDK.calcOpts == null) { return calcs; } TDK.Char = character; TDK.bo = character.BossOptions; // Make sure there is at least one attack in the list. // If there's not, add a Default Melee Attack for processing if (TDK.bo.Attacks.Count < 1) { TDK.Char.IsLoading = true; TDK.bo.DamagingTargs = true; TDK.bo.Attacks.Add(BossHandler.ADefaultMeleeAttack); TDK.Char.IsLoading = false; } // Make sure there is a default melee attack // If the above processed, there will be one so this won't have to process // If the above didn't process and there isn't one, add one now if (TDK.bo.DefaultMeleeAttack == null) { TDK.Char.IsLoading = true; TDK.bo.DamagingTargs = true; TDK.bo.Attacks.Add(BossHandler.ADefaultMeleeAttack); TDK.Char.IsLoading = false; } // Since the above forced there to be an attack it's safe to do this without a null check // Attack bossAttack = TDK.bo.DefaultMeleeAttack; #endregion #region Stats // Get base stats that will be used for paperdoll: StatsDK stats = GetCharacterStats(TDK.Char, additionalItem) as StatsDK; // validate that we get a stats object; if (null == stats) { return calcs; } // This is the point that SHOULD have the right values according to the paper-doll. StatsDK sPaperDoll = stats.Clone() as StatsDK; #endregion #region Evaluation Rawr.DPSDK.CharacterCalculationsDPSDK DPSCalcs = new Rawr.DPSDK.CharacterCalculationsDPSDK(); Rawr.DPSDK.CalculationOptionsDPSDK DPSopts = new Rawr.DPSDK.CalculationOptionsDPSDK(); DPSopts.presence = Presence.Blood; DKCombatTable ct = new DKCombatTable(TDK.Char, stats, DPSCalcs, DPSopts, TDK.bo); Rotation rot = new Rotation(ct, true); rot.PRE_BloodDiseased(); // Base calculation values. This will give us Mitigation, and Survival w/ base stats. basecalcs = GetCharacterCalculations(TDK, stats, rot, false, needsDisplayCalculations); // Setup max values w/ everything turned on. stats = GetCharacterStats(TDK.Char, additionalItem, StatType.Maximum, TDK, rot); calcs.SEStats = stats.Clone() as StatsDK; ct = new DKCombatTable(TDK.Char, stats, DPSCalcs, DPSopts, TDK.bo); rot = new Rotation(ct, true); rot.PRE_BloodDiseased(); calcs = GetCharacterCalculations(TDK, stats, rot, true, needsDisplayCalculations); #region Burst // Burst as On-Use Abilties. calcs.Burst = 0; calcs.BurstWeight = TDK.calcOpts.BurstWeight; if (calcs.BurstWeight > 0) { calcs.Burst += calcs.Survivability - basecalcs.Survivability; if (calcs.Burst < 0 || float.IsNaN(calcs.Burst)) { calcs.Burst = 0; } // This should never happen but just in case calcs.Burst += calcs.Mitigation - basecalcs.Mitigation; if (calcs.Burst < 0 || float.IsNaN(calcs.Burst)) { calcs.Burst = 0; } // This should never happen but just in case // Survival calcs.PhysicalSurvival = basecalcs.PhysicalSurvival; calcs.MagicSurvival = basecalcs.MagicSurvival; calcs.BleedSurvival = basecalcs.BleedSurvival; // Mitigation calcs.Mitigation = basecalcs.Mitigation; } #endregion #region **** Recovery: DS & Blood Shield **** float minDSHeal = stats.Health * .07f; // 4.1: DS Heals for 20% of Dam Taken over the last 5 secs. float DTPSFactor = calcs.DTPS * 5f; if (TDK.calcOpts.b_RecoveryInclAvoidance == false) { DTPSFactor = calcs.DTPSNoAvoidance * 5f; } float DamDSHeal = (DTPSFactor * .20f) * (1 + .15f * TDK.Char.DeathKnightTalents.ImprovedDeathStrike); // IDS increases heals by .15 * level float DSHeal = Math.Max(minDSHeal, DamDSHeal); calcs.DSHeal = DSHeal; calcs.DSOverHeal = DSHeal * TDK.calcOpts.pOverHealing; calcs.DSCount = TDK.bo.BerserkTimer * rot.m_DSperSec; float BloodShield = (DSHeal * .5f) * (1 + (stats.Mastery * .0625f)); calcs.BShield = BloodShield; // 4.3 Removing Hitchance for healing float DSHealsPSec = (DSHeal * rot.m_DSperSec * (1f - TDK.calcOpts.pOverHealing)); calcs.TotalDShealed = DSHealsPSec * TDK.bo.BerserkTimer; float BShieldPSec = BloodShield * rot.m_DSperSec; // A new shield w/ each DS. calcs.TotalBShield = BShieldPSec * TDK.bo.BerserkTimer; calcs.Recovery = BloodShield + (DSHeal * (1f - TDK.calcOpts.pOverHealing)); calcs.HPS += DSHealsPSec; calcs.DTPS -= BShieldPSec; calcs.RecoveryWeight = TDK.calcOpts.RecoveryWeight; #endregion #endregion #region Key Data Validation if (float.IsNaN(calcs.Threat) || float.IsNaN(calcs.Survivability) || float.IsNaN(calcs.Burst) || float.IsNaN(calcs.Recovery) || float.IsNaN(calcs.Mitigation) || float.IsNaN(calcs.OverallPoints)) { #if DEBUG throw new Exception("One of the Subpoints are Invalid."); #endif } #endregion #region Display only work calcs.Miss = sPaperDoll.Miss; calcs.Dodge = sPaperDoll.Dodge; calcs.Parry = sPaperDoll.Parry; calcs.BasicStats = sPaperDoll; calcs.SEStats = stats.Clone() as StatsDK; // The full character data. calcs.TargetLevel = TDK.bo.Level; if (null != rot.m_CT.MH) { calcs.TargetDodge = rot.m_CT.MH.chanceDodged; calcs.TargetMiss = rot.m_CT.MH.chanceMissed; calcs.TargetParry = rot.m_CT.MH.chanceParried; calcs.Expertise = rot.m_CT.m_Calcs.MHExpertise; } #endregion return calcs; }
public void TankDK_Rotation() { Rawr.TankDK.CharacterCalculationsTankDK CalcTankDK = new Rawr.TankDK.CharacterCalculationsTankDK(); CalculationOptionsTankDK calcOpts = new CalculationOptionsTankDK(); Rawr.DK.StatsDK TotalStats = new Rawr.DK.StatsDK(); Rawr.DPSDK.CharacterCalculationsDPSDK DPSCalcs = new Rawr.DPSDK.CharacterCalculationsDPSDK(); Rawr.DPSDK.CalculationOptionsDPSDK DPSopts = new Rawr.DPSDK.CalculationOptionsDPSDK(); Rawr.DK.DKCombatTable ct = new Rawr.DK.DKCombatTable(m_char, TotalStats, DPSCalcs, DPSopts, m_char.BossOptions); Rawr.DK.Rotation rot = new Rawr.DK.Rotation(ct, false); rot.PRE_BloodDiseased(); Assert.IsTrue(rot.m_TPS > 0, "rotation BloodDiseased produces 0 DPS"); }