private static string GetTypeFromSpell(string name, string type) { string key = Helpers.CreateRecordKey(type, name); if (string.IsNullOrEmpty(key) || !SpellTypeCache.TryGetValue(key, out string result)) { result = type; if (!string.IsNullOrEmpty(key)) { string spellName = DataManager.Instance.AbbreviateSpellName(name); SpellData data = DataManager.Instance.GetSpellByAbbrv(spellName); if (data != null) { switch (data.Proc) { case 1: result = Labels.PROC; break; case 2: result = Labels.BANE; break; } } } SpellTypeCache[key] = result; } return(result); }
private static void AddPlayerTime(Fight fight, DamageRecord record, string player, double time) { var isInitialTanking = fight.DamageBlocks.Count == 0; var segments = isInitialTanking ? fight.InitialTankSegments : fight.DamageSegments; var subSegments = isInitialTanking ? fight.InitialTankSubSegments : fight.DamageSubSegments; StatsUtil.UpdateTimeSegments(segments, subSegments, Helpers.CreateRecordKey(record.Type, record.SubType), player, time); }
internal static PlayerSubStats CreatePlayerSubStats(Dictionary <string, PlayerSubStats> individualStats, string subType, string type) { var key = Helpers.CreateRecordKey(type, subType); PlayerSubStats stats = null; lock (individualStats) { if (!individualStats.ContainsKey(key)) { stats = CreatePlayerSubStats(subType, type); individualStats[key] = stats; } else { stats = individualStats[key]; } } return(stats); }
private static string GetTypeFromSpell(string name, string type) { string key = Helpers.CreateRecordKey(type, name); if (string.IsNullOrEmpty(key) || !SpellTypeCache.TryGetValue(key, out string result)) { if (!string.IsNullOrEmpty(key)) { string spellName = DataManager.Instance.AbbreviateSpellName(name); SpellData data = DataManager.Instance.GetSpellByAbbrv(spellName); result = (data != null && data.IsProc) ? Labels.PROC : type; SpellTypeCache[key] = result; } else { result = type; } } return(result); }
internal void BuildTotalStats(GenerateStatsOptions options) { lock (HealingGroups) { try { FireNewStatsEvent(options); Reset(); Selected = options.Npcs; Title = options.Name; Selected.ForEach(fight => RaidTotals.Ranges.Add(new TimeSegment(fight.BeginTankingTime, fight.LastTankingTime))); if (RaidTotals.Ranges.TimeSegments.Count > 0) { // calculate totals first since it can modify the ranges RaidTotals.TotalSeconds = RaidTotals.Ranges.GetTotal(); RaidTotals.Ranges.TimeSegments.ForEach(segment => { var updatedHeals = new List <ActionBlock>(); var healedByHealerTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); var healedBySpellTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); var healerHealedTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); var healerSpellTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); DataManager.Instance.GetHealsDuring(segment.BeginTime, segment.EndTime).ForEach(heal => { var updatedHeal = new ActionBlock() { BeginTime = heal.BeginTime }; foreach (var record in heal.Actions.Cast <HealRecord>()) { if (PlayerManager.Instance.IsPossiblePlayerName(record.Healed) || PlayerManager.Instance.IsPetOrPlayer(record.Healed)) { bool valid = true; SpellData spellData; if (record.SubType != null && (spellData = DataManager.Instance.GetSpellByName(record.SubType)) != null) { if (spellData.Target == (byte)SpellTarget.TARGETAE || spellData.Target == (byte)SpellTarget.NEARBYPLAYERSAE || spellData.Target == (byte)SpellTarget.TARGETRINGAE) { valid = MainWindow.IsAoEHealingEnabled; } } if (valid) { updatedHeal.Actions.Add(record); // store substats and substats2 which is based on the player that was healed var key = Helpers.CreateRecordKey(record.Type, record.SubType); StatsUtil.UpdateTimeSegments(null, healedByHealerTimeSegments, record.Healer, record.Healed, heal.BeginTime); StatsUtil.UpdateTimeSegments(null, healedBySpellTimeSegments, key, record.Healed, heal.BeginTime); StatsUtil.UpdateTimeSegments(null, healerHealedTimeSegments, record.Healed, record.Healer, heal.BeginTime); StatsUtil.UpdateTimeSegments(null, healerSpellTimeSegments, key, record.Healer, heal.BeginTime); } } } if (updatedHeal.Actions.Count > 0) { updatedHeals.Add(updatedHeal); } }); Parallel.ForEach(healedByHealerTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealedByHealerTimeRanges, kv)); Parallel.ForEach(healedBySpellTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealedBySpellTimeRanges, kv)); Parallel.ForEach(healerHealedTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealerHealedTimeRanges, kv)); Parallel.ForEach(healerSpellTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealerSpellTimeRanges, kv)); if (updatedHeals.Count > 0) { HealingGroups.Add(updatedHeals); } }); ComputeHealingStats(options); } else if (Selected == null || Selected.Count == 0) { FireNoDataEvent(options, "NONPC"); } else { FireNoDataEvent(options, "NODATA"); } } #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); } } } }
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); } } }
internal void BuildTotalStats(GenerateStatsOptions options) { lock (HealingGroups) { try { FireNewStatsEvent(options); Reset(); Selected = options.Npcs.OrderBy(sel => sel.Id).ToList(); Title = options.Name; Selected.ForEach(fight => RaidTotals.Ranges.Add(new TimeSegment(fight.BeginTankingTime, fight.LastTankingTime))); if (RaidTotals.Ranges.TimeSegments.Count > 0) { // calculate totals first since it can modify the ranges RaidTotals.TotalSeconds = RaidTotals.MaxTime = RaidTotals.Ranges.GetTotal(); RaidTotals.Ranges.TimeSegments.ForEach(segment => { var updatedHeals = new List <ActionBlock>(); var healedByHealerTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); var healedBySpellTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); var healerHealedTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); var healerSpellTimeSegments = new Dictionary <string, Dictionary <string, TimeSegment> >(); double currentTime = double.NaN; Dictionary <string, HashSet <string> > currentSpellCounts = new Dictionary <string, HashSet <string> >(); Dictionary <double, Dictionary <string, HashSet <string> > > previousSpellCounts = new Dictionary <double, Dictionary <string, HashSet <string> > >(); Dictionary <string, byte> ignoreRecords = new Dictionary <string, byte>(); List <ActionBlock> filtered = new List <ActionBlock>(); DataManager.Instance.GetHealsDuring(segment.BeginTime, segment.EndTime).ForEach(heal => { // copy var newBlock = new ActionBlock { BeginTime = heal.BeginTime }; filtered.Add(newBlock); if (currentSpellCounts.Count > 0) { previousSpellCounts[currentTime] = currentSpellCounts; } currentTime = heal.BeginTime; currentSpellCounts = new Dictionary <string, HashSet <string> >(); foreach (var timeKey in previousSpellCounts.Keys.ToList()) { if (previousSpellCounts.ContainsKey(timeKey)) { if (currentTime != double.NaN && (currentTime - timeKey) > 7) { previousSpellCounts.Remove(timeKey); } } } foreach (var record in heal.Actions.Cast <HealRecord>()) { if (PlayerManager.Instance.IsPetOrPlayer(record.Healed) || PlayerManager.Instance.IsPossiblePlayerName(record.Healed)) { // if AOEHealing is disabled then filter out AEs if (!MainWindow.IsAoEHealingEnabled) { SpellData spellData; if (record.SubType != null && (spellData = DataManager.Instance.GetHealingSpellByName(record.SubType)) != null) { if (spellData.Target == (byte)SpellTarget.TARGETAE || spellData.Target == (byte)SpellTarget.NEARBYPLAYERSAE || spellData.Target == (byte)SpellTarget.TARGETRINGAE || spellData.Target == (byte)SpellTarget.CASTERPBPLAYERS) { // just skip these entirely if AOEs are turned off continue; } else if ((spellData.Target == (byte)SpellTarget.CASTERGROUP || spellData.Target == (byte)SpellTarget.TARGETGROUP) && spellData.Mgb) { // need to count group AEs and if more than 6 are seen we need to ignore those // casts since they're from MGB and count as an AE var key = record.Healer + "|" + record.SubType; if (!currentSpellCounts.TryGetValue(key, out HashSet <string> value)) { value = new HashSet <string>(); currentSpellCounts[key] = value; } value.Add(record.Healed); HashSet <string> totals = new HashSet <string>(); List <double> temp = new List <double>(); foreach (var timeKey in previousSpellCounts.Keys) { if (previousSpellCounts[timeKey].ContainsKey(key)) { foreach (var item in previousSpellCounts[timeKey][key]) { totals.Add(item); } temp.Add(timeKey); } } foreach (var item in currentSpellCounts[key]) { totals.Add(item); } if (totals.Count > 6) { ignoreRecords[heal.BeginTime + "|" + key] = 1; temp.ForEach(timeKey => { ignoreRecords[timeKey + "|" + key] = 1; }); } } } } newBlock.Actions.Add(record); } } }); filtered.ForEach(heal => { var updatedHeal = new ActionBlock() { BeginTime = heal.BeginTime }; foreach (var record in heal.Actions.Cast <HealRecord>()) { var ignoreKey = heal.BeginTime + "|" + record.Healer + "|" + record.SubType; if (!ignoreRecords.ContainsKey(ignoreKey)) { updatedHeal.Actions.Add(record); // store substats and substats2 which is based on the player that was healed var key = Helpers.CreateRecordKey(record.Type, record.SubType); StatsUtil.UpdateTimeSegments(null, healedByHealerTimeSegments, record.Healer, record.Healed, heal.BeginTime); StatsUtil.UpdateTimeSegments(null, healedBySpellTimeSegments, key, record.Healed, heal.BeginTime); StatsUtil.UpdateTimeSegments(null, healerHealedTimeSegments, record.Healed, record.Healer, heal.BeginTime); StatsUtil.UpdateTimeSegments(null, healerSpellTimeSegments, key, record.Healer, heal.BeginTime); } } if (updatedHeal.Actions.Count > 0) { updatedHeals.Add(updatedHeal); } }); Parallel.ForEach(healedByHealerTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealedByHealerTimeRanges, kv)); Parallel.ForEach(healedBySpellTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealedBySpellTimeRanges, kv)); Parallel.ForEach(healerHealedTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealerHealedTimeRanges, kv)); Parallel.ForEach(healerSpellTimeSegments, kv => StatsUtil.AddSubTimeEntry(HealerSpellTimeRanges, kv)); if (updatedHeals.Count > 0) { HealingGroups.Add(updatedHeals); } }); ComputeHealingStats(options); } else if (Selected == null || Selected.Count == 0) { FireNoDataEvent(options, "NONPC"); } else { FireNoDataEvent(options, "NODATA"); } } catch (Exception ex) { LOG.Error(ex); } } }