private void CreateTriggers(AttackModel am, Character character, Stats stats, CalculationOptionsProtPaladin calcOpts, BossOptions bossOpts, out Dictionary<Trigger, float> triggerIntervals, out Dictionary<Trigger, float> triggerChances) { triggerIntervals = new Dictionary<Trigger, float>(); triggerChances = new Dictionary<Trigger, float>(); float intervalRotation = 9.0f; triggerIntervals[Trigger.MeleeAttack] = triggerIntervals[Trigger.PhysicalAttack] = triggerIntervals[Trigger.MeleeHit] = triggerIntervals[Trigger.PhysicalHit] = triggerIntervals[Trigger.MeleeCrit] = triggerIntervals[Trigger.PhysicalCrit] = Lookup.WeaponSpeed(character, stats); // + calcOptsTargetsHotR / intervalHotR; // 939 has 1 direct damage spell cast in 9 seconds. triggerIntervals[Trigger.DamageSpellCast] = triggerIntervals[Trigger.DamageSpellHit] = triggerIntervals[Trigger.DamageSpellCrit] = 1f / intervalRotation; triggerIntervals[Trigger.DoTTick] = 1f; triggerIntervals[Trigger.SpellCast] = 1.5f; // 939 assumes casting a spell every gcd. Changing auras, and casting a blessing is disregarded. triggerIntervals[Trigger.DamageOrHealingDone] = triggerIntervals[Trigger.DamageDone] = 1f / (1f / triggerIntervals[Trigger.MeleeHit] + 1f / triggerIntervals[Trigger.SpellCast]); triggerIntervals[Trigger.JudgementHit] = 9.0f; triggerIntervals[Trigger.DamageTakenPhysical] = triggerIntervals[Trigger.DamageTakenPutsMeBelow35PercHealth] = triggerIntervals[Trigger.DamageTaken] = (1.0f / am.AttackerHitsPerSecond); triggerIntervals[Trigger.DivineProtection] = 60f; // temporary combat table, used for the implementation of special effects. float hitBonusPhysical = StatConversion.GetPhysicalHitFromRating(stats.HitRating, CharacterClass.Paladin) + stats.PhysicalHit; float hitBonusSpell = StatConversion.GetSpellHitFromRating(stats.HitRating, CharacterClass.Paladin) + stats.SpellHit; float expertiseBonus = StatConversion.GetDodgeParryReducFromExpertise(StatConversion.GetExpertiseFromRating(stats.ExpertiseRating, CharacterClass.Paladin) + stats.Expertise, CharacterClass.Paladin); float chanceMissSpell = Math.Max(0f, StatConversion.GetSpellMiss(character.Level - bossOpts.Level, false) - hitBonusSpell); float chanceMissPhysical = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[bossOpts.Level - character.Level] - hitBonusPhysical); float chanceMissDodge = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[bossOpts.Level - character.Level] - expertiseBonus); float chanceMissParry = Math.Max(0f, StatConversion.WHITE_PARRY_CHANCE_CAP[bossOpts.Level - character.Level] - expertiseBonus); float chanceMissPhysicalAny = chanceMissPhysical + chanceMissDodge + chanceMissParry; triggerChances[Trigger.MeleeAttack] = triggerChances[Trigger.PhysicalAttack] = 1.0f; triggerChances[Trigger.MeleeHit] = triggerChances[Trigger.PhysicalHit] = 1.0f - chanceMissPhysicalAny; triggerChances[Trigger.MeleeCrit] = triggerChances[Trigger.PhysicalCrit] = Math.Min(1f, Math.Max(0, StatConversion.GetPhysicalCritFromRating(stats.CritRating, CharacterClass.Paladin) + StatConversion.GetPhysicalCritFromAgility(stats.Agility, CharacterClass.Paladin) + stats.PhysicalCrit + StatConversion.NPC_LEVEL_CRIT_MOD[bossOpts.Level - character.Level])); triggerChances[Trigger.DamageSpellCrit] = StatConversion.GetSpellCritFromRating(stats.CritRating, CharacterClass.Paladin) + StatConversion.GetSpellCritFromIntellect(stats.Intellect, CharacterClass.Paladin) + stats.SpellCrit + stats.SpellCritOnTarget - (0.006f * (bossOpts.Level - character.Level) + (bossOpts.Level == 88 ? 0.03f : 0.0f)); triggerChances[Trigger.DamageSpellHit] = 1.0f - chanceMissSpell; triggerChances[Trigger.DoTTick] = triggerChances[Trigger.DamageSpellHit] * (character.PaladinTalents.GlyphOfConsecration ? 1.0f : 16.0f / 18.0f); // 16 ticks in 18 seconds of 9696 rotation. cba with cons. glyph atm. triggerChances[Trigger.DamageOrHealingDone] = triggerChances[Trigger.DamageDone] = (triggerIntervals[Trigger.MeleeHit] * triggerChances[Trigger.PhysicalHit] + triggerIntervals[Trigger.SpellCast] * triggerChances[Trigger.DamageSpellHit]) / (triggerIntervals[Trigger.MeleeHit] + triggerIntervals[Trigger.SpellCast]); triggerChances[Trigger.JudgementHit] = triggerChances[Trigger.SpellCast] = triggerChances[Trigger.DamageSpellCast] = triggerChances[Trigger.DamageTaken] = triggerChances[Trigger.DamageTakenPhysical] = 1f; triggerChances[Trigger.DamageTakenPutsMeBelow35PercHealth] = 0.35f; triggerChances[Trigger.DivineProtection] = 1f; }
private Stats GetSpecialEffectStats(Character character, StatsPaladin stats, CalculationOptionsProtPaladin calcOpts, BossOptions bossOpts) { StatsPaladin statsSpecialEffects = new StatsPaladin(); float weaponSpeed = 1.0f; if (character.MainHand != null) weaponSpeed = character.MainHand.Speed; AttackModel am = new AttackModel(character, stats, calcOpts, bossOpts); Dictionary<Trigger, float> triggerIntervals; Dictionary<Trigger, float> triggerChances; CreateTriggers(am, character, stats, calcOpts, bossOpts, out triggerIntervals, out triggerChances); GetSpecialEffectsStats_Child(triggerIntervals, triggerChances, weaponSpeed, calcOpts, bossOpts, stats, ref statsSpecialEffects); // Darkmoon card greatness & Paragon procs // These should always increase strength, which is going to be the Paladin's top stat outside of stamina statsSpecialEffects.Strength += statsSpecialEffects.HighestStat + statsSpecialEffects.Paragon; statsSpecialEffects.HighestStat = 0; statsSpecialEffects.Paragon = 0; if (statsSpecialEffects.HighestSecondaryStat > 0) { float paragon = statsSpecialEffects.HighestSecondaryStat; statsSpecialEffects.HighestSecondaryStat = 0; if ((statsSpecialEffects.CritRating > statsSpecialEffects.HasteRating) && (statsSpecialEffects.CritRating > statsSpecialEffects.MasteryRating)) statsSpecialEffects.CritRating += paragon; else if ((statsSpecialEffects.HasteRating > statsSpecialEffects.CritRating) && (statsSpecialEffects.HasteRating > statsSpecialEffects.MasteryRating)) statsSpecialEffects.HasteRating += paragon; else statsSpecialEffects.MasteryRating += paragon; } // Base Stats statsSpecialEffects.Stamina = (float)Math.Floor(statsSpecialEffects.Stamina * (1.0f + stats.BonusStaminaMultiplier)); statsSpecialEffects.Strength = (float)Math.Floor(statsSpecialEffects.Strength * (1.0f + stats.BonusStrengthMultiplier)); statsSpecialEffects.Agility = (float)Math.Floor(statsSpecialEffects.Agility * (1.0f + stats.BonusAgilityMultiplier)); statsSpecialEffects.Health += (float)Math.Floor(statsSpecialEffects.Stamina * 10.0f) + (float)Math.Floor(statsSpecialEffects.BattlemasterHealthProc); // Defensive Stats statsSpecialEffects.Armor = (float)Math.Floor(statsSpecialEffects.Armor * (1f + stats.BaseArmorMultiplier + statsSpecialEffects.BaseArmorMultiplier)); statsSpecialEffects.BonusArmor = (float)Math.Floor(statsSpecialEffects.BonusArmor * (1.0f + stats.BonusArmorMultiplier + statsSpecialEffects.BonusArmorMultiplier)); statsSpecialEffects.Armor += statsSpecialEffects.BonusArmor; // Offensive Stats statsSpecialEffects.AttackPower += statsSpecialEffects.Strength * 2.0f; statsSpecialEffects.AttackPower = (float)Math.Floor(statsSpecialEffects.AttackPower * (1.0f + stats.BonusAttackPowerMultiplier + statsSpecialEffects.BonusAttackPowerMultiplier)); return statsSpecialEffects; }
public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, bool referenceCalculation, bool significantChange, bool needsDisplayCalculations) { // First things first, we need to ensure that we aren't using bad data CharacterCalculationsProtPaladin calc = new CharacterCalculationsProtPaladin(); if (character == null) { return calc; } CalculationOptionsProtPaladin calcOpts = character.CalculationOptions as CalculationOptionsProtPaladin; if (calcOpts == null) { return calc; } BossOptions bossOpts = 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 (bossOpts.Attacks.Count < 1) { character.IsLoading = true; bossOpts.DamagingTargs = true; bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack); character.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 (bossOpts.DefaultMeleeAttack == null) { character.IsLoading = true; bossOpts.DamagingTargs = true; bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack); character.IsLoading = false; } Attack bossAttack = bossOpts.DefaultMeleeAttack; Base.StatsPaladin stats = GetCharacterStats(character, additionalItem, calcOpts, bossOpts); DefendModel dm = new DefendModel(character, stats, calcOpts, bossOpts); AttackModel am = new AttackModel(character, stats, calcOpts, bossOpts); calc.BasicStats = stats; // Target Info calc.TargetLevel = bossOpts.Level; calc.TargetArmor = bossOpts.Armor; calc.EffectiveTargetArmor = Lookup.GetEffectiveTargetArmor(calc.TargetArmor, stats.ArmorPenetration); calc.TargetArmorDamageReduction = Lookup.TargetArmorReduction(character.Level, stats.ArmorPenetration, calc.TargetArmor); calc.EffectiveTargetArmorDamageReduction = Lookup.EffectiveTargetArmorReduction(stats.ArmorPenetration, calc.TargetArmor, calc.TargetLevel); int levelDifference = bossOpts.Level - character.Level; if (levelDifference > 3) { levelDifference = 3; } else if (levelDifference < 0) { levelDifference = 0; } float levelDifferenceAvoidance = levelDifference * 0.002f; calc.ActiveBuffs = new List<Buff>(character.ActiveBuffs); calc.Abilities = am.Abilities; // Defensive stats calc.Mastery = 8f + StatConversion.GetMasteryFromRating(stats.MasteryRating, CharacterClass.Paladin); calc.Miss = dm.DefendTable.Miss; calc.Dodge = dm.DefendTable.Dodge; calc.Parry = dm.DefendTable.Parry; calc.Block = dm.DefendTable.Block; calc.DodgePlusMissPlusParry = calc.Dodge + calc.Miss + calc.Parry; calc.DodgePlusMissPlusParryPlusBlock = calc.Dodge + calc.Miss + calc.Parry + calc.Block; calc.CritReduction = character.PaladinTalents.Sanctuary * 0.02f; calc.CritVulnerability = dm.DefendTable.Critical; calc.ArmorReduction = Lookup.ArmorReduction(stats.Armor, calc.TargetLevel); calc.GuaranteedReduction = dm.GuaranteedReduction; calc.TotalMitigation = dm.Mitigation; calc.AttackerSpeed = bossOpts.DefaultMeleeAttack.AttackSpeed; calc.DamageTaken = dm.DamageTaken; calc.DPSTaken = dm.DamagePerSecond; calc.DamageTakenPerHit = dm.DamagePerHit; calc.DamageTakenPerBlock = dm.DamagePerBlock; calc.DamageTakenPerCrit = dm.DamagePerCrit; calc.CappedCritReduction = Math.Min(0.05f + levelDifferenceAvoidance, calc.CritReduction); #region Vengeance { // Vengeance Calc from Bear // == Evaluate damage taken once ahead of time for vengeance == //Out of 100 attacks, you'll take... float critsVeng = Math.Min(Math.Max(0f, 1f - dm.DefendTable.AnyMiss /*calc.AvoidancePostDR*/), (0.05f + levelDifferenceAvoidance) - calc.CappedCritReduction); //float crushes = targetLevel == 73 ? Math.Max(0f, Math.Min(15f, 100f - (crits + calculatedStats.AvoidancePreDR)) - stats.CritChanceReduction) : 0f; float hitsVeng = Math.Max(0f, 1f - (critsVeng + dm.DefendTable.AnyMiss /*calc.AvoidancePostDR*/)); //Apply armor and multipliers for each attack type... critsVeng *= (1f - calc.GuaranteedReduction) * 2f; //crushes *= (100f - calculatedStats.Mitigation) * .015f; hitsVeng *= (1f - calc.GuaranteedReduction); float damageTakenPercent = (hitsVeng + critsVeng) * (1f - stats.BossAttackSpeedReductionMultiplier); float damageTakenPerHit = bossAttack.DamagePerHit * damageTakenPercent - stats.DamageAbsorbed; float damageTakenPerSecond = damageTakenPerHit / bossAttack.AttackSpeed; float damageTakenPerVengeanceTick = damageTakenPerSecond * 2f; float vengeanceCap = stats.Stamina + BaseStats.GetBaseStats(character).Health * 0.1f; float vengeanceAPPreAvoidance = Math.Min(vengeanceCap, damageTakenPerVengeanceTick); double chanceHit = 1f - dm.DefendTable.AnyMiss /*calc.AvoidancePostDR*/; double vengeanceMultiplierFromAvoidance = //Best-fit of results from simulation of avoidance effects on vengeance -46.288470839554d * Math.Pow(chanceHit, 6) + 143.12528411194400d * Math.Pow(chanceHit, 5) - 159.9833254324610000d * Math.Pow(chanceHit, 4) + 74.0451030489808d * Math.Pow(chanceHit, 3) - 10.8422088672455d * Math.Pow(chanceHit, 2) + 0.935157126508557d * chanceHit; float vengeanceMultiplierFromSwingSpeed = bossAttack.AttackSpeed <= 2f ? 1f : (1f - 0.1f * (1f - 2f / bossAttack.AttackSpeed)); //A percentage of the ticks will be guaranteed decays for attack speeds longer than 2sec, due to no swings occuring between the current and last tick float vengeanceAP = (float)(vengeanceAPPreAvoidance * vengeanceMultiplierFromAvoidance * vengeanceMultiplierFromSwingSpeed); stats.AttackPower += vengeanceAP * (1f + stats.BonusAttackPowerMultiplier); calc.AverageVengeanceAP = vengeanceAP; }//*/ #endregion calc.ResistanceTable = StatConversion.GetResistanceTable(calc.TargetLevel, character.Level, stats.FrostResistance, 0.0f); calc.ArcaneReduction = (1.0f - Lookup.MagicReduction(stats, DamageType.Arcane, calc.TargetLevel)); calc.FireReduction = (1.0f - Lookup.MagicReduction(stats, DamageType.Fire, calc.TargetLevel)); calc.FrostReduction = (1.0f - Lookup.MagicReduction(stats, DamageType.Frost, calc.TargetLevel)); calc.NatureReduction = (1.0f - Lookup.MagicReduction(stats, DamageType.Nature, calc.TargetLevel)); calc.ShadowReduction = (1.0f - Lookup.MagicReduction(stats, DamageType.Shadow, calc.TargetLevel)); calc.ArcaneSurvivalPoints = stats.Health / Lookup.MagicReduction(stats, DamageType.Arcane, calc.TargetLevel); calc.FireSurvivalPoints = stats.Health / Lookup.MagicReduction(stats, DamageType.Fire, calc.TargetLevel); calc.FrostSurvivalPoints = stats.Health / Lookup.MagicReduction(stats, DamageType.Frost, calc.TargetLevel); calc.NatureSurvivalPoints = stats.Health / Lookup.MagicReduction(stats, DamageType.Nature, calc.TargetLevel); calc.ShadowSurvivalPoints = stats.Health / Lookup.MagicReduction(stats, DamageType.Shadow, calc.TargetLevel); // Offensive Stats calc.Hit = Lookup.HitChance(stats, calc.TargetLevel, character.Level); calc.SpellHit = Lookup.SpellHitChance(character.Level, stats, calc.TargetLevel); calc.Crit = Lookup.CritChance(stats, calc.TargetLevel, character.Level); calc.SpellCrit = Lookup.SpellCritChance(character.Level, stats, calc.TargetLevel); calc.Expertise = Lookup.BonusExpertisePercentage(stats); calc.PhysicalHaste = Lookup.BonusPhysicalHastePercentage(stats); calc.SpellHaste = Lookup.BonusSpellHastePercentage(stats); calc.AvoidedAttacks = am.Abilities[Ability.MeleeSwing].AttackTable.AnyMiss; calc.MissedAttacks = am.Abilities[Ability.MeleeSwing].AttackTable.Miss; calc.DodgedAttacks = am.Abilities[Ability.MeleeSwing].AttackTable.Dodge; calc.ParriedAttacks = am.Abilities[Ability.MeleeSwing].AttackTable.Parry; calc.GlancingAttacks = am.Abilities[Ability.MeleeSwing].AttackTable.Glance; calc.GlancingReduction = Lookup.GlancingReduction(character.Level, calc.TargetLevel); calc.BlockedAttacks = am.Abilities[Ability.MeleeSwing].AttackTable.Block; calc.WeaponSpeed = Lookup.WeaponSpeed(character, stats); calc.TotalDamagePerSecond = am.DamagePerSecond; // Ranking Points //calculatedStats.UnlimitedThreat = am.ThreatPerSecond; //am.RageModelMode = RageModelMode.Limited; calc.ThreatPerSecond = am.ThreatPerSecond; calc.ThreatModel = am.Name + "\n" + am.Description; calc.TankPoints = dm.TankPoints; calc.BurstTime = dm.BurstTime; calc.RankingMode = calcOpts.RankingMode; calc.ThreatPoints = calcOpts.ThreatScale * calc.ThreatPerSecond; //float scale = 0.0f; float VALUE_CAP = 1000000000f; switch (calcOpts.RankingMode) { #region Alternative Ranking Modes case 1: // Burst Time Mode float threatScale = Convert.ToSingle(Math.Pow(Convert.ToDouble(bossOpts.DefaultMeleeAttack.DamagePerHit) / 25000.0d, 4)); calc.SurvivabilityPoints = Math.Min(dm.BurstTime * 100.0f, VALUE_CAP); calc.MitigationPoints = 0.0f; calc.ThreatPoints = 0.0f; // Math.Min((calc.ThreatPoints / threatScale) * 2.0f, VALUE_CAP); calc.OverallPoints = calc.MitigationPoints + calc.SurvivabilityPoints + calc.ThreatPoints; break; case 3: // Damage Output Mode calc.SurvivabilityPoints = 0.0f; calc.MitigationPoints = 0.0f; calc.ThreatPoints = Math.Min(calc.TotalDamagePerSecond, VALUE_CAP); calc.OverallPoints = calc.MitigationPoints + calc.SurvivabilityPoints + calc.ThreatPoints; break; case 2: calc.SurvivabilityPoints = 0.0f; calc.MitigationPoints = 0.0f; calc.ThreatPoints = 0.0f; //calc.CTCPoints = StatConversion.MitigationScaler / (1f - dm.CTCCovered); calc.CTCPoints = dm.CTCovered * 10000f; calc.MitigationPoints = calc.CTCPoints; calc.OverallPoints = calc.CTCPoints; break; #endregion case 0: default: // Mitigation Scale Mode //calc.SurvivalPoints = Math.Min(CapSurvival(dm.EffectiveHealth, calcOpts), VALUE_CAP); //calc.MitigationPoints = Math.Min(calcOpts.MitigationScale / dm.DamageTaken, VALUE_CAP); //calc.ThreatPoints = Math.Min(calc.ThreatPoints, VALUE_CAP); calc.SurvivabilityPoints = CapSurvival(dm.EffectiveHealth, calcOpts, bossOpts); calc.MitigationPoints = StatConversion.MitigationScaler / (1f - dm.Mitigation); //calc.MitigationPoints = Math.Min(dm.Mitigation * bossOpts.DefaultMeleeAttack.DamagePerHit /** calcOpts.MitigationScale*/ * 10.0f, VALUE_CAP); calc.ThreatPoints = Math.Min(calc.ThreatPoints / 10.0f, VALUE_CAP); calc.OverallPoints = calc.MitigationPoints + calc.SurvivabilityPoints + calc.ThreatPoints; break; } return calc; }