public StatsSummary BuildSummary(string type, CombinedStats currentStats, List <PlayerStats> selected, bool showTotals, bool rankPlayers, bool _)
        {
            List <string> list = new List <string>();

            string title   = "";
            string details = "";

            if (currentStats != null && type == Labels.HEALPARSE)
            {
                if (selected?.Count > 0)
                {
                    foreach (PlayerStats stats in selected.OrderByDescending(item => item.Total))
                    {
                        string playerFormat = rankPlayers ? string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_RANK_FORMAT, stats.Rank, stats.Name) : string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_FORMAT, stats.Name);
                        string damageFormat = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_ONLY_FORMAT, StatsUtil.FormatTotals(stats.Total));
                        list.Add(playerFormat + damageFormat + " ");
                    }
                }

                details = list.Count > 0 ? ", " + string.Join(" | ", list) : "";
                title   = StatsUtil.FormatTitle(currentStats.TargetTitle, currentStats.TimeTitle, showTotals ? currentStats.TotalTitle : "");
            }

            return(new StatsSummary()
            {
                Title = title, RankedPlayers = details,
            });
        }
Esempio n. 2
0
        public StatsSummary BuildSummary(string type, CombinedStats currentStats, List <PlayerStats> selected, bool showTotals, bool rankPlayers, bool _, bool showTime)
        {
            List <string> list = new List <string>();

            string title   = "";
            string details = "";

            if (currentStats != null)
            {
                if (type == Labels.TANKPARSE)
                {
                    if (selected?.Count > 0)
                    {
                        foreach (PlayerStats stats in selected.OrderByDescending(item => item.Total))
                        {
                            string playerFormat = rankPlayers ? string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_RANK_FORMAT, stats.Rank, stats.Name) : string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_FORMAT, stats.Name);
                            string damageFormat = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_ONLY_FORMAT, StatsUtil.FormatTotals(stats.Total));
                            list.Add(playerFormat + damageFormat + " ");
                        }
                    }

                    details = list.Count > 0 ? ", " + string.Join(" | ", list) : "";
                    var timeTitle = showTime ? (" " + currentStats.TimeTitle) : "";
                    title = StatsUtil.FormatTitle(currentStats.TargetTitle, timeTitle, showTotals ? currentStats.TotalTitle : "");
                }
                else if (type == Labels.RECEIVEDHEALPARSE)
                {
                    if (selected?.Count == 1 && (selected[0] as PlayerStats).SubStats2.TryGetValue("receivedHealing", out PlayerSubStats subStats) && subStats is PlayerStats receivedHealing)
                    {
                        int  rank   = 1;
                        long totals = 0;
                        foreach (var stats in receivedHealing.SubStats.Values.OrderByDescending(stats => stats.Total).Take(10))
                        {
                            string playerFormat = rankPlayers ? string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_RANK_FORMAT, rank++, stats.Name) : string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_FORMAT, stats.Name);
                            string damageFormat = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_ONLY_FORMAT, StatsUtil.FormatTotals(stats.Total));
                            list.Add(playerFormat + damageFormat + " ");
                            totals += stats.Total;
                        }

                        var    hps        = (long)Math.Round(totals / currentStats.RaidStats.TotalSeconds, 2);
                        string totalTitle = showTotals ? (selected[0].Name + " Received " + StatsUtil.FormatTotals(totals) + " Healing") : (selected[0].Name + " Received Healing");
                        details = list.Count > 0 ? ", " + string.Join(" | ", list) : "";
                        var timeTitle = showTime ? (" " + currentStats.TimeTitle) : "";
                        title = StatsUtil.FormatTitle(currentStats.TargetTitle, timeTitle, totalTitle);
                    }
                }
            }

            return(new StatsSummary()
            {
                Title = title, RankedPlayers = details,
            });
        }
Esempio n. 3
0
        public StatsSummary BuildSummary(string type, CombinedStats currentStats, List <PlayerStats> selected, bool showTotals, bool rankPlayers, bool _, bool showTime)
        {
            List <string> list = new List <string>();

            string title   = "";
            string details = "";

            if (currentStats != null)
            {
                if (type == Labels.HEALPARSE)
                {
                    if (selected?.Count > 0)
                    {
                        foreach (PlayerStats stats in selected.OrderByDescending(item => item.Total))
                        {
                            string playerFormat = rankPlayers ? string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_RANK_FORMAT, stats.Rank, stats.Name) : string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_FORMAT, stats.Name);
                            string healsFormat  = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_ONLY_FORMAT, StatsUtil.FormatTotals(stats.Total));
                            list.Add(playerFormat + healsFormat + " ");
                        }
                    }

                    details = list.Count > 0 ? ", " + string.Join(" | ", list) : "";
                    var timeTitle = showTime ? (" " + currentStats.TimeTitle) : "";
                    title = StatsUtil.FormatTitle(currentStats.TargetTitle, timeTitle, showTotals ? currentStats.TotalTitle : "");
                }
                else if (type == Labels.TOPHEALSPARSE)
                {
                    if (selected?.Count == 1 && selected[0].SubStats.Count > 0)
                    {
                        int rank = 1;
                        foreach (var stats in selected[0].SubStats.Values.OrderByDescending(stats => stats.Total).Take(10))
                        {
                            string abbrv        = DataManager.Instance.AbbreviateSpellName(stats.Name);
                            string playerFormat = rankPlayers ? string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_RANK_FORMAT, rank++, abbrv) : string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_FORMAT, abbrv);
                            string healsFormat  = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_ONLY_FORMAT, StatsUtil.FormatTotals(stats.Total));
                            list.Add(playerFormat + healsFormat + " ");
                        }

                        string totalTitle = selected[0].Name + "'s Top Heals";
                        details = list.Count > 0 ? ", " + string.Join(" | ", list) : "";
                        var timeTitle = showTime ? (" " + currentStats.TimeTitle) : "";
                        title = StatsUtil.FormatTitle(currentStats.TargetTitle, timeTitle, totalTitle);
                    }
                }
            }

            return(new StatsSummary()
            {
                Title = title, RankedPlayers = details,
            });
        }
        public StatsSummary BuildSummary(string type, CombinedStats currentStats, List <PlayerStats> selected, bool showTotals, bool rankPlayers, bool showSpecial)
        {
            List <string> list = new List <string>();

            string title   = "";
            string details = "";

            if (currentStats != null && type == Labels.DAMAGEPARSE)
            {
                if (selected?.Count > 0)
                {
                    foreach (PlayerStats stats in selected.OrderByDescending(item => item.Total))
                    {
                        string playerFormat = rankPlayers ? string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_RANK_FORMAT, stats.Rank, stats.Name) : string.Format(CultureInfo.CurrentCulture, StatsUtil.PLAYER_FORMAT, stats.Name);
                        string damageFormat = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(stats.Total), "", StatsUtil.FormatTotals(stats.DPS));
                        string timeFormat   = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, stats.TotalSeconds);

                        var dps = playerFormat + damageFormat + " " + timeFormat;

                        if (showSpecial && !string.IsNullOrEmpty(stats.Special))
                        {
                            dps = string.Format(CultureInfo.CurrentCulture, StatsUtil.SPECIAL_FORMAT, dps, stats.Special);
                        }

                        list.Add(dps);
                    }
                }

                details = list.Count > 0 ? ", " + string.Join(", ", list) : "";
                title   = StatsUtil.FormatTitle(currentStats.TargetTitle, currentStats.TimeTitle, showTotals ? currentStats.TotalTitle : "");
            }

            return(new StatsSummary()
            {
                Title = title, RankedPlayers = details
            });
        }
        private void ComputeTankingStats(GenerateStatsOptions options)
        {
            lock (TankingGroupIds)
            {
                CombinedStats combined = null;
                Dictionary <string, PlayerStats> individualStats = new Dictionary <string, PlayerStats>();

                if (RaidTotals != null)
                {
                    // always start over
                    RaidTotals.Total = 0;

                    try
                    {
                        FireChartEvent(options, "UPDATE");

                        if (options.RequestSummaryData)
                        {
                            TankingGroups.ForEach(group =>
                            {
                                group.ForEach(block =>
                                {
                                    block.Actions.ForEach(action =>
                                    {
                                        if (action is DamageRecord record)
                                        {
                                            if (options.DamageType == 0 || (options.DamageType == 1 && IsMelee(record)) || (options.DamageType == 2 && !IsMelee(record)))
                                            {
                                                RaidTotals.Total += record.Total;
                                                PlayerStats stats = StatsUtil.CreatePlayerStats(individualStats, record.Defender);
                                                StatsUtil.UpdateStats(stats, record);
                                                PlayerSubStats subStats = StatsUtil.CreatePlayerSubStats(stats.SubStats, record.SubType, record.Type);
                                                StatsUtil.UpdateStats(subStats, record);
                                            }
                                        }
                                    });
                                });
                            });

                            RaidTotals.DPS = (long)Math.Round(RaidTotals.Total / RaidTotals.TotalSeconds, 2);
                            Parallel.ForEach(individualStats.Values, stats =>
                            {
                                StatsUtil.UpdateAllStatsTimeRanges(stats, PlayerTimeRanges, PlayerSubTimeRanges);
                                StatsUtil.UpdateCalculations(stats, RaidTotals);
                            });

                            combined = new CombinedStats
                            {
                                RaidStats   = RaidTotals,
                                TargetTitle = (Selected.Count > 1 ? "Combined (" + Selected.Count + "): " : "") + Title,
                                TimeTitle   = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, RaidTotals.TotalSeconds),
                                TotalTitle  = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(RaidTotals.Total), " Tanked ", StatsUtil.FormatTotals(RaidTotals.DPS))
                            };

                            combined.StatsList.AddRange(individualStats.Values.AsParallel().OrderByDescending(item => item.Total));
                            combined.FullTitle  = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, combined.TotalTitle);
                            combined.ShortTitle = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, "");

                            for (int i = 0; i < combined.StatsList.Count; i++)
                            {
                                combined.StatsList[i].Rank = Convert.ToUInt16(i + 1);
                                combined.UniqueClasses[combined.StatsList[i].ClassName] = 1;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        LOG.Error(ex);
                    }

                    if (options.RequestSummaryData)
                    {
                        // generating new stats
                        var genEvent = new StatsGenerationEvent()
                        {
                            Type          = Labels.TANKPARSE,
                            State         = "COMPLETED",
                            CombinedStats = combined
                        };

                        genEvent.Groups.AddRange(TankingGroups);
                        genEvent.UniqueGroupCount = TankingGroupIds.Count;
                        EventsGenerationStatus?.Invoke(this, genEvent);
                    }
                }
            }
        }
Esempio n. 6
0
        private void ComputeHealingStats(GenerateStatsOptions options)
        {
            lock (HealingGroups)
            {
                if (RaidTotals != null)
                {
                    CombinedStats combined = null;
                    Dictionary <string, PlayerStats> individualStats = new Dictionary <string, PlayerStats>();

                    // always start over
                    RaidTotals.Total = 0;

                    try
                    {
                        FireChartEvent(options, "UPDATE");

                        if (options.RequestSummaryData)
                        {
                            HealingGroups.ForEach(group =>
                            {
                                group.ForEach(block =>
                                {
                                    block.Actions.ForEach(action =>
                                    {
                                        if (action is HealRecord record)
                                        {
                                            RaidTotals.Total += record.Total;
                                            PlayerStats stats = StatsUtil.CreatePlayerStats(individualStats, record.Healer);
                                            StatsUtil.UpdateStats(stats, record);

                                            var spellStatName         = record.SubType ?? Labels.SELFHEAL;
                                            PlayerSubStats spellStats = StatsUtil.CreatePlayerSubStats(stats.SubStats, spellStatName, record.Type);
                                            StatsUtil.UpdateStats(spellStats, record);

                                            var healedStatName         = record.Healed;
                                            PlayerSubStats healedStats = StatsUtil.CreatePlayerSubStats(stats.SubStats2, healedStatName, record.Type);
                                            StatsUtil.UpdateStats(healedStats, record);
                                        }
                                    });
                                });
                            });

                            RaidTotals.DPS = (long)Math.Round(RaidTotals.Total / RaidTotals.TotalSeconds, 2);
                            Parallel.ForEach(individualStats.Values, stats => UpdateStats(stats, HealerSpellTimeRanges, HealerHealedTimeRanges));

                            combined = new CombinedStats
                            {
                                RaidStats   = RaidTotals,
                                TargetTitle = (Selected.Count > 1 ? "Combined (" + Selected.Count + "): " : "") + Title,
                                TimeTitle   = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, RaidTotals.TotalSeconds),
                                TotalTitle  = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(RaidTotals.Total), " Heals ", StatsUtil.FormatTotals(RaidTotals.DPS))
                            };

                            combined.StatsList.AddRange(individualStats.Values.AsParallel().OrderByDescending(item => item.Total));
                            combined.FullTitle  = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, combined.TotalTitle);
                            combined.ShortTitle = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, "");

                            for (int i = 0; i < combined.StatsList.Count; i++)
                            {
                                combined.StatsList[i].Rank = Convert.ToUInt16(i + 1);
                                combined.UniqueClasses[combined.StatsList[i].ClassName] = 1;
                            }
                        }
                    }
#pragma warning disable CA1031 // Do not catch general exception types
                    catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
                    {
                        if (ex is ArgumentNullException || ex is NullReferenceException || ex is ArgumentOutOfRangeException || ex is ArgumentException || ex is OutOfMemoryException)
                        {
                            LOG.Error(ex);
                        }
                    }

                    if (options.RequestSummaryData)
                    {
                        // generating new stats
                        var genEvent = new StatsGenerationEvent()
                        {
                            Type          = Labels.HEALPARSE,
                            State         = "COMPLETED",
                            CombinedStats = combined
                        };

                        genEvent.Groups.AddRange(HealingGroups);
                        EventsGenerationStatus?.Invoke(this, genEvent);
                    }
                }
            }
        }
        private void ComputeHealingStats(GenerateStatsOptions options)
        {
            lock (HealingGroups)
            {
                if (RaidTotals != null)
                {
                    CombinedStats combined = null;
                    Dictionary <string, PlayerStats> individualStats = new Dictionary <string, PlayerStats>();

                    // always start over
                    RaidTotals.Total = 0;

                    try
                    {
                        FireUpdateEvent(options);

                        if (options.RequestSummaryData)
                        {
                            HealingGroups.ForEach(group =>
                            {
                                // keep track of time range as well as the players that have been updated
                                Dictionary <string, PlayerSubStats> allStats = new Dictionary <string, PlayerSubStats>();

                                group.ForEach(block =>
                                {
                                    block.Actions.ForEach(action =>
                                    {
                                        if (action is HealRecord record)
                                        {
                                            RaidTotals.Total += record.Total;
                                            PlayerStats stats = StatsUtil.CreatePlayerStats(individualStats, record.Healer);

                                            StatsUtil.UpdateStats(stats, record, block.BeginTime);
                                            allStats[record.Healer] = stats;

                                            var spellStatName         = record.SubType ?? Labels.SELFHEAL;
                                            PlayerSubStats spellStats = StatsUtil.CreatePlayerSubStats(stats.SubStats, spellStatName, record.Type);
                                            StatsUtil.UpdateStats(spellStats, record, block.BeginTime);
                                            allStats[stats.Name + "=" + spellStatName] = spellStats;

                                            var healedStatName         = record.Healed;
                                            PlayerSubStats healedStats = StatsUtil.CreatePlayerSubStats(stats.SubStats2, healedStatName, record.Type);
                                            StatsUtil.UpdateStats(healedStats, record, block.BeginTime);
                                            allStats[stats.Name + "=" + healedStatName] = healedStats;
                                        }
                                    });
                                });

                                foreach (var stats in allStats.Values)
                                {
                                    stats.TotalSeconds += stats.LastTime - stats.BeginTime + 1;
                                    stats.BeginTime     = double.NaN;
                                }
                            });

                            RaidTotals.DPS = (long)Math.Round(RaidTotals.Total / RaidTotals.TotalSeconds, 2);
                            Parallel.ForEach(individualStats.Values, stats => StatsUtil.UpdateCalculations(stats, RaidTotals));

                            combined = new CombinedStats
                            {
                                RaidStats   = RaidTotals,
                                TargetTitle = (Selected.Count > 1 ? "Combined (" + Selected.Count + "): " : "") + Title,
                                TimeTitle   = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, RaidTotals.TotalSeconds),
                                TotalTitle  = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(RaidTotals.Total), " Heals ", StatsUtil.FormatTotals(RaidTotals.DPS))
                            };

                            combined.StatsList.AddRange(individualStats.Values.AsParallel().OrderByDescending(item => item.Total));
                            combined.FullTitle  = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, combined.TotalTitle);
                            combined.ShortTitle = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, "");

                            for (int i = 0; i < combined.StatsList.Count; i++)
                            {
                                combined.StatsList[i].Rank = Convert.ToUInt16(i + 1);
                                combined.UniqueClasses[combined.StatsList[i].ClassName] = 1;
                            }
                        }
                    }
                    catch (ArgumentNullException ane)
                    {
                        LOG.Error(ane);
                    }
                    catch (NullReferenceException nre)
                    {
                        LOG.Error(nre);
                    }
                    catch (ArgumentOutOfRangeException aro)
                    {
                        LOG.Error(aro);
                    }

                    FireCompletedEvent(options, combined, HealingGroups);
                }
            }
        }
        private void ComputeDamageStats(GenerateStatsOptions options)
        {
            lock (DamageGroups)
            {
                if (RaidTotals != null)
                {
                    CombinedStats combined = null;
                    ConcurrentDictionary <string, Dictionary <string, PlayerStats> > childrenStats = new ConcurrentDictionary <string, Dictionary <string, PlayerStats> >();
                    ConcurrentDictionary <string, PlayerStats> topLevelStats   = new ConcurrentDictionary <string, PlayerStats>();
                    ConcurrentDictionary <string, PlayerStats> aggregateStats  = new ConcurrentDictionary <string, PlayerStats>();
                    Dictionary <string, PlayerStats>           individualStats = new Dictionary <string, PlayerStats>();

                    // always start over
                    RaidTotals.Total = 0;

                    try
                    {
                        FireUpdateEvent(options);

                        if (options.RequestSummaryData)
                        {
                            DamageGroups.ForEach(group =>
                            {
                                // keep track of time range as well as the players that have been updated
                                Dictionary <string, PlayerSubStats> allStats = new Dictionary <string, PlayerSubStats>();

                                int found = -1;
                                if (MainWindow.IsIgnoreIntialPullDamageEnabled)
                                {
                                    // ignore initial low activity time
                                    double previousDps = 0;
                                    long rolling       = 0;
                                    for (int i = 0; group.Count >= 10 && i < 10; i++)
                                    {
                                        if (previousDps == 0)
                                        {
                                            rolling     = group[i].Actions.Sum(test => (test as DamageRecord).Total);
                                            previousDps = rolling / 1.0;
                                        }
                                        else
                                        {
                                            double theTime = group[i].BeginTime - group[0].BeginTime + 1;
                                            if (theTime > 12.0)
                                            {
                                                break;
                                            }

                                            rolling          += group[i].Actions.Sum(test => (test as DamageRecord).Total);
                                            double currentDps = rolling / (theTime);
                                            if (currentDps / previousDps > 1.75)
                                            {
                                                found = i - 1;
                                                break;
                                            }
                                            else
                                            {
                                                previousDps = currentDps;
                                            }
                                        }
                                    }
                                }

                                var goodGroups = found > -1 ? group.GetRange(found, group.Count - found) : group;

                                goodGroups.ForEach(block =>
                                {
                                    block.Actions.ForEach(action =>
                                    {
                                        if (action is DamageRecord record)
                                        {
                                            PlayerStats stats = StatsUtil.CreatePlayerStats(individualStats, record.Attacker);

                                            if (!MainWindow.IsBaneDamageEnabled && record.Type == Labels.BANE)
                                            {
                                                stats.BaneHits++;

                                                if (individualStats.TryGetValue(stats.OrigName + " +Pets", out PlayerStats temp))
                                                {
                                                    temp.BaneHits++;
                                                }
                                            }
                                            else
                                            {
                                                RaidTotals.Total += record.Total;
                                                StatsUtil.UpdateStats(stats, record, block.BeginTime);
                                                allStats[record.Attacker] = stats;

                                                if (!PetToPlayer.TryGetValue(record.Attacker, out string player) && !PlayerHasPet.ContainsKey(record.Attacker))
                                                {
                                                    // not a pet
                                                    topLevelStats[record.Attacker] = stats;
                                                }
                                                else
                                                {
                                                    string origName      = player ?? record.Attacker;
                                                    string aggregateName = (player == Labels.UNASSIGNED) ? origName : origName + " +Pets";

                                                    PlayerStats aggregatePlayerStats = StatsUtil.CreatePlayerStats(individualStats, aggregateName, origName);
                                                    StatsUtil.UpdateStats(aggregatePlayerStats, record, block.BeginTime);
                                                    allStats[aggregateName]      = aggregatePlayerStats;
                                                    topLevelStats[aggregateName] = aggregatePlayerStats;

                                                    if (!childrenStats.TryGetValue(aggregateName, out Dictionary <string, PlayerStats> children))
                                                    {
                                                        childrenStats[aggregateName] = new Dictionary <string, PlayerStats>();
                                                    }

                                                    childrenStats[aggregateName][stats.Name] = stats;
                                                }

                                                PlayerSubStats subStats = StatsUtil.CreatePlayerSubStats(stats.SubStats, record.SubType, record.Type);
                                                UpdateSubStats(subStats, record, block.BeginTime);
                                                allStats[stats.Name + "=" + Helpers.CreateRecordKey(record.Type, record.SubType)] = subStats;
                                            }
                                        }
                                    });
                                });

                                foreach (var stats in allStats.Values)
                                {
                                    stats.TotalSeconds += stats.LastTime - stats.BeginTime + 1;
                                    stats.BeginTime     = double.NaN;
                                }
                            });

                            RaidTotals.DPS = (long)Math.Round(RaidTotals.Total / RaidTotals.TotalSeconds, 2);

                            // add up resists
                            Dictionary <string, uint> resistCounts = new Dictionary <string, uint>();

                            Resists.ForEach(resist =>
                            {
                                ResistRecord record = resist as ResistRecord;
                                Helpers.StringUIntAddHelper.Add(resistCounts, record.Spell, 1);
                            });

                            // get special field
                            var specials = StatsUtil.GetSpecials(RaidTotals);

                            Parallel.ForEach(individualStats.Values, stats =>
                            {
                                if (topLevelStats.TryGetValue(stats.Name, out PlayerStats topLevel))
                                {
                                    if (childrenStats.TryGetValue(stats.Name, out Dictionary <string, PlayerStats> children))
                                    {
                                        foreach (var child in children.Values)
                                        {
                                            StatsUtil.UpdateCalculations(child, RaidTotals, resistCounts);

                                            if (stats.Total > 0)
                                            {
                                                child.Percent = Math.Round(Convert.ToDouble(child.Total) / stats.Total * 100, 2);
                                            }

                                            if (specials.TryGetValue(child.Name, out string special1))
                                            {
                                                child.Special = special1;
                                            }
                                        }
                                    }

                                    StatsUtil.UpdateCalculations(stats, RaidTotals, resistCounts);

                                    if (specials.TryGetValue(stats.OrigName, out string special2))
                                    {
                                        stats.Special = special2;
                                    }
                                }
                            });

                            combined = new CombinedStats
                            {
                                RaidStats   = RaidTotals,
                                TargetTitle = (Selected.Count > 1 ? "Combined (" + Selected.Count + "): " : "") + Title,
                                TimeTitle   = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, RaidTotals.TotalSeconds),
                                TotalTitle  = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(RaidTotals.Total), " Damage ", StatsUtil.FormatTotals(RaidTotals.DPS))
                            };

                            combined.StatsList.AddRange(topLevelStats.Values.AsParallel().OrderByDescending(item => item.Total));
                            combined.FullTitle  = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, combined.TotalTitle);
                            combined.ShortTitle = StatsUtil.FormatTitle(combined.TargetTitle, combined.TimeTitle, "");

                            for (int i = 0; i < combined.StatsList.Count; i++)
                            {
                                combined.StatsList[i].Rank = Convert.ToUInt16(i + 1);
                                combined.UniqueClasses[combined.StatsList[i].ClassName] = 1;

                                if (childrenStats.TryGetValue(combined.StatsList[i].Name, out Dictionary <string, PlayerStats> children))
                                {
                                    combined.Children.Add(combined.StatsList[i].Name, children.Values.OrderByDescending(stats => stats.Total).ToList());
                                }
                            }
                        }
                    }
                    catch (ArgumentNullException anx)
                    {
                        LOG.Error(anx);
                    }
                    catch (AggregateException agx)
                    {
                        LOG.Error(agx);
                    }

                    FireCompletedEvent(options, combined, DamageGroups);
                }
            }
        }