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;
        }