private void NpcDamageManager_EventsPlayerAttackProcessed(object sender, DamageProcessedEvent e) { lock (StatsLock) { var activeFights = DataManager.Instance.GetActiveFights(); // reset if stats if first time or first new damage is received if (Stats == null || (activeFights.Count == 1 && activeFights[0].DamageBlocks.Count == 1 && activeFights[0].DamageBlocks[0].Actions.Count == 1 && CurrentDamageSelectionMode == 0)) { Stats = new OverlayDamageStats { BeginTime = e.BeginTime, RaidStats = new PlayerStats() }; } Stats.ActiveFights = activeFights; var timeout = CurrentDamageSelectionMode == 0 ? DataManager.FIGHTTIMEOUT : CurrentDamageSelectionMode; DamageStatsManager.Instance.ComputeOverlayDamageStats(e.Record, e.BeginTime, timeout, Stats); if (UpdateTimer != null && !UpdateTimer.IsEnabled) { UpdateTimer.Start(); } } }
private void NpcDamageManager_EventsPlayerAttackProcessed(object sender, DamageProcessedEvent e) { lock (StatsLock) { Stats = DamageStatsManager.Instance.ComputeOverlayDamageStats(e.Record, e.BeginTime, Stats); if (UpdateTimer != null && !UpdateTimer.IsEnabled) { UpdateTimer.Start(); } } }
internal void ComputeOverlayDamageStats(DamageRecord record, double beginTime, int timeout, OverlayDamageStats overlayStats = null) { try { // set current time overlayStats.LastTime = beginTime; if (record != null && (record.Type != Labels.BANE || MainWindow.IsBaneDamageEnabled)) { overlayStats.RaidStats.Total += record.Total; var raidTimeRange = new TimeRange(); overlayStats.InactiveFights.ForEach(fight => raidTimeRange.Add(new TimeSegment(Math.Max(fight.BeginDamageTime, overlayStats.BeginTime), fight.LastDamageTime))); overlayStats.ActiveFights.ForEach(fight => raidTimeRange.Add(new TimeSegment(Math.Max(fight.BeginDamageTime, overlayStats.BeginTime), fight.LastDamageTime))); overlayStats.RaidStats.TotalSeconds = Math.Max(raidTimeRange.GetTotal(), overlayStats.RaidStats.TotalSeconds); // update pets UpdatePetMapping(record); bool isPet = PetToPlayer.TryGetValue(record.Attacker, out string player); bool needAggregate = isPet || (!isPet && PlayerPets.ContainsKey(record.Attacker) && overlayStats.TopLevelStats.ContainsKey(record.Attacker + " +Pets")); if (!needAggregate) { // not a pet PlayerStats stats = StatsUtil.CreatePlayerStats(overlayStats.IndividualStats, record.Attacker); overlayStats.TopLevelStats[record.Attacker] = stats; StatsUtil.UpdateStats(stats, record); stats.LastTime = beginTime; } else { string origName = player ?? record.Attacker; string aggregateName = origName + " +Pets"; PlayerStats aggregatePlayerStats; aggregatePlayerStats = StatsUtil.CreatePlayerStats(overlayStats.IndividualStats, aggregateName, origName); overlayStats.TopLevelStats[aggregateName] = aggregatePlayerStats; if (overlayStats.TopLevelStats.ContainsKey(origName)) { var origPlayer = overlayStats.TopLevelStats[origName]; StatsUtil.MergeStats(aggregatePlayerStats, origPlayer); overlayStats.TopLevelStats.Remove(origName); overlayStats.IndividualStats.Remove(origName); } if (record.Attacker != origName && overlayStats.TopLevelStats.ContainsKey(record.Attacker)) { var origPet = overlayStats.TopLevelStats[record.Attacker]; StatsUtil.MergeStats(aggregatePlayerStats, origPet); overlayStats.TopLevelStats.Remove(record.Attacker); overlayStats.IndividualStats.Remove(record.Attacker); } StatsUtil.UpdateStats(aggregatePlayerStats, record); aggregatePlayerStats.LastTime = beginTime; } overlayStats.RaidStats.DPS = (long)Math.Round(overlayStats.RaidStats.Total / overlayStats.RaidStats.TotalSeconds, 2); var list = overlayStats.TopLevelStats.Values.OrderByDescending(item => item.Total).ToList(); int found = list.FindIndex(stats => stats.Name.StartsWith(ConfigUtil.PlayerName, StringComparison.Ordinal)); var you = found > -1 ? list[found] : null; int renumber; if (found > 4) { you.Rank = Convert.ToUInt16(found + 1); overlayStats.StatsList.Clear(); overlayStats.StatsList.AddRange(list.Where(stats => (stats != null && stats == you) || beginTime - stats.LastTime <= timeout).Take(4)); overlayStats.StatsList.Add(you); renumber = overlayStats.StatsList.Count - 1; } else { overlayStats.StatsList.Clear(); overlayStats.StatsList.AddRange(list.Where(stats => (stats != null && stats == you) || beginTime - stats.LastTime <= timeout).Take(5)); renumber = overlayStats.StatsList.Count; } for (int i = 0; i < overlayStats.StatsList.Count; i++) { if (i < renumber) { overlayStats.StatsList[i].Rank = Convert.ToUInt16(i + 1); } // only update time if damage changed if (overlayStats.StatsList[i].LastTime == beginTime && overlayStats.StatsList[i].CalcTime != beginTime) { var timeRange = new TimeRange(); if (PlayerPets.TryGetValue(overlayStats.StatsList[i].OrigName, out ConcurrentDictionary <string, byte> mapping)) { mapping.Keys.ToList().ForEach(key => { AddSegments(timeRange, overlayStats.InactiveFights, key, overlayStats.BeginTime); AddSegments(timeRange, overlayStats.ActiveFights, key, overlayStats.BeginTime); }); } AddSegments(timeRange, overlayStats.InactiveFights, overlayStats.StatsList[i].OrigName, overlayStats.BeginTime); AddSegments(timeRange, overlayStats.ActiveFights, overlayStats.StatsList[i].OrigName, overlayStats.BeginTime); overlayStats.StatsList[i].TotalSeconds = Math.Max(timeRange.GetTotal(), overlayStats.StatsList[i].TotalSeconds); overlayStats.StatsList[i].CalcTime = beginTime; } StatsUtil.UpdateCalculations(overlayStats.StatsList[i], overlayStats.RaidStats); } var count = overlayStats.InactiveFights.Count + overlayStats.ActiveFights.Count; overlayStats.TargetTitle = (count > 1 ? "C(" + count + "): " : "") + record.Defender; overlayStats.TimeTitle = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, overlayStats.RaidStats.TotalSeconds); overlayStats.TotalTitle = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(overlayStats.RaidStats.Total), " Damage ", StatsUtil.FormatTotals(overlayStats.RaidStats.DPS)); } } #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); } } void AddSegments(TimeRange range, List <Fight> fights, string key, double start) { fights.ForEach(fight => { if (fight.DamageSegments.TryGetValue(key, out TimeSegment segment) && segment.EndTime >= start) { range.Add(new TimeSegment(Math.Max(segment.BeginTime, start), segment.EndTime)); } }); } }
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; var timeout = CurrentDamageSelectionMode == 0 ? DataManager.FIGHTTIMEOUT : CurrentDamageSelectionMode; if (Stats == null || (DateTime.Now - DateTime.MinValue.AddSeconds(Stats.LastTime)).TotalSeconds > timeout) { windowBrush.Opacity = 0.0; ButtonPopup.IsOpen = false; SetVisible(false); Height = 0; Stats = null; PrevList = null; UpdateTimer.Stop(); } else if (Active && Stats != null) { var list = Stats.StatsList.Take(MAX_ROWS).ToList(); if (list.Count > 0) { 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 < MAX_ROWS; i++) { if (list.Count > i) { if (ProcessDirection) { DamageRateList[i].Opacity = 0.0; } if (i == 0) { total = list[i].Total; RectangleList[i].Width = Width; } else { RectangleList[i].Visibility = Visibility.Hidden; // maybe it calculates width better RectangleList[i].Width = Convert.ToDouble(list[i].Total) / total * Width; } string playerName = ConfigUtil.PlayerName; var isMe = !string.IsNullOrEmpty(playerName) && list[i].Name.StartsWith(playerName, StringComparison.OrdinalIgnoreCase) && (playerName.Length >= list[i].Name.Length || list[i].Name[playerName.Length] == ' '); string updateText; if (IsHideOverlayOtherPlayersEnabled && !isMe) { updateText = string.Format(CultureInfo.CurrentCulture, "{0}. Hidden Player", list[i].Rank); } else { updateText = string.Format(CultureInfo.CurrentCulture, "{0}. {1}", list[i].Rank, list[i].Name); } if (IsShowOverlayCritRateEnabled) { List <string> critMods = new List <string>(); if (isMe && PlayerManager.Instance.IsDoTClass(list[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 && list[i].Total > 0) { topList[i] = list[i].Total; } else if (isMe) { me = list[i].Total; } var damage = StatsUtil.FormatTotals(list[i].Total) + " [" + list[i].TotalSeconds.ToString(CultureInfo.CurrentCulture) + "s @" + StatsUtil.FormatTotals(list[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 = DOWNBRUSH; DamageRateList[i].Opacity = DATA_OPACITY; } else if (PrevList[i] < diff) { DamageRateList[i].Icon = FontAwesomeIcon.LongArrowUp; DamageRateList[i].Foreground = UPBRUSH; DamageRateList[i].Opacity = 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 = OPACITY; ButtonPopup.IsOpen = true; } for (int i = 0; i < MAX_ROWS; i++) { SetRowVisible(i < goodRowCount, i); } } } } catch (Exception ex) { LOG.Error("Overlay Error", ex); } } }
internal OverlayDamageStats ComputeOverlayDamageStats(DamageRecord record, double beginTime, OverlayDamageStats overlayStats = null) { if (overlayStats == null) { overlayStats = new OverlayDamageStats { RaidStats = new PlayerStats() }; overlayStats.RaidStats.BeginTime = beginTime; } else { overlayStats.RaidStats = overlayStats.RaidStats; } if (overlayStats.UniqueNpcs.Count == 0 || (beginTime - overlayStats.RaidStats.LastTime > DataManager.FIGHT_TIMEOUT)) { overlayStats.RaidStats.Total = 0; overlayStats.RaidStats.BeginTime = beginTime; overlayStats.UniqueNpcs.Clear(); overlayStats.TopLevelStats.Clear(); overlayStats.AggregateStats.Clear(); overlayStats.IndividualStats.Clear(); } overlayStats.RaidStats.LastTime = beginTime; overlayStats.RaidStats.TotalSeconds = overlayStats.RaidStats.LastTime - overlayStats.RaidStats.BeginTime + 1; if (record != null && (record.Type != Labels.BANE || MainWindow.IsBaneDamageEnabled)) { overlayStats.UniqueNpcs[record.Defender] = 1; overlayStats.RaidStats.Total += record.Total; // see if there's a pet mapping, check this first string pname = PlayerManager.Instance.GetPlayerFromPet(record.Attacker); if (pname != null || !string.IsNullOrEmpty(pname = record.AttackerOwner)) { PlayerHasPet[pname] = 1; PetToPlayer[record.Attacker] = pname; } bool isPet = PetToPlayer.TryGetValue(record.Attacker, out string player); bool needAggregate = isPet || (!isPet && PlayerHasPet.ContainsKey(record.Attacker) && overlayStats.TopLevelStats.ContainsKey(record.Attacker + " +Pets")); if (!needAggregate || player == Labels.UNASSIGNED) { // not a pet PlayerStats stats = StatsUtil.CreatePlayerStats(overlayStats.IndividualStats, record.Attacker); StatsUtil.UpdateStats(stats, record, beginTime); overlayStats.TopLevelStats[record.Attacker] = stats; stats.TotalSeconds = stats.LastTime - stats.BeginTime + 1; } else { string origName = player ?? record.Attacker; string aggregateName = origName + " +Pets"; PlayerStats aggregatePlayerStats; aggregatePlayerStats = StatsUtil.CreatePlayerStats(overlayStats.IndividualStats, aggregateName, origName); overlayStats.TopLevelStats[aggregateName] = aggregatePlayerStats; if (overlayStats.TopLevelStats.ContainsKey(origName)) { var origPlayer = overlayStats.TopLevelStats[origName]; StatsUtil.MergeStats(aggregatePlayerStats, origPlayer); overlayStats.TopLevelStats.Remove(origName); overlayStats.IndividualStats.Remove(origName); } if (record.Attacker != origName && overlayStats.TopLevelStats.ContainsKey(record.Attacker)) { var origPet = overlayStats.TopLevelStats[record.Attacker]; StatsUtil.MergeStats(aggregatePlayerStats, origPet); overlayStats.TopLevelStats.Remove(record.Attacker); overlayStats.IndividualStats.Remove(record.Attacker); } StatsUtil.UpdateStats(aggregatePlayerStats, record, beginTime); aggregatePlayerStats.TotalSeconds = aggregatePlayerStats.LastTime - aggregatePlayerStats.BeginTime + 1; } overlayStats.RaidStats.DPS = (long)Math.Round(overlayStats.RaidStats.Total / overlayStats.RaidStats.TotalSeconds, 2); var list = overlayStats.TopLevelStats.Values.OrderByDescending(item => item.Total).ToList(); int found = list.FindIndex(stats => stats.Name.StartsWith(ConfigUtil.PlayerName, StringComparison.Ordinal)); int renumber; if (found > 4) { var you = list[found]; you.Rank = Convert.ToUInt16(found + 1); overlayStats.StatsList.Clear(); overlayStats.StatsList.AddRange(list.Take(4)); overlayStats.StatsList.Add(you); renumber = overlayStats.StatsList.Count - 1; } else { overlayStats.StatsList.Clear(); overlayStats.StatsList.AddRange(list.Take(5)); renumber = overlayStats.StatsList.Count; } for (int i = 0; i < renumber; i++) { overlayStats.StatsList[i].Rank = Convert.ToUInt16(i + 1); } // only calculate the top few Parallel.ForEach(overlayStats.StatsList, top => StatsUtil.UpdateCalculations(top, overlayStats.RaidStats)); overlayStats.TargetTitle = (overlayStats.UniqueNpcs.Count > 1 ? "Combined (" + overlayStats.UniqueNpcs.Count + "): " : "") + record.Defender; overlayStats.TimeTitle = string.Format(CultureInfo.CurrentCulture, StatsUtil.TIME_FORMAT, overlayStats.RaidStats.TotalSeconds); overlayStats.TotalTitle = string.Format(CultureInfo.CurrentCulture, StatsUtil.TOTAL_FORMAT, StatsUtil.FormatTotals(overlayStats.RaidStats.Total), " Damage ", StatsUtil.FormatTotals(overlayStats.RaidStats.DPS)); } return(overlayStats); }
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; if (Stats == null || (DateTime.Now - DateTime.MinValue.AddSeconds(Stats.RaidStats.LastTime)).TotalSeconds > DataManager.FIGHT_TIMEOUT) { windowBrush.Opacity = 0.0; ButtonPopup.IsOpen = false; SetVisible(false); this.Height = 0; Stats = null; PrevList = null; UpdateTimer.Stop(); } else if (Active && Stats != null && Stats.RaidStats.LastTime > LastUpdate) { var list = Stats.StatsList.Take(MAX_ROWS).ToList(); if (list.Count > 0) { TitleBlock.Text = Stats.TargetTitle; TitleDamageBlock.Text = StatsUtil.FormatTotals(Stats.RaidStats.Total) + " [" + Stats.RaidStats.TotalSeconds + "s @" + 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 < MAX_ROWS; i++) { if (list.Count > i) { if (ProcessDirection) { DamageRateList[i].Opacity = 0.0; } if (i == 0) { total = list[i].Total; RectangleList[i].Width = this.Width; } else { RectangleList[i].Visibility = Visibility.Hidden; // maybe it calculates width better RectangleList[i].Width = Convert.ToDouble(list[i].Total) / total * this.Width; } string playerName = ConfigUtil.PlayerName; var isMe = !string.IsNullOrEmpty(playerName) && list[i].Name.StartsWith(playerName, StringComparison.OrdinalIgnoreCase) && (playerName.Length >= list[i].Name.Length || list[i].Name[playerName.Length] == ' '); if (MainWindow.IsHideOverlayOtherPlayersEnabled && !isMe) { NameBlockList[i].Text = list[i].Rank + ". " + "Hidden Player"; } else { NameBlockList[i].Text = list[i].Rank + ". " + list[i].Name; } if (i <= 3 && !isMe && list[i].Total > 0) { topList[i] = list[i].Total; } else if (isMe) { me = list[i].Total; } var damage = StatsUtil.FormatTotals(list[i].Total) + " [" + list[i].TotalSeconds + "s @" + StatsUtil.FormatTotals(list[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 = DOWN_BRUSH; DamageRateList[i].Opacity = DATA_OPACITY; } else if (PrevList[i] < diff) { DamageRateList[i].Icon = FontAwesomeIcon.LongArrowUp; DamageRateList[i].Foreground = UP_BRUSH; DamageRateList[i].Opacity = DATA_OPACITY; } } } } PrevList = updatedList; } else { PrevList = null; } } var requested = (goodRowCount + 1) * CalculatedRowHeight; if (this.ActualHeight != requested) { this.Height = requested; } if (overlayCanvas.Visibility != Visibility.Visible) { overlayCanvas.Visibility = Visibility.Hidden; TitlePanel.Visibility = Visibility.Hidden; TitleRectangle.Visibility = Visibility.Hidden; TitleBlock.Visibility = Visibility.Hidden; TitleDamageBlock.Visibility = Visibility.Hidden; TitlePanel.Height = CalculatedRowHeight; TitleRectangle.Height = CalculatedRowHeight; TitleDamageBlock.Height = CalculatedRowHeight; TitleBlock.Height = CalculatedRowHeight; overlayCanvas.Visibility = Visibility.Visible; TitlePanel.Visibility = Visibility.Visible; TitleRectangle.Visibility = Visibility.Visible; TitleBlock.Visibility = Visibility.Visible; TitleDamageBlock.Visibility = Visibility.Visible; windowBrush.Opacity = OPACITY; ButtonPopup.IsOpen = true; } for (int i = 0; i < MAX_ROWS; i++) { SetRowVisible(i < goodRowCount, i); } } } } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { LOG.Error("Overlay Error", ex); } } }