internal TankingBreakdown(CombinedStats currentStats) { InitializeComponent(); InitBreakdownTable(dataGrid, selectedColumns); titleLabel.Content = currentStats?.ShortTitle; RaidStats = currentStats?.RaidStats; }
internal HealBreakdown(CombinedStats currentStats) { InitializeComponent(); titleLabel.Content = currentStats?.ShortTitle; choicesList.ItemsSource = ChoicesList; choicesList.SelectedIndex = 0; }
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, }); }
public DamageBreakdown(CombinedStats currentStats) { InitializeComponent(); titleLabel.Content = currentStats?.ShortTitle; RaidStats = currentStats.RaidStats; ChildStats = currentStats.Children; }
internal ReceivedHealingBreakdown(CombinedStats currentStats) { InitializeComponent(); InitBreakdownTable(dataGrid, selectedColumns); titleLabel.Content = currentStats?.ShortTitle; choicesList.ItemsSource = ChoicesList; choicesList.SelectedIndex = 0; }
internal void PopulateHealing(CombinedStats combined) { lock (HealingGroups) { List <PlayerStats> playerStats = combined.StatsList; Dictionary <string, PlayerStats> individualStats = new Dictionary <string, PlayerStats>(); Dictionary <string, long> totals = new Dictionary <string, long>(); HealingGroups.ForEach(group => { group.ForEach(block => { block.Actions.ForEach(action => { if (action is HealRecord record) { PlayerStats stats = StatsUtil.CreatePlayerStats(individualStats, record.Healed); StatsUtil.UpdateStats(stats, record); PlayerSubStats subStats = StatsUtil.CreatePlayerSubStats(stats.SubStats, record.Healer, record.Type); StatsUtil.UpdateStats(subStats, record); var spellStatName = record.SubType ?? Labels.SELFHEAL; PlayerSubStats spellStats = StatsUtil.CreatePlayerSubStats(stats.SubStats2, spellStatName, record.Type); StatsUtil.UpdateStats(spellStats, record); long value = 0; if (totals.ContainsKey(record.Healed)) { value = totals[record.Healed]; } totals[record.Healed] = record.Total + value; } }); }); }); Parallel.ForEach(playerStats, stat => { if (individualStats.ContainsKey(stat.Name)) { if (totals.ContainsKey(stat.Name)) { stat.Extra = totals[stat.Name]; } var indStats = individualStats[stat.Name]; stat.SubStats2["receivedHealing"] = indStats; UpdateStats(indStats, HealedBySpellTimeRanges, HealedByHealerTimeRanges); indStats.SubStats.Values.ToList().ForEach(subStat => StatsUtil.UpdateCalculations(subStat, indStats)); indStats.SubStats2.Values.ToList().ForEach(subStat => StatsUtil.UpdateCalculations(subStat, indStats)); } }); } }
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, }); }
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, }); }
private void FireCompletedEvent(GenerateStatsOptions options, CombinedStats combined, List <List <ActionBlock> > groups) { if (options.RequestSummaryData) { // generating new stats var genEvent = new StatsGenerationEvent() { Type = Labels.HEALPARSE, State = "COMPLETED", CombinedStats = combined }; genEvent.Groups.AddRange(groups); EventsGenerationStatus?.Invoke(this, genEvent); } }
internal SpellCastTable(string title, List <PlayerStats> selectedStats, CombinedStats currentStats) { InitializeComponent(); titleLabel.Content = title; selectedStats?.ForEach(stats => UniqueNames[stats.OrigName] = 1); RaidStats = currentStats?.RaidStats; dataGrid.ItemsSource = CollectionViewSource.GetDefaultView(Records); BindingOperations.EnableCollectionSynchronization(Records, LockObject); castTypes.ItemsSource = CastTypes; castTypes.SelectedIndex = 0; spellTypes.ItemsSource = SpellTypes; spellTypes.SelectedIndex = 0; Display(); }
internal void ShowSpells(List <PlayerStats> selectedStats, CombinedStats currentStats) { var raidStats = currentStats?.RaidStats; if (selectedStats != null && raidStats != null) { PlayerList = selectedStats.Select(stats => stats.OrigName).ToList(); TheSpellCounts = SpellCountBuilder.GetSpellCounts(PlayerList, raidStats); if (TheSpellCounts.PlayerCastCounts.Count > 0) { selectAll.IsEnabled = true; } Display(); } }
internal void AddParse(string type, ISummaryBuilder builder, CombinedStats combined, List <PlayerStats> selected = null, bool copy = false) { Parses[type] = new ParseData() { Builder = builder, CombinedStats = combined }; if (selected != null) { Parses[type].Selected.AddRange(selected); } if (!AvailableParses.Contains(type)) { Dispatcher.InvokeAsync(() => AvailableParses.Add(type)); } TriggerParseUpdate(type, copy); }
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 }); }
internal static CombinedStats ComputeOverlayStats(int mode, int maxRows, string selectedClass) { CombinedStats combined = null; var allDamage = 0L; var allTime = new TimeRange(); var playerTotals = new Dictionary <string, OverlayPlayerTotal>(); var playerHasPet = new Dictionary <string, bool>(); var updateTime = 0d; var oldestTime = 0d; var fights = DataManager.Instance.GetOverlayFights(); Fight oldestFight = null; bool baneEnabled = MainWindow.IsBaneDamageEnabled; // clear out anything pending in the queue DamageLineParser.CheckSlainQueue(DateUtil.ToDouble(DateTime.Now.AddSeconds(-3))); if (fights.Count > 0) { oldestFight = fights[0]; foreach (var fight in fights.Where(fight => !fight.Dead || mode > 0)) { foreach (var keypair in fight.PlayerTotals) { var player = keypair.Key; if (!string.IsNullOrEmpty(keypair.Value.PetOwner)) { player = keypair.Value.PetOwner; playerHasPet[player] = true; } else if (PlayerManager.Instance.GetPlayerFromPet(player) is string owner && owner != Labels.UNASSIGNED) { player = owner; playerHasPet[player] = true; } allDamage += baneEnabled ? keypair.Value.DamageWithBane : keypair.Value.Damage; allTime.Add(new TimeSegment(keypair.Value.BeginTime, fight.LastDamageTime)); if (updateTime == 0) { updateTime = keypair.Value.UpdateTime; oldestTime = keypair.Value.UpdateTime; oldestFight = fight; } else { updateTime = Math.Max(updateTime, keypair.Value.UpdateTime); if (oldestTime > keypair.Value.UpdateTime) { oldestTime = keypair.Value.UpdateTime; oldestFight = fight; } } if (playerTotals.TryGetValue(player, out OverlayPlayerTotal total)) { total.Damage += baneEnabled ? keypair.Value.DamageWithBane : keypair.Value.Damage; total.Range.Add(new TimeSegment(keypair.Value.BeginTime, keypair.Value.UpdateTime)); total.UpdateTime = Math.Max(total.UpdateTime, keypair.Value.UpdateTime); } else { playerTotals[player] = new OverlayPlayerTotal { Name = player, Damage = baneEnabled ? keypair.Value.DamageWithBane : keypair.Value.Damage, Range = new TimeRange(new TimeSegment(keypair.Value.BeginTime, keypair.Value.UpdateTime)), UpdateTime = keypair.Value.UpdateTime }; } } } var timeout = mode == 0 ? DataManager.FIGHTTIMEOUT : mode; var totalSeconds = allTime.GetTotal(); if (oldestFight != null && totalSeconds > 0 && allDamage > 0 && (DateTime.Now - DateTime.MinValue.AddSeconds(updateTime)).TotalSeconds <= timeout) { int rank = 1; var list = new List <PlayerStats>(); var totalDps = (long)Math.Round(allDamage / totalSeconds, 2); int myIndex = -1; foreach (var total in playerTotals.Values.OrderByDescending(total => total.Damage)) { var time = total.Range.GetTotal(); if (time > 0 && (DateTime.Now - DateTime.MinValue.AddSeconds(total.UpdateTime)).TotalSeconds <= DataManager.MAXTIMEOUT) { PlayerStats playerStats = new PlayerStats() { Name = playerHasPet.ContainsKey(total.Name) ? total.Name + " +Pets" : total.Name, Total = total.Damage, DPS = (long)Math.Round(total.Damage / time, 2), TotalSeconds = time, Rank = (ushort)rank++, ClassName = PlayerManager.Instance.GetPlayerClass(total.Name), OrigName = total.Name }; if (playerStats.Name.StartsWith(ConfigUtil.PlayerName, StringComparison.Ordinal)) { myIndex = list.Count; } if (myIndex == list.Count || selectedClass == Properties.Resources.ANY_CLASS || selectedClass == playerStats.ClassName) { list.Add(playerStats); } } } if (myIndex > (maxRows - 1)) { var me = list[myIndex]; list = list.Take(maxRows - 1).ToList(); list.Add(me); } else { list = list.Take(maxRows).ToList(); } combined = new CombinedStats(); combined.StatsList.AddRange(list); combined.RaidStats = new PlayerStats { Total = allDamage, DPS = totalDps, TotalSeconds = totalSeconds }; combined.TargetTitle = (fights.Count > 1 ? "C(" + fights.Count + "): " : "") + oldestFight.Name; // these are here to support copy/paste of the parse combined.TimeTitle = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, combined.RaidStats.TotalSeconds); combined.TotalTitle = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(combined.RaidStats.Total), " Damage ", StatsUtil.FormatTotals(combined.RaidStats.DPS)); } } return(combined); }
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); } } } }
internal HitLogViewer(CombinedStats currentStats, PlayerStats playerStats, List <List <ActionBlock> > groups, bool defending = false) { InitializeComponent(); CurrentGroups = groups; Defending = defending; PlayerStats = playerStats; titleLabel.Content = currentStats?.ShortTitle; var view = CollectionViewSource.GetDefaultView(Records); view.Filter = new Predicate <object>(item => { var record = (HitLogRow)item; return((string.IsNullOrEmpty(CurrentTypeFilter) || CurrentTypeFilter == record.Type) && (string.IsNullOrEmpty(CurrentActionFilter) || CurrentActionFilter == record.SubType) && (string.IsNullOrEmpty(CurrentActedFilter) || CurrentActedFilter == record.Acted) && (CurrentShowPetsFilter || !record.IsPet)); }); BindingOperations.EnableCollectionSynchronization(Records, CollectionLock); BindingOperations.EnableCollectionSynchronization(Actions, CollectionLock); BindingOperations.EnableCollectionSynchronization(Types, CollectionLock); var firstAction = groups?.First()?.First()?.Actions?.First(); if (firstAction is DamageRecord && !defending) { ActedOption = "All Defenders"; dataGrid.Columns[4].Header = "Damage"; dataGrid.Columns[10].Header = "Attacker"; dataGrid.Columns[11].Header = "Defender"; showPets.Visibility = Visibility.Visible; } else if (firstAction is DamageRecord && defending) { ActedOption = "All Attackers"; dataGrid.Columns[4].Header = "Damage"; dataGrid.Columns[7].Visibility = Visibility.Collapsed; dataGrid.Columns[8].Visibility = Visibility.Collapsed; dataGrid.Columns[9].Visibility = Visibility.Collapsed; dataGrid.Columns[10].Header = "Defender"; dataGrid.Columns[11].Header = "Attacker"; showPets.Visibility = Visibility.Collapsed; petDivider.Visibility = Visibility.Collapsed; } else if (firstAction is HealRecord) { ActedOption = "All Healed Players"; dataGrid.Columns[4].Header = "Heal"; dataGrid.Columns[5].Visibility = Visibility.Visible; dataGrid.Columns[10].Header = "Healer"; dataGrid.Columns[11].Header = "Healed"; showPets.Visibility = Visibility.Collapsed; petDivider.Visibility = Visibility.Collapsed; } actionList.ItemsSource = Actions; typeList.ItemsSource = Types; dataGrid.ItemsSource = view; Actions.Add("All Actions"); Types.Add("All Types"); actionList.SelectedIndex = 0; typeList.SelectedIndex = 0; Display(true); }
private void UpdateTimerTick(object sender, EventArgs e) { lock (StatsLock) { try { Topmost = true; // possible workaround // people wanted shorter delays for damage updates but I don't want the indicator to change constantly // so this limits it to 1/2 the current time value ProcessDirection = !ProcessDirection; Stats = DamageStatsManager.ComputeOverlayStats(CurrentDamageSelectionMode, CurrentMaxRows, SelectedClass); if (Stats == null) { windowBrush.Opacity = 0.0; ButtonPopup.IsOpen = false; SetVisible(false); Height = 0; PrevList = null; UpdateTimer.Stop(); DataManager.Instance.ResetOverlayFights(); } else if (Active) { TitleBlock.Text = Stats.TargetTitle; TitleDamageBlock.Text = string.Format(CultureInfo.CurrentCulture, "{0} [{1}s @{2}]", StatsUtil.FormatTotals(Stats.RaidStats.Total), Stats.RaidStats.TotalSeconds, StatsUtil.FormatTotals(Stats.RaidStats.DPS)); long total = 0; int goodRowCount = 0; long me = 0; var topList = new Dictionary <int, long>(); for (int i = 0; i < CurrentMaxRows; i++) { if (Stats.StatsList.Count > i) { if (ProcessDirection) { DamageRateList[i].Opacity = 0.0; } if (i == 0) { total = Stats.StatsList[i].Total; RectangleList[i].Width = Width; } else { RectangleList[i].Visibility = Visibility.Hidden; // maybe it calculates width better RectangleList[i].Width = Convert.ToDouble(Stats.StatsList[i].Total) / total * Width; } string playerName = ConfigUtil.PlayerName; var isMe = !string.IsNullOrEmpty(playerName) && Stats.StatsList[i].Name.StartsWith(playerName, StringComparison.OrdinalIgnoreCase) && (playerName.Length >= Stats.StatsList[i].Name.Length || Stats.StatsList[i].Name[playerName.Length] == ' '); string updateText; if (IsHideOverlayOtherPlayersEnabled && !isMe) { updateText = string.Format(CultureInfo.CurrentCulture, "{0}. Hidden Player", Stats.StatsList[i].Rank); NameIconList[i].Source = PlayerManager.UNK_ICON; } else { updateText = string.Format(CultureInfo.CurrentCulture, "{0}. {1}", Stats.StatsList[i].Rank, Stats.StatsList[i].Name); NameIconList[i].Source = PlayerManager.Instance.GetPlayerIcon(Stats.StatsList[i].OrigName); } if (IsShowOverlayCritRateEnabled) { List <string> critMods = new List <string>(); if (isMe && PlayerManager.Instance.IsDoTClass(Stats.StatsList[i].ClassName) && DataManager.Instance.MyDoTCritRateMod is uint doTCritRate && doTCritRate > 0) { critMods.Add(string.Format(CultureInfo.CurrentCulture, "DoT CR +{0}", doTCritRate)); } if (isMe && DataManager.Instance.MyNukeCritRateMod is uint nukeCritRate && nukeCritRate > 0) { critMods.Add(string.Format(CultureInfo.CurrentCulture, "Nuke CR +{0}", nukeCritRate)); } if (critMods.Count > 0) { updateText = string.Format(CultureInfo.CurrentCulture, "{0} [{1}]", updateText, string.Join(", ", critMods)); } } NameBlockList[i].Text = updateText; if (i <= 4 && !isMe && Stats.StatsList[i].Total > 0) { topList[i] = Stats.StatsList[i].Total; } else if (isMe) { me = Stats.StatsList[i].Total; } var damage = StatsUtil.FormatTotals(Stats.StatsList[i].Total) + " [" + Stats.StatsList[i].TotalSeconds.ToString(CultureInfo.CurrentCulture) + "s @" + StatsUtil.FormatTotals(Stats.StatsList[i].DPS) + "]"; DamageBlockList[i].Text = damage; goodRowCount++; } } if (ProcessDirection) { if (me > 0 && topList.Count > 0) { var updatedList = new Dictionary <int, double>(); foreach (int i in topList.Keys) { if (i != me) { var diff = topList[i] / (double)me; updatedList[i] = diff; if (PrevList != null && PrevList.ContainsKey(i)) { if (PrevList[i] > diff) { DamageRateList[i].Icon = FontAwesomeIcon.LongArrowDown; DamageRateList[i].Foreground = OverlayUtil.DOWNBRUSH; DamageRateList[i].Opacity = OverlayUtil.DATA_OPACITY; } else if (PrevList[i] < diff) { DamageRateList[i].Icon = FontAwesomeIcon.LongArrowUp; DamageRateList[i].Foreground = OverlayUtil.UPBRUSH; DamageRateList[i].Opacity = OverlayUtil.DATA_OPACITY; } } } } PrevList = updatedList; } else { PrevList = null; } } var requested = (goodRowCount + 1) * CalculatedRowHeight; if (ActualHeight != requested) { Height = requested; } if (overlayCanvas.Visibility != Visibility.Visible) { overlayCanvas.Visibility = Visibility.Hidden; TitleRectangle.Visibility = Visibility.Hidden; TitlePanel.Visibility = Visibility.Hidden; TitleDamagePanel.Visibility = Visibility.Hidden; TitleRectangle.Height = CalculatedRowHeight; TitleDamagePanel.Height = CalculatedRowHeight; TitlePanel.Height = CalculatedRowHeight; overlayCanvas.Visibility = Visibility.Visible; TitleRectangle.Visibility = Visibility.Visible; TitlePanel.Visibility = Visibility.Visible; TitleDamagePanel.Visibility = Visibility.Visible; windowBrush.Opacity = OverlayUtil.OPACITY; ButtonPopup.IsOpen = true; } for (int i = 0; i < CurrentMaxRows; i++) { SetRowVisible(i < goodRowCount, i); } } } catch (Exception ex) { LOG.Error("Overlay Error", ex); } } }
public TankingBreakdown(CombinedStats currentStats) { InitializeComponent(); titleLabel.Content = currentStats?.ShortTitle; RaidStats = currentStats?.RaidStats; }
internal void PopulateHealing(CombinedStats combined) { lock (HealingGroups) { List <PlayerStats> playerStats = combined.StatsList; Dictionary <string, PlayerStats> individualStats = new Dictionary <string, PlayerStats>(); Dictionary <string, long> totals = new Dictionary <string, long>(); 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) { PlayerStats stats = StatsUtil.CreatePlayerStats(individualStats, record.Healed); StatsUtil.UpdateStats(stats, record, block.BeginTime); allStats[record.Healed] = stats; PlayerSubStats subStats = StatsUtil.CreatePlayerSubStats(stats.SubStats, record.Healer, record.Type); StatsUtil.UpdateStats(subStats, record, block.BeginTime); allStats[record.Healer + "-" + record.Healed] = subStats; var spellStatName = record.SubType ?? Labels.SELFHEAL; PlayerSubStats spellStats = StatsUtil.CreatePlayerSubStats(stats.SubStats2, spellStatName, record.Type); StatsUtil.UpdateStats(spellStats, record, block.BeginTime); allStats[stats.Name + "=" + spellStatName] = spellStats; long value = 0; if (totals.ContainsKey(record.Healed)) { value = totals[record.Healed]; } totals[record.Healed] = record.Total + value; } }); }); foreach (var stats in allStats.Values) { stats.TotalSeconds += stats.LastTime - stats.BeginTime + 1; stats.BeginTime = double.NaN; } }); Parallel.ForEach(playerStats, stat => { if (individualStats.ContainsKey(stat.Name)) { if (totals.ContainsKey(stat.Name)) { stat.Extra = totals[stat.Name]; } var indStat = individualStats[stat.Name]; stat.SubStats2["receivedHealing"] = indStat; StatsUtil.UpdateCalculations(indStat, RaidTotals); indStat.SubStats.Values.ToList().ForEach(subStat => StatsUtil.UpdateCalculations(subStat, indStat)); indStat.SubStats2.Values.ToList().ForEach(subStat => StatsUtil.UpdateCalculations(subStat, indStat)); } }); } }
public GanttChart(CombinedStats currentStats, List <PlayerStats> selected, List <List <ActionBlock> > groups) { InitializeComponent(); Selected = selected; StartTime = groups.Min(group => group.First().BeginTime) - BUFFS_OFFSET; EndTime = groups.Max(group => group.Last().BeginTime) + 1; Length = EndTime - StartTime; switch (Selected.Count) { case 1: titleLabel1.Content = Selected[0].OrigName + "'s ADPS | " + currentStats?.ShortTitle; break; case 2: titleLabel1.Content = Selected[0].OrigName + " vs "; titleLabel2.Content = Selected[1].OrigName + "'s "; titleLabel3.Content = "ADPS | " + currentStats?.ShortTitle; break; } for (int i = 0; i < Selected.Count; i++) { var player = Selected[i].OrigName; var spellClass = PlayerManager.Instance.GetPlayerClassEnum(player); DataManager.Instance.GetReceivedSpellsDuring(StartTime, EndTime).ForEach(group => { foreach (var action in group.Actions.Where(action => action is ReceivedSpell spell && spell.Receiver == player && spell.SpellData.Adps > 0 && (spell.SpellData.MaxHits > 0 || spell.SpellData.Duration <= 1800))) { var received = action as ReceivedSpell; var spellData = received.SpellData; if (DataManager.Instance.CheckForSpellAmbiguity(spellData, spellClass, out SpellData replaced)) { spellData = replaced; } var spellName = spellData.NameAbbrv; if (string.IsNullOrEmpty(spellData.LandsOnOther)) { SelfOnly[spellName] = 1; } if (!SpellRanges.TryGetValue(spellName, out SpellRange spellRange)) { spellRange = new SpellRange() { Adps = spellData.Adps }; var duration = GetDuration(spellData, EndTime, group.BeginTime); spellRange.Ranges.Add(new TimeRange() { BlockBrush = BlockBrushes[i], BeginSeconds = (int)(group.BeginTime - StartTime), Duration = duration }); SpellRanges[spellName] = spellRange; } else { var last = spellRange.Ranges.LastOrDefault(range => range.BlockBrush == BlockBrushes[i]); var offsetSeconds = (int)(group.BeginTime - StartTime); if (last != null && offsetSeconds >= last.BeginSeconds && offsetSeconds <= (last.BeginSeconds + last.Duration)) { last.Duration = GetDuration(spellData, EndTime, group.BeginTime) + (offsetSeconds - last.BeginSeconds); } else { var duration = GetDuration(spellData, EndTime, group.BeginTime); spellRange.Ranges.Add(new TimeRange() { BlockBrush = BlockBrushes[i], BeginSeconds = (int)(group.BeginTime - StartTime), Duration = duration }); } } } }); } showSelfOnly.IsEnabled = showCasterAdps.IsEnabled = showMeleeAdps.IsEnabled = SpellRanges.Count > 0; addHeaderLabel(0, "Buffs (T-90)", 20); addHeaderLabel(BUFFS_OFFSET, DateUtil.FormatSimpleTime(StartTime), 10); int minutes = 1; for (int more = (int)(BUFFS_OFFSET + 60); more < Length; more += 60) { addHeaderLabel(more, minutes + "m", 0); minutes++; } Display(); }
internal Dictionary <string, List <HitFreqChartData> > GetHitFreqValues(PlayerStats selected, CombinedStats damageStats) { Dictionary <string, List <HitFreqChartData> > results = new Dictionary <string, List <HitFreqChartData> >(); // get chart data for player and pets if available if (damageStats?.Children.ContainsKey(selected.Name) == true) { damageStats?.Children[selected.Name].ForEach(stats => AddStats(stats)); } else { AddStats(selected); } return(results); void AddStats(PlayerStats stats) { results[stats.Name] = new List <HitFreqChartData>(); foreach (string type in stats.SubStats.Keys) { HitFreqChartData chartData = new HitFreqChartData() { HitType = stats.SubStats[type].Name }; // add crits chartData.CritXValues.AddRange(stats.SubStats[type].CritFreqValues.Keys.OrderBy(key => key)); chartData.CritXValues.ForEach(damage => chartData.CritYValues.Add(stats.SubStats[type].CritFreqValues[damage])); // add non crits chartData.NonCritXValues.AddRange(stats.SubStats[type].NonCritFreqValues.Keys.OrderBy(key => key)); chartData.NonCritXValues.ForEach(damage => chartData.NonCritYValues.Add(stats.SubStats[type].NonCritFreqValues[damage])); results[stats.Name].Add(chartData); } } }
private void ComputeDamageStats(GenerateStatsOptions options) { lock (DamageGroupIds) { 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; double stopTime = -1; try { FireChartEvent(options, "UPDATE"); if (options.RequestSummaryData) { if (options.MaxSeconds > -1 && options.MaxSeconds <= RaidTotals.MaxTime && options.MaxSeconds != RaidTotals.TotalSeconds) { var filteredGroups = new List <List <ActionBlock> >(); DamageGroups.ForEach(group => { var filteredBlocks = new List <ActionBlock>(); filteredGroups.Add(filteredBlocks); group.ForEach(block => { stopTime = stopTime == -1 ? block.BeginTime + options.MaxSeconds : stopTime; if (block.BeginTime <= stopTime) { filteredBlocks.Add(block); } }); }); DamageGroups = filteredGroups; RaidTotals.TotalSeconds = options.MaxSeconds; } else { DamageGroups = AllDamageGroups; } DamageGroups.ForEach(group => { group.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); if ((!PetToPlayer.TryGetValue(record.Attacker, out string player) && !PlayerPets.ContainsKey(record.Attacker)) || player == Labels.UNASSIGNED) { topLevelStats[record.Attacker] = stats; stats.IsTopLevel = true; } else { string origName = player ?? record.Attacker; string aggregateName = origName + " +Pets"; PlayerStats aggregatePlayerStats = StatsUtil.CreatePlayerStats(individualStats, aggregateName, origName); StatsUtil.UpdateStats(aggregatePlayerStats, record); topLevelStats[aggregateName] = aggregatePlayerStats; if (!childrenStats.TryGetValue(aggregateName, out Dictionary <string, PlayerStats> children)) { childrenStats[aggregateName] = new Dictionary <string, PlayerStats>(); } childrenStats[aggregateName][stats.Name] = stats; stats.IsTopLevel = false; } PlayerSubStats subStats = StatsUtil.CreatePlayerSubStats(stats.SubStats, record.SubType, record.Type); uint critHits = subStats.CritHits; StatsUtil.UpdateStats(subStats, record); // dont count misses or where no damage was done if (record.Total > 0) { Dictionary <long, int> values = subStats.CritHits > critHits ? subStats.CritFreqValues : subStats.NonCritFreqValues; Helpers.LongIntAddHelper.Add(values, record.Total, 1); } } } }); }); });
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); } } }
internal void AddAndCopyDamageParse(CombinedStats combined, List <PlayerStats> selected) { (playerParseTextWindow.Content as ParsePreview)?.AddParse(Labels.DAMAGEPARSE, DamageStatsManager.Instance, combined, selected, true); }
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); } } } }
internal GanttChart(CombinedStats currentStats, List <PlayerStats> selected, List <List <ActionBlock> > groups, bool tanking = false) { InitializeComponent(); if (selected != null && selected.Count > 0) { TankingMode = tanking; Selected = selected; StartTime = groups.Min(block => block.First().BeginTime) - DataManager.BUFFS_OFFSET; EndTime = groups.Max(block => block.Last().BeginTime) + 1; Length = EndTime - StartTime; string type = TankingMode ? "Defensive Skills" : "ADPS"; switch (Selected.Count) { case 1: titleLabel1.Content = Selected[0].OrigName + "'s " + type + " | " + currentStats?.ShortTitle; break; case 2: titleLabel1.Content = Selected[0].OrigName + " vs "; titleLabel2.Content = Selected[1].OrigName + "'s "; titleLabel3.Content = type + " | " + currentStats?.ShortTitle; break; } if (tanking) { showSelfOnly.Visibility = Visibility.Hidden; showMeleeAdps.Visibility = Visibility.Hidden; showCasterAdps.Visibility = Visibility.Hidden; } for (int i = 0; i < Selected.Count; i++) { var player = Selected[i].OrigName; var allSpells = new List <ActionBlock>(); allSpells.AddRange(DataManager.Instance.GetCastsDuring(StartTime, EndTime)); allSpells.AddRange(DataManager.Instance.GetReceivedSpellsDuring(StartTime, EndTime)); foreach (var block in allSpells.OrderBy(block => block.BeginTime).ThenBy(block => (block.Actions.Count > 0 && block.Actions[0] is ReceivedSpell) ? 1 : -1)) { foreach (var action in block.Actions) { if (action is SpellCast cast && !cast.Interrupted && cast.Caster == player && cast.SpellData != null && cast.SpellData.Target == (int)SpellTarget.SELF && cast.SpellData.Adps > 0 && (cast.SpellData.MaxHits > 0 || cast.SpellData.Duration <= 1800) && ClassFilter(cast.SpellData)) { if (string.IsNullOrEmpty(cast.SpellData.LandsOnOther)) { SelfOnlyOverride[cast.SpellData.NameAbbrv] = 1; if (SelfOnly.ContainsKey(cast.SpellData.NameAbbrv)) { SelfOnly.Remove(cast.SpellData.NameAbbrv); } } UpdateSpellRange(cast.SpellData, block.BeginTime, BlockBrushes[i]); } else if (action is ReceivedSpell received && received.Receiver == player) { var spellData = received.SpellData; if (spellData == null && received.Ambiguity.Count > 0 && DataManager.ResolveSpellAmbiguity(received, out SpellData replaced)) { spellData = replaced; } if (spellData != null && spellData.Adps > 0 && (spellData.MaxHits > 0 || spellData.Duration <= 1800) && ClassFilter(spellData)) { if (string.IsNullOrEmpty(spellData.LandsOnOther) && !SelfOnlyOverride.ContainsKey(spellData.NameAbbrv)) { SelfOnly[spellData.NameAbbrv] = 1; } UpdateSpellRange(spellData, block.BeginTime, BlockBrushes[i]); } } }
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); } } }