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