private static Damage GetDamage(this CombatantData data) { var job = data.GetJob(); if (job == null || !data.IsAlly() || data.Name == null) { return(null); } var key = data.Name.ToString(); var lastSwing = data.AllOut.ContainsKey(AttackType.All) ? data.AllOut[AttackType.All].Items.LastOrDefault() : null; var damage = damageCache.ContainsKey(key) && damageCache[key].LastSwing == lastSwing ? damageCache[key] : null; if (damage == null && lastSwing != null && data.Items[DamageTypeData.OutgoingDamage].Items.Count() > 0) { var allies = data.Parent.GetAllies().Select(x => x.Name).ToList(); var enemies = data.Parent.Items.Select(x => x.Value).Where(x => !allies.Contains(x.Name)).ToList(); // Buff taken var buffTakens = new List <BuffTaken>(); ActGlobalsExtension.Buffs.Where(x => x.Type == BuffType.Buff).ToList().ForEach(buff => { var conflictBuffs = ActGlobalsExtension.Buffs.Where(x => x.IsConflict(buff)).ToArray(); buffTakens.AddRange(data.GetBuffTakens(buff, conflictBuffs)); }); // Enemies debuff taken var enemiesDebuffTakens = new Dictionary <string, List <BuffTaken> >(); enemies.ForEach(enemy => { var debuffTakens = new List <BuffTaken>(); ActGlobalsExtension.Buffs.Where(x => x.Type == BuffType.Debuff).ToList().ForEach(debuff => { var conflictBuffs = ActGlobalsExtension.Buffs.Where(x => x.IsConflict(debuff)).ToArray(); debuffTakens.AddRange(enemy.GetBuffTakens(debuff, conflictBuffs)); }); enemiesDebuffTakens.Add(enemy.Name, debuffTakens); }); // Attacks var damageItems = data.Items[DamageTypeData.OutgoingDamage].Items[AttackType.All].Items.ToList(); var attacks = damageItems.Where(x => x.Damage.Number > 0).ToList(); var criticalRate = Math.Max(0.01 * data.CritDamPerc, 0.05); var directHitRate = 0.01 * data.GetDirectHitRate(); // Damage var totalDamage = 0.0; var buffTakenDamages = new List <BuffTakenDamage>(); foreach (var attack in attacks) { var actorID = (string)attack.Tags.GetValue(SwingTag.ActorID, ""); var targetId = (string)attack.Tags.GetValue(SwingTag.TargetID, ""); var damageValue = attack.Damage.Number - long.Parse((string)attack.Tags.GetValue(SwingTag.Overkill, "0")); totalDamage += damageValue; if (attack.Attacker == "Limit Break") { continue; } var skill = ActGlobalsExtension.Skills.Where(x => x.NameList.Contains(attack.AttackType)).FirstOrDefault(); var damageUpRates = new Dictionary <BuffTaken, double>(); var criticalUpRates = new Dictionary <BuffTaken, double>(); var directHitUpRates = new Dictionary <BuffTaken, double>(); var attackerBuffTakens = buffTakens.Where(x => (string)x.Swing.Tags.GetValue(SwingTag.TargetID) == actorID).ToList(); var victimDebuffTakens = enemiesDebuffTakens .Where(x => x.Key == attack.Victim).SelectMany(x => x.Value).Where(x => (string)x.Swing.Tags.GetValue(SwingTag.TargetID) == targetId).ToList(); attackerBuffTakens.Concat(victimDebuffTakens).Where(x => x.StartTime <= attack.Time && x.EndTime >= attack.Time).ToList() .ForEach(buffTaken => { var buff = buffTaken.Buff; // Damage var damageUpRate = 0.0; switch (buff.Group) { case BuffGroup.CardForMeleeDPSOrTank: damageUpRate = 0.01 * (job.IsMeleeDPSOrTank() ? 1.0 : 0.5) * buff.DamageRate; break; case BuffGroup.CardForRangedDPSOrHealer: damageUpRate = 0.01 * (job.IsRangedDPSOrHealer() ? 1.0 : 0.5) * buff.DamageRate; break; case BuffGroup.Embolden: if (buffTaken.Swing.Attacker == data.Name) { // Apply only Magical attack damageUpRate = 0.01 * (attack.DamageType.StartsWith("Magic") ? buff.DamageRate : 0); } else { // Apply only physical attack and decrease by time var damageRate = buff.DamageRate - (int)((attack.Time - buffTaken.StartTime).TotalSeconds / 4) * 2; damageUpRate = 0.01 * (attack.DamageType.StartsWith("Magic") ? 0 : damageRate); } break; default: damageUpRate = 0.01 * buff.DamageRate; break; } if (damageUpRate > 0 && buffTaken.Swing.Attacker != data.Name) { damageUpRates.Add(buffTaken, 1.0 + damageUpRate); } // Critical var criticalUpRate = 0.01 * buff.CriticalRate; if (criticalUpRate > 0) { criticalUpRates.Add(buffTaken, criticalUpRate); } // DirectHit var directHitUpRate = 0.01 * buff.DirectHitRate; if (directHitUpRate > 0) { directHitUpRates.Add(buffTaken, directHitUpRate); } }); // Calculate DPS Taken // see: https://www.fflogs.com/help/rdps // M var totalDamageUpRate = 1.0; if (damageUpRates.Count > 0) { totalDamageUpRate = damageUpRates.Values.Aggregate(((y, x) => y *= x)); } // L = N - (N / M) var upDamage = damageValue - damageValue / totalDamageUpRate; // Damage if (upDamage > 0) { foreach (KeyValuePair <BuffTaken, double> kv in damageUpRates) { buffTakenDamages.Add(new BuffTakenDamage() { BuffTaken = kv.Key, // gi = L * (log mi / log M) Value = upDamage * Math.Log10(kv.Value) / Math.Log10(totalDamageUpRate), Swing = attack }); } } // N' = (N / M) var baseDamageIncludeCDH = damageValue / totalDamageUpRate; // Mc var criticalDamageUpRate = 1.0; if (attack.Critical) { criticalDamageUpRate = 1.4 + criticalRate - 0.05; } // Md var directHitDamageupRate = 1.0; if (bool.Parse((string)attack.Tags.GetValue(SwingTag.DirectHit, "false"))) { directHitDamageupRate = 1.25; } // Mdc = Mc * Md var critDHDamageUpRate = criticalDamageUpRate * directHitDamageupRate; // (N' / Mdc) var baseDamage = baseDamageIncludeCDH / critDHDamageUpRate; // (Cb - Cu) var totalCritialUpRate = 0.0; if (criticalUpRates.Count > 0) { totalCritialUpRate = criticalUpRates.Values.Sum(); } if (skill != null) { // Apply skill-based increases totalCritialUpRate += 0.01 * skill.CriticalRate; } // Pc = (log Mc / log Mdc) * (N' - (N' / Mdc)) var criticalUpDamage = Math.Log10(criticalDamageUpRate) / Math.Log10(critDHDamageUpRate) * (baseDamageIncludeCDH - baseDamage); // Critical if (criticalUpDamage > 0 && totalCritialUpRate < 1.0) { foreach (KeyValuePair <BuffTaken, double> kv in criticalUpRates) { buffTakenDamages.Add(new BuffTakenDamage() { BuffTaken = kv.Key, // gi = (ci / Cb) * Pc Value = kv.Value / (totalCritialUpRate + criticalRate) * criticalUpDamage, Swing = attack }); } } // (Db - Cu) var totalDirectHitUpRate = 0.0; if (directHitUpRates.Count > 0) { totalDirectHitUpRate = directHitUpRates.Values.Sum(); } if (skill != null) { // Apply skill-based increases totalDirectHitUpRate += 0.01 * skill.DirectHitRate; } // Pd = (log 1.25 / log Mdc) * (N' - (N' / Mdc)) var directHitUpDamage = Math.Log10(directHitDamageupRate) / Math.Log10(critDHDamageUpRate) * (baseDamageIncludeCDH - baseDamage); // DirectHit if (directHitUpDamage > 0 && totalDirectHitUpRate < 1.0) { foreach (KeyValuePair <BuffTaken, double> kv in directHitUpRates) { buffTakenDamages.Add(new BuffTakenDamage() { BuffTaken = kv.Key, // gi = (di / Db) * Pd Value = kv.Value / (totalDirectHitUpRate + directHitRate) * directHitUpDamage, Swing = attack }); } } } damage = new Damage() { Value = totalDamage, BuffTakenDamages = buffTakenDamages, LastSwing = lastSwing }; if (damageCache.ContainsKey(key)) { damageCache[key] = damage; } else { damageCache.Add(key, damage); } } return(damage); }